diff --git a/_config.yml b/_config.yml index d4916414195c9..b820846f5623a 100644 --- a/_config.yml +++ b/_config.yml @@ -1,16 +1,20 @@ # # This file contains configuration flags to customize your site # - +theme: jekyll-theme-slate +highlighter: rouge +paginate: 5 +paginate_path: "page:num" +future: true # Name of your site (displayed in the header) -name: Your Name +name: Mohan Radhakrishnan # Short bio or description (displayed in the header) -description: Web Developer from Somewhere +description: Machine Learning Algorithms, TensorFlow , Haskell, OCaml and Racket. # URL of your avatar or profile pic (you could use your GitHub profile pic) -avatar: https://raw.githubusercontent.com/barryclark/jekyll-now/master/images/jekyll-logo.png - +#avatar: https://raw.githubusercontent.com/barryclark/jekyll-now/master/images/jekyll-logo.png +avatar: https://mohanr.github.io/images/jekyll-logo.png # # Flags below are optional # @@ -32,6 +36,11 @@ footer-links: googleplus: # anything in your profile username that comes after plus.google.com/ +#Stackoverflow flair is optional +stackoverflow: + profileurl: https://stackoverflow.com/users/1234909/mohan-radhakrishnan + flairimageurl: https://stackoverflow.com/users/flair/1234909.png + # Enter your Disqus shortname (not your username) to enable commenting on posts # You can find your shortname on the Settings page of your Disqus account disqus: diff --git a/_drafts/2016-12-21-JoyOfOCaml.md b/_drafts/2016-12-21-JoyOfOCaml.md new file mode 100644 index 0000000000000..1c26f2b6351df --- /dev/null +++ b/_drafts/2016-12-21-JoyOfOCaml.md @@ -0,0 +1,36 @@ +--- +layout: post +title: Joy of OCaml - Part I +published: true +--- + + +{% highlight ocaml %} +Let is_list_sorted l = +Let n – ref true in +For I = 0 to List.length l -1 do +If ( I + 1 <= List.length l -1 then + If( (String.compare (List.nth l i) (List.nth l (I + 1)) == -1 || + String.compare (List.nth l i) (List.nth l ( I + 1 )) == 0 ) then n := true +else n := false ) +done; +!n +;; +{% endhighlight %} +Imperative OCaml +This code checks if a List is sorted or not. Even though OCaml has such constructs +an aspiring functional programmer should be cautioned. It is all too easy to forget +that we are learning about functions and hark back to an earlier style we are used to. + +Let use write a more idiomatic OCaml function that does the same thing. +Let is_list_sorter1 l = +Let rec loop l1 = +Match l1 with +} a :: (b :: _ as t ) -> if ( String.compare a b = -1 || String.compare a b = 0 ) + Then loop t + Else false; +| _ :: [] -> true +| [] => true +In loop l +;; + diff --git a/_includes/toc.html b/_includes/toc.html new file mode 100644 index 0000000000000..3cff42f116f83 --- /dev/null +++ b/_includes/toc.html @@ -0,0 +1,182 @@ +{% capture tocWorkspace %} + {% comment %} + Copyright (c) 2017 Vladimir "allejo" Jimenez + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + {% endcomment %} + {% comment %} + Version 1.2.0 + https://github.com/allejo/jekyll-toc + + "...like all things liquid - where there's a will, and ~36 hours to spare, there's usually a/some way" ~jaybe + + Usage: + {% include toc.html html=content sanitize=true class="inline_toc" id="my_toc" h_min=2 h_max=3 %} + + Parameters: + * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll + + Optional Parameters: + * sanitize (bool) : false - when set to true, the headers will be stripped of any HTML in the TOC + * class (string) : '' - a CSS class assigned to the TOC + * id (string) : '' - an ID to assigned to the TOC + * h_min (int) : 1 - the minimum TOC header level to use; any header lower than this value will be ignored + * h_max (int) : 6 - the maximum TOC header level to use; any header greater than this value will be ignored + * ordered (bool) : false - when set to true, an ordered list will be outputted instead of an unordered list + * item_class (string) : '' - add custom class(es) for each list item; has support for '%level%' placeholder, which is the current heading level + * submenu_class (string) : '' - add custom class(es) for each child group of headings; has support for '%level%' placeholder which is the current "submenu" heading level + * base_url (string) : '' - add a base url to the TOC links for when your TOC is on another page than the actual content + * anchor_class (string) : '' - add custom class(es) for each anchor element + * skip_no_ids (bool) : false - skip headers that do not have an `id` attribute + + Output: + An ordered or unordered list representing the table of contents of a markdown block. This snippet will only + generate the table of contents and will NOT output the markdown given to it + {% endcomment %} + + {% capture newline %} + {% endcapture %} + {% assign newline = newline | rstrip %} + + {% capture deprecation_warnings %}{% endcapture %} + + {% if include.baseurl %} + {% capture deprecation_warnings %}{{ deprecation_warnings }}{{ newline }}{% endcapture %} + {% endif %} + + {% if include.skipNoIDs %} + {% capture deprecation_warnings %}{{ deprecation_warnings }}{{ newline }}{% endcapture %} + {% endif %} + + {% capture jekyll_toc %}{% endcapture %} + {% assign orderedList = include.ordered | default: false %} + {% assign baseURL = include.base_url | default: include.baseurl | default: '' %} + {% assign skipNoIDs = include.skip_no_ids | default: include.skipNoIDs | default: false %} + {% assign minHeader = include.h_min | default: 1 %} + {% assign maxHeader = include.h_max | default: 6 %} + {% assign nodes = include.html | strip | split: ' maxHeader %} + {% continue %} + {% endif %} + + {% assign _workspace = node | split: '' | first }}>{% endcapture %} + {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %} + + {% if include.item_class and include.item_class != blank %} + {% capture listItemClass %} class="{{ include.item_class | replace: '%level%', currLevel | split: '.' | join: ' ' }}"{% endcapture %} + {% endif %} + + {% if include.submenu_class and include.submenu_class != blank %} + {% assign subMenuLevel = currLevel | minus: 1 %} + {% capture subMenuClass %} class="{{ include.submenu_class | replace: '%level%', subMenuLevel | split: '.' | join: ' ' }}"{% endcapture %} + {% endif %} + + {% capture anchorBody %}{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}{% endcapture %} + + {% if htmlID %} + {% capture anchorAttributes %} href="http://github.com/%7B%%20if%20baseURL%20%%7D%7B%7B%20baseURL%20%7D%7D%7B%%20endif%20%%7D#{{%20htmlID%20}}"{% endcapture %} + + {% if include.anchor_class %} + {% capture anchorAttributes %}{{ anchorAttributes }} class="{{ include.anchor_class | split: '.' | join: ' ' }}"{% endcapture %} + {% endif %} + + {% capture listItem %}{{ anchorBody }}{% endcapture %} + {% elsif skipNoIDs == true %} + {% continue %} + {% else %} + {% capture listItem %}{{ anchorBody }}{% endcapture %} + {% endif %} + + {% if currLevel > lastLevel %} + {% capture jekyll_toc %}{{ jekyll_toc }}<{{ listModifier }}{{ subMenuClass }}>{% endcapture %} + {% elsif currLevel < lastLevel %} + {% assign repeatCount = lastLevel | minus: currLevel %} + + {% for i in (1..repeatCount) %} + {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} + {% endfor %} + + {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} + {% else %} + {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} + {% endif %} + + {% capture jekyll_toc %}{{ jekyll_toc }}{{ listItem }}{% endcapture %} + + {% assign lastLevel = currLevel %} + {% assign firstHeader = false %} + {% endfor %} + + {% assign repeatCount = minHeader | minus: 1 %} + {% assign repeatCount = lastLevel | minus: repeatCount %} + {% for i in (1..repeatCount) %} + {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} + {% endfor %} + + {% if jekyll_toc != '' %} + {% assign rootAttributes = '' %} + {% if include.class and include.class != blank %} + {% capture rootAttributes %} class="{{ include.class | split: '.' | join: ' ' }}"{% endcapture %} + {% endif %} + + {% if include.id and include.id != blank %} + {% capture rootAttributes %}{{ rootAttributes }} id="{{ include.id }}"{% endcapture %} + {% endif %} + + {% if rootAttributes %} + {% assign nodes = jekyll_toc | split: '>' %} + {% capture jekyll_toc %}<{{ listModifier }}{{ rootAttributes }}>{{ nodes | shift | join: '>' }}>{% endcapture %} + {% endif %} + {% endif %} +{% endcapture %}{% assign tocWorkspace = '' %}{{ deprecation_warnings }}{{ jekyll_toc -}} diff --git a/_layouts/default.html b/_layouts/default.html index b2939c0bc4483..f11cb62e0d705 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -8,11 +8,26 @@ - + +{% if page.style %} + +{% endif %} - + + + diff --git a/_layouts/post.html b/_layouts/post.html index d27c48066a71e..7118f21f7525e 100644 --- a/_layouts/post.html +++ b/_layouts/post.html @@ -6,6 +6,7 @@

{{ page.title }}

+ {% include toc.html html=content %} {{ content }}
diff --git a/_posts/2014-3-3-Hello-World.md b/_posts/2014-3-3-Hello-World.md deleted file mode 100644 index d4665b6d18e9e..0000000000000 --- a/_posts/2014-3-3-Hello-World.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: post -title: You're up and running! ---- - -Next you can update your site name, avatar and other options using the _config.yml file in the root of your repository (shown below). - -![_config.yml]({{ site.baseurl }}/images/config.png) - -The easiest way to make your first post is to edit this one. Go into /_posts/ and update the Hello World markdown file. For more instructions head over to the [Jekyll Now repository](https://github.com/barryclark/jekyll-now) on GitHub. \ No newline at end of file diff --git a/_posts/2016-12-21-JoyOfOCaml.md b/_posts/2016-12-21-JoyOfOCaml.md new file mode 100644 index 0000000000000..cf79292828824 --- /dev/null +++ b/_posts/2016-12-21-JoyOfOCaml.md @@ -0,0 +1,480 @@ +--- +layout: post +title: Joy of OCaml - Part I +published: true +--- + +Many programming problems lend themselves easily to solutions based on Functional Programming languages. It is not hard to convince ourselves of this after coding a Language like OCaml or Haskell. + +This article does not explain the basics of OCaml. Nor is it too advanced. The functions are +kept as simple as possible. This is the learner's perspective after all. + +Dr Xavier Leroy was awarded the Milner Award in 2016 for achivements including OCaml. + +### Development environment + +[OPAM](https://opam.ocaml.org) does not seem to install easily in Windows. As is my wont in such cases I started with Cygwin and after two days switched to a Ubuntu VM. I didn’t think I was gaining much by reporting Cygwin permission issues to owners of OPAM Windows installers. + +The IDE is the venerable emacs. All diagrams are drawn using the Tex package, Tikz. + +### _let_ keyword + +{% highlight OCaml %} +let min_index a = + +let b = Array.copy a in a; + +Array.sort compare a; + +let n = 0 in a.(n); + +let i := ref(-1) in + +let () = Array.iteri( fun x elt -> if a.(n) = elt then I := x else ()) 0 in +!i +;; +{% endhighlight %} + +This function returns the index of the smallest element in an _Array_. It illustrates some of the various +usages of _let_ but this function uses imperative constructs and cannot be considered a real example of a OCaml +_function_. + +
    +
  1. let is used to define a function called min_index
  2. + +
  3. b holds a copy of the Array a before it is sorted because
  4. + +
  5. +Array.sort does not return anything useful. + +{% highlight OCaml %} +# Array.sort compare [|1,2|];; +- : unit = () +{% endhighlight %} +
  6. + +
  7. +let can also be used to define a variable +
  8. + +
  9. + + +{% highlight OCaml %} +let i :- ref(-1) +{% endhighlight %} +i holds the value -1 +
  10. + +
  11. +Since Array.iteri updates i, +{% highlight OCaml %} + let () +{% endhighlight %} +expects only unit. Obviously this is a side-effect and Functional paradigms are based on languages that generally do not mutate any state in a program. Read the next section. +
  12. +
