cl-series
ellipse sampling again (only read this one)Generating the image above. The article writes get-ellipse-hulls
using the modern and historic common lisp cl-series macro package. 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.
(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*)
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-sbcl)
|#
(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)
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)
get-ellipse-hulls
(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* (
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 (degrees
(scan-range :below 360 :by delta-degrees)
)
(radians
(#M* (series (* 2 pi 1/360))
degrees))
I guess you can interactively execute (<F8>
) 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 (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.
(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.
(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
.
(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)))))))))
(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)))))))))
I would really like commentary and advice on the above. 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, 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?
screwlisp proposes kittens