--- layout: ../Site.layout.js --- # `cl-series` ellipse sampling again (only read this one) - «.eg» (to "eg") - «.intro» (to "intro") - «.eepitch» (to "eepitch") - «.package» (to "package") - «.series» (to "series") - «.defun» (to "defun") - «.let*» (to "let*") - «.scan-range» (to "scan-range") - «.xs-and-ys» (to "xs-and-ys") - «.rotating-xs-and-ys» (to "rotating-xs-and-ys") - «.translating-xs-and-ys» (to "translating-xs-and-ys") - «.collecting / bound box» (to "collecting / bound box") - «.altogether now» (to "altogether now") # Eg. - «eg» (to ".eg") Generating the image above. The article writes `get-ellipse-hulls` using the modern and historic common lisp [cl-series macro package](https://gitlab.common-lisp.net/rtoy/cl-series/\-/wikis/Series-User's-Guide). This is my own native common lisp walkthrough attempt using `series` and is not a historic *LISP* ellipse/graphing demo. Source by itself in [codeberg.org/tfw/screwlisps-knowledge.git](https://codeberg.org/tfw/screwlisps-knowledge). ``` (asdf:load-system :screwlisps-knowledge/ellipse-hulls) (use-package :screwlisps-knowledge/ellipse-hulls) (asdf:load-system :screwlisps-knowledge/simple-gnuplot) (use-package :screwlisps-knowledge/simple-gnuplot) (loop :repeat (1+ (random #o20)) :for major-axis := (+ 2 (random 10)) :for minor-axis := (1+ (random major-axis)) :for rot := (* 2/360 pi (random 360)) :for tx := (random 10) :for ty := (random 10) :for halves := (multiple-value-list (get-ellipse-hulls major-axis minor-axis rot tx ty)) :nconcing halves :into result :finally (defparameter *all-halves* result)) (apply 'gnuplot "all halves" *all-halves*) ``` # Intro - «intro» (to ".intro") At least as a point of comparison, let me retry just stating the points of an ellipse slightly better. And by better I mean more declaratively in terms of just `cl-series`. I am going to use emacs eev eepitch in a slightly annoying way so that I can interleave out a single set of `series` declarations with dialogue about them in this kitten markdown page. This document will also literally be my `~/common-lisp/screwlisps-knowledge/ellipse-hulls.lisp` if tangled somehow to extract only the code fragments. I am still thinking about my compatibility to Eduardo's eev online knowledge store. My hope is that it can be very clear what I am trying to do and how I am doing it while also being the literal, tight, optimized common lisp code. # eepitch block - «eepitch» (to ".eepitch") ``` #|  (eepitch-sbcl) |# ``` # Inferred package definition - «package» (to ".package") ``` (uiop:define-package :screwlisps-knowledge/ellipse-hulls (:import-from :series #:install) (:mix :series :asdf :cl) (:export #:get-ellipse-hulls)) (in-package :screwlisps-knowledge/ellipse-hulls) ``` # Install series - «series» (to ".series") `Series` is a macro package. What comes out of it is pure ansi common lisp. The macro can be added and removed from other packages after creating the native common lisp code. ``` (series::install) ``` # Begin defining `get-ellipse-hulls` - «defun» (to ".defun") ``` (defun get-ellipse-hulls (major-axis-magnitude minor-axis-magnitude rotation-radians translation-x translation-y &optional (delta-degrees 1)) ``` This is a totally normal lisp function definition of a function you would expect to describe an ellipse. The only oddity is that the function is going to return two lists of points, being the concave-down and concave-up portions of the rotated ellipse. # `series::let*` macro entrypoint - «let*» (to ".let*") ``` (let* ( ``` Remembering that all of `cl-series` happens at the macroexpansion step after reading, running `install` above clobbered `cl:let*` and similar with entry points to `series`' macro time graph building and static analysis + code generation. From here on, you will notice `series` only constructs being used such as `series`. # `scan-range` of degrees sampling the ellipse - «scan-range» (to ".scan-range") ``` (degrees (scan-range :below 360 :by delta-degrees) ) (radians (#M* (series (* 2 pi 1/360)) degrees)) ``` I guess you can interactively execute (`\`) just the `scan-range` line to see it made `#Z(0 1 2 ... 359)`. However, if the series was not finite, returning it is the same as collecting from an infinite loop. This is simply our sampling of the perimeter of the ellipse. The degrees are obviously converted into radians. `(series 'thing)` creates an infinite series repeating `'thing`. There are not normally constants per se. The `#M` reader macro prefix turns a 'normal function' into a 'series function' by expanding into a `mapping` in `series` parlance. # `x`s and `y`s of points on the ellipse - «xs-and-ys» (to ".xs-and-ys") ``` (x (#M* (series major-axis-magnitude) (#Mcos radians))) (y (#M* (series minor-axis-magnitude) (#Msin radians))) ``` Up til now, we have an unrotated ellipse centred on the `(0 0)` origin, and the angle in radians or degrees to each point. # Rotating the ellipse about the origin - «rotating-xs-and-ys» (to ".rotating-xs-and-ys") ``` (rx (#M- (#M* x (#Mcos (series rotation-radians))) (#M* y (#Msin (series rotation-radians))))) (ry (#M+ (#M* x (#Msin (series rotation-radians))) (#M* y (#Mcos (series rotation-radians))))) ``` In so many words. # Translating the ellipse - «translating-xs-and-ys» (to ".translating-xs-and-ys") ``` (trx (#M+ rx (series translation-x))) (try (#M+ ry (series translation-y))) ) ``` Up to here, we succeeded in sampling from, rotating and translating our ellipse in a single tight loop using `cl-series`. # Initially collecting the ellipse and its bounding box. - «collecting / bound box» (to ".collecting / bound box") ``` (let ((x (collect trx)) (y (collect try)) (xmin (collect-min trx)) (xmax (collect-max trx)) (ymin (collect-min try)) (ymax (collect-max try)) (rads (#M+ (series rotation-radians) radians))) (let* ((idx (search `(,xmin) x)) (len (length x)) (len/2 (truncate len 2)) (idx+len/2 (+ idx len/2))) (cond ((< idx len/2) (values (mapcar 'list (subseq x idx idx+len/2) (subseq y idx idx+len/2)) (mapcar 'list (append (subseq x idx+len/2) (subseq x 0 idx)) (append (subseq y idx+len/2) (subseq y 0 idx))))) (:otherwise (values (mapcar 'list (append (subseq x idx) (subseq x 0 (- idx+len/2 len))) (append (subseq y idx) (subseq y 0 (- idx+len/2 len)))) (mapcar 'list (subseq x (- idx+len/2 len) idx) (subseq y (- idx+len/2 len) idx))))))))) ``` # Altogether now. - «altogether now» (to ".altogether now") ``` (defun get-ellipse-hulls (major-axis-magnitude minor-axis-magnitude rotation-radians translation-x translation-y &optional (delta-degrees 1)) (let* ( (degrees (scan-range :below 360 :by delta-degrees) ) (radians (#M* (series (* 2 pi (/ 360))) degrees)) (x (#M* (series major-axis-magnitude) (#Mcos radians))) (y (#M* (series minor-axis-magnitude) (#Msin radians))) (rx (#M- (#M* x (#Mcos (series rotation-radians))) (#M* y (#Msin (series rotation-radians))))) (ry (#M+ (#M* x (#Msin (series rotation-radians))) (#M* y (#Mcos (series rotation-radians))))) (trx (#M+ rx (series translation-x))) (try (#M+ ry (series translation-y))) ) (let ((x (collect trx)) (y (collect try)) (xmin (collect-min trx)) (xmax (collect-max trx)) (ymin (collect-min try)) (ymax (collect-max try)) (rads (#M+ (series rotation-radians) radians))) (let* ((idx (search `(,xmin) x)) (len (length x)) (len/2 (truncate len 2)) (idx+len/2 (+ idx len/2))) (cond ((< idx len/2) (values (mapcar 'list (subseq x idx idx+len/2) (subseq y idx idx+len/2)) (mapcar 'list (append (subseq x idx+len/2) (subseq x 0 idx)) (append (subseq y idx+len/2) (subseq y 0 idx))))) (:otherwise (values (mapcar 'list (append (subseq x idx) (subseq x 0 (- idx+len/2 len))) (append (subseq y idx) (subseq y 0 (- idx+len/2 len)))) (mapcar 'list (subseq x (- idx+len/2 len) idx) (subseq y (- idx+len/2 len) idx))))))))) ``` # Feedback about what I did here on the Mastodon please I would [really like commentary and advice on the above](https://gamerplus.org/@screwlisp/114584147523320203). From what I can tell right now, what I wrote seems declarative and fairly sensible if verbose, though I can feel that it is really quite a bit too long. I did try to do a good job here ([the one yesterday was me coming down with a cold](../common-lisp-simple-rotated-ellipse-in-two-lines), is my excuse for it). However, I take the point > This should have been 6 lines of code in either python or lisp seriously. Allowing that I am not trying to show I know Euler's formula or about cool symmetries or integration packages here, what do you think, can you show me where I went off on a weird tangent, possibly with a counterexample or reference?