+ +### Mutation + + +The hardest concept to fathom is side-effect or mutation. OCaml is mostly a functional language but +It has imperative constructs too and mutable data structures which I have decided to gloss over as my intention is to highlight the functional programming paradigm. But an example of imperative code is given at the end. + +The OCaml code shown below does not mutate any value or data structure. It creates a new _List_. That is the hallmark of functional code. No side-effects unless we intend to create it using imperative constructs. + +{% highlight OCaml %} +let rec appendtolist l a = + match l with + |[] -> [a] + |h :: t -> (h :: appendtolist t a) +;; +{% endhighlight %} + +{% highlight OCaml %} + +let insert l a b = + if List.mem_assoc a l + then + let n = List.assoc a l in (a, (appendtolist n b))::(List.remove_assoc a l) + else (a, (appendtolist [] b))::l +;; +{% endhighlight %} + +### Higher-order functions +Composable functions can be combined to create higher-order functions. Let us assume we +want part of a list and the rest to be dropped. We want to _take n_ elements and drop +the rest. + + +These two functions take ‘n’ elements from a list and drop ‘n’ elements. This is not the Idiomatic OCaml style I have come across because the algorithmic complexity is off the scale as the length of the list is computed repeatedly. + +But these two functions can be composed to form other higher-order functions that operate +on the lists obtained. + +{% highlight OCaml %} +let take n words = + let rec loop i l1 = + if i = n + then l1 + else + if( n <= List.length words ) then + loop (i + 1) ( appendtolist l1 (List.nth words i) ) + else [] + in loop 0 [] +;; +{% endhighlight %} + +{% highlight OCaml %} +let drop n words = + let rec loop i l1 = + if i >= List.length words + then l1 + else + loop (i + 1) (appendtolist l1 (List.nth words i)) + in loop n [] +;; +{% endhighlight %} + +Let us assume we are working on lists of words to find out which word follows an n-gram. In this case we want to find out which word follows all sets of 2 words in a sentence. +This is something like a _Markov Chain_. + +![image-title-here](../images/higher-order.tex.preview.pdf.png){:class="img-responsive"} + +We _take 2_ and drop the rest. + +Now we slide the window to the right and drop the first element. + +{% highlight OCaml %} +let slidewindow l x = + match l with + | h :: t -> t @ [x] + | [] -> [] +;; +{% endhighlight %} + +![image-title-here](../images/higher-order1.tex.preview.pdf.png){:class="img-responsive"} + +We slide the window to the right and thereby get the following word. Our composable functions can be used to figure out a simple _Markov Chain_. + +### The Option type + +This obviates the need to litter code with checks for the presence or absence of an expected result. + +{% highlight OCaml %} +let store l = + let rec loop count hash l1 = + match l1 with + | h :: t -> Hashtbl.add hash h count; loop ( count + 1) hash t + | [] -> hash + in loop 0 (Hashtbl.create 42) l +;; + +{% endhighlight %} + +_'Some value'_ means that the value is found and _'None'_ means it isn't. + + +{% highlight OCaml %} +let optional hash a = + if Hashtbl.find hash a + then Some a + else + None +;; +{% endhighlight %} + +### Fold + +Let us consider the _store_ function shown above. We fold the _Hashtbl_ and accumulate +the count before returning it at the end. _Fold_ is the functional style of operation on +data structures. + +If the key matches a value we accumulate the count in _accum_. + +{% highlight OCaml %} +let foldhashtbl htbl = + Hashtbl.fold (fun k v accum -> (if ( k = "a" ) + then + ( accum + 1 ) + else + accum)) htbl 0 +;; +{% endhighlight %} + +_Fold_ has some semantics that originates in the deep bowels of the functional +paradigm but we print some values to understand that. Unlike a fold on _List_ which can be _left_ or _right_, a fold on _Hashtbl_ seems straightforward. + +{% highlight OCaml %} +let foldhashtbl htbl = + Hashtbl.fold (fun k v accum -> (if ( k = "a" ) + then + ( Printf.printf "%3s %3d\n" k v ;accum + 1 ) + else + ( Printf.printf "%3s %3d\n" k v ;accum) )) htbl 0 +;; +{% endhighlight %} + +A rather contrived example of _List.fold_left_ is + +{% highlight OCaml %} +let issorted l = + match l with + | [] -> true + | x::tl -> let (_,result) = List.fold_left + ( fun (old,result) cur -> + if (result = true && + (String.compare old cur = 0 || + String.compare old cur = -1)) + then + (cur,true) + else + (cur,false) ) (x,true ) tl in + result +;; +{% endhighlight %} + +This is the result. We move from the left storing the previous value in _old_ and the current value in _cur_. +The check _result=true_ short-circuits the logic in this simple example. + +> # issorted ["b";"c";"d";"a";"b"];; + - : bool = false + # issorted ["b";"c";"d";"a"];; + - : bool = false + # issorted ["b";"c";"d";"b"];; + - : bool = false + # issorted ["b";"c";"d"];; + - : bool = true + +### Imperative OCaml + +The contrast here is between pure functional style of programming without mutating any state +and the imperative features that operate based on side-effects. + +This code checks if a List is sorted or not. + +{% highlight OCaml %} +let is_list_sorted l = +let n – ref true in + for I = 0 to List.length l -1 do + if ( I + 1 <= List.length l -1 then + If( (String.compare (List.nth l i) (List.nth l (I + 1)) == -1 || + String.compare (List.nth l i) (List.nth l ( I + 1 )) == 0 ) + then n := true + else n := false ) + done; +!n +;; +{% endhighlight %} + +Even though OCaml has such constructs an aspiring functional programmer should be cautioned. It is all too easy to forget that we are learning about functions and hark back to an earlier style we are used to. + +Let us write a more idiomatic OCaml function that does the same thing. + +{% highlight OCaml %} +let is_list_sorter1 l = +let rec loop l1 = + match l1 with + } a :: (b :: _ as t ) -> if ( String.compare a b = -1 || + String.compare a b = 0 ) + then loop t + else false; + | _ :: [] -> true + | [] => true +in loop l +;; +{% endhighlight %} + +### Djikstra's shortest-path + +So based on some of the functions defined above we try to find the shortest-path. This is +from chapter 24. of _Introduction to Algorithms_ by Cormen et al. + + +So, for example, I represent this graph + +![image-title-here](../images/djikstra.tex.preview.pdf.png){:class="img-responsive"} + +as + +[ +[0;0;0;3;5]; + +[0;0;6;0;0]; + +[0;6;0;7;0]; + +[3;0;7;0;6]; + +[5;0;0;6;0]; +] + +A caveat is that this code is not as _functional_ as I want it to be. There are far too many loops and imperative structures here. + + +{% highlight OCaml %} + + + +let rec appendtolist l a = + match l with + |[] -> [a] + |h :: t -> (h :: appendtolist t a) +;; +{% endhighlight %} + + +{% highlight OCaml %} +(*infinity is used to initialize. So floats are used. Don't see any problem *) +let estimates n = +let rec loop n1 l = + match n1 with + | n1 when n1 < n -> loop (n1 + 1) ( appendtolist l infinity) + | n1 -> l +in loop 0 [] +;; +{% endhighlight %} + +{% highlight OCaml %} +let predecessor n = +let rec loop n1 l = + match n1 with + | n1 when n1 < n -> loop (n1 + 1) ( appendtolist l false ) + | n1 -> l +in loop 0 [] +;; +{% endhighlight %} + +{% highlight OCaml %} +let update l a b = + List.mapi( fun index value -> if index=a then b else value ) l + +;; +{% endhighlight %} + +{% highlight OCaml %} +(* This does not seem to be the right data structure. Use better OCaml *) +let creategraph = +[ +[0;0;0;3;5]; +[0;3;6;0;0]; +[0;6;0;7;0]; +[3;0;7;0;6]; +[5;0;0;6;0]; +] +;; +{% endhighlight %} + +{% highlight OCaml %} +let mindistance est pred n= +let rec loop l l1 min index accum = +match l,l1 with +| h :: t,h1 :: t1 when (index < (n - 1) ) -> + if ( (h1 = false) && (h <= min )) + then + loop t t1 h (succ index) index + else + loop t t1 min (succ index) accum +|[e],[e1] -> + if ( (e1 = false) && (e <= min )) + then + (e,accum) + else + (min,accum) +|[],[] -> (min,accum) +|_::_,_::_ -> (min,accum) +|_::_,[] -> (min,accum) +|[],_::_ -> (min,accum) + +in loop est pred infinity 0 0 +;; +{% endhighlight %} + +{% highlight OCaml %} +let rec find l x y = + ( List.nth (List.nth l x) y) +;; +{% endhighlight %} + +{% highlight OCaml %} +let printlist l = + List.iter (Printf.printf "%f ") l +;; +{% endhighlight %} + +{% highlight OCaml %} +let printpred l = + List.iter (Printf.printf "%B ") l +;; +{% endhighlight %} + +{% highlight OCaml %} +let printdistances l = + List.iteri( fun i x -> Printf.printf "\n%3d %3f\n" i x) l +;; +{% endhighlight %} + +{% highlight OCaml %} +let updateestimates est1 pred1 y graph n = +let rec loop1 times1 est pred= + if times1 < n then ( + if (( ( List.nth pred times1) = false ) && + ((find graph y times1) <> 0) && + ((List.nth est y) <> infinity) && + ((( List.nth est y ) +. (float_of_int (find graph y times1))) < ( List.nth est times1 ))) + then + ( + + loop1 (times1 + 1) + (update est times1 (( List.nth est y) +. float_of_int(find graph y times1))) pred; + ) + else + ( loop1 (times1 + 1) est pred) + ) + else + ( est) +in loop1 0 est1 pred1 +;; +{% endhighlight %} + +{% highlight OCaml %} +let djikstra graph n n1= + + let rec loop times est pred accum = + + if (accum <= (n * n1)) + then + (if times < n + then ( + + let (x,y) = mindistance est pred n in + let pr = (update pred y true) in + + ( + loop (times + 1) (updateestimates est pr y graph n1)) pr (succ accum) ; + ) + else + loop 0 est pred (succ accum) + ) + else + (printdistances est;est) + in loop 0 (update (estimates n) 0 (float_of_int 0)) (predecessor n1) 0 +;; +{% endhighlight %} + +{% highlight OCaml %} +let djikstratest = + let graph = + [[0; 4; 0; 0; 0; 0; 0; 8; 0]; + [4; 0; 8; 0; 0; 0; 0; 11; 0]; + [0; 8; 0; 7; 0; 4; 0; 0; 2]; + [0; 0; 7; 0; 9; 14; 0; 0; 0]; + [0; 0; 0; 9; 0; 10; 0; 0; 0]; + [0; 0; 4; 14; 10; 0; 2; 0; 0]; + [0; 0; 0; 0; 0; 2; 0; 1; 6]; + [8; 11; 0; 0; 0; 0; 1; 0; 7]; + [0; 0; 2; 0; 0; 0; 6; 7; 0] + ] in + djikstra graph 9 9 +;; +{% endhighlight %} diff --git a/_posts/2017-01-10-JoyOfOcaml1.md b/_posts/2017-01-10-JoyOfOcaml1.md new file mode 100644 index 0000000000000..77208c45e78e1 --- /dev/null +++ b/_posts/2017-01-10-JoyOfOcaml1.md @@ -0,0 +1,276 @@ +--- +layout: post +title: Joy of OCaml - Part II +published: true +--- +In the second part I explain some more language features and code. + +### _let_ keyword +We start with this keyword again as this tripped me up. Moreover one does not +proceed much further without understanding the semantics of this keyword. + +{% highlight OCaml %} +(let table = Hashtbl.create 10 in + + Hashtbl.add table "functional" ["practical";"utilitarian"]; + Hashtbl.add table "side-effect" ["outcome";"consequence"]; + table ) +;; +{% endhighlight %} + +{% highlight OCaml %} +let choose_randomly_hashtbl table (word : string) = + let n0 = Hashtbl.find table word in + let n1 = Random.int (List.length n0) in + let n2 = List.nth n0 n1 in + n2 +;; +{% endhighlight %} + +*choose_randomly_hashtbl* returns randomly one of the values from the list corresponding to the key . And we see that _n0_, _n1_ and _n2_ are used after they are defined one by one. This should give a clearer idea about _let_'s semantics. + +### User-defined types + +As the name implies a _UDF_ is a custom type defined by the user. + +{% highlight OCaml %} +type x = cell list;; +{% endhighlight %} + +### Record types + +A record type is a step further. It identifies each constituent part of the type by a name identifier like this. + + +{% highlight OCaml %} +type cell = { alive : bool ; column : int ; row : int } +;; +{% endhighlight %} + +#### Nested pattern matching Record types + +I plan to write about pattern matching concepts in another installment of this series as that is deeper than what would fit in one paragraph. + +{% highlight OCaml %} + | {gamegrid = line :: above},{gamegrid = below} -> ( +{% endhighlight %} + +The third line in the _up_ function shown below matches the _head(line)_ of the list and the _tail(above)_ of the list. +It is nested because the pattern matches the constituent part identified by a name inside a record type. _gamegrid_ is the name identifier of a grid defined by this code. It is a grid made of a list of lists of _cell_ which is another record type. + +{% highlight OCaml %} +type grid = {gamegrid : cell list list} +;; +{% endhighlight %} + +#### Partial updates of Record types + +In this function _G_, which is of type _gridzipper_ is updated partially. _above_ and _below_ +are not updated. The keyword _with_ enables us to do this. + +{% highlight OCaml %} +let left g = +match g.left with + [] -> None +| hd::tl -> let newgridzipper = { g with focus = hd; left = tl; right = g.right @ [g.focus] } in + Some(newgridzipper) +;; +{% endhighlight %} + +### Basic containers + +As I understand it these are library extensions. We have to install those libraries we require. So, for example, _CCList_ enables us to create a mxn list of lists easily. + +This is how I used it. + +{% highlight OCaml %} +#require "containers";; + let makegrid = CCList.init 2 ( fun i -> (CCList.init 2 (fun j -> { alive = true; column = j;row = i })) );; +{% endhighlight %} + +This create a 2x2 grid which is a list of lists.`_List_ seems to be the workhorse of functional programming languages. + +### More involved code + +Let us use the record types and UDF's to code a grid using the _zipper_ pattern which is a functional way of coding a grid. The code should serve as a foundation for a _game of life_ representation in the future. + +It should be pointed out that none of these functions mutate any data structure. So there is no side-effect. As you see it takes some effort to shed our memory of imperative mutable code. It is hard at the beginning but seems quite natural now. + +Some of the following functions are my OCaml port of existing Haskell code. Given that I am not an OCaml expert, this code is far more verbose than the equivalent Haskell code My Haskell chops were not enough and I turned to the IRC for much needed advice. + +The code focuses on a _cell_ like this picture shows. There are areas above,below, to the left and to the right. We move over this grid. +The focus is the green square. + + + +![image-title-here](../images/myhanddrawn.tex.preview.pdf.png){:class="img-responsive"} + + +{% highlight OCaml %} +type cell = { alive : bool ; column : int ; row : int } +;; +{% endhighlight %} + +{% highlight OCaml %} +type cell = { alive : bool ; column : int ; row : int } +;; +{% endhighlight %} + +{% highlight OCaml %} +type grid = {gamegrid : cell list list} +;; +{% endhighlight %} + +{% highlight OCaml %} +type gridzipper = + { above : grid + ; below : grid + ; left : cell list + ; right : cell list + ; focus : cell } +{% endhighlight %} + +{% highlight OCaml %} +let focuscell celllist n = + let rec loop acc n l = + match l,n with + | hd :: tl,n when n > 0 -> loop (hd :: acc) (n - 1) tl + | [],_ -> None + | hd :: tl,0 -> Some (acc, hd, tl) + in loop [] n celllist +;; +{% endhighlight %} + +{% highlight OCaml %} +let gridfocus x y g = + let a = focuscell g x in + match a with + | Some(before, line , after) -> ( + let b = focuscell line y in + match b with + Some (left , focus, right) -> + ( + + (let above = { gamegrid = before } in + let below = { gamegrid = after} in + Some( + { above + ; below + ; left + ; right + ; focus } + ) + ) + ) + | None -> None + ) + | None -> None +;; +{% endhighlight %} +### Move the focus inside the grid + + +![image-title-here](../images/myhanddrawn-move.tex.preview.png){:class="img-responsive"} + +The _left_ function moves the focus to the left. Similarly the other functions shift the focus +to other grids cells. + + +{% highlight OCaml %} +let left g = +match g.left with + [] -> None +| hd::tl -> let newgridzipper = { g with focus = hd; left = tl; right = g.right @ [g.focus] } in + Some(newgridzipper) +;; +{% endhighlight %} + +{% highlight OCaml %} +let right g = +match g.left with + [] -> None +| hd::tl -> let newgridzipper = { g with focus = hd; left = [g.focus]; right = tl } in + Some(newgridzipper) +;; +{% endhighlight %} + +{% highlight OCaml %} +(*pattern-matches on the list (of lists) , which should be non-empty, and introduces two bindings, + line for the head, and above for the tail.*) + +let up g = + match g.above,g.below with + | {gamegrid = line :: above},{gamegrid = below} -> ( + let below' = (List.rev g.left) :: ([g.focus] @ g.right) :: below in + let a = focuscell line (List.length g.left) in + match a with + Some (left, focus, right) -> + let above = { gamegrid = above } in + let below = { gamegrid = below'} in + Some{ above + ; below + ; left + ; right + ; focus } + |None -> + None + ) + |({gamegrid=[]},_) -> None +;; +{% endhighlight %} + +{% highlight OCaml %} + +let down g = + match g.below,g.above with + | {gamegrid = line :: below},{gamegrid = above} -> ( + let above' = (List.rev g.left) :: ([g.focus] @ g.right) :: above in + let a = focuscell line (List.length g.left) in + match a with + Some (left, focus, right) -> + let above = { gamegrid = above } in + let below = { gamegrid = above'} in + Some{ above + ; below + ; left + ; right + ; focus } + |None -> + None + ) + | ({gamegrid=[]},_)-> None + +;; +{% endhighlight %} + +{% highlight OCaml %} +#require "containers";; + let makegrid = CCList.init 2 ( fun i -> (CCList.init 2 (fun j -> { alive = true; column = j;row = i })) );; +{% endhighlight %} + +Function that tests if the focus stays within the grid. _None_ signifies an exceptional movement outside. + +{% highlight OCaml %} +let grid = makegrid in + let gf = gridfocus 0 1 grid in + match gf with + | Some(gf) -> + Printf.printf "Focus is on [ %B %d %d ]" gf.focus.alive gf.focus.column gf.focus.row;left gf + | None -> Printf.printf "No focus";gf +;; +{% endhighlight %} + +Another Function that tests _focuscell_, a lower-order function. + +{% highlight OCaml %} +let grid = makegrid in + let fc = focuscell grid 2 in + match fc with + | Some(before, line , after) -> Printf.printf "Focus is on [ %B %d %d ]" (List.nth line 0).alive (List.nth line 0).column (List.nth line 0).row + | None -> Printf.printf "No focus" +;; +{% endhighlight %} + +References : + +1. Huet, Gerard (September 1997). "Functional Pearl: The Zipper" diff --git a/_posts/2017-01-20-JoyOfOcaml2.md b/_posts/2017-01-20-JoyOfOcaml2.md new file mode 100644 index 0000000000000..f60e0b290c23e --- /dev/null +++ b/_posts/2017-01-20-JoyOfOcaml2.md @@ -0,0 +1,342 @@ +--- +layout: post +title: Joy of OCaml - Part III +published: true +--- + +_At this time this article is quite unwieldy and contains hundreds of lines of code without proper explanation. + It has to be split up , edited and published as another series. Moreover I learnt more about how to code a functional + language by consulting some experts. I will edit it in due course._ + +In this installment of the series there is OCaml functional code that shows a 'Game of life' implementation. As usual I will explain the functional part of the code. I specifically point out +functional pieces because there is indispensable boilerplate code needed because I use _lablgtk_ which is a UI toolkit. And unfortunately this toolkit uses the Object-oriented parts of OCaml. + +Did I mention that OCaml is a practical functional language ? Unlike Haskell it includes many imperative constructs and OO features. I do not explain the OO part of it because that is not the focus of this series. + +{% highlight OCaml %} +(*1) Any live cell with fewer than two live neighbors dies, as if caused by underpopulation. + 2) Any live cell with more than three live neighbors dies, as if by overcrowding. + 3) Any live cell with two or three live neighbors lives on to the next generation. + 4) Any dead cell with exactly three live neighbors becomes a live cell.*) + + let locale = GtkMain.Main.init () +;; +{% endhighlight %} + +{% highlight OCaml %} +let displayrectangle area (backing:GDraw.pixmap ref) x y width height= + let update_rect = Gdk.Rectangle.create ~x ~y ~width ~height in + !backing#set_foreground (`RGB (154*256, 205*256, 50*256)); + !backing#rectangle ~x ~y ~width ~height (); + area#misc#draw (Some update_rect); +;; +{% endhighlight %} + +{% highlight OCaml %} + +let triominoevolve area (backing:GDraw.pixmap ref) x y width height solid grid= + let rec loop i x y g= + if i < 3 then + let update_rect = Gdk.Rectangle.create ~x ~y ~width ~height in + !backing#set_foreground (`RGB (154*256, 205*256, 50*256)); + !backing#rectangle ~x ~y ~width ~height ~filled:solid (); + Printf.printf "Triomino %d %d \n" x y; + area#misc#draw (Some update_rect); + let newgrid = + List.mapi (fun i el -> List.mapi ( fun i el1 -> + if (x = el1.xc && y = el1.yc) + then + ({ el1 with alive = true} + ) else el1) el ) g in + + loop ( i + 1) x (y + 20) newgrid + else + g + in + loop 0 (x + 40) (y + 40) grid;; +{% endhighlight %} + +{% highlight OCaml %} +let tryleft gf = + match gf with + | Some(gridzipper) -> (let offorongrid = left gridzipper in + match offorongrid with + | Some(newgridzipper) -> if (newgridzipper.focus.alive = true ) + then 1 else 0 + | None -> 0 ) + | None -> 0 +;; +{% endhighlight %} + +{% highlight OCaml %} + +let tryright gf = + match gf with + | Some(gridzipper) -> (let offorongrid = right gridzipper in + match offorongrid with + | Some(newgridzipper) -> if (newgridzipper.focus.alive = true ) + then 1 else 0 + | None -> 0 ) + | None -> 0 +;; +{% endhighlight %} + +{% highlight OCaml %} +let tryup gf = + match gf with + | Some(gridzipper) -> (let offorongrid = up gridzipper in + match offorongrid with + | Some(newgridzipper) -> if (newgridzipper.focus.alive = true ) + then 1 else 0 + | None -> 0 ) + | None -> 0 +;; +{% endhighlight %} + +{% highlight OCaml %} +let tryup gf = + match gf with + | Some(gridzipper) -> (let offorongrid = up gridzipper in + match offorongrid with + | Some(newgridzipper) -> if (newgridzipper.focus.alive = true ) + then 1 else 0 + | None -> 0 ) + | None -> 0 +;; +{% endhighlight %} + +{% highlight OCaml %} +let trydown gf = + match gf with + | Some(gridzipper) -> (let offorongrid = down gridzipper in + match offorongrid with + | Some(newgridzipper) -> if (newgridzipper.focus.alive = true ) + then 1 else 0 + | None -> 0 ) + | None -> 0 +;;{% endhighlight %} + +{% highlight OCaml %} +let evolve area (backing:GDraw.pixmap ref) x y width height= + let update_rect = Gdk.Rectangle.create ~x ~y ~width ~height in + !backing#set_foreground (`RGB (154*256, 205*256, 50*256)); + !backing#rectangle ~x ~y ~width ~height (); + area#misc#draw (Some update_rect); +;;{% endhighlight %} + +{% highlight OCaml %} +let trycurrent gf = + match gf with + | Some(gridzipper) -> if (gridzipper.focus.alive = true ) + then 1 else 0 + + | None -> 0 +;; +{% endhighlight %} + +{% highlight OCaml %} +let drawrect area (backing:GDraw.pixmap ref) limit= +let rec loop1 m y = + match m with + | m when m < limit -> + (let rec loop x n = + match n with + | n when n < limit -> + let x = x + 20 in + let width, height = 20,20 in + displayrectangle area backing x y width height; + (*Printf.printf "%3d %3d\n" x y;*) + loop x (n + 1) + | n when n >= limit -> loop1 (m + 1) (y + 20) + in loop 0 0) + (* when m >= limit *) + | m when m >= limit -> () +in loop1 0 0 +;; +{% endhighlight %} + +{% highlight OCaml %} +let neighbours grid = +let rec loop1 limit m g1 accum= + match m with + | m when m < limit -> + (let rec loop n g acc= + match n with + | n when n < limit -> + let cellstatus = + acc @ [[trycurrent (gridfocus m n g1)] @ + [tryleft (gridfocus m n g1)] @ + [tryright (gridfocus m n g1)] @ + [tryup (gridfocus m n g1)]@ + [trydown (gridfocus m n g1)]] in + loop (n + 1) g cellstatus + | n when n >= limit -> loop1 (List.length grid) (m + 1) g acc + in loop 0 g1 accum) + (* when m >= limit *) + | m when m >= limit -> accum +in loop1 (List.length grid) 0 grid [] +;; +{% endhighlight %} + +{% highlight OCaml %} +let drawgridrepresentation area (backing:GDraw.pixmap ref) grid = +let rec loop1 limit m y g1= + match m with + | m when m < limit -> + (let rec loop x n g= + match n with + | n when n < limit -> + let x = x + 20 in + let width, height = 20,20 in + displayrectangle area backing x y width height; + (*Printf.printf "%3d %3d\n" x y;*) + let gridmapi = + List.mapi (fun i el -> List.mapi ( fun i el1 -> + if (n = el1.column && m = el1.row) + then + ({ el1 with xc = x; yc = y} + ) else el1) el ) g in + + loop x (n + 1) gridmapi + | n when n >= limit -> loop1 (List.length grid) (m + 1) (y + 20) g + in loop 0 0 g1) + (* when m >= limit *) + | m when m >= limit -> g1 +in loop1 (List.length grid) 0 0 grid +;; +{% endhighlight %} + +[lablgtk](http://lablgtk.forge.ocamlcore.org/) is the UI toolkit that was recommended to me. So some +of the code shown below is library code. + +{% highlight OCaml %} +(* Backing pixmap for drawing area *) +let backing = ref (GDraw.pixmap ~width:200 ~height:200 ()){% endhighlight %} + +{% highlight OCaml %} +((* Create a new backing pixmap of the appropriate size *) +let configure window backing ev = + let width = GdkEvent.Configure.width ev in + let height = GdkEvent.Configure.height ev in + let pixmap = GDraw.pixmap ~width ~height ~window () in + pixmap#set_foreground `WHITE; + pixmap#rectangle ~x:0 ~y:0 ~width ~height ~filled:true (); + backing := pixmap; + true +;; +{% endhighlight %} + +{% highlight OCaml %} +(* Redraw the screen from the backing pixmap *) +let expose (drawing_area:GMisc.drawing_area) (backing:GDraw.pixmap ref) ev = + let area = GdkEvent.Expose.area ev in + let x = Gdk.Rectangle.x area in + let y = Gdk.Rectangle.y area in + let width = Gdk.Rectangle.width area in + let height = Gdk.Rectangle.width area in + let drawing = + drawing_area#misc#realize (); + new GDraw.drawable (drawing_area#misc#window) + in + drawing#put_pixmap ~x ~y ~xsrc:x ~ysrc:y ~width ~height !backing#pixmap; + false +;;{% endhighlight %} + +{% highlight OCaml %} +let printgrid grid = + let rec loop g = + match g with + |hd :: tl -> + (let rec loop1 g1 = + match g1 with + |hd1::tl1 -> Printf.printf "[ %B x-%d y-%d ]" hd1.alive hd1.xc hd1.yc;loop1 tl1 + | [] -> Printf.printf "\n"; loop tl + in loop1 hd) + |[] -> Printf.printf "\n" +in loop grid +;; +{% endhighlight %} + +{% highlight OCaml %} +let triominorepeat drawing_area grid= + triominoevolve drawing_area backing 20 0 20 20 true grid; +;;{% endhighlight %} + +{% highlight OCaml %} +let main () = + + + let window = GWindow.window ~width:320 ~height:240 ~position:`CENTER () in + window#connect#destroy ~callback:GMain.Main.quit; + + let aspect_frame = GBin.aspect_frame + ~xalign:0.5 (* center x *) + ~yalign:0.5 (* center y *) + ~ratio:2.0 (* xsize/ysize = 2.0 *) + ~obey_child:false (* ignore child's aspect *) + ~packing:window#add () in + + let makegameoflifegrid = CCList.init 7 ( fun i -> (CCList.init 7 (fun j -> { alive = false; column = j;row = i;xc = 0;yc = 0 })) ) in + let drawing_area = GMisc.drawing_area ~width:200 ~height:200 ~packing:aspect_frame#add () in + drawing_area#event#connect#expose ~callback:(expose drawing_area backing); + drawing_area#event#connect#configure ~callback:(configure window backing); + drawing_area#event#add [`EXPOSURE]; + window#show (); + let newgrid = drawgridrepresentation drawing_area backing makegameoflifegrid in + let newgrid1 = triominorepeat drawing_area newgrid in + GMain.Main.main (); + let results = neighbours newgrid1 in + List.iteri (fun i x -> + ( Printf.printf "["; List.iter (fun x1 -> + Printf.printf " %d " x1) x); + if (( i mod 7 ) = 0) + then + Printf.printf "]\n" + else + Printf.printf "]" ) results; + (*let rec play newgrid1 = + + play newgrid1 in + play newgrid1 + printgrid newgrid1;*) + +;; +let _ = main () +;; +{% endhighlight %} + +I think I have a logical representation of the status of the cells in this game of life. + +![image-title-here](../images/cellstatus.tex.preview.pdf.png){:class="img-responsive"} + + +Still I have to fire the game + +and look at the game visually. This is the unfinished part that should start the game. + +{% highlight OCaml %} + (*let rec play newgrid1 = + + play newgrid1 in + play newgrid1 + printgrid newgrid1;*) +{% endhighlight %} + +To be continued. + +___ +[ 0 0 0 0 0 ] [ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ] + +[ 0 0 0 0 0 ] [ 0 0 0 0 0 ][ 0 0 0 0 1 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ] + +[ 0 0 0 0 0 ] [ 0 0 1 0 0 ][ 1 0 0 0 1 ][ 0 1 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ] + +[ 0 0 0 0 0 ] [ 0 0 1 0 0 ][ 1 0 0 1 1 ][ 0 1 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ] + +[ 0 0 0 0 0 ] [ 0 0 1 0 0 ][ 1 0 0 1 0 ][ 0 1 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ] + +[ 0 0 0 0 0 ] [ 0 0 0 0 0 ][ 0 0 0 1 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ] + +[ 0 0 0 0 0 ] [ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ][ 0 0 0 0 0 ] + +___ diff --git a/_posts/2017-02-17-reinforcementlearning.md b/_posts/2017-02-17-reinforcementlearning.md new file mode 100644 index 0000000000000..9d3634d242f8b --- /dev/null +++ b/_posts/2017-02-17-reinforcementlearning.md @@ -0,0 +1,379 @@ +--- +layout: post +title: Reinforcement Learning +published: true +comment: true +--- + +## Introduction + +I am going through Reinforcement Learning: An Introduction by Richard S. Sutton and Andrew G. Barto and porting the lisp examples to Haskell in order to learn RL mainly but also to hone my functional programming skills. I chose the lisp examples over C examples obviously because I am coding Haskell. Not that lisp and Haskell are similar. Far from it. They are dissimilar but I find that it is helpful to work off a language that is not procedural. + +The lisp code for this example I referred to is [this](http://incompleteideas.net/sutton/book/code/TTT.lisp) + +Except the Haskell code everything I explain is based on the references cited at the end. + +### What is reinforcement learning ? + +![image-title-here](../images/TE_temp_preview2092.pdf.png){:class="img-responsive"} + +### State Monad + +I started with a proper Haskell state management workflow but soon abandoned it in favour of mutable IOarrays. Global mutable state is virtually non-existent in Haskell code. + +This code just manages the board size but is unused now. The board is small and is of fixed size. + +{% highlight haskell %}fun :: Map.Map String Int +fun = Map.empty + + +store :: String -> Int-> State (Map.Map String Int) () +store x value = do + fun <- get + put (Map.insert x value fun) + +retrieve :: String -> State (Map.Map String Int) (Maybe (Int)) +retrieve roworcolumn = do + fun <- get + return (Map.lookup roworcolumn fun) + + +getrow = do {store "row" 1; retrieve "row"} +getcolumn = do {store "column" 1; retrieve "column"} +getboardsize = do + let x = (runState getrow fun) in + let y = (runState getcolumn fun) in + (Just (*) <*> (fst x) <*> (fst y) ) +{% endhighlight %} + + +### State of the board + +{% highlight haskell %} +data BoardState = BoardState { xloc :: [Int], + oloc :: [Int], + index :: Int + } deriving (Show) +{% endhighlight %} + +### Haskell Gloss + +The UI toolkit Gloss was used to initially display a grid. I hoped that eventually I could debug this visually. That hope turned out to be false as I misjudged the number of times one trains an algorithm like this. So this code is not useful for debugging. + +{% highlight haskell %} +translationaccumulator :: [Int] -> [Int] -> [(Float,Float)] -> [Picture] -> [Picture] +translationaccumulator [] _ _ ys = reverse ys +translationaccumulator _ [] _ ys = reverse ys +translationaccumulator (head1:xs1) (head:xs) angle ys = let (a,b) = (angle !!(head - 1)) in + let (c,d) = (angle !!(head1 - 1)) in + translationaccumulator xs1 xs angle ( ((translate a b) $ + drawx ) : ((translate c d) $ + drawo ):ys) + +drawBoard :: BoardState -> Picture +drawBoard (BoardState xloc oloc index)= + Pictures $ [ translate x y $ rectangleWire 90 90| x<-[0,90..180], y<-[0,90..180] ] ++ (translationaccumulator xloc oloc [(0,180),(90,180),(180,180),(0,90),(90,90),(180,90),(0,0),(90,0),(180,0)] []) + +drawx :: Picture +drawx = color green $ rotate 45 $ + pictures [rectangleWire 1 45, rectangleWire 45 1] + +drawo :: Picture +drawo = color rose $ thickCircle 25 2 +{% endhighlight %} + +![image-title-here](../images/grid.PNG){:class="img-responsive"} + +### Value table +The representation of the board state when Player X has moved to position 1 and Player O has learned to move to position 1 is [1] and [1]. This means that the two lists, representing the positions played by the two players X and O, each contain _1_ + +This is stored as a set of higher-order bits and a set of lower-order bits like this in the value table. The value of this set of bits shown in the image is _513_. + + +![image-title-here](../images/bits.png){:class="img-responsive"} + +{% highlight haskell %} +stateindex :: [Int] -> [Int] -> Int +stateindex xloc oloc = sum [2^(n-1)| n <- xloc] + + 512 * sum [2^(n-1) | n <- oloc] +{% endhighlight %} + +### How do we know that a Player has won ? + + +Just store all the winning combinations and check because we have less board positions. + +{% highlight haskell %} +winningcombination :: [[Int]] +winningcombination = [[1,2,3],[4,5,6],[7,8,9], + [1,4,7],[2,5,8],[3,6,9], + [1,5,9],[3,5,8]] + + +checkallcombination :: [Int] -> Bool +checkallcombination l = let wc = winningcombination in + loop wc + where + loop :: [[Int]] -> Bool + loop wc = + case wc of + [] -> False + (x:xs) -> if containscombination x l then True else loop xs + +containscombination :: [Int] -> [Int] -> Bool +containscombination xs xs1 = + case xs of + [] -> True + (x:xss) -> if (x `elem` xs1) then containscombination xss xs1 else False +{% endhighlight %} + +The ReaderT Monad transformer for reading and writing to arrays. + + +{% highlight haskell %} + +createarray :: IO ( IOArray Int Double) +createarray = do { + arr <- newArray (0,512*512) (-1.0); + return arr + } + +type ArrayAccess = ReaderT (IOArray Int Double) IO +type ArrayWriteAccess = ReaderT (IOArray Int Double) IO() +readvalue :: Int -> ArrayAccess Double +readvalue x = do + a <- ask + b <- liftIO( readArray a x); + return b +writevalue :: Int -> Double -> ArrayWriteAccess +writevalue x y = do + a <- ask + liftIO( writeArray a x y) +-- Test array accesses +readfromarray = do { a <- createarray; liftIO (runReaderT (readvalue 1) a) } +writetoarray = do { a <- createarray; liftIO (runReaderT (writevalue 1 2) a) } +{% endhighlight %} + +### The representation of a Player + +{% highlight haskell %} +data Player = X | O deriving Show +isX :: Player -> Bool +isX X = True +isX O = False +{% endhighlight %} + + +### Calculate the next state in the board. + + +Get a list of empty positions in the board. + +{% highlight haskell %} +-- Returns a list of unplayed locations +possiblemoves :: BoardState -> [Int] +possiblemoves (BoardState xloc oloc index) = + let xs = [1,2,3,4,5,6,7,8,9] in + (xs \\ xloc) \\ oloc +{% endhighlight %} +``` + +Select an empty position randomly + +{% highlight haskell %} +-- "Returns one of the unplayed locations, selected at random" +randommove :: BoardState -> IO Int +randommove state = + let possibles = possiblemoves state in + case possibles of + p -> fmap (p !! ) $ randomRIO(0, length p - 1) +{% endhighlight %} + +### Greedy move + +{% highlight haskell %} +greedymove :: (String -> IO()) ->( IOArray Int Double) ->Player -> BoardState -> IO (Int,IOArray Int Double) +greedymove log a player state = + let possibles = possiblemoves state in + case possibles of + [] -> return (0, a) + p -> let bestvalue = -1.0 in-- Since default value in array is -1.0 + let bestmove = (head p) in + choosebestmove a p bestvalue bestmove + where + choosebestmove arr [] bestvalue1 bestmove1 = return (0,a) + choosebestmove arr (x:xs) bestvalue1 bestmove1 = do + (nv,b) <- nextvalue logs player x arr state + xvalue <- catch (readthevalue b (ReinforcementLearning.index (nv)))(\(SomeException e) -> printf "Reading [%d} in greedy move" x >> print e >> throwIO e) + case compare bestvalue1 xvalue of + LT -> choosebestmove b xs xvalue x; + GT -> return (bestmove1,b) + EQ -> return (bestmove1,b) + + +{% endhighlight %} + +### Abandoning the functional approach with this function + +This is basically the original _Lisp_ converted line by line to Haskell. The Haskell programmers who I consulted dissuaded me from doing this but at this time my Haskell knowledge does not measure up to the task. + +{% highlight haskell %} +gameplanrevised :: (String -> IO()) ->( IOArray Int Double) -> BoardState -> BoardState -> IO (IOArray Int Double,BoardState,Double) +gameplanrevised log a state newstate = do + exploremove a state newstate + where + exploremove :: ( IOArray Int Double) -> BoardState -> BoardState ->IO (IOArray Int Double,BoardState,Double) + exploremove a state newstate = + do + r <- randombetween; + let em = exploratorymove r in + do + result <- (terminalstatep log a (ReinforcementLearning.index newstate)); + case result of + True -> do + b <- update a state newstate + valueofnewstate <- catch (readthevalue b (ReinforcementLearning.index newstate)) (\(SomeException e) -> print e >> mapM_ (putStr . show) [ (ReinforcementLearning.index newstate)]>> throwIO e) + return (b,newstate,valueofnewstate) + False -> do + if em + then do + rm <- randommove newstate + (nv,d) <- nextvalue logs O rm a newstate + result1 <- (terminalstatep log d (ReinforcementLearning.index nv)); + valueafterrandommove <- catch (readthevalue d (ReinforcementLearning.index nv)) (\(SomeException e) -> print e >> mapM_ (putStr . show) [ (ReinforcementLearning.index nv)]>> throwIO e) + if result1 + then do + return (d,nv,valueafterrandommove) + else do + r1 <- randommove nv + (ns,na) <- nextvalue logs X r1 d nv + exploremove na nv ns + else do + (gm,c) <- greedymove log a O newstate + (nv',d') <- nextvalue logs O gm c newstate + d'' <- update d' state nv' + result2 <- (terminalstatep log d'' (ReinforcementLearning.index nv')); + valueaftergreedymove <- catch (readthevalue d'' (ReinforcementLearning.index nv')) (\(SomeException e) -> print e >> mapM_ (putStr . show) [ (ReinforcementLearning.index nv')]>> throwIO e) + if result2 + then do + return (d'',nv',valueaftergreedymove) + else do + r1 <- randommove nv' + (ns,na) <- nextvalue logs X r1 d'' nv' + exploremove na nv' ns +{% endhighlight %} + + + + +### Equivalent of 'run' function in the example + + +{% highlight haskell %} +playntimes :: IOArray Int Double -> (String -> IO()) ->Int -> IO (IOArray Int Double,Double) +-- playntimes log n = do a <- createarray; +playntimes a log n = do writethevalue a 0 0.5 + r <- (randommove (BoardState [] [] 0)) + playtime (BoardState [] [] 0) (nextvalue logs X r a (BoardState [] [] 0)) n 0 r + where + playtime :: BoardState -> IO (BoardState,IOArray Int Double) -> Int -> Double -> Int -> IO (IOArray Int Double,Double) + playtime s ns n acc r --finala is the consolidation for the next run + | n == 0 = do logsresult $ printf "Played 100 times %f %f" acc (acc/100.0) + (_, b) <- ns + return (b,acc) + | n > 0 = do + (boardstate, b) <- ns + (updatedarray, _, result) <- game logs s boardstate b; + r1 <- randommove (BoardState [] [] 0) + playtime (BoardState [] [] 0) (nextvalue logs X r1 updatedarray (BoardState [] [] 0)) (n - 1) (acc + result) r1 + +{% endhighlight %} + +### Equivalent of 'runs' function in the example + +{% highlight haskell %} +numruns :: IOArray Int Double ->Int -> Int -> Int -> Int -> IO() +numruns arr n1 n bins binsize + | n == 0 = printf "\nPlayed numruns times" + | n > 0 = do + p <- createarray + writethevalue p 0 0.5 + b <- playrepeatedly p arr n1 bins binsize + numruns arr n1 (n -1) bins binsize + +playrepeatedly :: IOArray Int Double ->IOArray Int Double -> Int -> Int -> Int -> IO(IOArray Int Double) +playrepeatedly a arr numrun1 numbins binsize = do + loop a 0 binsize + where + loop a i bs + | i == numbins = let x = numrun1 + y = numbins + z = binsize in + loop1 a x 0 y z + | i < numbins = do + (b,acc) <- playntimes a logs bs; + lastvalue <- readthevalue arr i + writethevalue arr i (lastvalue + acc) + loop b (i+1) bs + where + loop1 a x j y z = if j < y + then do + fv <- readthevalue arr j + printf " Runs %f Final Value %f Binsize %d Numruns %d \n" (fv / fromIntegral( z * x)) fv z x + loop1 a x (j+1) y z + else + return a +{% endhighlight %} + +### Main function + +{% highlight Haskell %} +main = let numbins = 30 in + do + arr <- newArray (0,numbins) 0.0; + ReinforcementLearning.numruns arr 1 1 numbins 100 -- numruns numruns numbins binsize + return () +{% endhighlight %} + +### Number of times Player O wins over X + +The Reinforcement Learning agent should become unbeatable. That is what I used to think. But my code makes Player O learn and win roughly more than 50 % but less than or equal to 60%. But I have only played 100 games, 30 times each. + +I think the winning rate of Player O should be more. It is less because somewhere in my code there may be a bug. I will continue to test it and the latest code will be in my [repository](https://github.com/mohanr/Reinforcement-Learning-An-Introduction-by-Richard-S.-Sutton-and-Andrew-G.-Barto) + + + "Played 100 times 45.0 0.45" + "Played 100 times 53.0 0.53" + "Played 100 times 51.0 0.51" + "Played 100 times 52.0 0.52" + "Played 100 times 44.0 0.44" + "Played 100 times 46.0 0.46" + "Played 100 times 52.0 0.52" + "Played 100 times 51.0 0.51" + "Played 100 times 38.0 0.38" + "Played 100 times 39.0 0.39" + "Played 100 times 39.0 0.39" + "Played 100 times 43.0 0.43" + "Played 100 times 48.0 0.48" + "Played 100 times 49.0 0.49" + "Played 100 times 52.0 0.52" + "Played 100 times 56.0 0.56" + "Played 100 times 50.0 0.5" + "Played 100 times 43.0 0.43" + "Played 100 times 50.0 0.5" + "Played 100 times 51.0 0.51" + "Played 100 times 64.0 0.64" + "Played 100 times 50.0 0.5" + "Played 100 times 61.0 0.61" + "Played 100 times 58.0 0.58" + "Played 100 times 46.0 0.46" + "Played 100 times 60.0 0.6" + "Played 100 times 55.0 0.55" + "Played 100 times 51.0 0.51" + "Played 100 times 51.0 0.51" + "Played 100 times 56.0 0.56" + +#### References + +1. https://mitpress.mit.edu/books/reinforcement-learning +2. The code from 1998 when the book was published is http://incompleteideas.net/sutton/book/code/TTT.lisp diff --git a/_posts/2017-05-01-DeepLearning.md b/_posts/2017-05-01-DeepLearning.md new file mode 100644 index 0000000000000..8fed2e45498ff --- /dev/null +++ b/_posts/2017-05-01-DeepLearning.md @@ -0,0 +1,9 @@ +--- +layout: post +title: What did Yoshua Bengio had to say about Deep Learning ? +published: true +--- + +![image-title-here](../images/Bengio.jpg){:class="img-responsive"} +As part of the lecture series this year Yoshua Bengio spoke about Deep Learning research in Chennai. I will +add more details gradually as the topics that he covered have to be researched. diff --git a/_posts/2018-08-06-TensorFlow-Recipes.md b/_posts/2018-08-06-TensorFlow-Recipes.md new file mode 100644 index 0000000000000..0c74db90ec476 --- /dev/null +++ b/_posts/2018-08-06-TensorFlow-Recipes.md @@ -0,0 +1,257 @@ +--- +layout: post +title: TensorFlow Recipes +published: true +--- + +As part of my effort to engage with the Open-source community I started answering Stackoverflow questions. I started by asking +a few decent questions and then answered some of my own questions and accepted them. Gradually I understood that this community site is not only about answering but also asking good questions that others can answer. + +That is how one builds one's [reputation](https://stackoverflow.com/help/whats-reputation). I believe a key skill one develops by answering is the ability to clearly understand what is being asked. + +Here I've collected some Tensorflow recipes most of which are my answers to Stackoverflow questions. Not all though. Some are code +samples I built for myself to understand Tensorflow. I plan to add more explanations and some diagrams to make the code clearer. + +The Tensorflow version that I use is 1.xx. I mention this because the framework is under constant development. + +# Table of contents +1. [Index one matrix based on another](#matrixindex) +2. [Roots of a polynomial by Halley's method](#Halleysmethod) +3. [How does _tf.scan_ work ?](#tfscan) +4. [How do you use TensorArray ?](#tensorarray) +5. [Initialize a variable by feeding a placeholder](#variableinitialization) + +### How to get values of each row in a matrix according to the max and secondary value indexes which I got from another matrix ? + +{% highlight Python %} + +import tensorflow as tf + +A = tf.Variable([[10,11,12], + [13,14,15], + [16,17,18]], name='A') + +B = tf.Variable([[2,1], + [0,2], + [1,2]] , name='B') + + +sess = tf.Session() +sess.run(tf.global_variables_initializer()) + +indices = sess.run(B) + + +incre = tf.Variable(0) +template = tf.Variable(tf.zeros([6,2],tf.int32)) +sess.run(tf.global_variables_initializer()) +row = tf.gather( indices , [0,1,2]) + +for i in range(0, row.get_shape()[0] ) : + newrow = tf.gather(row, i) + + exprow1 = tf.concat([tf.constant([i]), newrow[1:]], axis=0) + exprow2 = tf.concat([tf.constant([i]), newrow[:1]], axis=0) + + template = tf.scatter_update(template, incre, exprow1) + template = tf.scatter_update(template, incre + 1, exprow2) + with tf.control_dependencies([template]): + incre = tf.assign(incre,incre + 2) + +r = tf.gather_nd(A,template) +print(sess.run(template)) + +{% endhighlight %} + +### Finding roots of a polynomial by Halley's method using tensorflow + +{% highlight Python %} + +import tensorflow as tf + +h = tf.constant(.00000001, dtype='float64') +eps = tf.constant(.000001, dtype='float64') +b = tf.constant(2.0, tf.float64) + +def f(x): + return tf.subtract( tf.multiply(x , x ) , 2. ) + +def fp(x): + return tf.divide( tf.subtract( f(tf.add(x, h)) , + f(x) + ) , + h + ) + +def fpp(x): + return tf.divide( tf.subtract( fp( tf.add(x , h)) , + fp(x) + ), + h + ) + +def cond(i, x_new, x_prev): + return tf.logical_and( i < 5, + tf.less_equal( tf.abs( tf.cast(tf.subtract( x_new , + x_prev),dtype='float64')), + eps + ) + ) + +def body( i, x_new, x_prev ): + fx = f( x_prev ) + fpx = fp( x_prev ) + x_new = tf.subtract( x_prev , + tf.divide( b * fx * fpx , + tf.subtract(b * fpx * fpx, + fx * fpp( x_prev ) + ) + ) + ) + xnew = tf.Print(x_new, [x_new], message="The root is : ") + + with tf.control_dependencies([x_new,xnew]): + x_prev = tf.identity(xnew) + + return [i + 1, xnew, x_prev ] + +sess = tf.Session() +sess.run(tf.global_variables_initializer()) + + +print( sess.run(tf.while_loop(cond, body, [1, b - fpp(b), b])) ) + +{% endhighlight %} + +### How does _tf.scan_ work ? + +I will add more explanation in due time. But for now this code updates one row of a _tf.zeros(5,5)_ tensor. But _tf.scan_ is way more powerful than this. + +{% highlight Python %} + +import tensorflow as tf + +input = tf.constant([3, 2, 4, 1, 0],tf.int16) + +zeros = tf.Variable(tf.zeros(5,5),tf.int32) + +def modify(zeros, x): + row1, row2, row3, row4, row5 = zeros + x_tensor = tf.convert_to_tensor([x]) + + return [row1, x_tensor, row3, row4, row5] + +update = tf.scan(modify, input, initializer=[zeros[0:1], + zeros[1:2], + zeros[2:3], + zeros[4:5], + zeros[4:5]]) + +with tf.Session() as sess: + sess.run(tf.global_variables_initializer()) + + print(sess.run([update])) + +{% endhighlight %} + +The output is this. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
00000
32410
00000
00000
00000
+ +### How do you use TensorArray ?
+ +{% highlight Python %} + +import numpy as np +import tensorflow as tf + +a = tf.get_variable("a",[5,2],dtype=tf.float32,initializer=tf.constant_initializer(np.array([[1.5,0.2],[2.3,0.1],[1.3,0.2],[2.2,0.09],[4.4,0.8]],dtype=np.float32))) + +rows = tf.shape(a)[0] +results = tf.TensorArray(dtype=tf.float32, size=0, dynamic_size=True,infer_shape=False) + +init_state = (0, results) +condition = lambda i, _: i < rows +body = lambda i, results: (i + 1, results.write(i, a[i] )) +n, r = tf.while_loop(condition, body, init_state) +unstacked_result = r.stack() + +# run the graph +with tf.Session() as sess: + init = tf.initialize_all_variables() + sess.run(init) + # print the output of unstacked_result + print(sess.run(unstacked_result)) +{% endhighlight %} + +### Can variable initialization depend on the fed value of a placeholder ? + +Even though we can initialize a variable that depends on a placeholder like this, independent variables like W will get initialized repeatedly. More checks are needed to initialize only variables like F which are dependent on placeholders. + +This is so because everytime a value is fed to the placeholder the dependent variable F will be initialized. But other independent variables need not be. + + +{% highlight Python %} +import tensorflow as tf + +sess = tf.InteractiveSession() + +X = tf.placeholder(tf.float32, name="X") + +W = tf.Variable([1, 2, 1], dtype=tf.float32, name="weights") +W = tf.reshape(W, [1, 3]) + +var = tf.reshape([X*X,X,1],[3,1]) +F = tf.get_variable('F', dtype=tf.float32, initializer=var) + +init = tf.global_variables_initializer() +Y=tf.matmul(W,F) + +for i in range(10): + sess.run([init], feed_dict={X: i}) + print(sess.run(Y)) + +{% endhighlight %} + diff --git a/_posts/2022-08-19-FunctionalReactiveProgramming.md b/_posts/2022-08-19-FunctionalReactiveProgramming.md new file mode 100644 index 0000000000000..2876479ab0122 --- /dev/null +++ b/_posts/2022-08-19-FunctionalReactiveProgramming.md @@ -0,0 +1,346 @@ +--- +layout: post +title: Functional Reactive Programming( FRP ) +published: true +comment: true +--- +## Spacemacs + +THe following are the keystrokes I needed to use Spacemacs editor for Haskell so that I could focus on the code +without too much distraction. More advanced customizations are possible but for now this suffices. + +|KeyStroke | | | +|--------------|----------------|----------------| +| | Windows | < | +|==============|----------------|----------------| +| C-x b | Switch Buffer| < | +| SPC b x | Kill Buffer | < | +| SPC w x | Kill Window | < | +| C-x 2 | Split Window | < | +| C-x k | Kill Buffer | < | + + +| KeyStroke | | | +|--------------|----------------|----------------| +| | Files | < | +|==============|----------------|----------------| +| C-x f | Open File | < | +| C-x C-s | Save File | < | +| SPC w x | Kill Window | < | + +| KeyStroke | | | +|--------------|----------------|----------------| +| |HASKELL code | < | +|==============|----------------|----------------| +| SPC m s b |Compile and load| < | +| |in REPL | < | + + +## Introduction + +There are details that are yet to be added to this post but this code works. Since I am a Haskell novice +I can explain only part of the code. The program itself will be refactored as I understand it better. + + +The code uses [reactive-banana](https://hackage.haskell.org/package/reactive-banana) + +### What is functional reactive programming ? + +Functional Reactive Programming (FRP) integrates time flow and compositional events into functional programming. This provides an elegant way to express computation in domains such as interactive animations, robotics, computer vision, user interfaces, and simulation. + +But this definition is from the Haskell Wiki. I am just showing a piece of code which is trivial anyway. + +### Pattern 1 + +My initial attempt samples an image and renders it in the window when an event is fired in +the following function. + +{% highlight haskell %} + +eventLoop :: EventSource () -> IO () +eventLoop ( displayvalueevent) = do + putStrLn "Fired Event" + fire displayvalueevent () + +{% endhighlight %} + +But it does not sample images based on different UI events generated by the KeyBoard or Mouse. + +This function is only for testing if the UI events are trapped or not. + +{% highlight haskell %} + +let handleEvent e@(EventKey k Down _ _) = case k of + (SpecialKey KeySpace) -> putStrLn "Space" + _ -> putStrLn "Case" + handleEvent e = event e + +{% endhighlight %} + +This is the code in its entirety. + +{% highlight haskell %} + + +------------------------------------------------------------------------------} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE BlockArguments #-} + +module Main where +import Data.IORef +import Data.Bool (bool) +import Data.IORef (newIORef, readIORef, writeIORef) +import Graphics.Gloss hiding (pictures) +import Reactive.Banana +import Reactive.Banana.Frameworks +import Graphics.Gloss.Interface.IO.Game( Event(..) ) +import Graphics.Gloss.Interface.IO.Game( MouseButton(..) ) +import Graphics.Gloss.Interface.IO.Game( KeyState( Down ) ) +import Graphics.Gloss.Interface.IO.Game +import qualified Graphics.Gloss.Interface.IO.Game as Gloss (Event, playIO) + + +main = do + + picRef ← newIORef blank + (eventHandler, event) ← newAddHandler + + sources <- makeSources + network <- compile $ networkDescriptor picRef sources + actuate network + eventLoop sources + let handleEvent e@(EventKey k Down _ _) = case k of + (SpecialKey KeySpace) -> putStrLn "Space" + _ -> putStrLn "Case" + handleEvent e = event e + + Gloss.playIO + (InWindow "Functional Reactive" (320, 240) (800, 200)) + white + 30 + () + (\() -> readIORef picRef) + -- (\ ev _ → quit ev >> () <$ handleEvent ev) + (\ ev () -> handleEvent ev) + (\_ () -> pure ()) + where + quit (EventKey (Char 's' ) + _ _ _) = reactToKeyPress + quit _ = return () + +reactToKeyPress :: IO () +reactToKeyPress = putStrLn "Key Pressed" + +drawBoard :: Picture +drawBoard = + Pictures $ [ color violet $ translate x y $ rectangleWire 90 90| x<-[0,90..180], y<-[0,90..180] ] + +makeSources = newAddHandler + +type EventSource a = (AddHandler a, a -> IO ()) + +addHandler :: EventSource a -> AddHandler a +addHandler = fst + +eventLoop :: EventSource () -> IO () +eventLoop ( displayvalueevent) = do + putStrLn "Fired Event" + fire displayvalueevent () + +fire :: EventSource a -> a -> IO () +fire = snd + +networkDescriptor :: IORef Picture -> EventSource() -> MomentIO () +networkDescriptor lastFrame displayGlossEvent = do + glossEvent <- fromAddHandler (addHandler displayGlossEvent ) + reactimate $ putStrLn . showValue <$> glossEvent + + picture <- liftMoment (handleKeys displayGlossEvent ) + changes picture >>= reactimate' . fmap (fmap (writeIORef lastFrame)) + valueBLater picture >>= liftIO . writeIORef lastFrame + +showValue value = "Value is " ++ show value + +handleKeys :: EventSource () -> Moment (Behavior Picture) +handleKeys glossEvent = do + + + let picture = drawBoard + return $ pure picture + +{% endhighlight %} + +![image-title-here](../images/reactivewindow.png){:class="img-responsive"} + +After debugging this code I came up with an improvement. This code can trap a specific `Gloss` KeyBoard event +and fire a `reactive-banana` event. + +{% highlight haskell %} +------------------------------------------------------------------------------} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE BlockArguments #-} + +module Main where +import Data.IORef +import Data.Bool (bool) +import Data.IORef (newIORef, readIORef, writeIORef) +import Graphics.Gloss hiding (pictures) +import Reactive.Banana +import Reactive.Banana.Frameworks +import Graphics.Gloss.Interface.IO.Game( Event(..) ) +import Graphics.Gloss.Interface.IO.Game( MouseButton(..) ) +import Graphics.Gloss.Interface.IO.Game( KeyState( Down ) ) +import Graphics.Gloss.Interface.IO.Game +import qualified Graphics.Gloss.Interface.IO.Game as Gloss (Event, playIO) + + +main = do + + (eventHandler,event)<- makeSources + picRef ← newIORef blank + + network <- compile $ networkDescriptor picRef eventHandler + actuate network + let handleEvent e@(EventKey k Down _ _) = case k of + (SpecialKey KeySpace) -> event e + _ -> return () + handleEvent e = return () + + Gloss.playIO + (InWindow "Functional Reactive" (550, 490) (800, 200)) + white + 30 + () + (\() -> readIORef picRef) + (\ ev () -> handleEvent ev) + (\_ () -> pure ()) + +reactToKeyPress :: IO () +reactToKeyPress = putStrLn "Key Pressed" + +drawBoard :: Picture +drawBoard = + Pictures $ [ color violet $ translate x y $ rectangleWire 90 90| x<-[0,90..180], y<-[0,90..180] ] + +makeSources = newAddHandler + +type EventSource a = (AddHandler a, a -> IO ()) + +addHandler :: EventSource a -> AddHandler a +addHandler = fst + +fire :: EventSource a -> a -> IO () +fire = snd + +networkDescriptor :: IORef Picture -> AddHandler Gloss.Event -> MomentIO () +networkDescriptor lastFrame displayGlossEvent = do + glossEvent <- fromAddHandler displayGlossEvent + reactimate $ putStrLn . showValue <$> glossEvent + + picture <- liftMoment (handleKeys glossEvent ) + changes picture >>= reactimate' . fmap (fmap (writeIORef lastFrame)) + valueBLater picture >>= liftIO . writeIORef lastFrame + +showValue value = "Value is " ++ show value + +handleKeys :: Reactive.Banana.Event e -> Moment (Behavior Picture) +handleKeys glossEvent = do + + let picture = drawBoard + return $ pure picture + +{% endhighlight %} + +### Pattern 2 + +This is my second attempt. Some functions are unused. As we see this time the actual Gloss UI events +generated by the KeyBoard or Mouse are printed inside +`networkDescriptor :: IORef Picture -> AddHandler Gloss.Event -> MomentIO ()` + +{% highlight haskell %} + +------------------------------------------------------------------------------} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE BlockArguments #-} + +module Main where +import Data.IORef +import Data.Bool (bool) +import Data.IORef (newIORef, readIORef, writeIORef) +import Graphics.Gloss hiding (pictures) +import Reactive.Banana +import Reactive.Banana.Frameworks +import Graphics.Gloss.Interface.IO.Game( Event(..) ) +import Graphics.Gloss.Interface.IO.Game( MouseButton(..) ) +import Graphics.Gloss.Interface.IO.Game( KeyState( Down ) ) +import Graphics.Gloss.Interface.IO.Game +import qualified Graphics.Gloss.Interface.IO.Game as Gloss (Event, playIO) + +main = do + + picRef ← newIORef blank + (eventHandler, event) ← newAddHandler + + network <- compile $ networkDescriptor picRef eventHandler + actuate network + + Gloss.playIO + (InWindow "Functional Reactive" (550, 490) (800, 200)) + white + 30 + () + (\() -> readIORef picRef) + (\ ev () -> event ev) + (\_ () -> pure ()) + +drawBoard :: Picture +drawBoard = + Pictures $ [ color violet $ translate x y $ rectangleWire 90 90| x<-[0,90..180], y<-[0,90..180] ] + + +makeSources = newAddHandler + +type EventSource a = (AddHandler a, a -> IO ()) + +addHandler :: EventSource a -> AddHandler a +addHandler = fst + +fire :: EventSource a -> a -> IO () +fire = snd + +networkDescriptor :: IORef Picture -> AddHandler Gloss.Event -> MomentIO () +networkDescriptor lastFrame displayGlossEvent = do + glossEvent <- fromAddHandler displayGlossEvent + reactimate $ putStrLn . showValue <$> glossEvent + + picture <- liftMoment (handleKeys glossEvent ) + changes picture >>= reactimate' . fmap (fmap (writeIORef lastFrame)) + valueBLater picture >>= liftIO . writeIORef lastFrame + +showValue value = "Value is " ++ show value + +handleKeys :: Reactive.Banana.Event e -> Moment (Behavior Picture) +handleKeys glossEvent = do + + let picture = drawBoard + return $ pure picture + +{% endhighlight %} + +```shell +Value is EventMotion (-798.0,56.0) +Value is EventMotion (-798.0,56.0) +Value is EventMotion (-798.0,57.0) +Value is EventKey (SpecialKey KeyEsc) Down (Modifiers {shift = Up, ctrl = Up, alt = Up}) (-798.0,57.0) +Value is EventKey (SpecialKey KeyEsc) Up (Modifiers {shift = Up, ctrl = Up, alt = Up}) (-798.0,57.0) +Value is EventKey (Char 'h') Down (Modifiers {shift = Up, ctrl = Up, alt = Up}) (-798.0,57.0) +Value is EventKey (Char 'h') Up (Modifiers {shift = Up, ctrl = Up, alt = Up}) (-798.0,57.0) +Value is EventKey (Char 'j') Down (Modifiers {shift = Up, ctrl = Up, alt = Up}) (-798.0,57.0) +Value is EventKey (Char 'j') Up (Modifiers {shift = Up, ctrl = Up, alt = Up}) (-798.0,57.0) +Value is EventKey (Char 'k') Down (Modifiers {shift = Up, ctrl = Up, alt = Up}) (-798.0,57.0) +Value is EventKey (Char 'k') Up (Modifiers {shift = Up, ctrl = Up, alt = Up}) (-798.0,57.0) +Value is EventMotion (-798.0,57.0) +Value is EventMotion (-798.0,59.0) +Value is EventMotion (-798.0,61.0) +``` + diff --git a/_posts/2022-12-12-tla.md b/_posts/2022-12-12-tla.md new file mode 100644 index 0000000000000..a53593b7a75ca --- /dev/null +++ b/_posts/2022-12-12-tla.md @@ -0,0 +1,110 @@ +--- +layout: post +title: Learning TLA+ Part I +published: true +--- + +![image-title-here](../images/TLAPlus.png){:class="img-responsive"} + +When I was reading papers on Distributed Systems it was apparent that the algorithms involved +were hard to understand just by drawing Lamport diagrams. Morever these research papers have Math +notation. + +I have rendered a lamport diagram like this. + +![image-title-here](../images/Paxos.jpg){:class="img-responsive"} + +Even at this abstract level it was hard to understand how this protocol works. So as usual I looked +at ways to render it in code. Writing code for these complex protocols and testing it using Unit tests +was impossible. Further research led me to believe that specifications for verification of these protocols +can be coded. +I had already come across Hillel Wayne's Practical TLA+ book a few years +back. So I started reading that and naturally I was drawn to TLA+ and the Pluscal +algorithm language. + +### Development environment + +I didn't find any IDE except the TLA+ Toolkit which was hard +to use. One could configure it and improve the font size in several places +but still it is a difficult development environment. + +At the basic level one can debug by printing short pieces of code. My main +reference book is Hillel Wayne's. Practical TLA+. + +So, for example, this code can be used to print values. + +{% highlight OCaml %} + +EXTENDS Sequences,TLC + +(*--algorithm test1 + +variables + + z = (2 :> 3)[2], + w = ("a" :> "b").a; +begin + print ( ToString(z) \o " " \o ToString(w)); + +end algorithm; *) + +{% endhighlight %} + + + +### Pluscal algorithm language +I have still not cleanly delineated the difference between Pluscal and TLA+syntax. + +Moreover I have used a small subset of the language features in the code. + +## CONSTANTS + +{% highlight OCaml %} +CONSTANTS Character, text, pattern +{% endhighlight %} + +These are constants than can be declared in the code and setup using the Toolkit's +UI as shown in [Model Values setup](#model-values-setup-title) + +## Operators + +"An operator is the TLA+ equivalent of a procedure in programming." +Wayne, Hillel. Practical TLA+ (p. 69). Apress. + +The code I used in the BayerMoore algorithm matches that definition. +{% highlight OCaml %} +Max(x,y) == IF x < y THEN y ELSE x +{% endhighlight %} +## Loops + + +## Pluscal Algorithm Language + +### BayerMoore Algorithm implementation. + +The Java example of this implementation used an array to represent the ASCII value of +the letters of the alphabet. So I was looking for a similar Pluscal/TLA+ function. + +_16.1.1 in Specifying Systems_ has an example. + +{% highlight OCaml %} +Ascii(char) =∆ 96 + choose i ∈ 1 .. 26 : + “abcdefghijklmnopqrstuvwxyz”[i] = char +{% endhighlight %} + +So I translated it to the following code but this doesn't compile. + +{% highlight OCaml %} +Ascii(char) == 96 + CHOOSE z \in 1 .. 26 :"abcdefghijklmnopqrstuvwxyz"[z] = char +{% endhighlight %} + + + +## Translaated TLA code + + + +## Model Values setup + +![image-title-here](../images/modelvalues.png){:class="img-responsive"} + diff --git a/_posts/2023-02-14-Reinforcement Learning Notes.md b/_posts/2023-02-14-Reinforcement Learning Notes.md new file mode 100644 index 0000000000000..5cbd6c8a076b0 --- /dev/null +++ b/_posts/2023-02-14-Reinforcement Learning Notes.md @@ -0,0 +1,41 @@ +--- +layout: post +title: Reinforcement Learning Notes(Work in Progress) +published: false +--- +These are the notes I collected while I was taking the [Hugging Face](https://huggingface.co/docs/hub/index) +Reinforcement Learning course. I have mentioned the Video lectures, Arxiv papers and books that I referred. +I have to point out though that this is not an expert's view at all. There is math that I don't fully +understand but I have tried to read and search. + +#Stochasticity + +#Proximal Policy Optimization + +{% highlight python %} +import argparse +import os +from torch.utils.tensorboard import SummaryWriter + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('--exp-name',type=str,default=os.path.basename(__file__).rstrip(".py"), + help='The name of this experiment') + args = parser.parse_args() + return args + +if __name__ == "__main__": + args = parse_args() + print(args) + + writer = SummaryWriter(f"runs/{args.exp_name}") + writer.add_text( + "hyperparameters", + "|param|value|\n|-|-|\n%s" % ("\n".join([f"|{key}|{value}|" for key, value in vars(args).items()])), + ) + for i in range(100): + writer.add_scalar("test",i * 2, global_step=i) +{% endhighlight %} + + +![image-title-here](../images/Tensorboard.png){:class="img-responsive"} diff --git a/_posts/2023-02-17-TikzNotes.md b/_posts/2023-02-17-TikzNotes.md new file mode 100644 index 0000000000000..edcfd48f19b3b --- /dev/null +++ b/_posts/2023-02-17-TikzNotes.md @@ -0,0 +1,162 @@ +--- +layout: post +title: Tikz Notes +published: true +--- + +![image-title-here](../images/tikzeditor.png){:class="img-responsive"} + +Latex code that I use to draw various types of diagrams. + +{% highlight OCaml %} + +[auto, node distance=5cm] +\tikzstyle{block} = [draw, rectangle, minimum height=3em, minimum width=3em] +\tikzstyle{virtual} = [coordinate] + + % Place nodes + \node [virtual] (input) {}; + \node [virtual] (input1) {}; + \node [rounded corners=5pt][name=t,block, right of=input] (model) {\( + \pi_\theta (u | s ) + \)}; + \node [virtual, right of=model] (output) {}; + \node [virtual, right of=model] (feedback) {}; + \node [rounded corners=5pt][name=e,block, below right of=input1] (model1) {Environment}; + \node [virtual, above left of=model1] (reward) {}; + \node [name=i,virtual, below left] (input1) {}; + \node [name=r, left of=model1] {\( reward \ r_t \)} (reward); + % Connect nodes + \draw [-] (model) -- node [name=y] + {\(action \ + u_t \)} + (output); + \draw [->] (y) |- (feedback) |- (model1.-10); + \draw [->] (model1.180) |- (r) |- (model.-180); + + +{% endhighlight %} + +![image-title-here](../images/policy.png){:class="img-responsive"} + +``` +{% raw %} + +\Large + \tikzstyle{block} = [draw=none,,rounded corners=2.9mm,minimum width=4.2mm,minimum height=3.7mm, draw=violet!80,thick, text width=.9cm,,font=\bfseries, align=center, fill=cyan!14] + + + %nodes + \node[block, anchor=north] (q) {\color{black}{\boldmath$Q_{1}$}}; + \node[block, above=40mm of q] (k) {\color{black}{\boldmath$K_{1}$}}; + \node at ($(q.north)-(2,-2)$) [block] (x1) {\color{black}{\boldmath$x_{1}$}}; + \node[block, right=2cm of x1.center, anchor=center] (v) {\color{black}{\boldmath$V_{1}$}}; + + + %lines + \draw[{Stealth[scale=1.3,angle'=45,open]}-,semithick][rounded corners=2] (q) -- node[midway, label=above:{{\boldmath$W^{q}$}}] {}($(q)-(1,0)$) |- ($(x1.east)+(0,.1)$); + \draw [{Stealth[scale=1.3,angle'=45,open]}-,semithick][rounded corners=2] (k) -- node[midway, label=above:{{\boldmath$W^{k}$}}] {}($(k)-(1,0)$) |- ($(x1.east)+(0,.1)$); + \draw [{Stealth[scale=1.3,angle'=45,open]}-,semithick][rounded corners=2] (v) -- node[midway, label=above:{{\boldmath$W^{v}$}}] {}($(v)-(1,0)$) |- ($(x1.east)+(0,.1)$); + + %nodes + \node[-{Latex[black,length=5mm,width=2mm]},semithick][block, below=54mm of k] (k1) {{\boldmath$K_{2}$}}; + \node[block, anchor=north, below=43mm of k1] (q1) {\color{black}{\boldmath$Q_{2}$}}; + \node at ($(q1.north)-(2,-2)$) [block , below=57mm of x1] (x2) {\color{black}{\boldmath$x_{2}$}}; + \node[block, right=2cm of x2.center, anchor=center] (v1) {\color{black}{\boldmath$V_{2}$}}; + + %lines + \draw[{Stealth[scale=1.3,angle'=45,open]}-,semithick][rounded corners=2pt] (q1) -- node[midway, label=:{{\boldmath$W^{q}$}}] {}($(q1)-(1,0)$) |- ($(x2.east)+(0,.1)$); + \draw [{Stealth[scale=1.3,angle'=45,open]}-,semithick][rounded corners=2] (k1) -- node[midway, label=left:{{\boldmath$W^{k}$}}] {}($(k1)-(1,0)$) |- ($(x2.east)+(0,.1)$); + \draw [{Stealth[scale=1.3,angle'=45,open]}-,semithick][rounded corners=2] (v1) -- node[midway, label=above:{{\boldmath$W^{v}$}}] {}($(v1)-(1,0)$) |- ($(x2.east)+(0,.1)$); + + %nodes + \node[block, below=57mm of k1] (k2) {\color{black}{\boldmath$K_{T}$}}; + \node[block, anchor=north, below=40mm of k2] (q2) {\color{black}{\boldmath$Q_{T}$}}; + \node at ($(q2.north)-(2,-2)$) [block , below=57mm of x2] (x3) {\color{black}{\boldmath$x_{T}$}}; + \node[block, right=2cm of x3.center, anchor=center] (v2) {\color{black}{\boldmath$V_{T}$}}; + + %lines + \draw [{Stealth[scale=1.3,angle'=45,open]}-,semithick][ rounded corners=2] (q2) -- node[midway, label=above:{{\boldmath$W^{q}$}}] {}($(q2)-(1,0)$) |- ($(x3.east)+(0,.1)$); + \draw [{Stealth[scale=1.3,angle'=45,open]}-,semithick][rounded corners=2] (k2) -- node[midway, label=left:{{\boldmath$W^{k}$}}] {}($(k2)-(1,0)$) |- ($(x3.east)+(0,.1)$); + \draw [{Stealth[scale=1.3,angle'=45,open]}-,semithick][rounded corners=2] (v2) -- node[midway, label=above:{{\boldmath$W^{v}$}}] {}($(v2)-(1,0)$) |- ($(x3.east)+(0,.1)$); + + \node (r1) [draw=none,rounded corners=.5cm,line width=.6mm, below=1mm of q, draw=violet!80, minimum width=5.2cm,minimum height=6.7cm]{}; + + \node (r2) [draw=none,rounded corners=.5cm,line width=.6mm, right=33mm of v, draw=blue!80, minimum width=3.5cm,minimum height=1.6cm]{}; + \node (r3) [draw=none,rounded corners=.5cm,line width=.5mm, below=37mm of r2, draw=blue!80, minimum width=3.5cm,minimum height=1.6cm]{}; + \node (r4) [draw=none,rounded corners=.5cm,line width=.5mm, below=56mm of r3, draw=blue!80, minimum width=3.5cm,minimum height=1.4cm]{}; + + + \node [block , right=37mm of v] (q21) {\color{black}{\boldmath$Q_{2}$}};; + \node [block , right=3mm of q21] (k11) {\color{black}{\boldmath$K_{1}$}};; + \node [below=of r2.north] {\color{black}{\boldmath$T$}};; + \node [below= of r3.north] {\color{black}{\boldmath$T$}};; + \node [below= of r4.north] {\color{black}{\boldmath$T$}};; + + \node [block , fill=YellowGreen!40,above=37mm of q21] (a21) at ([yshift=-2cm]$(q21)!0.5!(k11)$) {\color{black}{\boldmath$a_{2,1}$}};; + + \node [block , below=45mm of q21] (q22) {\color{black}{\boldmath$Q_{2}$}};; + \node [block , right=3mm of q22] (k12) {\color{black}{\boldmath$K_{2}$}};; + \node [block , fill=YellowGreen!40, above=30mm of q22] (a22) at ([yshift=-2cm]$(q22)!0.5!(k12)$) {\color{black}{\boldmath$a_{2,2}$}};; + + \node [block , below=64mm of q22] (q23) {\color{black}{\boldmath$Q_{T}$}};; + \node [block , right=3mm of q23] (k13) {\color{black}{\boldmath$K_{T}$}};; + \node [block , fill=YellowGreen!40,above=34mm of q23] (a23) at ([yshift=-2cm]$(q23)!0.5!(k13)$) {\color{black}{\boldmath$a_{2,3}$}};; + + \draw [color=Orange,line width=1.95pt,dotted][rounded corners=2] (v) -- (5.2,0); + \node [block , fill=Violet!20,right=83mm of v1] (A2) {\color{black}{\boldmath$A_{2}$}}; + \draw [-{Stealth[scale=1.3,angle'=45,open]},color=Orange,line width=1.95pt,dotted][rounded corners=2](5.2,0) -- ($(A2)-(0.7,0)$); + \draw [-{Stealth[scale=1.3,angle'=45,open]},line width=1.95pt,color=Orange,dotted][rounded corners=2](5.2,-8) -- ($(A2)-(0.7,0)$); + \draw [line width=1.95pt,color=Orange,dotted][rounded corners=2] (v2) -- (5.2,-8); + + + + + \draw [-{Stealth[scale=1.3,angle'=45,open]},line width=1.95pt,color=Orange,dotted][rounded corners=2] (v1) -- ($(A2)-(0.8,0)$); + + \draw [line width=1.95pt,color=Orange,dotted][rounded corners=2] ($(r2.south)-(1,0)$) -- (5.2,0); + \draw [line width=1.95pt,color=Orange,dotted][rounded corners=2] ($(r4.north)-(1.3,.1)$) -- (5.2,-8); + + \node[text=blue,align=left] at (13,6) {As in the simplified version\\ this is a form of \\ similarity or compatibility measure \\ "Multiplicative Attention" \\}; + + \node [text=blue,align=left,right=of A2] (text1) {For each query\\ model learns\\ which key-value input \\ it should attend to}; + + \node [ below=34mm of text1] (formula1) {\color{black}{\boldmath$A(q2,K,V)=\Sigma_{i=1}^T[\frac{\mathrm{exp(q_2 . k_i^T)}}{\Sigma_iexp(q_2 . k_i^T)} v^i] +$}}; + \node[text=RoyalPurple,below=3mm of formula1,align=left,font=\fontsize{12pt}{15pt}\selectfont] (sm) {Softmax\\}; + + \node[text=RoyalPurple,align=left,font=\fontsize{12pt}{15pt}\selectfont] at (1,-3.5) {Current input\\ "query"\\}; + + \node at ($(k2)!.5!(q2)$) [text=blue,left=25mm of k2,font=\fontsize{52pt}{35pt}\selectfont]{\ldots}; + + \node[above,font=\huge\bfseries] at (current bounding box.north) {Self-Attention Mechanism}; + +{% endraw %} +``` +![image-title-here](../images/selfattention.png){:class="img-responsive"} + +{% highlight OCaml %} + +[fill=blue!35,fill opacity=.5445] +\matrix[left delimiter=(,right delimiter=)] (magic) [matrix of nodes] +{ +0 & 1 & 2 & 3 & 4\\ +0 & 0 & 5 & 6 & 7\\ +0 & 0 & 0 & 8 & 9\\ +0 & 0 & 0 & 0 & 10\\ +0 & 0 & 0 & 0 & 0\\ +}; +\fill +(magic-1-1.north west) |- (magic-5-5.north east) |- +(magic-5-1.south west) |- (magic-5-5.east) |- +(magic-5-5.south) |- (magic-5-5.east)|- +(magic-5-1.north west) |- (magic-5-5.west)|- +(magic-5-2.north west) |- (magic-5-5.west)|- +(magic-5-3.north west) |- (magic-5-5.west)|- +(magic-5-4.north west) |- (magic-5-5.west)|- +(magic-5-5.north west) |- (magic-5-5.east); + +{% endhighlight OCaml %} + +![image-title-here](../images/matrixhighlighter.png){:class="img-responsive"} + diff --git a/_posts/2023-03-25-LearningCoq.md b/_posts/2023-03-25-LearningCoq.md new file mode 100644 index 0000000000000..fb194a622b4f3 --- /dev/null +++ b/_posts/2023-03-25-LearningCoq.md @@ -0,0 +1,100 @@ +--- +layout: post +title: Learning Coq(Work in Progress) +published: true +--- + +I started dabbling in Programs and proofs recently by using Spacemacs and its +Coq layer. Since I didn't study this subject before I start by setting up and +using the tools. + +My primary learning material is [CS6225 Programs and Proofs @ IITM (Coq + F*)](https://www.youtube.com/playlist?list=PLt0HgEXFOHdkfd7phdKKmTIuwHEvPX0qb) +but I will add more once I am past the preliminary stage. + +# Install Coq + +During the installation of _coq_ using _homebrew_ an issue was identified which +I reported [here](https://stackoverflow.com/questions/75843422/coqc-does-not-find-findlib-conf/75857649#75857649) +and it seems to have been fixed promptly. + +_coqc_ the compiler works like when it is invoked by C-c C-RET + +There is something pleasing about this IDE. + +# IDE setup + +The colour scheme of Spacemacs is very pleasing. I think. So Spacemacs is what I +like to use whenever possible. The other options are VSCode and CoqIDE. + +## Install Proof-general + +[Proof-general](https://proofgeneral.github.io) is a generic Emacs interface for proof assistants. + +Commands are M-x package-install and install-package + +![image-title-here](../images/coq3.png){:class="img-responsive"} + + +## .spacemacs configuration + +Enable the _coq_ layer like this. + +{% highlight lisp %} + + ;; List of configuration layers to load. + dotspacemacs-configuration-layers + '(racket + javascript + ;; ---------------------------------------------------------------- + ;; Example of useful layers you may want to use right away. + ;; Uncomment some layer names and press `SPC f e R' (Vim style) or + ;; `M-m f e R' (Emacs style) to install them. + ;; ---------------------------------------------------------------- + auto-completion + ;; better-defaults + emacs-lisp + ;; git + helm + (haskell :variables + haskell-enable-hindent-style "johan-tibell") + coq + ;; lsp + ;; markdown + multiple-cursors + ;; org + (shell :variables + shell-default-height 30 + shell-default-position 'bottom + shell-default-shell 'eshell) + ;; spell-checking + ;; syntax-checking + ;; version-control + treemacs) +{% endhighlight %} + +I added _add-hook_ to activate _Company-coq_ to enable auto-completion features. + +{% highlight lisp %} + +(defun dotspacemacs/user-config () + "Configuration for user code: +This function is called at the very end of Spacemacs startup, after layer +configuration. +Put your configuration code here, except for variables that should be set +before packages are loaded." +( global-company-mode t) +(setq-default + dotspacemacs-configuration-layers + '((auto-completion :variables + spacemacs-default-company-backends '(company-files company-capf)))) +(add-hook 'coq-mode-hook #'company-coq-mode) +) + +{% endhighlight %} + +The _auto-completion_ feature is powerful enough. + +![image-title-here](../images/coq2.png){:class="img-responsive"} + + +![image-title-here](../images/coq1.png){:class="img-responsive"} diff --git a/_posts/2023-04-20-Lexer.md b/_posts/2023-04-20-Lexer.md new file mode 100644 index 0000000000000..a5c3327c954db --- /dev/null +++ b/_posts/2023-04-20-Lexer.md @@ -0,0 +1,213 @@ +--- +layout: post +title: Lexer in Haskell and Racket +published: true +--- + + +# Crafting Interpreters + +![image-title-here](../images/CraftingInterpreters.jpg){:class="img-responsive"} + +This is the book I am reading and I started to code the interpreter using JDK19's preview features. +The book though uses an older version of the JDK. + +The preview features introduce _record_ pattern matching but soon I realized languages like Haskell and OCaml +were built with such pattern matching in mind. + +So I decided to code part of it using Haskell. And then I started to code the same parts using Racket +which is a far more lofty goal. I think. + + +# Explanation(TODO) + + As I mention every time I am still learning Functional Programming principles. So I put together + this code based on data structures generally used for such purposes. The intention is to drive + this test based on structures like these. + +# Haskell Code + +I will refactor this code and probably create a Git repo. + +{% highlight haskell %} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE OverloadedStrings #-} +import Data.Char ( isSpace, isDigit, isAlpha, isAlphaNum ) +import Data.Either +import Data.Maybe + +data Operator i = Literal i| Plus | Minus | Div + | InvalidChar i + deriving (Show, Eq) + +data Error i e = Error + { errorOffset :: Offset + , error :: ErrorType i e + } deriving (Eq, Show) + + +data ErrorType i e + = EndOfInput + | Unexpected i + | Expected i e + | ExpectedEndOfFile i + | BeginningOfInput i + | Empty + deriving (Eq, Show) + +type Offset = Int + +operator :: Char -> Operator Char +operator c | c == '+' = Plus + | c == '-' = Minus + | c == '/' = Div + +scanner :: Offset -> ErrorType Offset Char-> String -> Either [Error Offset (ErrorType Offset Char)] [Operator Char] +scanner offset error (x:xs) + | isDigit x = tokenize offset error (Literal x) + | x == '+' = tokenize offset error (operator x) + | otherwise = tokenize offset error (InvalidChar x) + where + tokenize offset error t = + case scanner offset error xs of + Left err -> Left err + Right tokens -> Right (t : tokens) +scanner offset error "" = Right [] + + +main :: IO () +main = do + let result = scanner 0 (BeginningOfInput 0) "1+x2" + case result of + Left err -> putStrLn $ "Error: " ++ show err + Right tokens -> putStrLn $ "Tokens: " ++ show tokens + +{% endhighlight %} + +The result is this. The error condition is not tested. + +{% highlight haskell %} +λ> :main +Tokens: [Literal '1',Plus,InvalidChar 'x',Literal '2'] +{% endhighlight %} + +# Typed Racket code + +I read that Racket has many levels and types of languages and here I am using Typed Racket. + +I have to note that this is my first ever Racket code and the intention is to port my haskell to Typed Racket. +So I have explained what I learnt using pieces of code. Neither the Racket code or Haskell code implementes +the entire interpreter or even the lexer. The git repo. will have the entire code if I manage to learn +Racket sufficiently to code the whole interpreter in the book. + +## Step 1 + +This compiles without errors. But the code is not tested. I will add more test code while I refactor. +In this case I have a feeling that the code is more verbose than the equivalent Haskell code. + +{% highlight racket %} +#lang typed/racket +(require racket/list) + +(: operator : Char -> (Operator Char)) +(define (operator c ) + (match c + ['+' Plus] + ['-' Minus] + ['/' Div])) + +(struct (i) Literal ([v : i]) + #:transparent + ) + + +(struct Plus ()) +(struct Minus ()) +(struct Div ()) +(struct (i) InvalidChar ([v : i])) + +(define-type (Operator i ) (U Plus Minus Div (Literal i) (InvalidChar i))) + +(struct EndOfInput()) +(struct Empty() ) +(struct (i) Unexpected([val : i])) +(define-type (Expected i e) (U Integer Char)) +(struct (i) ExpectedEndOfFile([val : i])) +(struct (i) BeginningOfInput ([val : i])) + +(define-type (ErrorType i e) + (U Empty EndOfInput + ( Unexpected i) + ( Expected i e) + ( ExpectedEndOfFile i) + ( BeginningOfInput i))) + +(define-type (Error i e) (U Integer + (ErrorType i e))) +(define (parse-exp e) + (match e + [(? number?) (Literal e)] + )) + + +(define input 2) + +(parse-exp input) + +{% endhighlight %} + +### Simple test + +{% highlight racket %} +(define (parse-exp e) + (match e + [(? number?) (Literal e)] + )) + + +(define input 2) + +(parse-exp input) + +{% endhighlight %} + +But this prints {% highlight racket %}#{% endhighlight %} without the value. So I have to learn how to print a _struct_. + + +{% highlight racket %} +(struct (i) Literal ([v : i]) + #:transparent + ) + + {% endhighlight %} + +This is the Racket way of printing the _struct_ with the value. + +{% highlight racket %}(Literal 2){% endhighlight %} + +## Step 2 + +This took longer than I expected as Racket seems to be very different than even Haskell. Many iterations later with help +from Racket experts I was able to understand how to code the function. + +{% highlight racket %} +(: parse-exp (-> (U (Listof Char) ) + (Listof(U (ErrorType Integer Char) (Operator Char) )))) +(define (parse-exp lst ) + (match lst + ['() (list(EndOfInput))] + [(list x xs ...) + (match x + [(? char-numeric?) (cons (Literal x) (parse-exp xs))] + [#\+ (cons (Plus) (parse-exp xs)) ] + [_ (list (Unexpected 0)) ])])) + + + {% endhighlight %} + +The type of this function is only slightly different than the Haskell code and the error conditions +are not tested fully. The _Offset_ of each charater is not tracked but that should not be difficult. + +The output is {% highlight racket %}(list (Literal #\1) # (Literal #\2) #){% endhighlight %} + diff --git a/_posts/2023-06-12-Exploring Transformer Architecture.md b/_posts/2023-06-12-Exploring Transformer Architecture.md new file mode 100644 index 0000000000000..3724bd8612579 --- /dev/null +++ b/_posts/2023-06-12-Exploring Transformer Architecture.md @@ -0,0 +1,175 @@ +--- +layout: post +title: Exploring Transformer Architecture (Work in Progress) +published: true +--- + +I set about learning the Transformer Architecture from first principles. Before long I realized +I need to read many Arxiv research papers starting with ['Attention is All you need'](https://arxiv.org/abs/1706.03762). +But there are other older foundational papers dealing with this. + +The key confounding aspect for me in the field of Machine Learning is the math. One could watch several videos +like I did and take notes. There is one other way to develop intuition. If one is familiar with a framework +then a few algorithms can be coded and executed either on CPU or GPU as the case may be. + +I believe both these efforts should be parallel to understand the algorithm and the math. + +But before understanding the _Transformer_ architecture we need some intuition of the architectures +that it improves like _Recurrent Neural Nets_. So here I start with an RNN, a simple LSTM to generate +characters. There are several steps before reaching the target Transformer Architecture. + + +# Tensorflow Code + +## tf.lookup.StaticHashTable + +There are simpler ways to store data in _lookup_ datastructures but there is also a _HashTable_ +which I use to store the individual character in the text and the associated indexes. + +There is one for looking up the index of a character and one for looking up the character with +an index. + +{% highlight Python %} + +indextoelem = tf.lookup.StaticHashTable( + initializer=tf.lookup.KeyValueTensorInitializer( + keys=tf.strings.as_string([idx for idx, inp in enumerate(elem)]), + values=elem, + ), + default_value=tf.constant('-1'), + name="indextoelem" +) +{% endhighlight %} + + +## Part I + +The first part can be coded using our Tensorflow knowledge as this is about data preparation. My code +is not based on any established pattern like Part 2. It may not be even idiomatic. This is a learning exercise and +the code is constructed by referring to the TensorFlow API. + +{% highlight Python %} +import datetime + +import tensorflow as tf +from keras import Sequential +from keras.layers import Embedding +from tensorflow import keras + +input = tf.io.read_file("/PycharmProjects/TensorFlow2/shakespeare.txt") +input = tf.strings.strip(input) +input = tf.strings.regex_replace(input,' +', '') +input = tf.strings.regex_replace(input,'\n', '') +length = int(tf.strings.length(input)) + +vocab = tf.strings.unicode_split_with_offsets(input, 'UTF-8') +elem,idx = tf.unique(vocab[0]) + +table = tf.lookup.StaticHashTable( + initializer=tf.lookup.KeyValueTensorInitializer( + keys=elem, + values=tf.constant([idx for idx, inp in enumerate(elem)]), + ), + default_value=tf.constant(-1), + name="elemtoindex" +) + +indextoelem = tf.lookup.StaticHashTable( + initializer=tf.lookup.KeyValueTensorInitializer( + keys=tf.strings.as_string([idx for idx, inp in enumerate(elem)]), + values=elem, + ), + default_value=tf.constant('-1'), + name="indextoelem" +) + +def random_sample(text): + rand = tf.random.uniform(shape=[], minval=1, maxval=length - 201) + start = int(rand) + # print(f'Start={int(rand)} Length={length} End={start + 200 + 1}') + return tf.strings.substr(text,start, 201, unit='BYTE') + +global samplelist,reversesamplelist +samplelist = [] +reversesamplelist = [] + +def reverse_map_fn(bytes): + reversesamplelist.append(indextoelem.lookup(tf.strings.as_string(bytes))) + # print(f'Reverse map{reversesamplelist}') + return bytes + +def map_fn(bytes): + samplelist.append(table.lookup(bytes)) + return bytes + +def draw_random_sample(): + for i in range(50): + sample = random_sample(input) + split_sample = tf.strings.bytes_split(sample) + # print(f'Sample {split_sample}') + tf.map_fn(map_fn, tf.strings.bytes_split(split_sample)) + global samplelist + reverse_map(samplelist[:-1]) + X,y = (tf.stack(samplelist[:-1]),tf.stack(samplelist[1:])) + samplelist = [] + yield X,y + +def reverse_map(X): + tf.map_fn(reverse_map_fn, X) + +logdir = "/Users/anu/PycharmProjects/TensorFlow2/logs/scalars/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") +tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir) + +EMBEDDING_DIM = 128 +HIDDEN_DIM = 1024 +INPUT_DIM=len(elem) +OUTPUT_DIM=len(elem) +EPOCHS = 200 +{% endhighlight %} + +## Part 2 + +This is the part that can be copied if one understands most of the meaning of the network +architecture. + +{% highlight Python %} + +def build_model(input_dim,embedding_dim,hidden_dim,output_dim): + # define model + model = Sequential() + model.add(Embedding(input_dim, embedding_dim,batch_input_shape=[50,None])) + model.add(keras.layers.LSTM(hidden_dim,return_sequences= True,stateful=True, + recurrent_initializer=tf.keras.initializers.GlorotNormal() + )) + model.add(keras.layers.Dense(output_dim)) + print(model.summary()) + return model + + +loss = tf.losses.SparseCategoricalCrossentropy(from_logits=True) + +model = build_model(INPUT_DIM, EMBEDDING_DIM,HIDDEN_DIM,OUTPUT_DIM) +model.compile(tf.keras.optimizers.Adam(learning_rate=1e-3), + loss=loss) +print(model.summary()) + +train_dataset = tf.data.Dataset.from_generator( + draw_random_sample, + output_types=(tf.int32, tf.int32), + output_shapes=((200,1), (200,1))).batch(50) + +history = model.fit(train_dataset,epochs=EPOCHS,callbacks=[tensorboard_callback],) +{% endhighlight %} + +# Graph of epoch loss + +I expected the epoch loss to be driven down lower than what the graph shows. This can be investigated. + +![image-title-here](../images/epochloss.png){:class="img-responsive"} + +# Self-Attention +Explanation follows. This diagram is drawn based on [Sebastian Raschka's](https://sebastianraschka.com/blog/2021/dl-course.html#l19-self-attention-and-transformer-networks) +explanatory diagram. I use Tikz and code is [here](https://mohanr.github.io/TikzNotes/) + +![image-title-here](../images/selfattention.png){:class="img-responsive"} + diff --git a/_posts/2023-07-28-ToyGPT.md b/_posts/2023-07-28-ToyGPT.md new file mode 100644 index 0000000000000..388af785383b6 --- /dev/null +++ b/_posts/2023-07-28-ToyGPT.md @@ -0,0 +1,880 @@ +--- +layout: post +title: Toy Generatively Pretrained Transformer (GPT) +published: true +--- + +Yet another attempt to closely replicate the popular PyTorch code of [https://karpathy.ai/zero-to-hero.html](Karpathy) +close on the heels of the exploration of basic [https://mohanr.github.io/Exploring-Transformer-Architecture/](Transformers). + +I have ported the code to TensorFlow and may have caused a bug or two which are not evident. But the code works as I +expected. But this is just the beginning stage. At this point there is no GPT. + +# tl;dr + +1. The flow of the article mirrors the way the code is developed iteratively +2. I haven't coded this to use batches at this time hoping to add it later. Nor have I split the dataset to + get a validation set. +3. In many cases I had to execute the PyTorch code to understand the shapes. Yet some shape here or there may + be wrong but the code executes without errors. +4. The final code is [here](https://github.com/mohanr/Generative-Pretrained-Transformer) and further improvements + will be committed. +5. I haven't specifically trained using a GPU but with some some simple code changes it can be. + +# Stage 1 + +The dataset used here is not cleaned. There are characters and numbers in it. So _newlines_ are +removed but data can be properly fed at a later stage. + +{% highlight python %} + +import tensorflow as tf +import tensorflow_probability as tfp +from keras.layers import Embedding + +input = tf.io.read_file("/Users/anu/PycharmProjects/TensorFlow2/shakespeare.txt") +input = tf.strings.strip(input) +input = tf.strings.regex_replace(input,' +', '') +input = tf.strings.regex_replace(input,'\n', '') +length = int(tf.strings.length(input)) + +vocab = tf.strings.unicode_split_with_offsets(input, 'UTF-8') +elem,idx = tf.unique(vocab[0]) +vocab_size = len(elem) +print(f'Size of vocabulary={vocab_size}') + +table = tf.lookup.StaticHashTable( + initializer=tf.lookup.KeyValueTensorInitializer( + keys=elem, + values=tf.constant([idx for idx, inp in enumerate(elem)]), + ), + default_value=tf.constant(-1), + name="elemtoindex" +) + +indextoelem = tf.lookup.StaticHashTable( + initializer=tf.lookup.KeyValueTensorInitializer( + keys=tf.strings.as_string([idx for idx, inp in enumerate(elem)]), + values=elem, + ), + default_value=tf.constant('-1'), + name="indextoelem" +) + +def random_sample(text): + rand = tf.random.uniform(shape=[], minval=1, maxval=length - 201) + start = int(rand) + # print(f'Start={int(rand)} Length={length} End={start + 200 + 1}') + return tf.strings.substr(text,start, 201, unit='BYTE') + +global samplelist,reversesamplelist +samplelist = [] +reversesamplelist = [] + +def reverse_map_fn(bytes): + reversesamplelist.append(indextoelem.lookup(tf.strings.as_string(bytes))) + return bytes + +def map_fn(bytes): + samplelist.append(table.lookup(bytes)) + return bytes + +def draw_random_sample(block_size): + sample = tf.strings.substr(input,0, block_size, unit='BYTE') + split_sample = tf.strings.bytes_split(sample) + tf.map_fn(map_fn, tf.strings.bytes_split(split_sample)) + global samplelist + reverse_map(samplelist[:-1]) + X,y = (tf.stack(samplelist[:-1]),tf.stack(samplelist[1:])) + samplelist = [] + return X,y + +def reverse_map(X): + tf.map_fn(reverse_map_fn, X) + +X,y = draw_random_sample(9) +print(reversesamplelist) +vocab_size = len(elem) + +def decode(idx): + return idx,indextoelem.lookup( + tf.strings.as_string([inp for inp, inp in enumerate(idx)])) + +{% endhighlight %} + +The model sub-class is simple and accepts the entire sequence of characters everytime. +But we are predicting the next character only based on the previous character. This will +be addressed later. + +{% highlight python %} +tf.Tensor([0], shape=(1,), dtype=int64) +tf.Tensor([0 8], shape=(2,), dtype=int64) +tf.Tensor([ 0 8 43], shape=(3,), dtype=int64) +tf.Tensor([ 0 8 43 67], shape=(4,), dtype=int64) +tf.Tensor([ 0 8 43 67 5], shape=(5,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31], shape=(6,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31], shape=(7,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21], shape=(8,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51], shape=(9,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51 23], shape=(10,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51 23 2], shape=(11,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51 23 2 2], shape=(12,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51 23 2 2 56], shape=(13,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51 23 2 2 56 1], shape=(14,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51 23 2 2 56 1 55], shape=(15,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51 23 2 2 56 1 55 36], shape=(16,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51 23 2 2 56 1 55 36 12], shape=(17,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51 23 2 2 56 1 55 36 12 27], shape=(18,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51 23 2 2 56 1 55 36 12 27 31], shape=(19,), dtype=int64) +tf.Tensor([ 0 8 43 67 5 31 31 21 51 23 2 2 56 1 55 36 12 27 31 38], shape=(20,), dtype=int64) +{% endhighlight %} + +The is the TensorFlow code that should produce the same result as the original PyTorch code. +The loop structures are all different though. + +{% highlight python %} + +class BigramModel(tf.keras.Model): + def __init__(self,vocab_size): + super().__init__() + self.token_embedding_table = Embedding(vocab_size,vocab_size) + def call(self,idx,targets=None): + logits=self.token_embedding_table(idx) + if targets is None: + loss = None + else: + bce = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) + loss = bce(targets,tf.squeeze(logits)).numpy() + return logits, loss + def generate(self,idx,max_new_tokens): + i = tf.constant(0) + c = lambda i, d: tf.less(i, max_new_tokens) + + def b(i, idx): + logits,loss = self(idx) + logits = logits[-1] + # print(f'Shape of logits is {tf.shape(logits)}') + probs = tf.nn.softmax(logits) + # print(f'Shape of probs is {tf.shape(probs)}') + idx_next = tfp.distributions.Multinomial(total_count=1,probs=probs) + # print(f'Shape of sample is {tf.shape(idx_next.sample(1))}') + idx = tf.concat([idx, + tf.reshape(tf.squeeze( + tf.cast(tf.where( + tf.reshape(idx_next.sample(1),(vocab_size))),tf.int64)) + ,(1,))],0) + return tf.add(i, 1), idx + + _, idx1 = tf.while_loop(c, b, loop_vars=[i, idx]) + return idx1 + +{% endhighlight %} + + + +## Last time step + +Only the last dimension in this output is considered for predictng the next _one_ character +at this time. + +So in this example the last dimension is highlighted. + +{% highlight python %} +x = tf.random.uniform((4,4)) +print(x) +print(x[-1]) +{% endhighlight %} + +{% highlight python %} +tf.Tensor( +[[0.6044643 0.9598156 0.84220576 0.6529906 ] + [0.03485656 0.1756084 0.9860773 0.8582853 ] + [0.45344257 0.6370505 0.9529482 0.4074465 ] + [0.27584124 0.44224763 0.7260096 0.16439259]], shape=(4, 4), dtype=float32) + {% endhighlight %} + +![image-title-here](../images/matrix.png){:class="img-responsive"} + +## Generation + +This generates these 20 characters. + +{% highlight python %} + +m = BigramModel(len(elem)) +out,loss = m(tf.reshape(X,(1,8)),tf.reshape(y,(1,8))) +idx, generation = decode(m.generate(tf.zeros((1,),tf.int64),20)) +print(["".join(i) for i in generation.numpy()[:].astype(str)]) + +{% endhighlight %} + + +['1', 'T', '0', 'V', 'S', '.', "'", '.', 'n', 'U', 't', '8', 'l', 'M', "'", 'T', 'g', 'b', 'N', 'i', 'h'] + +## Training Stage 1 + +This is the entire code again as small changes have been made to train. + +{% highlight python %} + +import tensorflow as tf +import tensorflow_probability as tfp +from keras.layers import Embedding + +input = tf.io.read_file("/Users/anu/PycharmProjects/TensorFlow2/shakespeare.txt") +input = tf.strings.strip(input) +input = tf.strings.regex_replace(input,' +', '') +input = tf.strings.regex_replace(input,'\n', '') +length = int(tf.strings.length(input)) + +vocab = tf.strings.unicode_split_with_offsets(input, 'UTF-8') +elem,idx = tf.unique(vocab[0]) +vocab_size = len(elem) +print(f'Size of vocabulary={vocab_size}') +block_size = 9 +table = tf.lookup.StaticHashTable( + initializer=tf.lookup.KeyValueTensorInitializer( + keys=elem, + values=tf.constant([idx for idx, inp in enumerate(elem)]), + + ), + default_value=tf.constant(-1), + name="elemtoindex" +) + +indextoelem = tf.lookup.StaticHashTable( + initializer=tf.lookup.KeyValueTensorInitializer( + keys=tf.strings.as_string([idx for idx, inp in enumerate(elem)]), + values=elem, + + ), + default_value=tf.constant('-1'), + name="indextoelem" +) + +def random_sample(text): + rand = tf.random.uniform(shape=[], minval=1, maxval=length - 201) + start = int(rand) + # print(f'Start={int(rand)} Length={length} End={start + 200 + 1}') + return tf.strings.substr(text,start, 201, unit='BYTE') + +global samplelist,reversesamplelist +samplelist = [] +reversesamplelist = [] + +def reverse_map_fn(bytes): + reversesamplelist.append(indextoelem.lookup(tf.strings.as_string(bytes))) + return bytes + +def map_fn(bytes): + samplelist.append(table.lookup(bytes)) + return bytes + +def random_sample(text,block_size): + rand = tf.random.uniform(shape=[], minval=1, maxval=length - (block_size + 1)) + start = int(rand) + # print(f'Start={int(rand)} Length={length} End={start + block_size + 1}') + return tf.strings.substr(text,start, block_size, unit='BYTE') + +def draw_random_sample(block_size): + sample = random_sample(input,block_size) + split_sample = tf.strings.bytes_split(sample) + tf.map_fn(map_fn, tf.strings.bytes_split(split_sample)) + global samplelist + reverse_map(samplelist[:-1]) + X,y = (tf.stack(samplelist[:-1]),tf.stack(samplelist[1:])) + samplelist = [] + return X,y + +def reverse_map(X): + tf.map_fn(reverse_map_fn, X) + +X,y = draw_random_sample(block_size) +print(reversesamplelist) +vocab_size = len(elem) + +def decode(idx): + return idx,indextoelem.lookup( + tf.strings.as_string([inp for inp, inp in enumerate(idx)])) + + +class BigramModel(tf.keras.Model): + def __init__(self,vocab_size): + super().__init__() + self.token_embedding_table = Embedding(vocab_size,vocab_size) + def call(self,idx,targets=None): + logits=self.token_embedding_table(idx) + if targets is None: + loss = None + else: + bce = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) + # loss = bce(targets,tf.squeeze(logits)).numpy() + loss = bce(targets, tf.squeeze(logits)) + return logits, loss + def generate(self,idx,max_new_tokens): + i = tf.constant(0) + c = lambda i, d: tf.less(i, max_new_tokens) + + def b(i, idx): + logits,loss = self(idx) + logits = logits[-1] + # print(f'Shape of logits is {tf.shape(logits)}') + probs = tf.nn.softmax(logits) + # print(f'Shape of probs is {tf.shape(probs)}') + idx_next = tfp.distributions.Multinomial(total_count=1,probs=probs) + # print(f'Shape of sample is {tf.shape(idx_next.sample(1))}') + idx = tf.concat([idx, + tf.reshape(tf.squeeze( + tf.cast(tf.where( + tf.reshape(idx_next.sample(1),(vocab_size))),tf.int64)) + ,(1,))],0) + return tf.add(i, 1), idx + + _, idx1 = tf.while_loop(c, b, loop_vars=[i, idx]) + return idx1 + + +m = BigramModel(len(elem)) +out,loss = m(tf.reshape(X,(1,block_size -1)),tf.reshape(y,(1,block_size-1))) +idx, generation = decode(m.generate(tf.zeros((1,),tf.int64),20)) +print(["".join(i) for i in generation.numpy()[:].astype(str)]) + +optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3) + +epochs = 2 +for epoch in range(epochs): + print("\nStart of epoch %d" % (epoch,)) + + for step in range(20000): + with tf.GradientTape() as tape: + x,y = draw_random_sample(block_size) + logits,loss = m(tf.reshape(x, (1, block_size - 1)), tf.reshape(y, (1, block_size - 1))) + + grads = tape.gradient(loss, m.trainable_weights) + + # Run one step of gradient descent by updating + # the value of the variables to minimize the loss. + optimizer.apply_gradients(zip(grads, m.trainable_weights)) + + # Log every 200 batches. + if step % 200 == 0: + print( + "Training loss at step %d: %.4f" + % (step, float(loss)) + ) + print("Seen so far: %s samples" % ((step + 1))) + +_, generation = decode(m.generate(tf.zeros((1,),tf.int64),20)) +print(["".join(i) for i in generation.numpy()[:].astype(str)]) + +{% endhighlight %} + +# Matrix multiplication trick using triangular matrix + +I am reusing this code I contributed to [Stackoverflow](https://stackoverflow.com/questions/56159487/transform-an-array-into-a-matrix-with-its-elements-filling-the-upper-triangle-of/56183515#56183515) years back +with appropriate changes introduced by TensorFlow 2 API. This code can be refactored and improved but at this time it works. + +What does the resulting matrix look like ? The logic if the code now creates a lower triangular matrix( or upper triangular matrix, if we want it) +like this. + +![image-title-here](../images/matrixhighlighter.png){:class="img-responsive"} + + +{% highlight python %} + +x = tf.constant(tf.ones(10,)) +ones = tf.ones((5,5),dtype=tf.int64) #size of the output matrix +mask_a = tf.linalg.band_part(ones, -1, 0) # Upper triangular matrix of 0s and 1s +mask_b = tf.linalg.band_part(ones, 0, 0) # Diagonal matrix of 0s and 1s +mask = tf.subtract(mask_a, mask_b) # Mask of upper triangle above diagonal + +zero = tf.constant(0, dtype=tf.int64) +non_zero = tf.not_equal(mask, zero) #Conversion of mask to Boolean matrix +indices = tf.where(non_zero) # Extracting the indices of upper trainagle elements + +out = tf.SparseTensor(indices,x,dense_shape=tf.cast((5,5),dtype=tf.int64)) +dense = tf.slice(tf.sparse.to_dense(out), [1, 0], [3, 3]) +tf.print(dense) + +dense = dense / tf.reduce_sum(dense,1,keepdims=True) +print(dense) + +random_ints = tf.random.uniform(shape=(3,2), minval=1., maxval=5.) +print(random_ints) + +print(tf.matmul(dense,random_ints)) +{% endhighlight %} + +The output is this. + +{% highlight python %} + +[[1 0 0] + [1 1 0] + [1 1 1]] +tf.Tensor( +[[1. 0. 0. ] + [0.5 0.5 0. ] + [0.33333334 0.33333334 0.33333334]], shape=(3, 3), dtype=float32) +tf.Tensor( +[[2.1591725 2.7532902] + [3.748231 4.6269817] + [2.0407896 2.2444978]], shape=(3, 2), dtype=float32) +tf.Tensor( +[[2.1591725 2.7532902] + [2.9537017 3.690136 ] + [2.6493979 3.2082565]], shape=(3, 2), dtype=float32) + +{% endhighlight %} + +The trick give us a matrix that has rows based on weighted averages like this. + + +![image-title-here](../images/matrixhighlighter2.png){:class="img-responsive"} + +# Self-attention + +Explanation will be added in due course as this code implements the first view of +self-attention. This is the test code executed separately. + +Please note that the elaborate example shown above can be shortened for our purposes to + +{% highlight python %} + +wei = tf.linalg.band_part(wei, -1, 0) + +{% endhighlight %} + +## Test code is this. + +{% highlight python %} +head_size = 16 + + +tf.random.set_seed(3227) +head_size = 16 +B,T,C = 1,8,32 # batch, time, channels + +x = tf.random.normal((B,T,C)) + +key = tf.keras.layers.Dense( head_size, input_shape=(32,), activation=None,use_bias=False) +query = tf.keras.layers.Dense(head_size,input_shape=(32,),activation=None,use_bias=False) +value = tf.keras.layers.Dense(head_size,input_shape=(32,), activation=None,use_bias=False) + +k = key(x) # (B, T, 16) +q = query(x) # (B, T, 16) + +wei = tf.matmul(q,tf.transpose(k,perm=[0,2,1])) +wei = tf.linalg.band_part(wei, -1, 0) +wei = tf.where( + tf.equal(wei,tf.constant(0, dtype=tf.float32)), + -np.inf, + wei) +wei = tf.nn.softmax(wei,-1) +print(wei) + +v = value(x) +out = tf.matmul(wei, v) +print(out.shape) + +{% endhighlight %} + +## Single-head self-attention + +After the code shown in the previous section is understood we can integrate it with the +main body like this. But from this point onwards I feel the shapes or matrices could have +introduced a bug or two even though the code executed without errors. Further investigation +is needed. Primarily the mechanism used to port the original Pytorch to TensorFlow is somewhat +arduous. + +Here I have shown only the relevant changes. I changed _block_size_ to _32_ and I also noticed +I could generate only upto _32_ characters after training. + +{% highlight python %} +head_size = 16 +dropout = 0.0 +n_embd = 32 +block_size = 32 + +class BigramModel(tf.keras.Model): + def __init__(self,vocab_size): + super().__init__() + self.token_embedding_table = Embedding(vocab_size,n_embd) + self.position_embedding_table = Embedding(block_size, n_embd) + self.sa_head = Head(n_embd) + self.lm_head = tf.keras.layers.Dense(vocab_size, input_shape=(n_embd,), activation=None, use_bias=False) + + + def call(self,idx,targets=None): + # print(f'idx in call is {idx} and shape is {tf.shape(idx)}') + B = 1 + if tf.size(tf.shape(idx)) == 1: + T = tf.shape(idx) + else: + T = tf.shape(idx)[1] + + tok_emb = self.token_embedding_table(idx) + pos_emb = self.position_embedding_table(tf.range(T)) + x = tf.add(tok_emb, tf.expand_dims(pos_emb,axis=0)) # (B,T,C) + # print(f'Shape of tf.add(tok_emb, pos_emb) is {tf.shape(x)}') + x = self.sa_head(x) # (B,T,C) + logits = self.lm_head(x) # (B,T,vocab_size) + + if targets is None: + loss = None + else: + bce = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) + # loss = bce(targets,tf.squeeze(logits)).numpy() + loss = bce(targets, tf.squeeze(logits)) + return logits, loss + def generate(self,idx,max_new_tokens): + i = tf.constant(0) + c = lambda i, d: tf.less(i, max_new_tokens) + + def b(i, idx): + # print(idx) + logits,loss = self(idx) + # print(f'Shape of logits is {tf.shape(logits)}') + logits = logits[:,-1,:] + probs = tf.nn.softmax(logits) + # print(f'Shape of probs is {tf.shape(probs)}') + idx_next = tfp.distributions.Multinomial(total_count=1,probs=probs) + idx = tf.concat([idx, + tf.reshape(tf.squeeze( + tf.cast(tf.where( + tf.reshape(idx_next.sample(1),(vocab_size))),tf.int64)) + ,(1,))],0) + return tf.add(i, 1), idx + + _, idx = tf.while_loop(c, b, loop_vars=[i, idx]) + # print(f'idx in generate is {idx}') + return idx + +class Head(tf.keras.Model): + def __init__(self, head_size): + super().__init__() + self.key = tf.keras.layers.Dense(head_size, input_shape=(n_embd,), activation=None, use_bias=False) + self.query = tf.keras.layers.Dense(head_size, input_shape=(n_embd,), activation=None, use_bias=False) + self.value = tf.keras.layers.Dense(head_size, input_shape=(n_embd,), activation=None, use_bias=False) + self.dropout = tf.keras.layers.Dropout(dropout) + + def call(self, x): + B = 1 + T = 8 + C = 32 + k = self.key(x) # (B, T, 16) + q = self.query(x) # (B, T, 16) + transpose = tf.transpose(k,perm=[0,2,1]) + matmul = tf.matmul(q,transpose) + wei = tf.divide(matmul, 1/tf.sqrt(tf.cast(C,tf.float32))) + tril = tf.linalg.band_part(wei, -1, 0) + tril = tf.where( + tf.equal(tril,tf.constant(0, dtype=tf.float32)), + -np.inf, + tril) + wei = tf.nn.softmax(tril,-1) + # print(wei) + + v = self.value(x) + out = tf.matmul(wei, v) + # print(f'Shape of wei is {tf.shape(out)}') + return out + +{% endhighlight %} +Even though there is no error and the loss goes down there is something missing here. + +{% highlight python %} + +Start of epoch 0 +Training loss at step 0: 4.3814 +Seen so far: 1 samples +Training loss at step 200: 3.0663 +Seen so far: 201 samples +Training loss at step 400: 3.7423 +Seen so far: 401 samples +Training loss at step 600: 3.2426 +Seen so far: 601 samples +Training loss at step 800: 3.7633 +Seen so far: 801 samples +Training loss at step 1000: 3.2032 +Seen so far: 1001 samples +Training loss at step 1200: 3.3238 +Seen so far: 1201 samples +Training loss at step 1400: 2.9144 +Seen so far: 1401 samples +Training loss at step 1600: 2.8216 +Seen so far: 1601 samples +Training loss at step 1800: 2.8043 +Seen so far: 1801 samples +Training loss at step 2000: 2.9060 +Seen so far: 2001 samples +Training loss at step 2200: 2.8439 +Seen so far: 2201 samples +Training loss at step 2400: 2.6887 +Seen so far: 2401 samples +Training loss at step 2600: 3.2501 +Seen so far: 2601 samples +Training loss at step 2800: 2.8547 +Seen so far: 2801 samples +Training loss at step 3000: 2.5416 +Seen so far: 3001 samples +Training loss at step 3200: 3.0969 +Seen so far: 3201 samples +Training loss at step 3400: 2.8165 +Seen so far: 3401 samples +Training loss at step 3600: 2.6081 +Seen so far: 3601 samples +Training loss at step 3800: 3.3640 +Seen so far: 3801 samples +Training loss at step 4000: 2.5854 +Seen so far: 4001 samples +Training loss at step 4200: 2.6794 +Seen so far: 4201 samples +Training loss at step 4400: 2.5642 +Seen so far: 4401 samples +Training loss at step 4600: 3.0458 +Seen so far: 4601 samples +Training loss at step 4800: 2.7516 +Seen so far: 4801 samples +Training loss at step 5000: 2.6254 +Seen so far: 5001 samples +Training loss at step 5200: 2.6835 +Seen so far: 5201 samples +Training loss at step 5400: 2.8002 +Seen so far: 5401 samples +Training loss at step 5600: 2.8748 +Seen so far: 5601 samples +Training loss at step 5800: 2.8769 +Seen so far: 5801 samples +Training loss at step 6000: 3.0199 +Seen so far: 6001 samples +Training loss at step 6200: 3.1819 +Seen so far: 6201 samples +Training loss at step 6400: 2.8952 +Seen so far: 6401 samples +Training loss at step 6600: 2.7828 +Seen so far: 6601 samples +Training loss at step 6800: 2.9310 +Seen so far: 6801 samples +Training loss at step 7000: 3.0834 +Seen so far: 7001 samples +Training loss at step 7200: 2.9211 +Seen so far: 7201 samples +Training loss at step 7400: 2.7214 +Seen so far: 7401 samples +Training loss at step 7600: 2.9595 +Seen so far: 7601 samples +Training loss at step 7800: 3.1095 +Seen so far: 7801 samples +Training loss at step 8000: 2.7729 +Seen so far: 8001 samples +Training loss at step 8200: 2.6387 +Seen so far: 8201 samples +Training loss at step 8400: 3.0497 +Seen so far: 8401 samples +Training loss at step 8600: 2.9283 +Seen so far: 8601 samples +Training loss at step 8800: 2.7154 +Seen so far: 8801 samples +Training loss at step 9000: 2.8881 +Seen so far: 9001 samples +Training loss at step 9200: 2.8348 +Seen so far: 9201 samples +Training loss at step 9400: 2.8528 +Seen so far: 9401 samples +Training loss at step 9600: 2.5401 +Seen so far: 9601 samples +Training loss at step 9800: 3.1643 +Seen so far: 9801 samples +['1', 'G', "'", 'n', 't', 'c', 'o', 'm', 'e', ',', 'I', 'f', 'A', 'n', 'd', 't', 'h', 'i', 'm', "'", 's', 'w', 'o', 't', 'h', 'i', 's', 'i', 'n', 'g', 'a', 't'] + +{% endhighlight %} + +### Debugging + +_block_size_ value is the context that we want to keep track of. But if we want to generate more than the _block_size_, + +{% highlight python %} + +pos_emb = self.position_embedding_table(tf.range(T)) + +{% endhighlight %} + +does not have that in its scope. So I clip the stream of characters like this. This fixes the problem. + +{% highlight python %} + + idx_cond = idx[-block_size:] + logits,loss = self(idx_cond) +{% endhighlight %} + +Last 2 can be selected like this example shows. + +![image-title-here](../images/boxes.png){:class="img-responsive"} + +## Multi-head attention + +At this stage I find that the TensorFlow code looks almost similar to the original Pytorch code +and it is easier to reason about. It also works without posing significant dificulties. + +The change to the _BigramModel_ is just one line when we introduce multiple heads of attention. +A diagram or two will make this clear. + +{% highlight python %} + +class BigramModel(tf.keras.Model): + def __init__(self,vocab_size): + super().__init__() + self.token_embedding_table = Embedding(vocab_size,n_embd) + self.position_embedding_table = Embedding(block_size, n_embd) + self.sa_head = MultiHeadAttention(n_head, head_size) #Head(n_embd) + self.lm_head = tf.keras.layers.Dense(vocab_size, input_shape=(n_embd,), activation=None, use_bias=False) +{% endhighlight %} + +The _class_ that implements it is this. _n_head_ is set to 4. I use my laptop's CPU and till this stage +it works. But if I tune these hyperparameters and make the network deeper I will need a GPU. + +{% highlight python %} + +class MultiHeadAttention(tf.keras.Model): + """ multiple heads of self-attention in parallel """ + + def __init__(self, num_heads, head_size): + super().__init__() + self.heads = [Head(head_size) for _ in range(num_heads)] + self.proj = tf.keras.layers.Dense(n_embd, input_shape=(n_embd,), activation=None, use_bias=False) + self.dropout = tf.keras.layers.Dropout(dropout) + + def call(self, x): + out = tf.concat([h(x) for h in self.heads],-1) + out = self.dropout(self.proj(out)) + return out +{% endhighlight %} + +I also changed the dataset(https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt) +and it seamlessly worked after that. + +{% highlight python %} + +input = tf.io.read_file("/Users/anu/PycharmProjects/TensorFlow2/input.txt") +length = int(tf.strings.length(input)) + +{% endhighlight %} + +The code prints properly now. + +{% highlight python %} + +_, generation = decode(m.generate(tf.zeros((1,),tf.int64),300)) + +array = np.array(["".join(i) for i in generation.numpy()[:].astype(str)]) +s = ''.join(array) +print(s) + +{% endhighlight %} + +The output is this. It is still not trained sufficiently but the loss goes down further than before. + +FERENIO: +Phath athill bof: aser EA E: Mureadest whhere De Ave Po. +YiH,Ud Pre SO S: Dos Ton Kosed I I I Hond I LO EcVAwe RURE: I H Kun Id Rhatlt I. +Pal Gasnt I, VU HE +VISNE + + + +KRING E ITIRIRCGOBEUUSESIMSIOFURI: Lop E: +Arse, +LOLOS: Khas Ame I I RI: +SO Ar-Bu'teo Ofshup Otm 3 Sunscand Reten, Cutxy Tou fl + +# Further architectural changes + +I followed the original Pytorch code closely and this is the final piece of the puzzle. A few changes +are introduced to fine-tune the networks. But we have to remember that I am training this using a GPU. +So the loss is lower than before now but the text is still gibberish. + +## Changes to the _BigramModel_ + +{% highlight python %} + + +class BigramModel(tf.keras.Model): + def __init__(self,vocab_size): + super().__init__() + self.token_embedding_table = Embedding(vocab_size,n_embd) + self.position_embedding_table = Embedding(block_size, n_embd) + # self.sa_head = MultiHeadAttention(n_head, head_size) #Head(n_embd) + self.blocks = Block(n_embd, n_head=n_head) + + self.ln_f = tf.keras.layers.LayerNormalization() # final layer norm + self.lm_head = tf.keras.layers.Dense(vocab_size, input_shape=(n_embd,), activation=None, use_bias=False) + + + def call(self,idx,targets=None): + # print(f'idx in call is {idx} and shape is {tf.shape(idx)}') + B = 1 + if tf.size(tf.shape(idx)) == 1: + T = tf.shape(idx) + else: + T = tf.shape(idx)[1] + + tok_emb = self.token_embedding_table(idx) + pos_emb = self.position_embedding_table(tf.range(T)) + x = tf.add(tok_emb, tf.expand_dims(pos_emb,axis=0)) # (B,T,C) + # print(f'Shape of tf.add(tok_emb, pos_emb) is {tf.shape(x)}') + x = self.blocks(x) # (B,T,C) + x = self.ln_f(x) # (B,T,C) + logits = self.lm_head(x) # (B,T,vocab_size) + +{% endhighlight %} + +## New Model classes + +{% highlight python %} + +class FeedFoward(tf.keras.Model): + """ a simple linear layer followed by a non-linearity """ + + def __init__(self, n_embd): + super().__init__() + self.net = tf.keras.Sequential( + layers=[ + tf.keras.layers.Dense(4 * n_embd, input_shape=(None,n_embd), activation=None, use_bias=False), + tf.keras.layers.ReLU(), + tf.keras.layers.Dense(n_embd, input_shape=(4 * n_embd,), activation=None, use_bias=False), + tf.keras.layers.Dropout(dropout) + ] + ) + + def call(self, x): + return self.net(x) + +class Block(tf.keras.Model): + """ Transformer block: communication followed by computation """ + + def __init__(self, n_embd, n_head): + # n_embd: embedding dimension, n_head: the number of heads we'd like + super().__init__() + head_size = n_embd // n_head + self.sa = MultiHeadAttention(n_head, head_size) + self.ffwd = FeedFoward(n_embd) + self.ln1 = tf.keras.layers.LayerNormalization() + self.ln2 = tf.keras.layers.LayerNormalization() + + def call(self, x): + x = tf.add(x , self.sa(self.ln1(x))) + x = tf.add(x , self.ffwd(self.ln2(x))) + return x + +{% endhighlight %} + +# Conclusion + +This exercise helped me understand most of the _Transformer_ architecture. But I coudln't code +this myself after reading research papers directly. I have to watch some videos or port code to TensorFlow. +I can only work with an existing knowledge base. +I will push the code to Git and look at other architectures as the field is advancing rapidly. + + + diff --git a/_posts/2023-08-19-Toy-Llama-Architecture.md b/_posts/2023-08-19-Toy-Llama-Architecture.md new file mode 100644 index 0000000000000..2f3e6c31b4195 --- /dev/null +++ b/_posts/2023-08-19-Toy-Llama-Architecture.md @@ -0,0 +1,346 @@ +--- +layout: post +title: Llama Architecture(GPT) +published: true +--- + +# tl;dr +1. The architecture is described in [Llama: Open and Efficient Foundation Language Models](http://arxiv.org/pdf/2302.13971.pdf) +2. The code is ported from PyTorch to TensorFlow 2.x using various references. (e.g) https://nn.labml.ai/transformers/rope/index.html + TensorFlow is very verbose and sometimes it poses difficulties as every line of PyTorch has to be ported. +4. In very few cases the code can be directly ported with minimal changes. But this situation isn't common. +5. The math supporting the algorithm is only partially understood. There are several research papers to read. +6. The RoPE embeddings and attention need more insight and explanation.In fact each section will need multiple + diagrams and descriptions. + + +# Batches + +{% highlight python %} +def random_sample(text,block_size): + rand = tf.random.uniform(shape=(batch_size,), minval=1, maxval=length - (block_size + 1),dtype=tf.int32) + return [tf.strings.substr(text,i, block_size, unit='BYTE') for i in rand] + +def draw_random_sample_batches(block_size): + sample = random_sample(input,block_size) + tf.map_fn(map_fn,tf.strings.bytes_split(sample)) + global samplelist + X = tf.stack([inp[ : -1] for inp in samplelist]) + y = tf.stack([inp[ 1 : ] for inp in samplelist]) + samplelist = [] + return X,y + +{% endhighlight %} + +# RMSNorm + +{% highlight python %} + +import numpy as np +import tensorflow as tf + + +class RMSNorm(tf.keras.Model): + + def __init__(self, layer_shape): + super(RMSNorm, self).__init__() + self.scale = tf.Variable(initial_value=np.ones(layer_shape), trainable=True,dtype=tf.float32) + + def call(self, x): + normalized_mat, norm = tf.linalg.normalize(x, axis=(1, 2)) + # print(f'Normalize {norm}') + rms = tf.multiply(norm , + tf.pow(tf.cast(tf.size(x[0]),tf.float32),-0.5)) + r = tf.divide(x , rms ) + return tf.multiply(self.scale[:tf.shape(x)[1], :] , r) + +{% endhighlight %} + +## TensorFlow Tests + +Simple tests like these are supported by TensorFlow. I have learnt to use the Python console from within PyCharm +as the tests are sometimes not recognized by the IDE. + +{% highlight python %} + +/opt/anaconda3/envs/tensorflow2/bin/python3 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pydev/pydevconsole.py" --mode=client --port=51221 +import sys; print('Python %s on %s' % (sys.version, sys.platform)) +sys.path.extend(['/Users/anu/PycharmProjects/illama']) +Python 3.7.10 (default, Feb 26 2021, 10:16:00) +Type 'copyright', 'credits' or 'license' for more information +IPython 7.24.1 -- An enhanced Interactive Python. Type '?' for help. +PyDev console: using IPython 7.24.1 +Python 3.7.10 (default, Feb 26 2021, 10:16:00) +[Clang 10.0.0 ] on darwin +runfile('/Users/anu/PycharmProjects/illama/testRotaryEmbedding.py', wdir='/Users/anu/PycharmProjects/illama') +Running tests under Python 3.7.10: /opt/anaconda3/envs/tensorflow2/bin/python3 +[ RUN ] testRotaryEmbedding.test_RotaryEmbedding +2023-08-25 12:29:24.820540: I tensorflow/core/platform/cpu_feature_guard.cc:151] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA +To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags. +16 +tf.Tensor([32 32], shape=(2,), dtype=int32) +tf.Tensor([ 1 32], shape=(2,), dtype=int32) +INFO:tensorflow:time(__main__.testRotaryEmbedding.test_RotaryEmbedding): 1.01s +I0825 12:29:25.825525 4538162624 test_util.py:2309] time(__main__.testRotaryEmbedding.test_RotaryEmbedding): 1.01s +[ OK ] testRotaryEmbedding.test_RotaryEmbedding +[ RUN ] testRotaryEmbedding.test_session +[ SKIPPED ] testRotaryEmbedding.test_session +---------------------------------------------------------------------- +Ran 2 tests in 1.008s +OK (skipped=1) +Process finished with exit code 0 +{% endhighlight %} + + +{% highlight python %} + + +import tensorflow as tf + +from Parameters import batch_size, block_size, n_embd + + +class testRMSNorm(tf.test.TestCase): + + def setUp(self): + super(testRMSNorm, self).setUp() + self.batch = tf.random.normal((batch_size, block_size, n_embd)) + + def test_RMSNormTest(self): + normalized_mat, norm = tf.linalg.normalize(self.batch, axis=(1, 2)) + ff_rms = tf.multiply(norm, + tf.pow(tf.cast(tf.size(self.batch[0]), tf.float32), -0.5)) + ffx = tf.Variable(tf.zeros_like(self.batch)) + print(tf.shape(ffx)) + for i in range(self.batch.shape[0]): + ffx[i, :, : ].assign(tf.divide(self.batch[i] , ff_rms[i])) + normalized_mat, norm = tf.linalg.normalize(self.batch, axis=(1, 2)) + print(tf.pow(norm,2)) + + # The values are close to 1024 but not close enough for default + # tolerance levels to pass the test. So it will fail unless + # I pass a different tolerance level. I believe this is a temporary + # fix until I understand the issue. + self.assertAllClose(tf.pow(norm,2), + tf.reshape( + tf.repeat([tf.constant(1024,tf.float32)], repeats=[4], axis=0), + (4,1,1)),50,50) + +tf.test.main() + +{% endhighlight %} + +There is one other issue that I don't understand fully. + +The test failed as the values are not close enough as per the tolerance. + +My questions are these. + +1. Is the RMSNorm algorithm correct ? Should I read any material/code to improve it if it is wrong ? +2. Can I use different tolerance levels to pass the test ? The API _ self.assertAllClose_ takes tolerance levels as + parameters.And if I pass 50( for example ) for the upper and lower limit the test passes. + +I can also ignore the failure as the values seem to be close. + +{% highlight python %} + +AssertionError: +Not equal to tolerance rtol=1e-09, atol=0.0001 +Mismatched value: a is different from b. +not close where = (array([0, 1, 2, 3]), array([0, 0, 0, 0]), array([0, 0, 0, 0])) +not close lhs = [1019.3864 1056.9813 1021.6669 1046.128 ] +not close rhs = [1024. 1024. 1024. 1024.] +not close dif = [ 4.6135864 32.981323 2.33313 22.128052 ] +not close tol = [0.00010102 0.00010102 0.00010102 0.00010102] +dtype = float32, shape = (4, 1, 1) +Mismatched elements: 4 / 4 (100%) +Max absolute difference: 32.981323 +Max relative difference: 0.03220832 + +{% endhighlight %} + +# Rotary Positional Embeddings( RoPE) + +Code used to test _RoPE_ embeddings + +{% highlight python %} + +from matplotlib import pyplot as plt +import tensorflow as tf +from Parameters import n_embd +from RotaryPositionalEmbeddings import RotaryPositionalEmbeddings + + +def rotaryPositionalEmbeddingsTest(): + K = 3 + rotary_emb = RotaryPositionalEmbeddings() + R = rotary_emb.rotary_matrix(tf.pow(K,2),n_embd) + fig, ax = plt.subplots(K, K, figsize=(K * 3, K * 4)) + + for i in range(K): + for j in range(K): + ax[i, j].imshow(R[i * K + j, :, :]) + ax[i, j].set_title(f'rotation at {i * K + j}') + # plt.show() + plt.savefig("rotraryembeddings.png") + +if __name__ == "__main__": + rotaryPositionalEmbeddingsTest() + +{% endhighlight %} + +# RoPE + +{% highlight python %} + +import tensorflow as tf +import numpy as np + +class RotaryPositionalEmbeddings(tf.keras.Model): + + def __init__(self): + super(RotaryPositionalEmbeddings, self).__init__() + + def rotary_matrix( self,block_size, embedding_dim): + R = tf.Variable(tf.zeros((block_size, embedding_dim, embedding_dim))) + i = tf.constant(0) + p_i = tf.constant(0) + neg_2 = tf.constant(-2) + emb = lambda i, d: tf.less(i, int(tf.divide(embedding_dim , 2) - 1)) + p = lambda p_i, d: tf.less(p_i, block_size ) + print(int(tf.divide(embedding_dim , 2))) + def position(p_i, p_idx): + def embedding(i, idx): + theta = tf.pow(10000. , tf.divide(tf.multiply(neg_2 , tf.subtract(i , 1)) , embedding_dim)) + m_theta = tf.multiply(tf.cast(p_i,tf.float32) , tf.cast(theta,tf.float32)) + R[p_i, tf.multiply(2, i),tf.multiply(2, i)].assign(tf.cos(m_theta)) + # print(i, p_i, tf.multiply(2, i), tf.multiply(2, tf.add(i , 1))) + R[p_i, tf.multiply(2, i), tf.multiply(2, tf.add(i , 1))].assign(- tf.sin(m_theta)) + R[p_i, tf.multiply(2, tf.add(i , 1)), tf.multiply(2, i)].assign(tf.sin(m_theta)) + R[p_i, tf.multiply(2, tf.add(i , 1)), tf.multiply(2, tf.add(i , 1))].assign(tf.cos(m_theta)) + + return tf.add(i, 1), idx + + _, idx = tf.while_loop(emb, embedding, loop_vars=[i, embedding_dim]) + return tf.add(p_i, 1), p_idx + + + _, idx = tf.while_loop(p, position, loop_vars=[p_i, block_size]) + return R +{% endhighlight %} + +![image-title-here](../images/rotaryembeddings.png){:class="img-responsive"} + +# RoPE Attention + +This is one section that exemplifies the lack of diagrams and descriptions. This can be added as more intuition +is gained. But for now the code is [here](https://github.com/mohanr/Llama) + +This visualization of attention weights seems to be different from the PyTorch code I was using as reference. +The difference is in terms of the shape of the attention weights. But by slightly changing the indexing mechanism +we can still visualize it. I need to revise this later. + +![image-title-here](../images/RoPEAttention.png){:class="img-responsive"} + +# Masked RoPE Attention + +At this stage one loses count of the shapes and sizes of matrices. It is hard to keep track +of the _ranks_ and _shapes_ as it is not possible to visually inspect them. + +But based on the concept of upper and lower triangular matrices we can mask so that data from +newer time steps is not visible because the model is supposed to predict it. + +I use _block_size(tf.shape(x)[1]_ for this as the shape of the data is (batch_size, block_size, embedding_dim) + +{% highlight python %} + + activations, attn_weights = self.multihead(query=q_out, + value=v_out, + key=k_out, + return_attention_scores=True, + attention_mask = (1 - tf.linalg.band_part(tf.ones((tf.shape(x)[1], + tf.shape(x)[1])), -1, 0)) * -1e9 +{% endhighlight %} + +Even though the mask seems to work I couldn't debug too deeply. + +![image-title-here](../images/maskedRoPEAttention.png){:class="img-responsive"} + +The loss after all this effort is shown here. + +![image-title-here](../images/illama_loss.png){:class="img-responsive"} + +# Stacking multiple blocks + +In this final part we segragate these blocks and stack them in the mode. + +{% highlight python %} + +class Block(tf.keras.Model): + def __init__(self): + super().__init__() + + self.rms = RMSNorm([block_size, n_embd]) + self.rope_attention = MaskedRoPEAttention() + + self.net = tf.keras.Sequential( + layers=[ + tf.keras.layers.Dense(n_embd, input_shape=(None,n_embd), activation=None, use_bias=False), + # tf.keras.layers.ReLU(), + SwiGLU(n_embd), + ] + ) + + def call(self, x): + x = self.rms(x) # rms pre-normalization + x = x + self.rope_attention(x) + + x = self.rms(x) # rms pre-normalization + x = x + self.net(x) + return x + +{% endhighlight %} + +There are now 4 of them stacked like this. + +{% highlight python %} + + def __init__(self): + super().__init__() + self.token_embedding_table = Embedding(vocab_size,n_embd) + layers = [Block() for _ in range(num_layers)] + self.llama_blocks = tf.keras.Sequential( + layers + ) + self.net = tf.keras.Sequential( + layers=[ + tf.keras.layers.Dense(n_embd, input_shape=(None,n_embd), activation=None, use_bias=False), + # tf.keras.layers.ReLU(), + SwiGLU(n_embd), + tf.keras.layers.Dense(vocab_size, input_shape=(n_embd,), activation=None, use_bias=False), + ] + ) + + def call(self,idx,targets=None): + x = self.token_embedding_table(idx) + + x = self.llama_blocks(x) + logits = self.net(x) + +{% endhighlight %} + +# Hyper-parameter tuning + +This is a separate task that is pending. We need GPUs and more training epochs But the Llama Transformer architecture itself works now. + +The final losses are worse than before. Moreover the test generated after this minimal training is gibberish and is not even as good as +the text from a general GPT model with a similar architecture. + +![image-title-here](../images/illama_finalloss.png){:class="img-responsive"} + +_References_ + +1. https://blog.briankitano.com/llama-from-scratch/ +2. https://nn.labml.ai/transformers/rope/index.html + + diff --git a/_posts/2023-09-19-Data-Streaming-Algorithms.md b/_posts/2023-09-19-Data-Streaming-Algorithms.md new file mode 100644 index 0000000000000..7847f8525248f --- /dev/null +++ b/_posts/2023-09-19-Data-Streaming-Algorithms.md @@ -0,0 +1,46 @@ +--- +layout: post +title: Data Streaming Algorithms +published: true +--- + +I hope to add details as I understand this field better. +I may also use multiple languages to code parts of algorithms. + +# Sketching + + +# Hadamard Transform + + + +{% highlight python %} +import tensorflow as tf + +def hademard(M): + i = tf.constant(1) + loop_var = int(tf.math.log(tf.cast(tf.shape(M)[0],tf.float32))/tf.math.log(2.0)) + c = lambda i, d: tf.less_equal(i, tf.cond(loop_var < 1 , lambda: 1, lambda : loop_var)) + T = tf.constant([[1,1],[1,-1]],dtype=tf.float32) + + def logic(i, H1): + operator_1 = tf.linalg.LinearOperatorFullMatrix(H1) + operator_2 = tf.linalg.LinearOperatorFullMatrix(T/2) + operator = tf.linalg.LinearOperatorKronecker([operator_1, operator_2]) + + H1 = operator.to_dense() + return tf.add(i, 1),H1 + + H0 = tf.ones((1,1)) + i,H2 = tf.while_loop( c,logic,[i, H0 ]) + H2 = tf.matmul(tf.matmul(H2, M), H2) + + return H2 + + +#M = tf.ones((4,4),tf.float32) +#M = tf.constant([[2, 3], [2, 5]], dtype=tf.float32) +M = tf.linalg.band_part(tf.ones((4,4)), -1, 0) +print(f'Hademard {hademard(M)}') + +{% endhighlight %} diff --git a/_posts/2023-10-01-RayTracerInRacket.md b/_posts/2023-10-01-RayTracerInRacket.md new file mode 100644 index 0000000000000..34d12cbd908a8 --- /dev/null +++ b/_posts/2023-10-01-RayTracerInRacket.md @@ -0,0 +1,403 @@ +--- +layout: post +title: Ray Tracer in Racket - Part I +published: true +--- + +This is my attempt to code a Ray Tracer in Racket. Details are forthcoming. + +The [Book Series]([http://arxiv.org/pdf/2302.13971.pdf](https://raytracing.github.io)) is cited +by many who code ray tracers using any languages they are familiar with. Recently some OCaml developers +showed how to code simple ray tracers. These videos inspired me to port their code to Racket. + +1. I post code here as I develop it. It will eventually be committed to Git. +2. I also learn Racket as I code this. So I will add some explanation of the functional + programming paradigm that Racket is based on. + + +# Development environment + +The IDE is the venerable emacs. + +![image-title-here](../images/racket-development.png){:class="img-responsive"} + + +# _Vec3d_ module + +{% highlight racket %} +#lang racket +(module Vec3d racket + (provide vadd vminus) + (define ( vadd v v1) + (vector-map + v v1)) + (define ( vminus v v1) + (vector-map - v v1)) + (define ( vmult v v1) + (vector-map * v v1)) + (define ( vdiv v v1) + (vector-map / v v1)) +) + +{% endhighlight %} + + +# _rackunit_ tests + +{% highlight racket %} +#lang racket + + (require rackunit (submod "rays.rkt" Vec3d )) + (check-equal? (vadd '#(1 2 3) '#(0 1 2)) '#(1 3 5)) + (check-equal? (vminus '#(1 2 3) '#(0 1 2)) '#(1 1 1)) + +{% endhighlight %} + + +# Reading and writing files + +{% highlight racket %} + +#lang racket + +(require rackunit (submod "rays.rkt" Vec3d )(submod "rays.rkt" IO)) + (check-equal? (vadd '#(1 2 3) '#(0 1 2)) '#(1 3 5)) + (check-equal? (vminus '#(1 2 3) '#(0 1 2)) '#(1 1 1)) + (with-handlers ([exn:fail? + (lambda (v) + (displayln "File operation problem") + (displayln (exn-message v) ))]) + (write-file)) + (print(read-file + )) + (check-equal? (read-file) "P3") +{% endhighlight %} + +# _rackunit_ tests + +{% highlight racket %} + + (with-handlers ([exn:fail? + (lambda (v) + (displayln "File operation problem") + (displayln (exn-message v) ))]) + (write-file)) + (print(read-file + )) + (check-equal? (read-file) "P3") + +{% endhighlight %} + +# The _Pixel_ module + +{% highlight racket %} + +(module Pixel racket +(provide create write-pixel) +(define (create r g b) + + (let ((r (arithmetic-shift 16 ( bitwise-and r 255 ))) + (g (arithmetic-shift 8 ( bitwise-and g 255 ) )) + (b (bitwise-and b 255 )) + (x ( bitwise-ior r ( bitwise-ior g b)))) + x) +) +(define (write-pixel t) + + (format "~a ~a ~a" (bitwise-ior t 16) + (bitwise-ior 8 (bitwise-and t 255)) + (bitwise-and t 255)) + + ) +) +{% endhighlight %} + +# Racket Data Structures +## Arrays + +I pause here to understand how to operate arrays. As soon as I started to code I faced problems. +No explanation is profferred at this stage. + +1. The annotation _inexact->exact_ seems to be needed when I use function + parameters directly inside the code. +2. _array->mutable-array_ and _vector_ are used together. +3. Arrays have to mutable to be changed. +4. Two different versions of the _for_ loop are used. I may have to refactor the first one depending + on what the outcome should be. + +{% highlight racket %} +(module Image racket +(require math/array) +(provide create-image-array ) + + +(define (make-array rows columns) + (array->mutable-array ( axis-index-array (vector (inexact->exact rows) (inexact->exact columns)) 0) )) + +(define (create-image-array height width ) + (let + ((pixels (make-array height width))) + (for* ([i (in-range (inexact->exact (- height 1 )))] + [j (in-range (inexact->exact (- width 1 )))]) + (array-set! pixels (vector i j) '(0 0 0)) + ) + pixels + ) + ) +) + +{% endhighlight %} + +{% highlight racket %} +(define (pretty-print array) + (for ((i (in-range ( vector-ref (array-shape array) 0)))) + (for ((j (in-range (vector-ref (array-shape array) 1 )))) + (printf "~a\t" (array-ref array (vector i j) ))) + (newline))) + ;; print( vector-ref (array-shape array) 0)) + + (pretty-print (create-image-array 22 2)) + +{% endhighlight %} + +# The first version + +The following version produces an image but with the wrong color. But the code is complete and can be debugged. + +![image-title-here](../images/ray.png){:class="img-responsive"} + +The _for_ loop in the _Image_ module has been changed to the correct form. + +## The IO module used to write the pixels to a file + +{% highlight racket %} + +#lang racket +#lang racket +(module IO racket + (require math/array (submod "rays.rkt" Pixel)) + (provide write-file read-file ) + (define (write-file pixels) + (with-handlers ([exn:fail? + (lambda (v) + (display "write-file File operation problem") + (displayln (exn-message v) ))]) + + (with-output-to-file "sample.ppm" + ( + lambda() (printf "P3~n~a ~a~n255~n" ( vector-ref (array-shape pixels) 0) ( vector-ref (array-shape pixels) 1 )) + (for ((i (in-range ( vector-ref (array-shape pixels) 0)))) + (for ((j (in-range (vector-ref (array-shape pixels) 1 )))) + ( printf (write-pixel (array-ref pixels (vector i j))))) + ) + ) + + #:exists 'replace ) + ) + ) + + (define (read-file) + (call-with-input-file "sample.ppm" + (lambda(in) (read-string 2 in) ) + ) + ) + + ) + +{% endhighlight %} + +## Other modules + +{% highlight racket %} + +#lang racket + + +(module Vec3d racket + (provide vadd vminus) + (define ( vadd v v1) + (vector-map + v v1)) + (define ( vminus v v1) + (vector-map - v v1)) + (define ( vmult v v1) + (vector-map * v v1)) + (define ( vdiv v v1) + (vector-map / v v1)) +) + +(module Pixel racket +(provide create write-pixel) +(define (create r g b) + + (let ((r (arithmetic-shift ( bitwise-and r 255 ) 16 )) + (g (arithmetic-shift ( bitwise-and g 255 ) 8)) + (b (bitwise-and b 255 )) + (x ( bitwise-ior r ( bitwise-ior g b)))) + x) +) +(define (write-pixel t) + + (format "~a ~a ~a~n" (bitwise-ior t 16) + (bitwise-ior 8 (bitwise-and t 255)) + (bitwise-and t 255)) + + ) +) + +(module Image racket +(provide create-image-array ) + +(require math/array) + +(define (make-array rows columns) + (array->mutable-array ( axis-index-array (vector (inexact->exact rows) (inexact->exact columns)) 0) )) + +(define (create-image-array height width renderarray ) + (let + ((pixels (make-array height width))) + (for ((i (range ( vector-ref (array-shape pixels) 0)))) + (for ((j (range (vector-ref (array-shape pixels) 1 )))) + (array-set! pixels (vector i j) (renderarray i j)) + )) + + pixels + ) + ) +) + +{% endhighlight %} + +## main.rkt + +{% highlight racket %} + + +#lang racket +(provide sample_image render) +(require (submod "rays.rkt" Pixel)(submod "rays.rkt" Image)(submod "io.rkt" IO)) +(define (render row col) + (let* ([image_width 256] + [image_height 256] + [r [/ row (- image_height 1)]] + [g [/ col (- image_width 1)]] + [ b 0.1] + [factor 255.999] + [r1 [ * factor r]] + [g1 [ * factor g]] + [b1 [* factor b]]) + (create (exact-round r1) (exact-round g1) (exact-round b1)) + ) +) + +(define (sample_image ) + (write-file (create-image-array 256 256 render ) render ) + + ) + + +(define raycast ( lambda () (sample_image))) + +{% endhighlight %} + +# The final version as of now + +![image-title-here](../images/ray1.png){:class="img-responsive"} + +This is the image I was looking for. There may still be a bug in the bit manipulation code but this works. + +The key change is the bit manipulation code shown here. + +{% highlight racket %} + +(module Pixel racket +(provide create write-pixel) +(define (create r g b) + (bitwise-ior (arithmetic-shift (bitwise-and r #xFF) 16) + (bitwise-ior (arithmetic-shift (bitwise-and g #xFF) 8) + (bitwise-and b #xFF))) +) + +(define (write-pixel t) + + (format "~a ~a ~a~n" (arithmetic-shift (bitwise-and t #xFF0000) -16) + (bitwise-and (arithmetic-shift t -8) #xFF) + (bitwise-and t #xFF)) + + ) +) +{% endhighlight %} + +# Ray Traced image + +## Color Module + +{% highlight racket %} + +(module Color racket +(provide to_pixel) +(require (submod ".." Vec3d)(submod ".." Pixel)) +(define (to_pixel t) + (let* ([factor 255.99] + [vec (vmult factor t)] + [r [vector-ref vec 0]] + [g [vector-ref vec 1]] + [b [vector-ref vec 2]]) + (create (exact-round r) (exact-round g) (exact-round b)) + + ) +) +) +{% endhighlight %} + +## Function to create a ray traced imaged + +{% highlight racket %} + +(struct Ray (camera_centre ray_direction ) #:transparent) +(define (ray_color ray) + (let* ([v [ unit (Ray-ray_direction ray)]] + [ blend_factor [* 0.6 (+ (vector-ref v 1) 1.0)]] + [blend [vadd (vmult (- 0.9 blend_factor) '#(1. 1. 1.)) (vmult blend_factor '#(0.6 0.7 1.9))]]) + blend ) +) + + +(define (raytraced_image) + + (let* ([aspect_ratio [/ 16. 9.]] + [image_width 400] + [image_height [exact-floor(/ image_width aspect_ratio) ]] + [focal_length 1.0] + [viewport_height 2.] + [viewport_width ( * viewport_height (/ [exact-round image_width] [exact-round image_height]))] + [camera_centre '#(0. 0. 0.)] + [viewport_lr (vector viewport_width 0. 0.)] + [viewport_td (vector 0. (- 0 viewport_height) 0.)] + [pixel_delta_lr (vdiv viewport_lr [exact-floor image_width]) ] + [pixel_delta_td (vdiv viewport_td [exact-floor image_height]) ] + [viewport_upperleft + (vminus (vminus (vminus camera_centre (vector 0. 0. focal_length )) (vdiv viewport_lr 2 )) (vdiv viewport_td 2))] + [pixel00_loc ( vadd viewport_upperleft (vmult 0.5 (vadd pixel_delta_lr pixel_delta_td )))]) + (define (renderray row col) + (let* ([pixel_centre [vadd pixel00_loc [vadd [ vmult col (vector-map exact-round pixel_delta_lr) ] [vmult row (vector-map exact-round pixel_delta_td)]]]] + [ ray_direction [vminus pixel_centre camera_centre]] + [ a-ray (Ray camera_centre ray_direction)] + [ray a-ray] + [color ( ray_color ray)]) + (to_pixel color) + ;; a-ray + ) + ) + + (write-file (create-image-array image_width image_height renderray )) + ) + ) + +(define raycast ( lambda () (raytraced_image))) +;; (define raycast ( lambda () (sample_image))) + +{% endhighlight %} + +And the final image created by this code is this. + +![image-title-here](../images/ray2.png){:class="img-responsive"} + diff --git a/_posts/2023-11-01-ProbabilisticDataStructures.md b/_posts/2023-11-01-ProbabilisticDataStructures.md new file mode 100644 index 0000000000000..830941e9d322a --- /dev/null +++ b/_posts/2023-11-01-ProbabilisticDataStructures.md @@ -0,0 +1,609 @@ +--- +layout: post +title: Probabilistic and other Data Structures +published: true +toc: true +--- + +# tl;dr +1. The code will be gradually improved and be committed to git finally. +2. Performance considerations are not paramount here. +3. The language is OCaml and it is imperative even though I will attempt to use functional Data structures. +4. The repository is [this](https://github.com/mohanr/Algorithms) + +# Development Environment + +![image-title-here](../images/dev_ide.png){:class="img-responsive"} + +![image-title-here](../images/dune_auto_promote.png){:class="img-responsive"} + + +# Probabilistic and other Data Structures + +## Bloom Filter + +{% highlight ocaml %} + + let jenkins ss : int32 = + let rec hash_accu ( accu, l ):int32 = + match l with + | [] -> + let hs = Int32.add accu (Int32.shift_left accu 3) in + let hs1 = Int32.logxor hs (Int32.shift_right_logical hs 11) in + Int32.add (Int32.shift_left hs1 15) hs1 + | hd :: tl -> + let h = Int32.add accu hd in + let accu = Int32.add h (Int32.shift_left h 10) in + hash_accu (Int32.logxor accu (Int32.shift_right_logical accu 6), tl) + (* | [] -> *) + (* let hs = accu + (accu lsl 3) in *) + (* let hs1 = hs lxor (hs lsr 11) in *) + (* Int32.of_int (hs1 + (hs1 lsl 15)) *) + (* | hd :: tl ->let h = accu + hd in *) + (* let accu = h + (h lsl 10) in *) + (* hash_accu ((accu lxor (accu lsr 6) ), tl) *) + in + hash_accu ((Int32.of_int 0 ),ss) + +{% endhighlight %} + +### Initial set of tests + +As I mentioned I am not considering space allocation here as the focus is on +working code. + +{% highlight ocaml %} + + +let string_to_print_list s = + + let str = s |> String.to_seq |> List.of_seq in + let int_list = List.map int_of_char str in + List.iter (fun c -> Printf.printf "%d\n" c) int_list + + +let string_to_int_list s = + + let str = s |> String.to_seq |> List.of_seq in + let int_list = List.map int_of_char str in + List.map (fun c -> Int32.of_int c) int_list + +let%expect_test _= + let hash = Bloomfilter.Ds.jenkins (string_to_int_list "Hello") in + Printf.printf "%d\n" (Int32.to_int hash); + [%expect {| 1901914092 |}] + +let%expect_test _= + string_to_print_list "Hello"; + [%expect {| + 72 + 101 + 108 + 108 + 111 |}] +{% endhighlight %} + +### Initial version with a list of hash functions. + +{% highlight ocaml %} +type 'hf element = + { value : 'hf + ; mutable next : 'hf element option + } +type 'hf t = 'hf element option ref + +let insert_hashfunc t value = + let new_hashfunc = { next = !t; value } in + (match !t with + | Some old_hashfunc -> old_hashfunc.next + <- Some new_hashfunc + | None -> ()); + t := Some new_hashfunc; + new_hashfunc + +{% endhighlight %} + +### Test +{% highlight ocaml %} +let%expect_test "hash" = + let empty_list() : 'hf Bloomfilter.Ds.t = ref None in + let l = empty_list() in + let hf = Bloomfilter.Ds.insert_hashfunc l Bloomfilter.Ds.jenkins in + let hash = hf.value (string_to_int_list "Hello") in + Printf.printf "%d\n" (Int32.to_int hash); + [%expect {| 1901914092 |}] + +{% endhighlight %} + +### Test for Bit set and get + +{% highlight ocaml %} +let%expect_test "bitset" = + let empty_list() : 'hf Bloomfilter.Ds.t = ref None in + let l = empty_list() in + let hf = Bloomfilter.Ds.insert_hashfunc l Bloomfilter.Ds.jenkins in + let bit = Bloomfilter.Ds.set_indices (Bloomfilter.Ds.create_filter 9) "Hello" hf.value + in + Batteries.BitSet.print (BatInnerIO.output_channel stdout) bit ; + [%expect {| 0000000000001000 |}] + +let%expect_test "bitget" = + let empty_list() : 'hf Bloomfilter.Ds.t = ref None in + let l = empty_list() in + let hf = Bloomfilter.Ds.insert_hashfunc l Bloomfilter.Ds.jenkins in + let bit = Bloomfilter.Ds.get_indices (Bloomfilter.Ds.create_filter 9) "Hello" hf.value in + Printf.printf "%s\n" (string_of_bool bit); + [%expect {| true |}] + +{% endhighlight %} + +### Bit set and get + +The code will be further refactored and committed to my repository. + +{% highlight ocaml %} +let set_indices filt element hf = + let length = Batteries.BitSet.capacity filt.bits in + let hash = hf (string_to_int_list element) in + let () = Batteries.BitSet.set filt.bits ((Int32.to_int hash) mod length) in + filt.bits + + +let get_indices filt element hf = + let length = Batteries.BitSet.capacity filt.bits in + let hash = hf (string_to_int_list element) in + let () = Batteries.BitSet.set filt.bits ((Int32.to_int hash) mod length) in + let bit = Batteries.BitSet.mem filt.bits ((Int32.to_int hash ) mod length) in + bit +{% endhighlight %} + +# An implementation from the source of RocksDB + +RocksDB(rocksdb.org) is an embeddable persistence key-value store + +This is the code based on research papers intended for serious production systems. I've tried to port one +of their implementations to OCaml. I am building gradually. + +This particular implementation found in _RocksDB_ source is considered legacy as they have a better implementation. +Nevertheless I have attempted to port it to OCaml. + +This is a simple test to convince myself that OCaml _Int32_ has to be shift_right_logical to avoid overflow that result in a negative +number. _Int64_ doesn't need this as it is bigger. I think it is not apparent as it is bigger. + + +{% highlight ocaml %} + + let high : int32 = 2100000000l in + let low : int32 = 2000000000l in + Printf.printf "mid using >>> 1 = %ld mid using / 2 = %ld" + (Int32.shift_right_logical (Int32.add low high) 1) (Int32.div (Int32.add low high) (Int32.of_int 2)) ; + +{% endhighlight %} + +> mid using >>> 1 = 2050000000 mid using / 2 = -97483648 + +> This should be tested thoroughly as _unsigned_ integers are absent in OCaml. +{% highlight ocaml %} + + +open Batteries + +module type BLOOM_MATH = sig + + val standard_fprate : float -> float -> float + val finger_print_fprate : float -> float -> float + val cache_local_fprate : float -> float -> float -> float + val independent_probability_sum : float -> float -> float + +end + +module Bloom : BLOOM_MATH = struct + + let standard_fprate bits_per_key num_probes : float = + Float.pow (1. -. Float.exp (-. num_probes /. bits_per_key)) num_probes + + let cache_local_fprate bits_per_key num_probes + cache_line_bits = + if bits_per_key <= 0.0 then + 1.0 + else + + let keys_per_cache_line = cache_line_bits /. bits_per_key in + let keys_stddev = sqrt keys_per_cache_line in + let crowded_fp = standard_fprate ( + cache_line_bits /. (keys_per_cache_line +. keys_stddev)) num_probes in + let uncrowded_fp = standard_fprate ( + cache_line_bits /. (keys_per_cache_line -. keys_stddev)) num_probes in + (crowded_fp +. uncrowded_fp) /. 2. + + let finger_print_fprate num_keys fingerprint_bits : float = + let inv_fingerprint_space = Float.pow 0.5 fingerprint_bits in + let base_estimate = num_keys *. inv_fingerprint_space in + if base_estimate > 0.0001 then + 1.0 -. Float.exp (-.base_estimate) + else + base_estimate -. (base_estimate *. base_estimate *. 0.5) + + let independent_probability_sum rate1 rate2 = + rate1 +. rate2 -. (rate1 *. rate2) + +end + + open Bloom + + type 'bloombits filter = + { + bits : Batteries.BitSet.t + } + + let estimated_fprate keys bytes num_probes = + let bits_per_key = 8.0 *. bytes /. keys in + let filterRate = cache_local_fprate bits_per_key num_probes 512. in (* Cache line size is 512 *) + let filter_rate = filterRate +. 0.1 /. (bits_per_key *. 0.75 +. 22.) in + let finger_print_rate = finger_print_fprate keys 32. in + independent_probability_sum filter_rate finger_print_rate + + let getline (h:int32) (num_lines:int32) : int32 = + Int32.rem h num_lines + + let add_hash filt (h:int32) (num_lines:int32) num_probes (log2_cacheline_bytes:int) = + + + let log2_cacheline_bits = Int32.add (Int32.of_int log2_cacheline_bytes) (Int32.of_int 3) in + let base_offset = Int32.shift_left (getline h num_lines) log2_cacheline_bytes in + let delta = Int32.logor (Int32.shift_right_logical h 17) + (Int32.shift_left h 15) in + + let rec probe i numprobes base_offset = + let log2c = Int32.shift_left (Int32.of_int 1) (Int32.to_int log2_cacheline_bits) in + let bitpos = Int32.sub log2c (Int32.of_int 1) in + let byteindex = (Int32.add base_offset (Int32.div bitpos (Int32.of_int 8))) in + let () = Batteries.BitSet.set filt.bits (Int32.to_int (Int32.logor byteindex (Int32.shift_left (Int32.rem bitpos (Int32.of_int 8)) 1))) in + if i < num_probes then + probe (i + 1) numprobes base_offset + else + (Int32.add h delta) + in probe 0 num_probes base_offset + + (* Recommended test to just check the effect of logical shift on int32. *) + (* int64 doesn't seem to need it *) + + (* let high : int32 = 2100000000l in *) + (* let low : int32 = 2000000000l in *) + (* Printf.printf "mid using >>> 1 = %ld mid using / 2 = %ld" *) + (* (Int32.shift_right_logical (Int32.add low high) 1) (Int32.div (Int32.add low high) (Int32.of_int 2)) ; *) + + + let hash_maymatch_prepared filt h num_probes offset log2_cacheline_bytes = + let log2_cacheline_bits = Int32.add (Int32.of_int log2_cacheline_bytes) (Int32.of_int 3) in + let delta = Int32.logor (Int32.shift_right_logical h 17) + (Int32.shift_left h 15) in + + let rec probe h i numprobes base_offset = + let log2c = Int32.shift_left (Int32.of_int 1) (Int32.to_int log2_cacheline_bits) in + let bitpos = Int32.sub log2c (Int32.of_int 1) in + let byteindex = (Int32.add base_offset (Int32.div bitpos (Int32.of_int 8))) in + let () = Batteries.BitSet.set filt.bits (Int32.to_int (Int32.logor byteindex + (Int32.shift_left (Int32.of_int 1) + (Int32.to_int (Int32.rem bitpos (Int32.of_int 8))) ))) in + if i < num_probes then + let h = (Int32.add h delta) in + probe h (i + 1) numprobes base_offset; + in probe h 0 num_probes offset + + + let hash_may_match filt h num_lines num_probes log2_cacheline_bytes = + let base_offset = Int32.shift_left (getline h num_lines) log2_cacheline_bytes in + hash_maymatch_prepared filt h num_probes base_offset log2_cacheline_bytes +{% endhighlight %} + +# An attempt to port _Murmurhash_ to OCaml to test the Bloom filter. + + +> This should be tested thoroughly as _unsigned_ integers are absent in OCaml. +> I think that this is correct based on some tests. + + + +{% highlight ocaml %} +let murmurhash chunks len seed = + let c1 = 0xcc9e2d51l in + let c2 = 0x1b873593l in + let r1:int32 = (Int32.of_int 15) in + let r2:int32 = (Int32.of_int 13) in + let m = (Int32.of_int 5) in + let n = (Int32.of_string "0xe6546b64") in + let h = ref Int32.zero in + let k = ref Int32.zero in + let l = Int32.div len (Int32.of_int 4) in + h := seed; + + (* Printf.eprintf " %ld" l; *) + + for i = 0 to (Int32.to_int l) - 1 do + k := + Int32.logor + (Int32.logor + (Int32.of_int (Bytes.get_uint8 chunks (i * 4))) + (Int32.shift_left (Int32.of_int (Bytes.get_uint8 chunks ((i * 4)+ 1))) 8)) + (Int32.logor + (Int32.shift_left (Int32.of_int (Bytes.get_uint8 chunks ((i * 4)+ 2))) 16) + (Int32.shift_left (Int32.of_int (Bytes.get_uint8 chunks ((i * 4)+ 3))) 24)); + + k := Int32.mul !k c1 ; + k := Int32.logor (Int32.shift_left !k (Int32.to_int r1)) (Int32.shift_right_logical !k (Int32.to_int (Int32.sub (Int32.of_int 32) r1))); + k := Int32.mul !k c2; + + h := Int32.logxor !h !k; + h := Int32.logor (Int32.shift_left !h (Int32.to_int r2)) (Int32.shift_right_logical !h (Int32.to_int (Int32.sub (Int32.of_int 32) r2))); + h := Int32.add ( Int32.mul !h m) n; + + done; + + let k = ref (Int32.of_int 0) in +let tail = Int32.to_int (Int32.mul l 4l) in +let l = (Int32.to_int len) - tail in + +if l >= 3 then k := Int32.logxor !k (Int32.shift_left (Int32.of_int (Bytes.get_uint8 chunks (tail + 2))) 16); +if l >= 2 then k := Int32.logxor !k (Int32.shift_left (Int32.of_int (Bytes.get_uint8 chunks (tail + 1))) 8); +if l >= 1 then begin + k := Int32.logxor !k (Int32.of_int (Bytes.get_uint8 chunks tail)); + + (* if l >= 3l then k := Int32.logxor !k (Int32.shift_left (Int32.of_int (Bytes.get_uint8 chunks 2)) 16); *) + (* if l >= 2l then k := Int32.logxor !k (Int32.shift_left (Int32.of_int (Bytes.get_uint8 chunks 1)) 8); *) + (* if l >= 1l then begin *) + (* k := Int32.logxor !k (Int32.of_int ((Char.code (Bytes.get chunks 0)))); *) + k := Int32.mul !k c1; + k := Int32.logxor (Int32.shift_left !k (Int32.to_int r1)) + (Int32.shift_right_logical !k (Int32.to_int (Int32.sub (Int32.of_int 32) r1))); + k := Int32.mul !k c2; + h := Int32.logxor !h !k; + end; + + h := Int32.logxor !h len; + + h := Int32.logxor !h (Int32.shift_right_logical !h 16); + h := Int32.mul !h (Int32.of_string "0x85ebca6b"); + h := Int32.logxor !h (Int32.shift_right_logical !h 13); + h := Int32.mul !h (Int32.of_string "0xc2b2ae35"); + h := Int32.logxor !h (Int32.shift_right_logical !h 16); + + !h + +{% endhighlight %} + +# Splay Tree + +## Initial set of tests + +{% highlight ocaml %} + +type 'a r_tree = Leaf | Node of 'a node1 +and 'a node1 = { value : 'a; left : 'a r_tree; right : 'a r_tree; } + +let rec check_splay_tree t = + match t with + |Leaf -> false + | Node {left; value = v; right}-> + match left, right with + | Node { left = _; value = v0; _}, Node {left = _; value = v1; _} -> v == v1 + v0 + 1 + | Node { left ; _}, Leaf -> check_splay_tree left + | Leaf, Node { left = _ ;value = _; right} -> check_splay_tree right + | _ -> false + + + +let insert= + Node { + value = 2; + left = Node {value = 1; left = Leaf; right = Leaf}; + right = Node {value = 3; left = Leaf; right = Leaf} + } + + +let%expect_test _= + Printf.printf "%s" (string_of_bool (check_splay_tree insert)); + [%expect {| false |}] + + +{% endhighlight %} + +We can print a small tree like this for debugging. + +{% highlight ocaml %} + +let rec print_sTree (sTree : int s_tree ) (d : int) : unit = + match sTree with + |Leaf -> () + | Node { left ;value ; right} -> + print_sTree right (d + 1); + for __i=0 to (d - 1) do + Printf.printf " " + done; + Printf.printf "%d\n" value; + print_sTree left (d+1) + +{% endhighlight %} + +_dune runtest --auto-promote_ updates the test output automatically. + +![image-title-here](../images/print_splay.png.png){:class="img-responsive"} + + +## Core Splay algorithm + +At this stage the compiler is happy but very less progress is made. There is a steep learning curve here as I have to learn the language +deeply. + + +## Insert Key into a binary tree + +At this stage the mutable imperative style is hard to debug.Moreover _None_ and _Some Leaf_ are +both used redundantly. This led to a bug. + +{% highlight ocaml %} + +let rec insert_key (k : int ) (t : int splay_tree option ref) : int splay_tree option ref= + match !t with + | None |Some Leaf -> + let new_node = Node { key = k; value = 0; left = None; right = None } in + t := Some new_node; + t + | Some tree -> + let insert_node tree = + + match tree with + | Node old_key -> + begin match old_key with + | ok -> + if k > ok.key then( + match ok.right with + | None | Some Leaf -> + let r = ref (Some (Node { key = k ;value = 0 ; right = Some Leaf; left = Some Leaf} ))in + ok.right <- !r; + t + | Some _r -> + insert_key k (ref (ok.right )) + ) + else + if k < ok.key then( + match ok.left with + | None -> + let l = ref (Some (Node { key = k ;value = 0 ; right = Some Leaf; left = Some Leaf} ))in + ok.left <- !l; + t + | Some _l -> + insert_key k (ref (ok.left)); + ) + else + t + end; + |Leaf ->t + in + insert_node tree + +{% endhighlight %} + +## Porting SML to OCaml + +I spent several days coding a Splay tree using my inefficient _mutable ref_ data structure. It didn't work satisfactily. +Eventually I picked up basic SML and ported the SML code to OCaml. This was a great learning experience as I learnt how +to use _Functors_ and abstractions and modules. + +{% highlight ocaml %} + +let rec splay (l, v, r) (k:Params.key) = + match compare k (keyOf (v)) with + | EQUAL -> (l, v, r) + | LESS -> + (match l with + | Empty -> (l, v, r) (* not found *) + | Node (ll, lv, lr) -> + match compare k (keyOf (lv)) with + | EQUAL -> (ll, lv, Node(lr, v, r)) (* 1: zig *) + | LESS -> + (match ll with + | Empty -> (Empty, lv, Node(lr, v, r)) + (* not found *) + | Node (lln, lvn, lrn) as n -> (* 2: zig-zig *) + let (lll, llv, llr) = splay (lln, lvn, lrn) k in + (lll,llv,Node(llr,lv,Node(lr,v,r))) + ) + | GREATER -> + (match lr with + | Empty -> (ll, lv, Node(Empty, v, r)) + |Node (lln, lvn, lrn) as n -> (* 3: zig-zag *) + let (lrl, lrv, lrr) = splay (lln, lvn, lrn) k in + (Node(ll,lv,lrl),lrv,Node(lrr,v,r)) + )) + | GREATER -> + (match r with + | Empty -> (l, v, r) (* not found *) + | Node (rl, rv, rr) -> + match compare k (keyOf (rv)) with + |EQUAL -> (Node(l,v,rl),rv,rr) (* 1: zag *) + | GREATER -> + (match rr with + | Empty -> (Node(l,v,rl),rv,rr) (* not found *) + | Node (lln, lvn, lrn) as n -> (* 3: zag-zag *) + let (rrl, rrv, rrr) = splay (lln, lvn, lrn) k in + (Node(Node(l,v,rl),rv,rrl),rrv,rrr) + ) + | LESS -> + (match rl with + | Empty -> (Node(l,v,rl),rv,rr) (* not found *) + | Node (lln, lvn, lrn) as n -> (* 2: zag-zig *) + let (rll, rlv, rlr) = splay (lln, lvn, lrn) k in + (Node(l,v,rll),rlv,Node(rlr,rv,rr)) + )) + + let size s tr = s + + type 'b folder = ((elem*'b)->'b) -> 'b -> key -> set -> 'b + + let rec add ((size,tr):set) (e:elem) = let + ((l,v,r), b) = add_tree !tr e in + let node = splay (l,v,r) (keyOf(e)) in + let size' = if b then size else size+1 + in + let _ = Printf.printf "Size %d" size' in + ((size', ref (Node((l,v,r)))),b) and + + add_tree (t: tree) (e: elem) :node * bool = + match t with + |Empty -> ((Empty, e, Empty), false) + | Node (l,v,r) -> + (match compare (keyOf(v)) (keyOf(e)) with + | EQUAL -> ((l,e,r),true) + (* | GREATER -> let (n',b) = add_tree l e in *) + (* ((Node(n'),v,r),b) *) + (* | LESS -> let (n',b) = add_tree r e in *) + (* ((l,v,Node(n')),b) *) + | GREATER -> let ((x,y,z),b) = add_tree l e in + ((Node(x,y,z),v,r),b) + | LESS -> let ((x,y,z),b) = add_tree r e in + ((l,v,Node (x,y,z)),b) + ) + + + +{% endhighlight %} + +## Range-Minimum-Query + +I set up the basic code for this. There is no query now. Code is in Git. + +{% highlight ocaml %} +let preprocess_a l mk = + let ps = + let k = 0 -- mk + and i = 0 -- (List.length l -1 ) in + (k,i) in + + let v = Array.make ((List.length l) * ( mk + 1)) 0 in + + List.iter (fun (k, i) -> + + let () = Printf.printf "[mk %d] [k %d] [i %d]\n" mk k i in + let ind = indx (List.length l ) in + match k with + | 0 -> + let index = ind i k in + let value = List.nth l (ind i 0) in + (* let () = Printf.printf "Value set is %d [k %d] [i %d]\n" value k i in *) + + let v' = Array.set v index value in + Array.iter (fun elem -> Printf.printf " %d " elem) v + + | _ -> + let i' = i + (Batteries.Int.pow 2 ( k - 1)) in + let p1 = Array.get v ( ind i (k - 1) ) in + let p2 = Array.get v ( ind i' (k - 1)) in + (* let () = Printf.printf "p1 is %d p2 is %d [k %d] [i %d]\n" p1 p2 k i in *) + + let v' = Array.set v (ind i k ) ( min p1 p2) in + Array.iter (fun elem -> Printf.printf " %d " elem) v + ) (enum_to_list ps) + + +{% endhighlight %} diff --git a/_posts/2024-01-30-BTreeVariant.md b/_posts/2024-01-30-BTreeVariant.md new file mode 100644 index 0000000000000..5316a5f8f5219 --- /dev/null +++ b/_posts/2024-01-30-BTreeVariant.md @@ -0,0 +1,127 @@ +--- +layout: post +title: BTree Variants +published: true +--- + +Whenever I code Data structures like BTree,LSM etc. in OCaml I read SML,Rust or C++ code. The code I read is in a repo. +that implements a caching layer or DBs like https://rethinkdb.com. I don't find any direct OCaml references. +So I ported this Haskell two-three tree by [http://matthew.brecknell.net/post/btree-gadt/….](https://matthew.brecknell.net/posts/btree-gadt/) +Moreover since I am learning it is hard to identify succinct functional code as some examples seem to be imperative. + + +# Two-Three BTree + +{% highlight ocaml %} +type ('n, 'a) n = + | Type1 of ('n, 'a) t * 'a * ('n, 'a) t + | Type2 of ('n, 'a) t * 'a * ('n, 'a) t * 'a * ('n, 'a) t +and ('n, 'a) t = + | BR of('n, 'a) n + | LF + + +type ('n, 'a ) tree = Tree of ('n, 'a ) t + +type ('n, 'a, 't ) normal_replace = ('n, 'a ) t -> 't +type ('n, 'a, 't ) insert_or_pushup = ('n, 'a ) t -> 'a -> ('n, 'a ) t -> 't + +type order = LESS | EQUAL | GREATER + +let compare k1 k2 : order = + if k1 < k2 then LESS + else if k1 = k2 then EQUAL +else GREATER + + + +let compare_normal v1 v2 v1_lessthan_V2 v1_equalto_v2 v1_greaterthan_v2 = + match compare v2 v2 with + | LESS -> v1_lessthan_V2 + | EQUAL -> v1_equalto_v2 + | GREATER -> v1_greaterthan_v2 + + +let compare_pushup v1 v2 v3 v1_lessthan_V2 v1_equalto_v2 v1_between v1_equalto_v3 v1_greaterthan_v3 = + compare_normal v1 v2 v1_lessthan_V2 v1_equalto_v2 ( compare_normal v1 v3 v1_between v1_equalto_v3 v1_greaterthan_v3 ) + +let insert (value:int) tree = + let rec ins t normal_replace insert_or_pushup = + match t with + | LF -> insert_or_pushup LF value LF + | BR br -> + match br with + | Type1 ( a, b, c ) -> + let v1_lessthan_v2 = + + ins a (fun k -> normal_replace (BR (Type1 (k,b,c)))) + (fun p q r -> normal_replace (BR (Type2 (p, q, r, b, c )))) in + + let v1_greaterthan_v2 = + + ins c (fun k -> normal_replace (BR ((Type1 (a, b, k))))) + (fun p q r -> normal_replace (BR ((Type2 (a, b, p, q, r ))))) in + + let v1_equalto_v2 = normal_replace (BR ((Type1 (a, value, c)))) in + compare_normal value b v1_lessthan_v2 v1_greaterthan_v2 v1_equalto_v2 + + | Type2( a, b, c, d, e ) -> + let v1_lessthan_v2 = + + ins a (fun k -> normal_replace (BR (Type2 (k,b,c,d,e)))) + (fun p q r -> insert_or_pushup (BR (Type1 (p, q, r))) b (BR (Type1 (c, d, e)))) in + + let v1_between = + ins c (fun k -> normal_replace (BR (Type2 (a,b,k,d,e)))) + (fun p q r -> insert_or_pushup (BR (Type1 (a, b, p))) q (BR (Type1 (r, d, a) ))) in + + let v1_greaterthan_v3 = + ins e (fun k -> normal_replace (BR (Type2 (a,b,c,d,k)))) + (fun p q r -> insert_or_pushup (BR (Type1 (a, b, c))) d (BR (Type1 (p, q, r) ))) in + + let v1_equalto_v2 = normal_replace (BR ((Type2 (a, value, c, d, e)))) in + + let v1_equalto_v3 = normal_replace (BR ((Type2 (a, b, c, value, e)))) in + compare_pushup value b d v1_lessthan_v2 v1_between v1_greaterthan_v3 v1_equalto_v2 v1_equalto_v3 + + + in + ins tree (fun t -> t)(fun a b c -> BR(Type1( a, b, c))) + + +let rec print_bTree (bTree: ('n, 'a) t) d : unit = + begin match bTree with + |LF -> () + |BR n-> + begin match n with + | (Type1 ( a,b,c)) -> + print_bTree a (d + 1); + for __i=0 to (d - 1) do + Printf.printf " " + done; + Printf.printf "%d \n" b; + print_bTree c (d+1); + + | (Type2 ( a,b,c,d,e)) -> + print_bTree a (d + 1); + for __i=0 to (d - 1) do + Printf.printf " " + done; + Printf.printf "%d %d\n" b d; + print_bTree c (d+1); + print_bTree e (d+1); + end; + end; + + + +{% endhighlight %} + +The _print_bTree_ function prints all the values when I tested even though it may not be in an order that +proves that the two-three BTree is storing values properly. + +# Development Environment + +Since my Spacemacs IDE broke after I upgraded to a newer version of Emacs I decided to move to a new environment - Doom Emacs. + +![image-title-here](../images/doomemacs.png){:class="img-responsive"} diff --git a/_posts/2024-03-10-SemanticsOfProgrammingLanguages.md b/_posts/2024-03-10-SemanticsOfProgrammingLanguages.md new file mode 100644 index 0000000000000..9438a6f571aa2 --- /dev/null +++ b/_posts/2024-03-10-SemanticsOfProgrammingLanguages.md @@ -0,0 +1,303 @@ +--- +layout: post +title: Semantics of Simple Programming Languages +published: true +--- +I am reading about _Semantics of Programming Languages_ and here is an attempt to code Racket +to parse and interpret toy languages. More details will be added. +The code here is not the final version but it compiles. It shows the progress made as I learn the nuances of +Racket and types. + +# Semantics of a simple language + +I use typed Racket and my IDE is Doom Emacs. But I may also code OCaml. The code here is ported from SML/NJ. + +# Pre-requisites + +I have mentioned what was useful for me when I attempted to learn the subject. + +1. Knowledge of SML because some key books use OCaml or SML. +2. Ability to understand basic proof techniques. This book helped me even though I haven't finished it. +3. Professor David Solow's lectures are available and he clearly explains the steps involved in writing and + reading condensed proofs. Highly recommended. + +![image-title-here](../images/DavidSolow.png){:height="50%" width="50%"} + +## Attempt 1 + +1. This does not use Abstract Data Types. +2. This makes the pattern matchers hard to implement and reason about. + +{% highlight racket %} + +#lang typed/racket + + +(require racket/list) +(provide updates Some None) + +(define-type Loc String) +(define-type Store (U Loc Integer)) + +(: operator : Char -> (Operator Char)) +(define (operator c ) + (match c + ['+' Plus] + ['>=' GTEQ])) + +(struct Plus()) +(struct GTEQ()) +(struct Expr ()) +(define-type Value (U Integer Char)) +(define-type If (U Expr Expr Expr)) +(define-type (Op c) (U Expr (Operator c) Expr)) +(define-type Assign (U Loc Expr)) +(struct Deref()) +(struct Seq()) +(struct While()) +(struct Skip()) +(define-type (Operator c) (U Plus GTEQ)) + +(define-type (Expression c) + (U Value + + (Op c) + + If + + Assign + + (U Deref Loc ) + + (U Seq Expr Expr) + + (U While Expr Expr) + + Skip) + ) + +(struct None () + #:transparent) +(struct (i) Some ([v : i]) + #:transparent) +(define-type (Opt a) (U None (Some a))) + +(: lookup ((Listof Number) Number -> Number)) +(define (lookup ls l) + (match ls + ['() 0] + [(cons (cons (== l) n) ls) n] + [(cons _ ls) (lookup ls l)])) + + +(: updates ((Listof Any) Number -> + (Opt (Listof Any)))) +(define (updates ls l) + (match ls + ['() (None)] + [(cons (cons (== l) n) ls) (Some (append ls (list (list l n))))] + [(cons _ ls) (updates ls l)])) + +{% endhighlight %} + + +{% highlight racket %} + +#lang typed/racket + +(require typed/rackunit "l1.rkt") + + + (print( check-equal? (updates '((2,1)) 3) (None) "Test successfull")) + (print( check-equal? (updates '((3,1)) 3) (Some '((3 (,1)))) "Test successfull")) + ( check-equal? (updates '((3,1)) 3) (Some '((3 (,1)))) "Test successfull") + +{% endhighlight %} + +## Attempt 2 +I had to use a macro and create a proper ADT(Abstract Data Type ) to proceed. The previous +code was too verbose. The ADT makes the pattern matcher easier to implement. +The current version is in my Git. + + + +{% highlight racket %} + +#lang typed/racket/base + +(provide Boolean printexpr updates None Some Seq Deref Assign) + +;; Macro by Alexis King + (require (for-syntax racket/base + racket/sequence + racket/syntax + syntax/parse + syntax/stx) + racket/match) +(begin-for-syntax + (define-syntax-class type + (pattern name:id + #:attr [param 1] '() + #:attr [field-id 1] '()) + (pattern (name:id param ...+) + #:attr [field-id 1] (generate-temporaries #'(param ...))))) + +(define-syntax define-datatype + (syntax-parser + [(_ type-name:type data-constructor:type ...) + + (define/with-syntax [data-type ...] + (for/list ([name (in-syntax #'(data-constructor.name ...))]) + (if (stx-null? #'(type-name.param ...)) + name + #`(#,name type-name.param ...)))) + + #'(begin + (struct (type-name.param ...) data-constructor.name + ([data-constructor.field-id : data-constructor.param] ...)) ... + (define-type type-name (U data-type ...)))])) +;; End of Macro by Alexis King + +(define-type Loc String) +(struct Plus()) +(struct GTEQ()) +(define-type (Operator c) (U Plus GTEQ)) + +(: operator : (U Plus GTEQ)-> Char) +(define (operator c ) + (match c + ['Plus #\+] + ['GTEQ #\=])) + +(define-datatype Expr + ( Op Expr (U Plus GTEQ) Expr) + ( If Expr Expr Expr) + ( Assign Loc Expr) + ( Deref Loc ) + ( Seq Expr Expr) + ( While Expr Expr) + Skip +) + + +(: printexpr (Expr -> Void )) +(define (printexpr expr) + (match expr + [(Deref l) (printf "( ~a ~a ~n)" "!" l)] + [( Op e1 operate e2 ) + (printf "( ~a ~a ~a~n)" (printexpr e1) (operator operate) + (printexpr e2 ))] + [( If e1 e2 e3 ) + (printf "( ~a ~a ~a~n)" (printexpr e1) (printexpr e2 )(printexpr e2 ))] + [ (Assign l e ) = (printf "~a := ~a" l (printexpr e ))] + [ (Skip) ( printf "skip")] + [ (Seq e1 e2 ) (printf "~a ; ~a" (printexpr e1 ) + (printexpr e2))] + [ (While e1 e2 ) (printf "while ~a do ~a " (printexpr e1 ) + (printexpr e2))] + )) + +(struct None () + #:transparent) +(struct (i) Some ([v : i]) + #:transparent) +(define-type (Opt a) (U None (Some a))) + +(: lookup ((Listof Number) Number -> Number)) +(define (lookup ls l) + (match ls + [(cons (cons (== l) n) ls) n] + [(cons _ ls) (lookup ls l)])) + + +(: updates ((Listof Any) Positive-Byte -> + (Opt (Listof Any)))) +(define (updates ls l) + (match ls + ['() (None)] + [(cons (cons (== l) n) ls) (Some (append ls (list (list l n))))] + [(cons _ ls) (updates ls l)])) +{% endhighlight %} + +But this code is not complete. It has some serious faults in as far as the types and patterns are +concerned. So I decided to pay close attention to the types and compare the SML output and the +output of my Racket code. +Even though I am porting from SML to Racket I don't use idiomatic Racket everywhere. Parts of +the code may not seem coherent but I ensure that the basic unit tests pass. + +## Final Attempt + +1. Fixed may type annotation bugs +2. Fixed all the patterns after reading the documentation. +3. Fixed most of the logic bugs. The code still doesn't behave exactly like the SML + code. +4. This code is only one part of the attempt to learn and implement types and toy languages. + +{% highlight racket %} +(: reduce ((Pairof (Opt Expr) (Listof (Pairof Loc LocValue))) -> + (Pairof (Opt Expr ) (Listof (Pairof Loc LocValue))))) +(define (reduce expr) + (match expr + [ (cons (Some (Op (? integer? n1) Plus (? integer? n2))) + store) + (cons (Some (IntValue (+ n1 n2))) store)] + [ (cons (Some ( Op (? integer? n1) GTEQ (? integer? n2 ))) store) + (cons (Some (BoolValue (>= n1 'n2))) store)] + [ (cons (Some (Op (? integer? n1) Skip (? boolean? n2))) store) + (match (reduce (cons (Some n2) store)) + [ (cons (Some (IntValue nn2)) store) (cons (Some ((Op n1 Skip nn2))) store)] + [ (None) (None) ] + ) + (match (reduce n1 store) + [ (cons (Some (IntValue nn1)) store) (cons (Some (Op nn1 Skip n2)) store)] + [ (None) (None) ] + )] + [ (cons (Some (IntValue n )) store) (cons (None) store )] + [ (cons (Some (If e1 e2 e3)) store) + (match e1 + [#t (cons (Some e2) store) ] + [#f (cons (Some e3) store) ] + [_ (match (reduce (cons (Some e1) store )) + [ (cons (Some e1) store) ( cons (Some (If e1 e2 e3)) store) ] + [ (None) (None) ] + )] + )] + [ (cons (Some (Deref l)) store) + (match (lookup store l) + [ (cons (Some n ) store ) (cons (Some (IntValue n)) store)] + [ (cons (None) store) (cons (None) store )] + )] + [ (cons (Some (Assign l e )) store) + (match e + [(IntValue n) + (match (updates store (cons (Loc l) (LocValue n))) + [ (Some store ) (cons (Some (Skip)) store)] + [ (None) (cons (None) store ) ] + [ _ (match (reduce (cons (Some e) store)) + [(cons (Some e) store) (cons (Some (Assign l e)) store)] + [ (None) (None) ] + ) + ] + )] + )] + + [ (cons (Some (Seq e1 e2)) store) + (match e1 + [Skip (cons (Some e2) store) ] + [ _ ( match (reduce (cons (Some e1) store )) + [ (cons (Some e1) store) + (cons (Some (Seq e1 e2)) store ) ] + [ (None) (None) ] + + )] + )] + [ (cons (Some (Skip)) store) ( cons (None) store )] + + [ (cons (Some (While e1 e2)) store) + (cons (Some ( If e1 (Seq e2 (While e1 e2)) (Skip))) store) ] + +)) + + +{% endhighlight %} + diff --git a/_posts/2024-05-14-GameOfLifeInRacket.md b/_posts/2024-05-14-GameOfLifeInRacket.md new file mode 100644 index 0000000000000..19fde202bb3e4 --- /dev/null +++ b/_posts/2024-05-14-GameOfLifeInRacket.md @@ -0,0 +1,664 @@ +--- +layout: post +title: Game Of Life in Racket +published: true +--- +I know of a few methods to learn Functional Programming languages. One could read a book or read source +code. My attempt involves source code that I port to my favorite language. And I learn two different languages +at the same time. The source and the target. It has been very productive for me. + +I will continue to post code here directly as and when I manage to compile it. This code is directly +ported from OCaml and I will add the link to the source once it is finished. + +# coord.rkt + +{% highlight racket %} + +#lang typed/racket/base +(provide preimg) +(require racket/set) +(require racket/hash) + + (require "coord.rkt") + +(: preimg ( ( Integer -> Boolean )(HashTable Coord Integer) -> (Setof Coord))) +(define (preimg p m ) + ;; (let ([ s :(Setof Integer) (list->set '())]) + + (let ([ s :(Setof Coord) (set)]) + (hash-for-each m + (lambda ([k : Coord] + [v : Integer] + ) + (when (p v) + (set! s (set-add s k)) + (print s) + ) ) ) + s + ) + ) +{% endhighlight %} + +# gameset.rkt + +{% highlight racket %} + +#lang typed/racket +(provide mapper oflist mem) +(require racket/set) +(require "coord.rkt") + + +(: mapper ((Integer -> Integer) (Setof Integer) -> + (Setof Integer))) +(define (mapper f s) +(list->set (map f (set->list s)))) + ;; (set-map s f)) + ;; + +(: oflist ( (Listof Integer) -> (Setof Integer))) +(define (oflist lst ) +(foldl (lambda ([x : Integer] [s : (Setof Integer)]) ( set-add s x )) + (list->set '()) lst)) + +(: coord-comparator ( (Pairof Integer Integer ) (Pairof Integer Integer )-> Integer)) +(define (coord-comparator a b) + (cond + [(equal a b) 0] + [(false? (compare a b)) -1] + [else 1])) + +(define empty (make-hasheq)) + + +(: add ( (HashTable (Pairof Integer Integer ) Integer) + (Pairof Integer Integer ) -> + (Immutable-HashTable (Pairof Integer Integer ) Integer))) +(define (add s c) (hash-set s c 1)) + +(: mem ( (HashTable (Pairof Integer Integer ) Integer) + (Pairof Integer Integer ) -> + Boolean)) +(define (mem s c) (hash-has-key? s c)) +{% endhighlight %} + +# gamemap.rkt + +{% highlight racket %} + +#lang typed/racket/base +(provide preimg) +(require racket/set) +(require racket/hash) + + (require "coord.rkt") + +(: preimg ( ( Integer -> Boolean )(HashTable Coord Integer) -> (Setof Coord))) +(define (preimg p m ) + ;; (let ([ s :(Setof Integer) (list->set '())]) + + (let ([ s :(Setof Coord) (set)]) + (hash-for-each m + (lambda ([k : Coord] + [v : Integer] + ) + (when (p v) + (set! s (set-add s k)) + (print s) + ) ) ) + s + ) + ) +{% endhighlight %} + +# game.rkt +{% highlight racket %} +#lang typed/racket + + +(require typed/rackunit) +(module I typed/racket +(require "datatypemacro.rkt" "coord.rkt" "gameset.rkt") +(provide t Hcompose width height Empty dim) + +(struct dim ([width : Integer] [height : Integer])) +(define-datatype t + ( Hcompose t t dim) + ( Vcompose t t dim) + Empty +) + + +(: width : ( t -> Integer )) +(define ( width datatype ) + (match datatype + [(Hcompose left right d) (dim-width d)] + [(Vcompose left right d) (dim-width d )] + [(Empty) 0])) + + +(: height : ( t -> Integer )) +(define ( height datatype ) + (match datatype + [(Hcompose left right d) (dim-height d)] + [(Vcompose left right d) (dim-height d )] + [(Empty) 0])) + +(provide <#> <-> ) + +(: <#> : ( t t -> t )) +(define ( <#> t1 t2) + (match (list t1 t2) + [ (cons _ Empty) t1] + [ (cons Empty _) t2] + [ _ (let* ([w (+ (width t1) (width t2))] + [ h (max (height t1) (height t2))]) + (Hcompose t1 t2 (dim w h)) + ) + ] + ) +) + +(: <-> : ( t t -> t )) +(define ( <-> t1 t2) + (match (list t1 t2) + [ (cons _ Empty) t1] + [ (cons Empty _) t2] + [ _ (let* ([w (max (width t1) (width t2))] + [h (+ (height t1) (height t2))]) + (Vcompose t1 t2 (dim w h)) + ) + ] + ) +) +) + + +(module Shape typed/racket + + (require (submod ".." I)) + (require threading) + (require "gameset.rkt" "coord.rkt" "gamemap.rkt") + (require typed/racket/gui) + + (provide render step torus erem linspcm background ) + + (: erem ( Integer Integer -> + Integer)) + (define (erem x y) + (modulo (+ (modulo x y) y) y) + ) + + (: square ( (Pairof Integer Integer) (Pairof Integer Integer) -> + (Pairof Real Real))) + (define (square wh ab) + (match ab + [(cons x y) + (cond + [(or (< (car ab) 0) (>= (car ab) (car wh)) (< (cdr ab) 0) (>= (cdr ab) (cdr wh))) + (cons -1 -1)] + [else ab])] + )) + + (: torus : ( Coord -> (-> Coord + Coord))) + (define (torus wh ) + (lambda ([ab : Coord]) + (cons (erem (car ab) (car wh)) (erem (cdr ab) (cdr wh))) + ) + ) + + + (: mobius ( (Pairof Integer Integer) (Pairof Integer Integer) -> + (Pairof Real Real))) + (define (mobius wh ab) + (match ab + [(cons x y) + (cond + [(or (< (car ab) 0) (>= (car ab) (car wh))) + (cons (erem (car ab) (car wh)) (- (- (cdr wh) (cdr ab )) 1 ))] + [else ab])] + )) + + +( : neigh : (Coord -> Coord ) + (Pairof Integer Integer) -> (Listof (Pairof Integer Integer))) +(define (neigh topo ab) + (let* ([a (car ab)] + [b (cdr ab)] + [a-1 (sub1 (car ab))] + [a+1 (add1 (car ab))] + [b-1 (sub1 (cdr ab))] + [b+1 (add1 (cdr ab))] + [neighbours + `((,a-1 . ,b) + (,a+1 . ,b) + (,a-1 . ,b-1) + (,a-1 . ,b+1) + (,a . ,b-1) + (,a . ,b+1) + (,a+1 . ,b-1) + (,a+1 . ,b+1))]) + (map topo neighbours )) + ) + + + +(: background : ( (Instance DC<%>) Integer (Pairof Integer Integer) -> + t )) +(define (background dc step nm) +(let* ([float-sum (/ (exact->inexact (+ step (cdr nm) (car nm))) 10.0)] + [sin-value (* 24.0 (sin float-sum))] + [k (truncate sin-value)]) + (cond [(> k 0) + (begin + (define gray (make-object color% 128 128 128)) + ;; (send dc set-pen gray 1 'solid) + ;; (send dc set-text-foreground gray) + ;; (send dc draw-text "●" (cdr nm) (car nm)) + ) + (Hcompose (Empty) (Empty) (dim 50 50)) + ] + [else + (begin + ;; (send dc set-pen "red" 1 'solid) + ;; (send dc draw-rectangle (car nm) (cdr nm) 14 24) + ) + (Hcompose (Empty) (Empty) (dim 5 5)) + ])) +) + + +(define-syntax-rule (@) append) + +(: linspcm : (t Integer Integer (Integer -> t) (t t -> t) -> t)) +(define (linspcm z x n f op) + (match n + [0 z] + [1 (f x)] + [_ (let* ([m (quotient n 2)]) + (op (linspcm z x m f op) + (linspcm z (+ x m) (- n m) f op)))])) + +(: tabulate : ( Integer Integer + (Integer Integer -> t) -> t)) +(define (tabulate m n f) + (let* ([m (max m 0)] + [n (max n 0)]) + (linspcm (Empty) 0 n (lambda (y) + (linspcm (Empty) 0 m (lambda (x) + (begin + ;; (printf "Tabulate with ~a ~a ~n" x y) + (f x y) + )) + <#>)) + <->) + ) +) + +(: step : ( (-> Coord Coord )(Listof (Pairof Integer Integer)) -> + (Setof Coord))) +(define (step topo life) +(: nlive : ((Pairof Integer Integer) -> + Integer)) + (define (nlive pt) + (printf " nlive ~a " pt ) + ( let* ([neighbours (neigh topo pt )]) + (length + ( filter (lambda (neighbour) (set-member? life neighbour )) neighbours ) + ) + ) + ) + +(: f1 : ( Coord (Immutable-HashTable Coord Integer)-> (Immutable-HashTable Coord Integer))) + (define (f1 pt acc ) + ;; (printf "pair ~a~n " pt ) + ;; (for/hash ([(k v) (in-hash acc)]) (values (printf " Key ~a" k) (printf "Value ~a~n" v))) + ( let* ([neighbour (cons pt (neigh topo pt))]) + ;; (printf "Neighbour ~a~n " neighbour ) + + (foldl + (lambda ([pt1 : Coord ][acc : (Immutable-HashTable Coord Integer)]) + (begin + (match pt1 + [ (cons -1 -1) acc] + [ (cons x1 x2) + #:when (mem acc pt1) + acc ] + [ (cons x1 x2) + (let* ([n ( nlive pt1 )]) + (hash-set acc pt1 + (if (and (or (= n 3) (= n 2)) ( set-member? life pt1)) + 0 + 1) + ))]))) + acc neighbour ) + ) + ) +(define (eliminate) + (define acc : (Immutable-HashTable Coord Integer) (make-immutable-hasheq)) + (for/fold ([acc : (Immutable-HashTable Coord Integer) acc]) + ([pair life]) + (begin + (for/hash ([(k v) (in-hash acc)]) (values (printf " Key ~a" k) (printf "Value ~a~n" v))) + (let ([accu (f1 pair acc)]) + accu))) +); Return acc after folding + + (let* ([s (preimg (lambda ([ x : Number] )( = x 0)) (eliminate))]) + s + ) + + +) +(: live-pair : ( (HashTable Coord Integer) (Pairof Integer Integer) -> Void)) +(define (live-pair life pt) + (for ([(k v) (in-hash life)]) + (when (equal? k pt) + (printf "Key ~a~n" k) + (printf "Value ~a~n" v)))) + +(: render : ( (Instance DC<%>) Integer Integer Integer (HashTable Coord Integer) -> + t)) +(define (render dc w h step life ) + (define lightred (make-object color% 255 10 10)) + (printf "Life count ~a ~n" (hash-count life)) + (tabulate w (- h 1) (lambda (x y) + ;; (live-pair life (cons x y)) + + (let* ([pt (cons x y)]) + (for ([(k v) (in-hash life)]) + (if (equal? k pt) + (begin + ;; (printf "Drawing at ~a , ~a~n" x y) + + (send dc set-pen "blue" 2 'solid) + (send dc draw-rectangle x y 1 2) + ;; (send dc set-text-foreground lightred) + ;; (send dc draw-text "●" x y ) + (Hcompose (Empty) (Empty) (dim 50 50))) + (begin + (background dc step pt)) + ) + ) + (Hcompose (Empty) (Empty) (dim 50 50)) + ) + ) +) +) +) + + +(module Gui typed/racket + +(require typed/racket/gui) +(require (submod ".." Shape)) + (require (submod ".." I)) + (require "coord.rkt" "gameset.rkt") +(provide start) + +(define black-brush (new brush% [color "black"])) +(define dc #f) + +(define black-pen + (new pen% + [color "red"] + [width 10] + [style 'solid] + [cap 'round] + [join 'round] + [stipple #f])) + + + +(define game-window% + (class frame% + (super-new) + ;; (define/augment (on-close) ;; stop timer + ;; ) + ) +) + +(define lifeseed + '((2 . 1) (3 . 2) (1 . 3) (2 . 3) (3 . 3 ))) + + +(: renderer : ( (Instance DC<%>) -> Void)) +(define (renderer dc) +(let* ([life (step (torus (cons 300 300)) lifeseed)]) + (begin + (printf "Set count ~a\n" (set-count life)) + + (: updated-hash : (Immutable-HashTable Coord Integer) ) + (define updated-hash + (for/fold ([acc : (Immutable-HashTable Coord Integer) (make-immutable-hasheq)]) + ([pair (in-set life)]) + (hash-set acc pair 0))) + ) + + (render dc 300 300 1 updated-hash ) + (void) + ) +) + +;; The GUI frame showing our game +(define the-frame (new game-window% [label "Game of Life"] [width 800] [height 450])) +(define game-canvas + (class canvas% + (super-new) + ( define/override (on-paint) + (define dc (send this get-dc)) + (send dc set-font (make-font #:size 24 #:face "Fira Code")) + (send dc set-pen "black" 0 'solid) + (send dc set-smoothing 'unsmoothed) + (send dc set-brush "black" 'transparent) + (send dc set-pen black-pen) + (send this suspend-flush) + (send this resume-flush) + (renderer dc) + ) + (send the-frame show #t) + (send the-frame focus) + ) +) + +(define game-console + (new game-canvas + [parent the-frame] + )) + +(define (handle-on-timer) + (send game-console on-paint) + (send game-console refresh) +) +(define (start) + (define timer (new timer% + [notify-callback handle-on-timer] + [interval 1000])) ; milliseconds + (send timer start 1) + ) +) + + +( require 'Gui) +(require 'Shape) +(start) +{% endhighlight %} + + +# game-test.rkt +A simple incomplete test shown here as an example. + +{% highlight racket %} +( check-equal? (preimg (lambda (x) (= x 0)) + (make-immutable-hash '([3 . 1] [1 . 2] [10 . 0]))) (set 10) "Test unsuccessfull") +{% endhighlight %} + +# Racket GUI +Almost all the code shown above except the UI code is directly ported from OCaml. See the references. +Almost all the code shown below is from a template. It is just Racket's way of creating a GUI. + +{% highlight racket %} + +(define black-brush (new brush% [color "black"])) +(define dc #f) + +(define black-pen + (new pen% + [color "red"] + [width 10] + [style 'solid] + [cap 'round] + [join 'round] + [stipple #f])) + + + +(define game-window% + (class frame% + (super-new) + )) + +;; The GUI frame showing our game +(define the-frame (new game-window% [label "Game of Life"] [width 800] [height 450])) +(define game-canvas + (class canvas% + (super-new) + ( define/override (on-paint) + (define dc (send this get-dc)) + (let ((dc (send this get-dc))) + (send dc set-font (make-font #:size 24 #:face "Fira Code")) + (send dc set-pen "black" 0 'solid) + (send dc set-smoothing 'unsmoothed) + (send dc set-brush "black" 'transparent)) + (send dc set-pen black-pen) + (send dc draw-rectangle 50 50 100 100) + (send this suspend-flush) + (send this resume-flush) + ) + (send the-frame show #t) + (send the-frame focus) +) +) + +(define game-console + (new game-canvas + [parent the-frame] + )) + +(define (handle-on-timer) + (send game-console on-paint) +) +(define (start) + (define timer (new timer% + [notify-callback handle-on-timer] + [interval 1000])) ; milliseconds + (send timer start 1) + ) +(start) + +{% endhighlight %} + +# Experiment with Syntax-rule + +the syntax-rule I needed is minimal. I also learnt that the infix between two dots (. (@) .) becomes a prefix. + +{% highlight racket %} + +(define-syntax-rule (@) append) + +;; (define-syntax-rule (linspcm z (@) x n f) +(: linspcm : ((Listof Integer) Integer Integer + (Integer -> Integer) -> (Listof Integer))) +(define (linspcm z x n f) + + (match n + [0 z] + [1 (list (f x))] + [_ (let* ([m (quotient n 2)]) + (displayln n) + ((linspcm z x m f) . (@) . + (linspcm z (+ x m) (- n m) f)) + ) + ] + ) +) +{% endhighlight %} + +# Ported code from _notty.ml_ which is a OCaml library + +At this stage just to render a UI I have to port some deep part of an OCaml library +to Racket. This wasn't envisaged. + +{% highlight racket %} + +(module I typed/racket +(require "datatypemacro.rkt" "coord.rkt" "gameset.rkt") +(provide t Hcompose width height Empty dim) + +(struct dim ([width : Integer] [height : Integer])) +(define-datatype t + ( Hcompose t t dim) + ( Vcompose t t dim) + Empty +) + + +(: width : ( t -> Integer )) +(define ( width datatype ) + (match datatype + [(Hcompose left right d) (dim-width d)] + [(Vcompose left right d) (dim-width d )] + [(Empty) 0])) + + +(: height : ( t -> Integer )) +(define ( height datatype ) + (match datatype + [(Hcompose left right d) (dim-height d)] + [(Vcompose left right d) (dim-height d )] + [(Empty) 0])) + +(provide <#> <-> ) + +(: <#> : ( t t -> t )) +(define ( <#> t1 t2) + (match (list t1 t2) + [ (cons _ Empty) t1] + [ (cons Empty _) t2] + [ _ (let* ([w (+ (width t1) (width t2))] + [ h (max (height t1) (height t2))]) + (Hcompose t1 t2 (dim w h)) + ) + ] + ) +) + +(: <-> : ( t t -> t )) +(define ( <-> t1 t2) + (match (list t1 t2) + [ (cons _ Empty) t1] + [ (cons Empty _) t2] + [ _ (let* ([w (max (width t1) (width t2))] + [h (+ (height t1) (height t2))]) + (Vcompose t1 t2 (dim w h)) + ) + ] + ) +) +) + +{% endhighlight %} + +# Conclusion + +There is a logical end to this. The code is almost complete. It renders the UI but a part +of the code does not properly draw the live cells in the UI. But at this time I have concluded +that I have exercised my Racket Functional Programming skills sufficiently. +And I learnt many aspects of Racket and this knowledge will be useful when I tackle other +interesting problems. + +_References +1. https://github.com/pqwy/notty ( This isn't utilized because my Racket code is just a canvas, Not + a terminal ) +2. https://github.com/mfelleisen/7GUI is the GUI referenced code. diff --git a/_posts/2024-06-01-StorageEngine.md b/_posts/2024-06-01-StorageEngine.md new file mode 100644 index 0000000000000..d4f4ed09a24e7 --- /dev/null +++ b/_posts/2024-06-01-StorageEngine.md @@ -0,0 +1,136 @@ +--- +layout: post +title: Storage Engine +published: true +--- +# Storage Engine + +As I learn to implement a simple Database I post OCaml code to create Data Structures. + +1. The OCaml code is raw even though it compiles and simple tests pass. I look at other source code + written in Haskell, Rust, SML etc. to understand the Data Structures. +3. The code format and FP principles have to be reviewed and revised. +4. Eventually I hope the toy Database will have SQL parsers and optimizers. + +## Log-Structured Merge + +{% highlight OCaml %} + +open Stdlib + +let state = Random.State.make_self_init() + +type 'v values = Int +type 'k keys = Int + +type ('k, 'v) node = + | Empty + | Node of { + keys : 'k; + values : 'v; + sibling : ('k, 'v) node; + child : ('k, 'v) node; + } + | Head of + { sibling : ('k, 'v) node; + child : ('k, 'v) node + } + +type ( 'k, 'v) skip_list = + { skpstate : Random.State.t; (*Seed *) + skpinternal : ('k, 'v) node } + +let ltekey (node : ('k, 'v ) node) key : bool = + match node with + | Node {keys = k; _} -> k <= key + | Empty -> false + | _ -> true + +let ltkey (node : ('k, 'v ) node) key : bool = + match node with + | Node {keys = k; _} -> k < key + | Empty -> false + | _ -> true + +let eqkey (node : ('k, 'v ) node) key : bool = + match node with + | Node {keys = k; _} -> k == key + | _ -> false + +let empty_check (skp : ('k, 'v) skip_list) : bool = + match skp.skpinternal with + | Head { sibling = Empty; child = Empty } -> true + | _ -> false + +let create_empty_skiplist state : ('k, 'v) skip_list = + let head = Head { sibling = Empty; child = Empty } in + let st = state in + { skpstate = st ; skpinternal = head } + +let rec addlevels k v (internal, levels ): ('k, 'v ) node = + match levels with + | levels when levels > 0 -> create_level k v levels ( splitlevel internal) + | _ -> internal + and + create_level k v levels (l, r) = + match levels with + | 1 -> let node = Node { keys = k; values = v; sibling = Empty; child = r} in + Head { sibling = node ; child = r } + | _ -> + let head = Head { sibling = Empty; child = l } in + let head1 = Head { sibling = Empty; child = r } in + create_level k v (levels - 1) (head , head1) + and + splitlevel ( node : ('k, 'v) node ) : ('k, 'v) node * ('k, 'v) node = + match node with + | Head { sibling = _; child = _ } as head1 -> + let head = Head { sibling = Empty; child = head1 } in + (head, node) + | ns -> + let (next,r) = splitlevel ns in + (next , r) + +let number (state : Random.State.t) = + let rec randomgen n (state : Random.State.t) = + let b = Random.State.bool state + in if b then randomgen (n + 1) state + else (n, state ) + in randomgen 0 state + + +let rec insert k v (skp : ('k, 'v) skip_list ) = + let (rg, state') = number skp.skpstate in + let skplist = addlevels k v (create_skiplist k v rg skp.skpinternal) in + { skpstate = state'; skpinternal = skplist } + and + create_skiplist k v rg (node : ('k, 'v) node) : ('k, 'v) node*int = + + match node with + | Empty -> (Empty, rg) + | Head _ -> (Empty, rg) + | Node { keys = _; values = _; sibling = s; child = c } -> + + if ( ltkey s k ) then + begin + match (create_skiplist k v rg s) with + | (a, _) -> + (Node { keys = k; values = v; sibling = a; child = c }, -1) + end + else if ( ltekey s k ) then + begin + let ns = Node { keys = k; values = v; sibling = s; child = c } in + (ns, -1) + end + else + begin + let (cn, w) = (create_skiplist k v rg c) in + if (w < 0) then + (Node { keys = k; values = v; sibling = s; child = cn }, -1) + else if w == 0 then + (Node { keys = k; values = v; + sibling = Node {keys = k; values = v;sibling = s; child = c}; child = cn }, -1) + else (Node {keys = k; values = v; + sibling = Head {sibling = s; child = cn}; child =c}, w - 1) + end + +{% endhighlight %} diff --git a/_posts/2024-10-01-CollaborativeEditorPatterns.md b/_posts/2024-10-01-CollaborativeEditorPatterns.md new file mode 100644 index 0000000000000..bef26fc793e5c --- /dev/null +++ b/_posts/2024-10-01-CollaborativeEditorPatterns.md @@ -0,0 +1,240 @@ +--- +layout: post +title: Collaborative Editor +published: true +--- + +Collaborative editor patterns that I try to code could be of many kinds. There are +string manipulation algorithms, CRDTs and even LLM inference. So even though a full-fledged +editor is not in scope here, many algorithms like these can be explored and a functional +editor can be created. + +The code will be updated directly here in phases. I don't commit this to a separate repository. + +Please note that this is a random collection of algorithms that eventually could be part of a simple editor. +There are too many books and papers that deal with a multitude of algorithms. + +# OCaml 5 Effect Handlers + +I will add some sections like this to explain the reason for experimenting with new paradigms. In many cases the code is too dense and will seem complicated when new techniques are introduced needlessly but Effect handlers are interesting to learn. Application though should be selective. There will be many usecases for these in the future. + +So the following is an experiment in the sense that the code quality will be fixed only later. So, for example, global references are used to complete the code even though they +are unnecessary. + +# Breaking paragraphs into lines + +The title describes the essence of one such algorithm( Plass and Knuth ) to break paragraphs. + +{% highlight OCaml %} +open Stdlib +open Effect.Deep +open Effect + +type entry = { + first : int; + last : int; + mutable next : int; + mutable score : int} + + +let gl = ref [] + +let is_space= function ' ' -> true | _ -> false + +let rec printlist l = + match l with + | hd ::tl -> + Printf.printf " %d %d %d %d\n" hd.first hd.last hd.next hd.score; + printlist tl + | [] -> () + +let final_state l (start, idx) = + + (* let e = {first = start; last = idx; next = -1; score = -1} in *) + (* l := !l @ [e]; *) + let e = {first = -1; last = -1; next = -1; score = 0} in + l := !l @ [e]; + Printf.printf " Final state %d %d\n" start idx; + Printf.printf " Size of list is %d\n" (List.length !l); + l + +type _ Effect.t += Skipping_spaces : (int * int ) -> unit Effect.t + +let parabreak l text ideal_width max_width = + Printf.printf " parabreak\n"; + let start = ref 0 in + let idx,_ = + String.fold_left (fun (idx,word_or_space) c -> + match (is_space c, word_or_space) with + | (true,true) + -> + Printf.printf "Space at index %d, skipping\n" idx; + + if !start < idx then + perform (Skipping_spaces (!start,idx) ); + + start := idx + 1; + (idx + 1,false); + | (true,false) + -> Printf.printf "No Space at index %d, skipping\n" idx; + (idx + 1,false) + | (false,_) + -> + (idx + 1,true)) (0,false) text + in + + if !start < idx then + Printf.printf "Final - Skipping spaces %d %d\n" !start idx; + perform (Skipping_spaces (!start,idx)); + final_state l (!start,idx) +let effective text l = + match_with (fun () -> parabreak l text 10 29) + () + { effc = (fun (type c) (eff1: c Effect.t) -> + match eff1 with + | Skipping_spaces (s,s1) -> Some (fun (k: (c,_) continuation) -> + Printf.printf "Skipping spaces \"%d %d\"\n" s s1; + + let e = {first = s; last = s1; next = -1; score = -1} in + l := !l @ [e]; + continue k () + ) + | _ -> None + ); + exnc = (function + | e -> raise e + ); + (* retc = fun _ -> failwith "Fatal error" *) + (* retc = (fun res -> Printf.printf "Computation returned %d: \n" (List.length !l)) *) + + retc = (fun _ -> l) + + } + + +let rec plassbreak indent idx idealwidth maxwidth = + + let jdx = ref( idx + 1 ) in + let lastrecord = List.nth !gl idx in + let llen = ref (lastrecord.last - lastrecord.first) in + let bscore = idealwidth - !llen in + let bscore = ref (bscore * bscore) in + let btail = ref !jdx in + let rec loop_while j_dx = + if j_dx < (List.length !gl) then( + Printf.printf "llen: %d, bscore: %d, btail: %d\n" !llen !bscore !btail; + let {first; last; next; score} = List.nth !gl j_dx in + let wwidth = last - first in + if ((!llen + wwidth) < maxwidth) then( + + let lscore = ref (idealwidth - (!llen + wwidth)) in + lscore := !lscore * !lscore; + llen := !llen + wwidth + 1; + + if score = -1 then + begin + plassbreak (indent + 1) j_dx idealwidth maxwidth; + end; + + let score1 = List.nth !gl !jdx in + if ((!lscore + score1.score) < !bscore) then( + bscore := !lscore + score1.score; + btail := !jdx; + ); + loop_while (j_dx + 1) + )else () + )else () + in + loop_while !jdx; + let record = List.nth !gl idx in + record.score <- !bscore; + record.next <- !btail; + if (record.next + 1) = List.length !gl then( + record.score <- 0; + ) + +let rec loop_while line text lines idx next acc = + if acc > next || (acc + 1) >= List.length !gl then + ( + line + ) + else + let {first; last; _} = List.nth lines acc in + if (last - first) <= 0 then + ( + line; + ) + else( + let new_line = + line ^ (if acc == idx then "" else " ") ^ + String.sub text first (last - first) + in + loop_while new_line text lines idx next (acc + 1) + ) + + +let layout text idealwidth maxwidth = + + let rec loop idx lines line = + if (idx < (List.length !gl - 1)) then + + let entry = List.nth lines idx in + let line = loop_while line text lines idx entry.next idx in + let line = + if (String.length line < maxwidth) + then + ( + let line = line ^ String.make (maxwidth - String.length line) ' ' in + line + )else line + in + let line = Bytes.of_string line in + let _ = Bytes.set line idealwidth '+' in + let line = Bytes.to_string line ^ "|" in + Printf.printf " %s \n" line; + loop entry.next !gl "" + in + loop 0 !gl "" + +(* read the entire file *) +let read_file() = + let contents = In_channel.with_open_text "/Users/anu/Documents/go/testwiki.txt" In_channel.input_all in + contents + +let pbreak_main = + try + let l = ref [] in + let file_contents = read_file() in + + let l1 = effective file_contents l in + gl := !l1; + + let _ = plassbreak 0 0 10 29 in + Printf.printf "Global list %d\n" (List.length !gl) ; + printlist !gl; + + let _ = layout file_contents 10 20 in + + () + + + with + | exn -> Printf.printf "Unhandled exception: %s\n" (Printexc.to_string exn) +{% endhighlight OCaml %} + +# Validation of the algorithm + +The test is the only validation now. The algorithm is not verified based on the original +reference to the algorithm. This OCaml code is ported from C++. +There is a single bug that increases the _score_ and as a consequence breaks the paragraph +at the wrong position. But even otherwise the breaks don't seem to be perfect. + +This has to be surely improved when used by an editor. That is a task for the future. + + The first + | + first know+ | + known use + | + use of + | + of the + | + the terms.+ | + terms. + | diff --git a/_posts/2024-11-05-Raft.md b/_posts/2024-11-05-Raft.md new file mode 100644 index 0000000000000..51d06209ac4fd --- /dev/null +++ b/_posts/2024-11-05-Raft.md @@ -0,0 +1,465 @@ +--- +layout: post +title: Raft +published: true +--- + +An attempt to implement the Raft distributed protocol using OCaml 5 Eio, Effect Handlers, and capnp-rpc-lwt, which is a RPC library based on Eio +. Eio allows integration of Lwt framework. + + + +1. The Eio _capnp-rpc_ library is still in a branch waiting to be merged. This will be a RPC library entirely based on _eio_. +2. The older LWT _capnp-rpc-lwt_ is used as Eio can integrate with it. This is done because the code started using Lwt-Eio integration. +3. The newer _capnp-rpc_ based entirely on Eio will be used later. + + Everything involved in this has to be learned. Code is incrementally developed and updated as new logic is added. + +# Simple Eio Client/Server and LWT RPC + +## Client + +The details of the setup using _dune_ to build this will eventually be added to the README of the Git repository. Code is now directly posted here. +A simple echo RPC call is included to test the capability of the _capnp-rpc_lwt_ library. + +{% highlight ocaml %} +open Eio.Std +open Capnp_rpc_lwt +open Lwt.Infix +(* Prefix all trace output with "client: " *) +let traceln fmt = traceln ("client: " ^^ fmt) + +module Read = Eio.Buf_read +module Write = Eio.Buf_write +module Api = Echo_api.MakeRPC(Capnp_rpc_lwt) +module Echo = Api.Client.Echo + +let ping t msg = + let open Echo.Ping in + let request, params = Capability.Request.create Params.init_pointer in + Params.msg_set params msg; + Capability.call_for_value_exn t method_id request >|= Results.reply_get + +let rec loop clock to_server from_server = + Write.string to_server "Request"; + Write.char to_server ' '; + Write.string to_server "from client\n"; + Eio.Time.sleep clock 0.5; + traceln "Waiting for server " ; + let reply = Read.line from_server in + traceln "Got reply %S" reply; + Eio.Time.sleep clock 0.5; + loop clock to_server from_server + +let run ~net ~clock ~addr = + Eio.Time.sleep clock 0.5; + traceln "Connecting to server at %a..." Eio.Net.Sockaddr.pp addr; + Switch.run @@ fun sw -> + let flow = Eio.Net.connect ~sw net addr in + Write.with_flow flow @@ fun to_server -> + let from_server = Read.of_flow flow ~max_size:100 in + loop clock to_server from_server + +{% endhighlight %} + +## Server + +{% highlight ocaml %} + +open Eio.Std + +(* Prefix all trace output with "server: " *) +let traceln fmt = traceln ("server: " ^^ fmt) + +module Read = Eio.Buf_read +module Write = Eio.Buf_write +module Api = Echo_api.MakeRPC(Capnp_rpc_lwt) + +open Capnp_rpc_lwt + +module Api = Echo_api.MakeRPC(Capnp_rpc_lwt) +module Echo = Api.Client.Echo +let local = + let module Echo = Api.Service.Echo in + Echo.local @@ object + inherit Echo.service + + method ping_impl params release_param_caps = + let open Echo.Ping in + let msg = Params.msg_get params in + release_param_caps (); + let response, results = Service.Response.create Results.init_pointer in + Results.reply_set results ("echo:" ^ msg); + Service.return response + end +(* Read one line from [client] and respond with "OK". *) +let rec handle_client to_client from_client = + traceln "Received: %S" (Read.line from_client); + Write.string to_client "OK\n"; + Write.flush to_client; + traceln "Written to client "; + handle_client to_client from_client + + +let rec server_loop socket = + + Switch.run @@ fun sw -> + let flow,addr = Eio.Net.accept ~sw socket in + traceln "Accepted connection from %a" Eio.Net.Sockaddr.pp addr; + Fiber.fork ~sw (fun () -> + Write.with_flow flow @@ fun to_client -> + let from_client = Read.of_flow flow ~max_size:100 in + handle_client to_client from_client + ); + server_loop socket + +(* Accept incoming client connections on [socket]. + We can handle multiple clients at the same time. + Never returns (but can be cancelled). *) +let run socket = + server_loop socket + + {% endhighlight %} + +## main.ml + +This file has code that implements + +1. A busy wait loop to continuously send and receive messages.( Commented now ) + +3. A RPC call.( This would be the relevant design for implementing the Raft distributed consensus protocol ) + +{% highlight ocaml %} + +open Eio.Std +open Lwt.Infix + +let addr = `Tcp (Eio.Net.Ipaddr.V4.loopback, 8080) + +module Api = Echo_api.MakeRPC(Capnp_rpc_lwt) +module Echo = Api.Client.Echo + +(* Run a server and a test client, communicating using [net]. *) +let main ~net ~clock = + Switch.run @@ fun sw -> + (* We create the listening socket first so that we can be sure it is ready + as soon as the client wants to use it. *) + let listening_socket = Eio.Net.listen net ~sw ~reuse_addr:true ~backlog:5 addr in + (* Start the server running in a new fiber. + Using [fork_daemon] here means that it will be stopped once the client is done + (we don't wait for it to finish because it will keep accepting new connections forever). *) + Fiber.fork_daemon ~sw (fun () -> Server.run listening_socket); + (* Test the server: *) + Fiber.fork ~sw ( fun () -> Client.run ~net ~clock ~addr); + () + +let () = + Logs.set_level (Some Logs.Warning); + Logs.set_reporter (Logs_fmt.reporter ()) + +let () = +(* Eio_main.run @@ fun env -> *) + + (* let net = Eio.Stdenv.net env in *) + (* let clock = Eio.Stdenv.clock env in *) + (* main ~net ~clock *) + + Lwt_main.run begin + let service = Server.local in + Client.ping service "foo" >>= fun reply -> + Fmt.pr "Got reply %S@." reply; + Lwt.return_unit + end +(* main.ml *) + +{% endhighlight %} + +## Consensus module +This module holds the core logic to elect a leader. +This is the current state of this module. + +{% highlight ocaml %} + +open Eio.Std + + +module RaftConsensus = +struct + + let get_state = function + | `Leader -> "leader." + | `Follower -> "follower." + | `Candidate -> "candidate." + | `Dead -> " dead." + + type 'a cmid = int + + type 'a peerids = 'a list + + + (* Mutable term tracker *) + type 'a currentterm = int ref + + type 'a votedfor = int + + type 'cms cmstate = + [ `Leader + | `Follower + | `Candidate + | `Dead + ] + type election_reset_time = Mtime.t + + + let election_reset_time = + Eio_main.run @@ fun env -> + let clock = Eio.Stdenv.clock env in + let current_time = Eio.Time.now clock in + Mtime.to_uint64_ns (Mtime.of_uint64_ns (Int64.of_float(current_time *. 1_000_000_000.))) + + let election_timeout = + let time = Random.int 150 in + time + Int64.to_int election_reset_time + + let currentterm = ref 0 + + let setterm mutex value= + let termstarted = ref 0 in + Eio.Mutex.use_rw ~protect:false mutex (fun () -> termstarted := !value); + termstarted + +let periodic_timer () = + Eio_main.run @@ fun env -> + let cond = Eio.Condition.create () in + Fiber.fork (fun () -> + let rec loop () = + let timeout = Eio.Time.Timeout.seconds (Eio.Stdenv.mono_clock env) (float_of_int election_timeout) in + Eio.Time.Timeout.run_exn timeout (fun () -> + Eio.Condition.broadcast cond; + Printf.printf "Broadcast\n" + ); + Fiber.yield (); + loop () + in + loop () + ) +end + +{% endhighlight %} + +## Timer events + +My first attempt to raise a timer event and wait for it needs two loops, a mutex and a _Eio.Condition_. I did explore +other ways to set up a reactive, event-driven program but that doesn't seem to be built into eio. We have to develop a framework +on top of eio. +The code does seem complicated and the _mutex_ may not be required. I need to investigate the exact reason for a mutex( Go +code I drew inspiration from uses mutexes but eio works with Fibers and domains). I will take a look at this later. + +_Fibers_ are lightweight-threads. + + + +{% highlight ocaml %} + + let await_timeout timeout_mutex = + Eio.Condition.await_no_mutex timeout_mutex; + traceln "Timeout condition is set" + + + let periodic_timer () = + Eio_main.run @@ fun env -> + let cond = Eio.Condition.create () in + let clock = Eio.Stdenv.clock env in + Fiber.both (fun () -> + let rec loop () = + Eio.Time.sleep clock 1.0; + + Eio.Condition.broadcast cond; + + Fiber.yield (); + loop () + in + loop () + ) + (fun () -> + let rec loop () = + traceln "Waiting for timeout event "; + await_timeout cond; + traceln "timeout event "; + Fiber.yield (); + loop () + in + loop () + ); + + +{% endhighlight %} + +## Election Timer + +So the function that executes the election timer based on some conditions is this. + +{% highlight ocaml %} + + let checkelection_state mutex timeout_duration term_started = + + Eio.Mutex.use_rw ~protect:false mutex (fun () -> + (match current_state with + | state when state <> `Candidate && state <> `Follower -> + traceln "in election timer state is %s" (get_state state); + | state -> + traceln "in election timer state is %s" (get_state state); + ); + traceln "checkelection_state"; + if term_started <> current_term.currentterm then( + traceln "in election timer term changed from %d to %d" term_started + current_term.currentterm); + let elapsed_time = Mtime.span( now() ) (Mtime.of_uint64_ns election_reset_event_time) in + if (Mtime.Span.compare elapsed_time (Mtime.Span.of_uint64_ns (Int64.of_int timeout_duration)) < 0) then + traceln " timeout_duration %.3f < elapsed_time %.3f" + (Mtime.Span.to_float_ns (Mtime.Span.of_uint64_ns (Int64.of_int timeout_duration))) + (Mtime.Span.to_float_ns elapsed_time) + else + traceln " Start election"; + + ) + + + let periodic_timer () = + Eio_main.run @@ fun env -> + let timeout_duration = election_timeout in + let term_started = current_term.currentterm in + let mutex = Eio.Mutex.create() in + let cond = Eio.Condition.create () in + let clock = Eio.Stdenv.clock env in + Fiber.both (fun () -> + let rec loop () = + Eio.Time.sleep clock 1.0; + + Eio.Condition.broadcast cond; + + Fiber.yield (); + loop () + in + loop () + ) + (fun () -> + let rec loop () = + traceln "Waiting for timeout event "; + await_timeout cond; + checkelection_state mutex timeout_duration term_started; + traceln "timeout event "; + Fiber.yield (); + loop () + in + loop () + ); + +{% endhighlight %} + +## Refactoring timer event code + +The key requirement that lead to this is a mechanism to pause and restart the timer at various stages. Further testing +may disprove this hypothesis but for now we will try to reuse the timer instead of discarding and starting a new one. + +The OCaml eio library has just such a facility. It is cleaner than the original timer shown above. +I have shown only that part of the code below. + +{% highlight ocaml %} + + let unpaused = ref (Promise.create_resolved ()) + let unpaused_resolver = ref None + + let pause () = + let p, r = Promise.create() in + unpaused := p; + unpaused_resolver := Some r + + let unpause () = + match !unpaused_resolver with + | Some r -> + Promise.resolve r () + | None -> () + + let periodic_timer env = + Eio.Switch.run @@ fun _ -> + let timeout_duration = election_timeout in + let term_started = current_term.currentterm in + let mutex = Eio.Mutex.create() in + let cond = Eio.Condition.create () in + let clock = Eio.Stdenv.clock env in + Fiber.both (fun () -> + while true do + Promise.await !unpaused; + Eio.Condition.broadcast cond; + Eio.Time.sleep clock 0.5; + done + ) + + (fun () -> + let rec loop () = + traceln "Waiting for timer event "; + await_timeout cond; + checkelection_state mutex timeout_duration term_started; + + traceln "timer event "; + Fiber.yield (); + loop () + in + loop () + ); + + +{% endhighlight %} + +This code facilitates a mechanism that allows use to _pause_ the timer when we don't need it and start +it again. + +# Using effect handlers + +This is from the documentation. + +**Effect handlers are a mechanism for modular programming with user-defined effects. Effect handlers allow the programmers to describe +computations that perform effectful operations, whose meaning is described by handlers that enclose the computations** + +And I use _Early_return_ as my effect to return from my function. + +{% highlight ocaml %} + +type _ Effect.t += Early_return : int -> int Effect.t + +let rec effective_request_vote + (mutex : Eio.Mutex.t) + (id : int) + (term : int) + (votes_received : int) : int Lwt.t = + + match_with (fun () -> requestvote mutex id term votes_received) + () + { effc = (fun (type c) (eff1: c Effect.t) -> + match eff1 with + | Early_return v -> Some (fun (k: (c,_) continuation) -> + Printf.printf "Early Return" ; + continue k v + ) + | _ -> None + ); + exnc = (function + | e -> raise e + ); + retc = (fun _ -> Lwt.return votes_received) + + } + {% endhighlight %} + + +But there is a complex interplay between the effect, _LWT_ and _EIO_ as the older LWT _capnp-rpc-lwt_ is used. Eio can integrate with it. +This is done because the code started using Lwt-Eio integration. The newer _capnp-rpc_ based entirely on Eio will be used later. + +This necessitates the line in the calling functiion which produces the effect. + + {% highlight ocaml %} + Lwt.return (perform (Early_return votes_received)); + {% endhighlight %} + +At this stage the code compiles but has to be tested more. diff --git a/_posts/2024-12-14-IntervalTreeClocks.md b/_posts/2024-12-14-IntervalTreeClocks.md new file mode 100644 index 0000000000000..c024ec8cf857c --- /dev/null +++ b/_posts/2024-12-14-IntervalTreeClocks.md @@ -0,0 +1,98 @@ +--- +layout: post +title: Joy of OCaml - Part I +published: false +--- + +{% highlight ocaml %} + + +type 'a event = Empty | Leaf of 'a node | Node of 'a node +and 'a node = { value : 'a; + left : 'a event; + right : 'a event} +let change_r e = + (match e.right with + |Empty -> Empty + |_ -> e.right + ) + +let change_l e = + (match e.left with + |Empty -> Empty + |_ -> e.left + ) + +let copy e = + match e with + |Node n -> + let el = n.left in + let er = n.right in + Leaf { value = n.value; left = change_l { n with left = el }; right = change_r { n with right = er } } + + |Leaf l -> + let el = l.left in + let er = l.right in + Leaf { value = l.value; left = change_l { l with left = el }; right = change_r { l with right = er } } + |Empty -> Empty + +let rec join (e1 : 'a event) ( e2 : 'a event) : 'a event = + + match e1,e2 with + | Node node1,Node node2-> + if node1.value > node2.value then + begin + join (copy e2) e1; + end + else + let d = node2.value - node1.value in + let n_left = join node1.left node2.left in + let n_right = join node1.right node2.right in + Node { value = node2.value + d; left = n_left; right = n_right } + | Leaf _,Node _-> + let n = Node { value = 0; left = Empty; right = Empty } in + join n e2; + | Node _,Leaf _-> + let n = Node { value = 0; left = Empty; right = Empty } in + join e1 n; + | Leaf node1,Leaf node2-> + Node { value = max node1.value node2.value; left = node1.left; right = node1.right }; + | Empty,Empty -> Printf.printf "Event failure"; Empty; + | Empty, _ -> + e2 + | _, Empty -> e1 + (* e1.normalize(); *) + +let drop val1 val2 = + if val1 <= val2 then + val2 - val1 + else + val1 + +let normalize e1 = + match e1 with + | Node node-> + (match node.left,node.right with + | Leaf node1,Leaf node2 when node1.value == node2.value -> + Leaf { value = node.value + node1.value; left = node.left; right = node.right } + | Node node1,Node node2 -> + let mm = (min node1.value node2.value) in + let n_l = Node { value = (drop node1.value mm); left = node1.left; right = node1.right } in + let n_r = Node { value = drop node2.value mm; left = node2.left; right = node2.right } in + Node { value = node.value + mm ; left = n_l; right = n_r } + | Node node1, (Empty | Leaf _) -> + Node { value = node.value; left = Node node1; right = node.right } + | (Empty | Leaf _), Node node2 -> + Node { value = node.value; left = node.left; right = Node node2 } + | (Empty, Empty) -> + Node { value = node.value; left = Empty; right = Empty } + | (Empty, Leaf node ) -> + Node { value = node.value; left = Empty; right = Leaf node } (* Case is not clear *) + | (Leaf node, (Empty|Leaf _)) -> + Node { value = node.value; left = Leaf node ; right = Leaf node } (* Case is not clear *) + ) + | Leaf node -> + Leaf { value = node.value ; left = node.left; right = node.right } + | Empty -> Empty + +{% endhighlight %} diff --git a/_posts/2025-01-11-ImplementingaJITCompiledLanguagewithOCamlandLLVM.md b/_posts/2025-01-11-ImplementingaJITCompiledLanguagewithOCamlandLLVM.md new file mode 100644 index 0000000000000..303460cc74c55 --- /dev/null +++ b/_posts/2025-01-11-ImplementingaJITCompiledLanguagewithOCamlandLLVM.md @@ -0,0 +1,135 @@ +--- +layout: post +title: Implementing a JIT compiled language using OCaml and LLVM +published: true +--- + +Notwithstanding the loftiness of the title, this is just a tutorial inspired by [Implementing a JIT Compiled Language with Haskell and LLVM](https://smunix.github.io/www.stephendiehl.com/llvm/index.html). I chose OCaml and decided to select my own libraries to accomplish this. +So, for example, the parser using Angstrom, which I know nothing about. + +While I code I also strive to learn. + +1. The code will be developed and refactored incrementally. +2. The version posted here is compiled and tested but it is incomplete. The final version will be in Git if everything goes + according to plan. + +# Parser combinators + +Ported from Haskell to OCaml and uses _Angstrom_. Lightly tested. + +{% highlight ocaml %} +module LibAngstrom = struct + open Angstrom + + let is_whitespace = function + | '\x20' | '\x0a' | '\x0d' | '\x09' -> true + | _ -> false + let ws = take_while is_whitespace + let reservedops = ["+","*","-",";"] + let reservednames = ["def","extern"] + let comma = char ',' <* ws + let colon = char ':' <* ws + + let is_comment = function + | '#' -> false + | x -> not @@ is_whitespace x + +let is_integer = function '0'..'9' -> true | _ -> false +let is_double= function | '0'..'9' | '.' -> true | _ -> false + + +let integer = + take_while1 is_integer>>| int_of_string + +let double= + take_while1 is_double>>| float_of_string + +let parens p = + char '(' >>= fun _ -> + p >>= fun x -> + char ')' >>= fun _ -> return x + + +let reservedops = function + |ops -> () + +let reservednames = function + |names -> () + +let lexer = + fix(fun lexer -> + ws + *> choice + [ + parens integer ; + ]) +end + +let main text = + Angstrom.parse_string ~consume:All LibAngstrom.lexer text +{% endhighlight %} + +# The ADT + +{% highlight ocaml %} + +module Syntax = struct + +type name = string + +type 'a expr = + Const of float + | BinOp of 'a op * 'a expr * 'a expr + | Var of string + | Call of name * 'a expr list + | Function of name * 'a expr list * 'a expr + | Extern of name * 'a expr list +and 'a op + = Plus + | Minus + | Times + | Divide + +end + +{% endhighlight %} + +The following is a simple test to drive the code shown above. This code will be improved further. + +{% highlight ocaml %} + +open Scanner +type parsed_text = (int, string ) result +[@@deriving show] + + let%expect_test _= + + let parsed_text = main "(1)" in + Printf.printf "%s" (show_parsed_text parsed_text); + [%expect {| (Ok 1) |}]; + +{% endhighlight %} + +# The REPL +The driver is intended to invoke the expressions in a loop to create the small REPL. The loop is now commented temporarily. + +{% highlight ocaml %} + +type parsed_text = (int, string ) result +[@@deriving show] + +let cli ~stdin ~stdout = + let buf = Eio.Buf_read.of_flow stdin ~initial_size:100 ~max_size:1_000_000 in + (* while true do *) + + let line = Eio.Buf_read.line buf in + traceln "> %s" line; + match line with + |line -> let parsed_text = main line in + Eio.Flow.copy_string (Fmt.str " %S\n" (show_parsed_text parsed_text)) stdout + ; + (* done *) + +{% endhighlight %} + +> TODO diff --git a/_posts/2025-02-18-MachineLearningNotes.md b/_posts/2025-02-18-MachineLearningNotes.md new file mode 100644 index 0000000000000..7c87ec8770d79 --- /dev/null +++ b/_posts/2025-02-18-MachineLearningNotes.md @@ -0,0 +1,18 @@ +--- +layout: post +title: Machine Learning notes +published: false +--- + +This is an experimental document dealing with Machine Learning Mathematics and demonstrative code. It is experimental because +I am noting down the derivations based on several notable University courses. I will annotate each section to indicate the course content +I copy here. Eventually I will research and validate if my mental model is correct or not using additional material like diagrams and +TensorFlow code. I am yet to learn JAX. + +The sections are random as I view course videos in no particular order. But Generative models form the underlying theme. + +Errors will creep into this as my experience with this field is rather limited. + +# Flow and Diffusion Models + +This sentence uses `$` delimiters to show math inline: $\sqrt{3x-1}+(1+x)^2$ diff --git a/_posts/2025-03-04-Query_compiler.md b/_posts/2025-03-04-Query_compiler.md new file mode 100644 index 0000000000000..9de9b255b106f --- /dev/null +++ b/_posts/2025-03-04-Query_compiler.md @@ -0,0 +1,35 @@ +--- +layout: post +title: Query compilers +published: false +--- + +## OCaml Meta programming + +My simplest code generator. There is a scarcity of tutorials that explain OCaml PPX and its variants. +It is a pity as I believe there exist some good code generation usecases. + +{% highlight ocaml%} + +let function_generator ~ctxt _input _is_private = + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + let expr = [%expr [1; 2; 3]] in + let pattern = ppat_var ~loc { txt = "Pattern_test"; loc } in + let match_expr = + [%expr + match [%e expr] with + | [%p pattern] -> [%e evar ~loc "Pattern_test"] + ] + in + let code = + [[Ast_builder.Default.value_binding ~loc ~pat:pattern ~expr:match_expr] |> pstr_value ~loc Nonrecursive ] + in + List.iter (fun item -> + Format.printf "Generated Code: %a@." Pprintast.structure_item item + ) code; + code + +let generator () = Deriving.Generator.V2.make (args ()) function_generator +let _ = Deriving.add "name" ~str_type_decl:(generator ()) + +{% endhighlight %} diff --git a/_posts/2025-05-12-LambdaCalculus.md b/_posts/2025-05-12-LambdaCalculus.md new file mode 100644 index 0000000000000..1ef35453db31f --- /dev/null +++ b/_posts/2025-05-12-LambdaCalculus.md @@ -0,0 +1,405 @@ +--- +layout: post +title: Explore Lambda Calculus - Part I +published: true +--- + +# Simple Lambda Calculus interpreter + +> This is inspired currently by Artem Pianykh's F# code( Video ). +But I do come across many more videos posted years back. I cling to the hope +to incorporate more aspects as I learn. +> There is also [similary AST manipulation code here](https://github.com/nunchaku-inria/nunchaku/tree/a69d3ebce2fb83c40824420c4d93cc615c8a5fa1) +## Porting FSharp code to OCaml + +The code is directly ported to OCaml. So the pattern matching aspects are exactly like +the F# code with slight modifications. + +My only learning goal that lead to an improvement for me was the modularity in OCaml. + +The aspects I learnt to use are + +1. Module Types +2. Functors +3. Instantiation of modules like this + +But truly the aspects I should have learnt are + +1. Term Reduction by substitution +2. Closures +3. let-bindings + +and other deeper concepts. That is now aspirational. + +{% highlight ocaml%} + + module Language = + Lang(struct + type t = arithmetic_fn + [@@deriving show] + type a = int + type b = int + let apply fn a b : int = + match fn with + | Add -> a + b + | Sub -> a - b + | Mul -> a * b + | Div -> a / b + end) + (struct + type t = comparison_fn + [@@deriving show] + type a = int + type b = int + let apply fn a b : int = + match fn with + | Less -> if a < b then 1 else 0 + | Greater -> if a > b then 1 else 0 + | Equal -> if a = b then 1 else 0 + end) + +{% endhighlight %} + +This is the entire code. _[@@deriving show]_ is used in many locations to pass the test shown below. + +{% highlight ocaml%} +type arithmetic_fn = | Add | Sub | Mul | Div +[@@deriving show] + +module type ARITHMETIC_FN= +sig + type t = arithmetic_fn + [@@deriving show] + type a = int + type b = int + val apply : t -> a -> b -> a +end + +type comparison_fn = + | Less + | Equal + | Greater +[@@deriving show] + +module type COMPARISON_FN= +sig + type t = comparison_fn + [@@deriving show] + type a = int + type b = int + val apply : t -> a -> b -> a +end + + +module Lang(ArithmeticType : ARITHMETIC_FN) + (ComparisonType : COMPARISON_FN) = struct + + module ArithmeticType = ArithmeticType + module ComparisonType = ComparisonType + type var_name = string + [@@deriving show] + type btype = int + [@@deriving show] + + +exception Type_error + + +type builtin_fn = + |Arithmetic of ArithmeticType.t * expr * expr + |Comparison of ComparisonType.t * expr * expr +[@@deriving show] +and +expr = + | Var of var_name + | Abs of var_name * expr + | App of expr * expr + | Lit of btype + | Builtin of builtin_fn + | Cond of expr * expr * expr +[@@deriving show] + +type eval_error = WrongType of expr * string + +exception EvalException of eval_error + +end + +module Language = + Lang(struct + type t = arithmetic_fn + [@@deriving show] + type a = int + type b = int + let apply fn a b : int = + match fn with + | Add -> a + b + | Sub -> a - b + | Mul -> a * b + | Div -> a / b + end) + (struct + type t = comparison_fn + [@@deriving show] + type a = int + type b = int + let apply fn a b : int = + match fn with + | Less -> if a < b then 1 else 0 + | Greater -> if a > b then 1 else 0 + | Equal -> if a = b then 1 else 0 + end) + + +module Expr = struct +include Language + + let asInt = function + | Lit btype -> btype + | other -> raise( EvalException(WrongType( other, "int" )) ) + let asAbs = function + | Abs (var, body ) -> var, body + | other -> raise( EvalException(WrongType( other, "lambda" )) ) + + + let rec subst (replaceable : var_name ) (replaceWith : expr ) (expr : expr) = + let substFn = subst replaceable replaceWith in + match expr with + | Lit _ -> expr + | Builtin ( Arithmetic( fn, opA, opB ) ) -> + Builtin ( Arithmetic( fn, substFn opA, substFn opB ) ) + | Builtin ( Comparison( fn, opA, opB ) ) -> + Builtin ( Comparison( fn, substFn opA, substFn opB ) ) + | Cond (pred, trueBranch, falseBranch) -> + Cond ( substFn pred, substFn trueBranch, substFn falseBranch) + | App ( expr, arg ) -> App( substFn expr , substFn arg ) + | Var boundName -> if boundName = replaceable then replaceWith else expr + | Abs (boundName, body ) -> if boundName = replaceable then expr else + Abs( boundName, substFn body ) + + + and eval( expr : expr ) : expr = + match expr with + | Lit _ -> expr + | Builtin ( Arithmetic( fn, opA, opB ) ) -> + let valA = eval opA |> asInt in + let valB = eval opB |> asInt in + Lit ( ArithmeticType.apply fn valA valB) + | Builtin ( Comparison( fn, opA, opB ) ) -> + let lhs = eval opA |> asInt in + let rhs = eval opB |> asInt in + Lit (ComparisonType.apply fn lhs rhs ) + | Cond (pred, trueBranch, falseBranch) -> + let valPred = eval pred |> asInt in + if valPred <> 0 then eval trueBranch else eval falseBranch + | Abs _ -> expr + + | App( expr, arg ) -> + let lambdaVar, lambdaBody = eval expr |> asAbs in + subst lambdaVar arg lambdaBody |> eval + + | Var _ -> failwith "Wrong evaluation " + +end + +{% endhighlight%} + +## Test + +{% highlight ocaml %} + + +let lazyFixpoint = + let innerAbs = + Abs( + "x", + App( Var "f", App( Var "x", Var "x" ) )) in + Abs( "f", App ( innerAbs, innerAbs )) + +let fibStep = + let xMinus n = Builtin (Arithmetic( Sub, Var "x", Lit n )) in + let fb = Builtin( Arithmetic( Add, App( Var "f", xMinus 1 ), App( Var "f", xMinus 2 ) ) ) in + Abs( "f", + Abs( + "x", + Cond( + Builtin( Comparison( Less, Var "x", Lit 2 ) ), + Lit 1, + fb + ) + ) + ) + +let fib( n : int ) = + let fn = App( lazyFixpoint, fibStep ) in + App( fn, Lit n ) |> eval + + +let%expect_test _= + + Printf.printf "%s" (show_expr (fib 5)); + [%expect {| (Lang.Lang.Lit 8) |}] + +{% endhighlight%} + +## Closures + +The next part of the code is this. Currently it throws 'Not_Found' but my learning goal +here was set by my lack of knowledge of how _[@@deriving_show]_ works. More on that later. + + +{% highlight% ocaml%} + +open Containers +open Stdlib +type arithmetic_fn = | Add | Sub | Mul | Div +[@@deriving show] + +module type ARITHMETIC_FN= +sig + type t = arithmetic_fn + [@@deriving show] + type a = int + type b = int + val apply : t -> a -> b -> a +end + +type comparison_fn = + | Less + | Equal + | Greater +[@@deriving show] + +module type COMPARISON_FN= +sig + type t = comparison_fn + [@@deriving show] + type a = int + type b = int + val apply : t -> a -> b -> a +end + + +module Lang(ArithmeticType : ARITHMETIC_FN) + (ComparisonType : COMPARISON_FN) = struct + + module ArithmeticType = ArithmeticType + module ComparisonType = ComparisonType + type var_name = string + [@@deriving show] + type btype = BInt of int + [@@deriving show] + + module EnvKey = struct + type t = var_name + let compare s s1 = if s < s1 then -1 else if s > s1 then 1 else 0 + (* String.compare *) + end +exception Type_error + + +type builtin_fn = + |Arithmetic of ArithmeticType.t * expr * expr + |Comparison of ComparisonType.t * expr * expr +[@@deriving show] +and +expr = + | Var of var_name + | Abs of var_name * expr + | App of expr * expr + | Lit of btype + | Builtin of builtin_fn + | Cond of expr * expr * expr +[@@deriving show] + +module PPMap =CCMap.Make(EnvKey) + + + type value = + | VInt of int + | Closure of closure + and closure = {env : env ; var : var_name ; body : expr} + and + env = + | EnvMap of value PPMap.t + [@printer + fun fmt map -> fprintf fmt "%a" (PPMap.pp CCString.pp pp_value) map] +[@@deriving show] (* only one call to `deriving show` is enough *) + +type eval_error = WrongType of value * string + +exception EvalException of eval_error + +end + +module Language = + Lang(struct + type t = arithmetic_fn + [@@deriving show] + type a = int + type b = int + let apply fn a b : int = + match fn with + | Add -> a + b + | Sub -> a - b + | Mul -> a * b + | Div -> a / b + end) + (struct + type t = comparison_fn + [@@deriving show] + type a = int + type b = int + let apply fn a b : int = + match fn with + | Less -> if a < b then 1 else 0 + | Greater -> if a > b then 1 else 0 + | Equal -> if a = b then 1 else 0 + end) + + +module Value = struct +include Language + + let asInt = function + | VInt i-> i + | other -> raise( EvalException(WrongType( other, "int" )) ) + + let asClosure = function + | Closure c-> c + | other -> raise( EvalException(WrongType( other, "closure" )) ) + + + let rec eval env ( expr : expr ) : value = + match expr with + | Lit (BInt i) -> VInt i + | Builtin ( Arithmetic( fn, opA, opB ) ) -> + let valA = eval env opA |> asInt in + let valB = eval env opB |> asInt in + VInt (ArithmeticType.apply fn valA valB ) + | Builtin ( Comparison( fn, opA, opB ) ) -> + let lhs = eval env opA |> asInt in + let rhs = eval env opB |> asInt in + VInt( ComparisonType.apply fn lhs rhs ) + | Cond (pred, trueBranch, falseBranch) -> + let valPred = eval env pred |> asInt in + if valPred <> 0 then eval env trueBranch else eval env falseBranch + | Abs ( var, body ) -> Closure { env = env; var = var; body = body } + + | App( expr, arg ) -> + let { env = closureEnv ; var = closureVar; body = closureBody} = eval env expr |> asClosure in + let argValue = eval env arg in + let _var_name = PPMap.add closureVar argValue in + Printf.printf "%s" (Format.asprintf "%a" pp_value argValue); + eval env closureBody + + | Var name -> + let EnvMap map = env in + PPMap.find name map + +end + +{% endhighlight%} + + + diff --git a/_sass/_highlights.scss b/_sass/_highlights.scss index 57c7b72f07617..0e8384ebde073 100644 --- a/_sass/_highlights.scss +++ b/_sass/_highlights.scss @@ -1,84 +1,91 @@ +highlight { + background-color: #ffffff; + padding: 5px 10px; + margin: 20px 0; +} -.highlight { - background-color: #efefef; - padding: 7px 7px 7px 10px; - border: 1px solid #ddd; - -moz-box-shadow: 3px 3px rgba(0,0,0,0.1); - -webkit-box-shadow: 3px 3px rgba(0,0,0,0.1); - box-shadow: 3px 3px rgba(0,0,0,0.1); - margin: 20px 0 20px 0; - overflow: scroll; +.highlight pre { + /* overflow: scroll; Prefer no word wrap? Uncomment this line and comment out the 2 lines below. */ + word-break: break-all; + word-wrap: break-word; } + code { font-family:'Bitstream Vera Sans Mono','Courier', monospace; + font-size: 15px; + font-weight: bold; } -.highlight .c { color: #586E75 } /* Comment */ -.highlight .err { color: #93A1A1 } /* Error */ -.highlight .g { color: #93A1A1 } /* Generic */ -.highlight .k { color: #859900 } /* Keyword */ -.highlight .l { color: #93A1A1 } /* Literal */ -.highlight .n { color: #93A1A1 } /* Name */ -.highlight .o { color: #859900 } /* Operator */ -.highlight .x { color: #CB4B16 } /* Other */ -.highlight .p { color: #93A1A1 } /* Punctuation */ -.highlight .cm { color: #586E75 } /* Comment.Multiline */ -.highlight .cp { color: #859900 } /* Comment.Preproc */ -.highlight .c1 { color: #586E75 } /* Comment.Single */ -.highlight .cs { color: #859900 } /* Comment.Special */ -.highlight .gd { color: #2AA198 } /* Generic.Deleted */ -.highlight .ge { color: #93A1A1; font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #DC322F } /* Generic.Error */ -.highlight .gh { color: #CB4B16 } /* Generic.Heading */ -.highlight .gi { color: #859900 } /* Generic.Inserted */ -.highlight .go { color: #93A1A1 } /* Generic.Output */ -.highlight .gp { color: #93A1A1 } /* Generic.Prompt */ -.highlight .gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #CB4B16 } /* Generic.Subheading */ -.highlight .gt { color: #93A1A1 } /* Generic.Traceback */ -.highlight .kc { color: #CB4B16 } /* Keyword.Constant */ -.highlight .kd { color: #268BD2 } /* Keyword.Declaration */ -.highlight .kn { color: #859900 } /* Keyword.Namespace */ -.highlight .kp { color: #859900 } /* Keyword.Pseudo */ -.highlight .kr { color: #268BD2 } /* Keyword.Reserved */ -.highlight .kt { color: #DC322F } /* Keyword.Type */ -.highlight .ld { color: #93A1A1 } /* Literal.Date */ -.highlight .m { color: #2AA198 } /* Literal.Number */ -.highlight .s { color: #2AA198 } /* Literal.String */ -.highlight .na { color: #93A1A1 } /* Name.Attribute */ -.highlight .nb { color: #B58900 } /* Name.Builtin */ -.highlight .nc { color: #268BD2 } /* Name.Class */ -.highlight .no { color: #CB4B16 } /* Name.Constant */ -.highlight .nd { color: #268BD2 } /* Name.Decorator */ -.highlight .ni { color: #CB4B16 } /* Name.Entity */ -.highlight .ne { color: #CB4B16 } /* Name.Exception */ -.highlight .nf { color: #268BD2 } /* Name.Function */ -.highlight .nl { color: #93A1A1 } /* Name.Label */ -.highlight .nn { color: #93A1A1 } /* Name.Namespace */ -.highlight .nx { color: #555 } /* Name.Other */ -.highlight .py { color: #93A1A1 } /* Name.Property */ -.highlight .nt { color: #268BD2 } /* Name.Tag */ -.highlight .nv { color: #268BD2 } /* Name.Variable */ -.highlight .ow { color: #859900 } /* Operator.Word */ -.highlight .w { color: #93A1A1 } /* Text.Whitespace */ -.highlight .mf { color: #2AA198 } /* Literal.Number.Float */ -.highlight .mh { color: #2AA198 } /* Literal.Number.Hex */ -.highlight .mi { color: #2AA198 } /* Literal.Number.Integer */ -.highlight .mo { color: #2AA198 } /* Literal.Number.Oct */ -.highlight .sb { color: #586E75 } /* Literal.String.Backtick */ -.highlight .sc { color: #2AA198 } /* Literal.String.Char */ -.highlight .sd { color: #93A1A1 } /* Literal.String.Doc */ -.highlight .s2 { color: #2AA198 } /* Literal.String.Double */ -.highlight .se { color: #CB4B16 } /* Literal.String.Escape */ -.highlight .sh { color: #93A1A1 } /* Literal.String.Heredoc */ -.highlight .si { color: #2AA198 } /* Literal.String.Interpol */ -.highlight .sx { color: #2AA198 } /* Literal.String.Other */ -.highlight .sr { color: #DC322F } /* Literal.String.Regex */ -.highlight .s1 { color: #2AA198 } /* Literal.String.Single */ -.highlight .ss { color: #2AA198 } /* Literal.String.Symbol */ -.highlight .bp { color: #268BD2 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #268BD2 } /* Name.Variable.Class */ -.highlight .vg { color: #268BD2 } /* Name.Variable.Global */ -.highlight .vi { color: #268BD2 } /* Name.Variable.Instance */ -.highlight .il { color: #2AA198 } /* Literal.Number.Integer.Long */ \ No newline at end of file +.highlight { + background-color: #f6f8fa; + margin-bottom: 1.5em; + pre { + overflow-y: auto; + position: relative; + margin: 0; + padding: 1em; + } + .lineno { padding-right: 24px; color: lighten(#333332,50);} + .hll { background-color: #ffffcc } + .c { color: #999988; font-style: italic } /* Comment */ + .err { color: #a61717; background-color: #e3d2d2 } /* Error */ + .k { color: #000000; font-weight: bold } /* Keyword */ + .o { color: #000000; font-weight: bold } /* Operator */ + .cm { color: #999988; font-style: italic } /* Comment.Multiline */ + .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ + .c1 { color: #999988; font-style: italic } /* Comment.Single */ + .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ + .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ + .ge { color: #000000; font-style: italic } /* Generic.Emph */ + .gr { color: #aa0000 } /* Generic.Error */ + .gh { color: #999999 } /* Generic.Heading */ + .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ + .go { color: #888888 } /* Generic.Output */ + .gp { color: #555555 } /* Generic.Prompt */ + .gs { font-weight: bold } /* Generic.Strong */ + .gu { color: #aaaaaa } /* Generic.Subheading */ + .gt { color: #aa0000 } /* Generic.Traceback */ + .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ + .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ + .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ + .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ + .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ + .kt { color: #445588; font-weight: bold } /* Keyword.Type */ + .m { color: #009999 } /* Literal.Number */ + .s { color: #d01040 } /* Literal.String */ + .na { color: #008080 } /* Name.Attribute */ + .nb { color: #0086B3 } /* Name.Builtin */ + .nc { color: #445588; font-weight: bold } /* Name.Class */ + .no { color: #008080 } /* Name.Constant */ + .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ + .ni { color: #800080 } /* Name.Entity */ + .ne { color: #990000; font-weight: bold } /* Name.Exception */ + .nf { color: #990000; font-weight: bold } /* Name.Function */ + .nl { color: #990000; font-weight: bold } /* Name.Label */ + .nn { color: #555555 } /* Name.Namespace */ + .nt { color: #000080 } /* Name.Tag */ + .nv { color: #008080 } /* Name.Variable */ + .ow { color: #000000; font-weight: bold } /* Operator.Word */ + .w { color: #bbbbbb } /* Text.Whitespace */ + .mf { color: #009999 } /* Literal.Number.Float */ + .mh { color: #009999 } /* Literal.Number.Hex */ + .mi { color: #009999 } /* Literal.Number.Integer */ + .mo { color: #009999 } /* Literal.Number.Oct */ + .sb { color: #d01040 } /* Literal.String.Backtick */ + .sc { color: #d01040 } /* Literal.String.Char */ + .sd { color: #d01040 } /* Literal.String.Doc */ + .s2 { color: #d01040 } /* Literal.String.Double */ + .se { color: #d01040 } /* Literal.String.Escape */ + .sh { color: #d01040 } /* Literal.String.Heredoc */ + .si { color: #d01040 } /* Literal.String.Interpol */ + .sx { color: #d01040 } /* Literal.String.Other */ + .sr { color: #009926 } /* Literal.String.Regex */ + .s1 { color: #d01040 } /* Literal.String.Single */ + .ss { color: #990073 } /* Literal.String.Symbol */ + .bp { color: #999999 } /* Name.Builtin.Pseudo */ + .vc { color: #008080 } /* Name.Variable.Class */ + .vg { color: #008080 } /* Name.Variable.Global */ + .vi { color: #008080 } /* Name.Variable.Instance */ + .il { color: #009999 } /* Literal.Number.Integer.Long */ +} diff --git a/_sass/_variables.scss b/_sass/_variables.scss index e56c1c5a68e15..53978953b174c 100644 --- a/_sass/_variables.scss +++ b/_sass/_variables.scss @@ -2,7 +2,7 @@ // // VARIABLES // - +$base-font-family: 'Josefin Slab', serif; // Colors $blue: #4183C4; @@ -24,4 +24,4 @@ $georgia: Georgia, serif; @media screen and (max-width: 640px) { @content; } -} \ No newline at end of file +} diff --git a/about.md b/about.md index bc21f5731bf4b..9c7f4e06fdcd1 100644 --- a/about.md +++ b/about.md @@ -4,7 +4,8 @@ title: About permalink: /about/ --- -Some information about you! +Interested in Machine Learning Algorithms, TensorFlow, Distributed Computing and Functional +Programming languages like Haskell, OCaml and Racket. ### More Information @@ -12,4 +13,4 @@ A place to include any other types of information that you'd like to include abo ### Contact me -[email@domain.com](mailto:email@domain.com) \ No newline at end of file +[email@domain.com](mailto:email@domain.com) diff --git a/images/404.jpg b/images/404.jpg deleted file mode 100644 index 166a17ef05d5f..0000000000000 Binary files a/images/404.jpg and /dev/null differ diff --git a/images/Bengio.jpg b/images/Bengio.jpg new file mode 100644 index 0000000000000..ad36fc92225b8 Binary files /dev/null and b/images/Bengio.jpg differ diff --git a/images/CraftingInterpreters.jpg b/images/CraftingInterpreters.jpg new file mode 100644 index 0000000000000..9bc7f1b60e700 Binary files /dev/null and b/images/CraftingInterpreters.jpg differ diff --git a/images/DavidSolow.png b/images/DavidSolow.png new file mode 100644 index 0000000000000..d709a42cca793 Binary files /dev/null and b/images/DavidSolow.png differ diff --git a/images/Paxos.jpg b/images/Paxos.jpg new file mode 100644 index 0000000000000..a662fbc7b5e81 Binary files /dev/null and b/images/Paxos.jpg differ diff --git a/images/RoPEAttention.png b/images/RoPEAttention.png new file mode 100644 index 0000000000000..42069863ec2e3 Binary files /dev/null and b/images/RoPEAttention.png differ diff --git a/images/TE_temp_preview2092.pdf.png b/images/TE_temp_preview2092.pdf.png new file mode 100644 index 0000000000000..829270591313f Binary files /dev/null and b/images/TE_temp_preview2092.pdf.png differ diff --git a/images/TLAPlus.png b/images/TLAPlus.png new file mode 100644 index 0000000000000..82e314f40714f Binary files /dev/null and b/images/TLAPlus.png differ diff --git a/images/Tensorboard.png b/images/Tensorboard.png new file mode 100644 index 0000000000000..861839d55ceea Binary files /dev/null and b/images/Tensorboard.png differ diff --git a/images/bits.png b/images/bits.png new file mode 100644 index 0000000000000..95b3ac28560cf Binary files /dev/null and b/images/bits.png differ diff --git a/images/boxes.png b/images/boxes.png new file mode 100644 index 0000000000000..7b233da44b949 Binary files /dev/null and b/images/boxes.png differ diff --git a/images/cellstatus.tex.preview.pdf.png b/images/cellstatus.tex.preview.pdf.png new file mode 100644 index 0000000000000..62aaec1f4f33a Binary files /dev/null and b/images/cellstatus.tex.preview.pdf.png differ diff --git a/images/coq1.png b/images/coq1.png new file mode 100644 index 0000000000000..40948a14c4a65 Binary files /dev/null and b/images/coq1.png differ diff --git a/images/coq2.png b/images/coq2.png new file mode 100644 index 0000000000000..b9c6115804b7a Binary files /dev/null and b/images/coq2.png differ diff --git a/images/coq3.png b/images/coq3.png new file mode 100644 index 0000000000000..98369b9d4f0ff Binary files /dev/null and b/images/coq3.png differ diff --git a/images/dev_ide.png b/images/dev_ide.png new file mode 100644 index 0000000000000..880868e92ac71 Binary files /dev/null and b/images/dev_ide.png differ diff --git a/images/djikstra.tex.preview.pdf.png b/images/djikstra.tex.preview.pdf.png new file mode 100644 index 0000000000000..061d728881e86 Binary files /dev/null and b/images/djikstra.tex.preview.pdf.png differ diff --git a/images/doomemacs.png b/images/doomemacs.png new file mode 100644 index 0000000000000..b03f7b5eb166b Binary files /dev/null and b/images/doomemacs.png differ diff --git a/images/dune_auto_promote.png b/images/dune_auto_promote.png new file mode 100644 index 0000000000000..f27aaac0ce0b5 Binary files /dev/null and b/images/dune_auto_promote.png differ diff --git a/images/epoch loss.png b/images/epoch loss.png new file mode 100644 index 0000000000000..6702fe98c50c2 Binary files /dev/null and b/images/epoch loss.png differ diff --git a/images/epochloss.png b/images/epochloss.png new file mode 100644 index 0000000000000..6702fe98c50c2 Binary files /dev/null and b/images/epochloss.png differ diff --git a/images/grid.PNG b/images/grid.PNG new file mode 100644 index 0000000000000..2dee3915898ad Binary files /dev/null and b/images/grid.PNG differ diff --git a/images/higher-order.tex.preview.pdf.png b/images/higher-order.tex.preview.pdf.png new file mode 100644 index 0000000000000..5fbe1918813ce Binary files /dev/null and b/images/higher-order.tex.preview.pdf.png differ diff --git a/images/higher-order1.tex.preview.pdf.png b/images/higher-order1.tex.preview.pdf.png new file mode 100644 index 0000000000000..bcaee5d43c09a Binary files /dev/null and b/images/higher-order1.tex.preview.pdf.png differ diff --git a/images/illama_finalloss.png b/images/illama_finalloss.png new file mode 100644 index 0000000000000..02a8d9d6a3794 Binary files /dev/null and b/images/illama_finalloss.png differ diff --git a/images/illama_loss.png b/images/illama_loss.png new file mode 100644 index 0000000000000..1ca89acf4963a Binary files /dev/null and b/images/illama_loss.png differ diff --git a/images/jekyll-logo.png b/images/jekyll-logo.png index 66c09a6286a72..abbbc0f0456e7 100644 Binary files a/images/jekyll-logo.png and b/images/jekyll-logo.png differ diff --git a/images/maskedRoPEAttention.png b/images/maskedRoPEAttention.png new file mode 100644 index 0000000000000..8d49a024e4d81 Binary files /dev/null and b/images/maskedRoPEAttention.png differ diff --git a/images/matrix.png b/images/matrix.png new file mode 100644 index 0000000000000..10c38f4304de3 Binary files /dev/null and b/images/matrix.png differ diff --git a/images/matrixhighlighter.png b/images/matrixhighlighter.png new file mode 100644 index 0000000000000..51da41cd48bc6 Binary files /dev/null and b/images/matrixhighlighter.png differ diff --git a/images/matrixhighlighter2.png b/images/matrixhighlighter2.png new file mode 100644 index 0000000000000..e0523609ff064 Binary files /dev/null and b/images/matrixhighlighter2.png differ diff --git a/images/modelvalues.png b/images/modelvalues.png new file mode 100644 index 0000000000000..1ababd10f21c1 Binary files /dev/null and b/images/modelvalues.png differ diff --git a/images/myhanddrawn-move.tex.preview.png b/images/myhanddrawn-move.tex.preview.png new file mode 100644 index 0000000000000..a24df5b79e30f Binary files /dev/null and b/images/myhanddrawn-move.tex.preview.png differ diff --git a/images/myhanddrawn.tex.preview.pdf.png b/images/myhanddrawn.tex.preview.pdf.png new file mode 100644 index 0000000000000..90b377c92eb56 Binary files /dev/null and b/images/myhanddrawn.tex.preview.pdf.png differ diff --git a/images/policy.png b/images/policy.png new file mode 100644 index 0000000000000..6792c92e46800 Binary files /dev/null and b/images/policy.png differ diff --git a/images/print_splay.png.png b/images/print_splay.png.png new file mode 100644 index 0000000000000..c28de002ea4c6 Binary files /dev/null and b/images/print_splay.png.png differ diff --git a/images/racket-development.png b/images/racket-development.png new file mode 100644 index 0000000000000..4d0596bb4b3a8 Binary files /dev/null and b/images/racket-development.png differ diff --git a/images/ray.png b/images/ray.png new file mode 100644 index 0000000000000..077a33e867a82 Binary files /dev/null and b/images/ray.png differ diff --git a/images/ray1.png b/images/ray1.png new file mode 100644 index 0000000000000..510b8b8372473 Binary files /dev/null and b/images/ray1.png differ diff --git a/images/ray2.png b/images/ray2.png new file mode 100644 index 0000000000000..ef3853bd0d5ba Binary files /dev/null and b/images/ray2.png differ diff --git a/images/reactivepicture.png b/images/reactivepicture.png new file mode 100644 index 0000000000000..1b6561e32b711 Binary files /dev/null and b/images/reactivepicture.png differ diff --git a/images/reactivewindow.png b/images/reactivewindow.png new file mode 100644 index 0000000000000..1c9cd7577b190 Binary files /dev/null and b/images/reactivewindow.png differ diff --git a/images/rotaryembeddings.png b/images/rotaryembeddings.png new file mode 100644 index 0000000000000..c841c35b8d91a Binary files /dev/null and b/images/rotaryembeddings.png differ diff --git a/images/selfattention.png b/images/selfattention.png new file mode 100644 index 0000000000000..3bc116ef00545 Binary files /dev/null and b/images/selfattention.png differ diff --git a/images/spacemacs.png b/images/spacemacs.png new file mode 100644 index 0000000000000..2352a7a85b5df Binary files /dev/null and b/images/spacemacs.png differ diff --git a/images/tikzeditor.png b/images/tikzeditor.png new file mode 100644 index 0000000000000..0700514b6bdf6 Binary files /dev/null and b/images/tikzeditor.png differ diff --git a/style.scss b/style.scss index 3915a90244691..da7f4cb055106 100644 --- a/style.scss +++ b/style.scss @@ -14,24 +14,24 @@ /**************/ html { - font-size: 100%; + font-size: 70%; } body { background: $white; - font: 18px/1.4 $helvetica; + font: 17px/1.4 $base-font-family; color: $darkGray; } .container { margin: 0 auto; - max-width: 740px; + max-width: 990px; padding: 0 10px; width: 100%; } h1, h2, h3, h4, h5, h6 { - font-family: $helveticaNeue; + font-family: $base-font-family; color: $darkerGray; font-weight: bold;