geomotif.motifs.polar
¶
Roses, harmonics and the sunflower: curves written as angle and radius.
Two halves that share a page because they share an idea -- a shape made by letting something oscillate.
The polar half is :class:Rose and its relatives, where the radius is a
function of the angle. The harmonic half is :class:Lissajous,
:class:Harmonic and :class:Harmonograph, where x and y each oscillate on
their own and the shape is what their beat produces. :class:Phyllotaxis
belongs to neither and to both: it is a point set rather than a stroke, and
it is the one motif in the library that plants ship.
Classes:
| Name | Description |
|---|---|
Rose |
The rhodonea |
MaurerRose |
A rose walked in whole-degree steps and joined by straight chords. |
Lissajous |
Two perpendicular oscillations, plotted against each other. |
Harmonic |
Sums of sines on each axis: :class: |
Pendulum |
One swinging weight of a :class: |
Harmonograph |
The Victorian drawing machine: swinging pendulums, slowly running down. |
Phyllotaxis |
The sunflower head: |
PolarExpression |
Any radius function you like, wrapped as a motif. |
Rose
dataclass
¶
Rose(n: int = 5, d: int = 1, size: float = 100.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The rhodonea r = cos(n/d * theta), with the petal count right.
The petal count is the part everyone gets wrong, because it depends on
the parity of the reduced fraction rather than on the numbers as typed.
With k = n/d in lowest terms the curve has n petals when n*d
is odd and 2*n when it is even, and it closes after d*pi or
2*d*pi respectively. This class works that out and sweeps exactly
that far, so no petal is ever traced twice.
d = 1 covers the familiar roses: three petals at n = 3, eight at
n = 4. Larger denominators give the tangled many-lobed rhodoneas.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Numerator of the angular frequency. |
5
|
d
|
int
|
Denominator of the angular frequency. Reduced against |
1
|
size
|
float
|
Petal length, measured from the center. |
100.0
|
center
|
(float, float)
|
Where the petals meet. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
petal_count |
Return how many petals this rose actually has. |
closure |
Return the angular sweep after which the curve returns to its start. |
MaurerRose
dataclass
¶
Bases: PolygonMotif
A rose walked in whole-degree steps and joined by straight chords.
Peter Maurer's construction, and the best return on effort in the whole
catalog: take the points of a rose at 0, degrees, 2*degrees
and so on, join them with straight lines, and the chords weave a
filigree the underlying curve gives no hint of. Change degrees by one
and the whole pattern reorganizes.
This is a :class:~geomotif.PolygonMotif rather than a curve: the
vertices are the design, and measuring the chords at even parameters
would round off every corner that makes the pattern.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Petal frequency of the underlying rose, |
6
|
degrees
|
int
|
Whole degrees per step. Coprime with 360 gives the full 360-chord figure; a common factor closes the walk early on a coarser one. |
71
|
size
|
float
|
Petal length of the underlying rose. |
100.0
|
center
|
(float, float)
|
Where the petals meet. |
(0.0, 0.0)
|
Methods:
| Name | Description |
|---|---|
chord_count |
Return how many chords the walk takes before it closes. |
chord_count
¶
Lissajous
dataclass
¶
Lissajous(a: int = 3, b: int = 2, delta: float = pi / 2.0, width: float = 200.0, height: float = 200.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
Two perpendicular oscillations, plotted against each other.
What an oscilloscope draws with a signal on each axis, and how frequency
ratios were measured before there was anything better: the figure is
stable only when the ratio is exactly rational, and it stands still only
when the phase is too. a = b degenerates to an ellipse, and to a
circle when delta is a quarter turn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
int
|
Frequencies on x and y. Whole numbers, because the figure closes only when their ratio is rational. |
3
|
b
|
int
|
Frequencies on x and y. Whole numbers, because the figure closes only when their ratio is rational. |
3
|
delta
|
float
|
Phase offset applied to x, in radians. |
pi / 2.0
|
width
|
float
|
Full extent on each axis. |
200.0
|
height
|
float
|
Full extent on each axis. |
200.0
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
Harmonic
dataclass
¶
Harmonic(x_terms: tuple[tuple[float, float, float], ...] = ((100.0, 1.0, 0.0), (40.0, 5.0, 0.0)), y_terms: tuple[tuple[float, float, float], ...] = ((100.0, 1.0, pi / 2.0), (40.0, 5.0, pi / 2.0)), center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
Sums of sines on each axis: :class:Lissajous with more terms.
Each term is (amplitude, frequency, phase), and the axes are
independent: matching term for term across the two gives a clean
rosette, mismatching them gives a knot, and a single fast term against a
slow one gives a ribbon with a ripple in it::
Harmonic(
x_terms=((100.0, 1.0, 0.0),),
y_terms=((100.0, 3.0, 0.0), (40.0, 17.0, 0.0)),
)
The frequencies are whole numbers, because that is what makes the figure close.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x_terms
|
tuple of (float, float, float)
|
The sine terms driving each axis. At least one each. |
((100.0, 1.0, 0.0), (40.0, 5.0, 0.0))
|
y_terms
|
tuple of (float, float, float)
|
The sine terms driving each axis. At least one each. |
((100.0, 1.0, 0.0), (40.0, 5.0, 0.0))
|
center
|
(float, float)
|
Middle of the figure. |
(0.0, 0.0)
|
Pendulum
dataclass
¶
Pendulum(amplitude: float = 100.0, frequency: float = 2.0, phase: float = 0.0, damping: float = 0.006)
One swinging weight of a :class:Harmonograph.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
amplitude
|
float
|
How far it swings at the start. |
100.0
|
frequency
|
float
|
Radians per unit of time. Two pendulums at almost the same frequency are what makes a harmonograph drift instead of repeat. |
2.0
|
phase
|
float
|
Where in its swing it is released, in radians. |
0.0
|
damping
|
float
|
Exponential decay per unit of time. Zero never settles. |
0.006
|
Methods:
| Name | Description |
|---|---|
at |
Return this pendulum's displacement at time |
at
¶
Return this pendulum's displacement at time t.
Harmonograph
dataclass
¶
Harmonograph(x_pendulums: tuple[Pendulum, ...] = (Pendulum(140.0, 2.0, 0.0, 0.006), Pendulum(60.0, 3.0, pi / 4.0, 0.0018)), y_pendulums: tuple[Pendulum, ...] = (Pendulum(140.0, 2.005, pi / 2.0, 0.006), Pendulum(60.0, 4.0, 0.0, 0.0018)), duration: float = 50.0, center: Point = (0.0, 0.0), *, resolution: int | None = None)
Bases: ParametricMotif
The Victorian drawing machine: swinging pendulums, slowly running down.
Two pendulums per axis, each decaying, and a pen where their motions
meet. What makes the figure rather than a scribble is detuning: set two
frequencies to 2.0 and 2.005 and the loops precess a little on
every pass, so the curve fills a band instead of retracing itself. The
damping is what closes the spiral inwards and ends the drawing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x_pendulums
|
tuple of Pendulum
|
What drives each axis. At least one each; two is the classic machine. |
(Pendulum(140.0, 2.0, 0.0, 0.006), Pendulum(60.0, 3.0, pi / 4.0, 0.0018))
|
y_pendulums
|
tuple of Pendulum
|
What drives each axis. At least one each; two is the classic machine. |
(Pendulum(140.0, 2.0, 0.0, 0.006), Pendulum(60.0, 3.0, pi / 4.0, 0.0018))
|
duration
|
float
|
How long to let it swing. Longer means a denser figure, up to the point where the damping has stopped it. |
50.0
|
center
|
(float, float)
|
Where the pen rests once everything has settled. |
(0.0, 0.0)
|
Phyllotaxis
dataclass
¶
Phyllotaxis(count: int = 500, spacing: float = 8.0, angle: float = GOLDEN_ANGLE, center: Point = (0.0, 0.0))
Bases: Motif
The sunflower head: r = c*sqrt(n) at n golden angles.
Vogel's model of how a plant packs seeds, and the best dot art in the library for the least work. The square root keeps the density even from the middle to the rim; the golden angle keeps consecutive seeds from ever lining up, which is why the spiral arms you see are an illusion of the packing rather than anything the formula mentions. Their count is always a Fibonacci number.
Produces loose points rather than a stroke: joining them in order would draw a line no sunflower has.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
count
|
int
|
Number of seeds. |
500
|
spacing
|
float
|
Scale factor. The head's radius works out at |
8.0
|
angle
|
float
|
Turn between consecutive seeds, in radians. Defaults to
:data: |
GOLDEN_ANGLE
|
center
|
(float, float)
|
Middle of the head. |
(0.0, 0.0)
|
PolarExpression
dataclass
¶
PolarExpression(formula: Callable[[float], float] = _ripple, *, resolution: int | None = None, center: Point = (0.0, 0.0), theta_start: float = 0.0, theta_span: float = tau)
Bases: PolarMotif
Any radius function you like, wrapped as a motif.
The escape hatch for a one-off polar curve that does not deserve a class of its own::
PolarExpression(lambda t: 60 + 20 * math.sin(9 * t))
Subclassing :class:~geomotif.PolarMotif is still the better answer for
anything you will use twice -- it gets a name, a docstring, parameters
and a registry entry. This is for the other times.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
callable
|
Maps an angle in radians to a radius. Called across the sweep only.
Named |
_ripple
|
Notes
The design this builds records the function object in its metadata, so it round-trips within a session but not through a file. Anything that has to survive being written down wants a real registered class.