Reference
Derived systems¶
Wrappers that re-present an existing system through a new lens while
keeping the System protocol intact, so analysis functions
compose with them transparently. Prose introduction:
the mental model.
PoincareMap
¶
PoincareMap(
system: Any,
plane: tuple[Any, ...],
*,
direction: int | str = +1,
dt: float = 0.01,
max_time: float = 10000.0,
)
Bases: DerivedSystem
Present a flow as the discrete map of its crossings through a hyperplane.
One step() advances the underlying system until the trajectory
crosses the section plane in the chosen direction, refines the crossing
point by cubic Hermite interpolation of the bracketing samples (using the
system's numeric RHS for endpoint derivatives — O(dt⁴) accuracy), and
returns the full-dimensional crossing state.
Because a PoincareMap is a discrete system, everything written for
maps applies to flows through it — e.g. an orbit diagram over a
PoincareMap is a bifurcation diagram of the flow.
| PARAMETER | DESCRIPTION |
|---|---|
system
|
A continuous-time system (ODE or DDE).
TYPE:
|
plane
|
The section, in any of three spellings:
TYPE:
|
direction
|
Count only crossings with
TYPE:
|
dt
|
March step used for crossing detection. The refinement makes the
crossing itself far more accurate than
TYPE:
|
max_time
|
Raise if no crossing is found within this much time (e.g. the plane misses the attractor).
TYPE:
|
Examples:
>>> pmap = PoincareMap(Rossler(), plane=("x", 0.0, "up"))
>>> section = pmap.trajectory(500) # 500 crossings → PoincareSection
>>> section.y.shape
(500, 3)
Source code in src/tsdynamics/derived/poincare.py
step
¶
Advance to the n-th next crossing and return it (full-dim coords).
Source code in src/tsdynamics/derived/poincare.py
set_state
¶
set_state(u: Any) -> None
Overwrite the inner flow state and reset crossing bookkeeping.
as_events
¶
as_events() -> list[Event]
Return the section as a one-element [Event] for system.run(events=...).
A Poincaré section is an event: the crossing of g(u) = normal·u −
offset in the map's :attr:direction. This exposes it as an
:class:~tsdynamics.engine.run.Event so the general events= API
reproduces the section — PoincareMap is one consumer of the same
wired engine seam (stream WS-EVENTSAPI / WS-CROSSKERNEL). Driven at the
same fixed-step march (method="rk4" at this map's dt) from the
same initial condition, the crossings of
inner.run(events=pmap.as_events(), ...) match
:meth:trajectory to the engine's refinement accuracy.
Examples:
>>> pmap = PoincareMap(Rossler(), plane=("y", 0.0, "up"), dt=0.01)
>>> sol = Rossler().run(final_time=400, dt=0.01, method="rk4",
... events=pmap.as_events())
>>> sol.meta["y_events"][0][:5].shape # the same crossing states
(5, 3)
Source code in src/tsdynamics/derived/poincare.py
reinit
¶
Restart the inner flow and clear crossing bookkeeping.
Source code in src/tsdynamics/derived/poincare.py
trajectory
¶
trajectory(
steps: int = 100,
*,
transient: int = 0,
backend: str | None = None,
**kwargs: Any,
) -> PoincareSection
Collect crossings as a :class:PoincareSection.
t holds the continuous crossing times; y the full-dimensional
crossing states. transient crossings are discarded first. The
returned :class:PoincareSection is a :class:~tsdynamics.data.Trajectory
carrying section intent (so a renderer draws the in-plane scatter) plus a
.summary() / .to_dict() readout.
For an ordinary (non-stiff) ODE on the compiled engine this marches the
whole attractor and refines every crossing in one engine call (the
wired Rust integrate_events, stream WS-CROSSKERNEL) — ~100× faster than
the per-dt Python loop it replaces. DDEs, systems without a numeric
RHS, stiff defaults, and backend="reference" keep the Python loop. The
engine path is answer-identical to that loop's fixed-step (rk4)
refinement; see :mod:tsdynamics.derived._crossings.
| PARAMETER | DESCRIPTION |
|---|---|
steps
|
Number of crossings to collect.
TYPE:
|
transient
|
Number of leading crossings to discard.
TYPE:
|
backend
|
Engine evaluator for the fast path; defaults to the inner system's
backend.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
PoincareSection
|
The collected crossings (continuous times in |
| RAISES | DESCRIPTION |
|---|---|
ConvergenceError
|
If the inner flow diverges (non-finite state) or no crossing is found
within |
Notes
Live-cursor semantics. trajectory advances the inner system as
a side effect, so a subsequent :meth:step continues forward rather than
re-yielding the crossings just collected. The two collection paths leave
the inner cursor at slightly different places: the Python loop stops just
past the last collected crossing, while the engine path stops at the
span end it marched to (which can be a little beyond the last
crossing). Both invariants — that step() resumes after the
collected crossings — hold; do not rely on the exact cursor offset. Call
:meth:reinit first if you need a deterministic restart point.
Source code in src/tsdynamics/derived/poincare.py
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 | |
StroboscopicMap
¶
Bases: DerivedSystem
Present a forced flow as the discrete map of once-per-period samples.
One step() advances the underlying continuous system by exactly one
forcing period and returns the new state. Orbit diagrams over a
StroboscopicMap are the standard way to study forced oscillators
(Duffing, forced van der Pol, ...).
| PARAMETER | DESCRIPTION |
|---|---|
system
|
A continuous-time system.
TYPE:
|
period
|
Sampling period (the forcing period).
TYPE:
|
Examples:
>>> smap = StroboscopicMap(ForcedVanDerPol(), period=2 * np.pi / 0.63)
>>> samples = smap.trajectory(300, transient=100)
Source code in src/tsdynamics/derived/stroboscopic.py
step
¶
Advance n forcing periods (default 1) and return the new state.
n_or_dt is a period count, not a time increment — the wrapper
presents a discrete map, so the argument is coerced to an integer with
int() (a float is truncated toward zero, matching the discrete-view
convention; pass a whole number to be explicit).
| PARAMETER | DESCRIPTION |
|---|---|
n_or_dt
|
Number of forcing periods to advance.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
ndarray
|
The full-dimensional state after |
Source code in src/tsdynamics/derived/stroboscopic.py
trajectory
¶
trajectory(
steps: int = 100, *, transient: int = 0, **kwargs: Any
) -> Trajectory
Collect steps once-per-period samples (after transient periods).
Sampling starts from the inner flow's live cursor and advances by
exactly one forcing :attr:period per sample; transient leading
periods are stepped through and discarded first. Any keyword (ic=,
solver options) triggers a :meth:reinit before sampling.
| PARAMETER | DESCRIPTION |
|---|---|
steps
|
Number of once-per-period samples to collect.
TYPE:
|
transient
|
Number of leading periods to step through and discard.
TYPE:
|
**kwargs
|
Forwarded to :meth:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Trajectory
|
The strobed samples — continuous sample times in |
Source code in src/tsdynamics/derived/stroboscopic.py
to_plot_spec
¶
Describe the strobe sampling as a scatter of sampled states.
A stroboscopic map is a discrete sampling — once per forcing period —
so the natural picture is a cloud of sampled points (the strobed orbit /
attractor), not a connected flow line. This collects steps
samples and builds a 2-D / 3-D SCATTER spec over the first two / three
components (a 1-D system is a sample-index time series of dots).
The :mod:tsdynamics.viz.spec import is lazy, so building a spec never
pulls in a plotting backend.
| PARAMETER | DESCRIPTION |
|---|---|
kind
|
Override the auto-dispatched semantic kind (e.g.
TYPE:
|
steps
|
Number of once-per-period samples to collect. Default
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
PlotSpec
|
|
Notes
Sampling starts from the inner flow's live cursor (this calls
:meth:trajectory, which steps the wrapped system as a side effect with
no transient discarded), so the picture reflects wherever the system
currently sits — :meth:reinit first for a deterministic start state, or
burn the transient in beforehand to image the attractor rather than the
approach to it.
Source code in src/tsdynamics/derived/stroboscopic.py
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
TangentSystem
¶
Bases: DerivedSystem
Evolve a system together with k deviation (tangent) vectors.
Each step() advances the state and the deviation vectors, then
QR-reorthonormalises; :meth:growths exposes the per-step logarithmic
stretch factors log |diag R| and :meth:exponents their running
time-average — the Lyapunov spectrum estimate. :meth:lyapunov_spectrum
wraps that into the standard burn-in + time-averaged estimate, and is the
single implementation every family's lyapunov_spectrum delegates to.
Implementation per family
- Maps: pure NumPy —
W ← J(x)·Wwith_jacobianevaluated at the pre-image (the correct tangent-map convention), then QR. - ODEs: the extended ODE (state ⊕
ktangent vectors, see :mod:tsdynamics.derived._variational) is lowered to an engine tape and integrated per step on the Rust engine (or the pure-Python reference oracle), then QR-reorthonormalised here. Select the variational backend withbackend=:"interp"(default),"jit", or"reference". - DDEs: not supported — tangent dynamics of a DDE lives in an
infinite-dimensional history space; use
DelaySystem.lyapunov_spectrum(the engine DDE Lyapunov estimator).
| PARAMETER | DESCRIPTION |
|---|---|
system
|
The base system whose tangent dynamics to evolve.
TYPE:
|
k
|
Number of deviation vectors (
TYPE:
|
backend
|
ODE variational backend (ignored for maps):
TYPE:
|
Examples:
>>> tang = TangentSystem(Henon(), k=2)
>>> tang.reinit([0.1, 0.1])
>>> for _ in range(5000):
... tang.step()
>>> tang.exponents() # ≈ [0.42, -1.62]
Source code in src/tsdynamics/derived/tangent.py
reinit
¶
reinit(
u: Any | None = None,
*,
t: float | None = None,
params: dict[str, Any] | None = None,
**kwargs: Any,
) -> None
Restart state, deviation vectors, and accumulated growth sums.
Source code in src/tsdynamics/derived/tangent.py
step
¶
Advance state + deviation vectors and reorthonormalise.
For maps n_or_dt is the number of iterations (QR after each);
for ODEs it is the time increment (default 0.1).
Returns the new state.
Source code in src/tsdynamics/derived/tangent.py
state
¶
state() -> ndarray
Return a copy of the current base-system state.
Source code in src/tsdynamics/derived/tangent.py
set_state
¶
set_state(u: Any) -> None
Overwrite the base state (map mode only — ODE tangent vectors would desync).
Source code in src/tsdynamics/derived/tangent.py
deviations
¶
deviations() -> ndarray
Return the current orthonormal deviation vectors, shape (dim, k).
Available on every supported backend — maps and the ODE engine backends both carry the deviation matrix explicitly.
Source code in src/tsdynamics/derived/tangent.py
growths
¶
growths() -> ndarray
Return the log stretch factors log|diag R| from the most recent step.
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
In map mode, after a batch engine |
Source code in src/tsdynamics/derived/tangent.py
exponents
¶
exponents() -> ndarray
Return the running Lyapunov-spectrum estimate (accumulated growths / elapsed).
convergence
¶
convergence(
steps: int = 2000,
n_or_dt: float | None = None,
*,
ic: Any | None = None,
) -> tuple[ndarray, ndarray]
Record the running Lyapunov estimates as they converge.
Reinitialises the tangent frame and steps it steps times, capturing
the running :meth:exponents estimate after every step. The estimates
settle as the time-average accumulates — the curve a user inspects to
judge whether a Lyapunov run has converged.
| PARAMETER | DESCRIPTION |
|---|---|
steps
|
Number of tangent steps to record. Default
TYPE:
|
n_or_dt
|
Per-step increment (iterations for a map,
TYPE:
|
ic
|
Initial condition;
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
(times, estimates)
|
|
Source code in src/tsdynamics/derived/tangent.py
to_plot_spec
¶
to_plot_spec(
kind: str | None = None,
*,
steps: int = 2000,
n_or_dt: float | None = None,
ic: Any | None = None,
) -> PlotSpec
Describe the Lyapunov-estimate convergence as a :class:PlotSpec.
Builds a :data:~tsdynamics.viz.spec.PlotKind.DIAGNOSTIC_CURVE of each
exponent's running estimate against time — a labelled family of lines
(one LINE layer per exponent, legended), the standard read-out for
"has the Lyapunov spectrum settled?". The estimates are collected via
:meth:convergence.
The :mod:tsdynamics.viz.spec import is lazy, so building a spec never
pulls in a plotting backend.
| PARAMETER | DESCRIPTION |
|---|---|
kind
|
Override the semantic kind.
TYPE:
|
steps
|
Number of tangent steps to record. Default
TYPE:
|
n_or_dt
|
Per-step increment (iterations for a map,
TYPE:
|
ic
|
Initial condition;
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
PlotSpec
|
|
Source code in src/tsdynamics/derived/tangent.py
lyapunov_spectrum
¶
Estimate the Lyapunov spectrum — the unified engine for every family.
Map and ODE families both delegate their lyapunov_spectrum here, so
the QR/variational machinery lives in exactly one place. Mode-specific
keywords:
- maps:
steps(default 5000),ic,reortho_interval(1). - ODEs:
final_time(200.0),dt(0.1),ic,burn_in(50.0),method,rtol(1e-6),atol(1e-9), and any extra integrator keywords.
The estimate is recorded in self.meta['lyapunov_spectrum'] (the inner
system's :class:~tsdynamics.families.base.MetaStore).
| RETURNS | DESCRIPTION |
|---|---|
(ndarray, shape(k))
|
Lyapunov exponents, largest first (QR order). |
Source code in src/tsdynamics/derived/tangent.py
EnsembleSystem
¶
Many copies of one system, advanced synchronously from different states.
Used for two-trajectory Lyapunov estimates, basin sampling, and ensemble statistics. Members are independent copies — parameters are shared at construction, states are per-member.
| PARAMETER | DESCRIPTION |
|---|---|
system
|
The template system (copied per member; the original is untouched).
TYPE:
|
states
|
One initial state per member. |
Examples:
>>> ens = EnsembleSystem(Lorenz(), [[1, 1, 1], [1.001, 1, 1]])
>>> ens.step(0.01)
array([[...], [...]])
Source code in src/tsdynamics/derived/ensemble.py
step
¶
Advance every member synchronously and return the stacked states.
| PARAMETER | DESCRIPTION |
|---|---|
n_or_dt
|
The per-member increment (a |
| RETURNS | DESCRIPTION |
|---|---|
ndarray
|
The new states, shape |
Source code in src/tsdynamics/derived/ensemble.py
set_states
¶
set_states(states: Any) -> None
Overwrite every member's state.
| PARAMETER | DESCRIPTION |
|---|---|
states
|
One new state per member, in member order. |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in src/tsdynamics/derived/ensemble.py
collect
¶
Step every member steps times and stack the sampled states.
Advances the whole ensemble synchronously, recording each member's state
after every step. This is the trajectory collector the static fan chart
(:meth:to_plot_spec) summarises into a median line + percentile band.
| PARAMETER | DESCRIPTION |
|---|---|
steps
|
Number of samples to collect (one per step).
TYPE:
|
n_or_dt
|
The per-step increment forwarded to each member's |
| RETURNS | DESCRIPTION |
|---|---|
(times, states)
|
|
Source code in src/tsdynamics/derived/ensemble.py
to_plot_spec
¶
to_plot_spec(
kind: str | None = None,
*,
steps: int = 200,
component: int = 0,
band: float = 90.0,
) -> PlotSpec
Describe the ensemble as a static fan chart (median + percentile band).
Collects the ensemble's evolution of one component and summarises the
spread across members at each time as a shaded percentile band (an
AREA layer carrying "lo" / "hi" band edges, with lo <= hi)
under the across-member median line — the standard, animation-free way
to read an ensemble's dispersion. This is not an animation: it is one
:data:~tsdynamics.viz.spec.PlotKind.ENSEMBLE_FAN static spec.
The :mod:tsdynamics.viz.spec import is lazy, so building a spec never
pulls in a plotting backend.
| PARAMETER | DESCRIPTION |
|---|---|
kind
|
Override the semantic kind.
TYPE:
|
steps
|
Number of samples to collect across the ensemble. Default
TYPE:
|
component
|
Which state component to chart. Default
TYPE:
|
band
|
Central percentile mass to shade (
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
PlotSpec
|
|
Source code in src/tsdynamics/derived/ensemble.py
ProjectedSystem
¶
ProjectedSystem(
system: Any,
components: Any,
*,
complete: Callable[[ndarray], Any] | None = None,
)
Bases: DerivedSystem
View a system through a subset of its components.
The full system is stepped underneath; only state()/step()
outputs are projected. set_state needs the inverse direction and
therefore requires a complete callable mapping a projected state back
to a full state.
| PARAMETER | DESCRIPTION |
|---|---|
system
|
The full system.
TYPE:
|
components
|
Component indices (or names, when the system declares
TYPE:
|
complete
|
TYPE:
|
Examples:
Source code in src/tsdynamics/derived/projected.py
variables
property
¶
Component names of the projected view (the inner names, subset).
Overrides :class:DerivedSystem's pass-through (which would return the
inner system's full names and mislabel the projected columns). Returns
None when the inner system declares no variables.
step
¶
Advance the full system; return the projected new state.
set_state
¶
set_state(u: Any) -> None
Overwrite the state (projected inputs need a complete callable).
reinit
¶
Restart the full system (projected inputs need a complete callable).
Source code in src/tsdynamics/derived/projected.py
trajectory
¶
trajectory(*args: Any, **kwargs: Any) -> Trajectory
Full-system trajectory with projected columns.
Source code in src/tsdynamics/derived/projected.py
WrappedSystem
¶
WrappedSystem(
step_fn: Callable[[ndarray, float], Any],
*,
dim: int,
is_discrete: bool = True,
initial: Any | None = None,
default_dt: float = 1.0,
variables: tuple[str, ...] | None = None,
)
Wrap any external stepping rule as a first-class :class:System.
Give it a step_fn(state, n_or_dt) -> new_state and a dimension, and the
whole analysis toolkit (orbit diagrams, Lyapunov-from-rescaling, Poincaré
sections, ensembles, basins) applies to your own simulation code — a
foreign ODE solver, an agent-based model, a hardware-in-the-loop rig,
anything that advances a state vector.
| PARAMETER | DESCRIPTION |
|---|---|
step_fn
|
TYPE:
|
dim
|
State-space dimension.
TYPE:
|
is_discrete
|
Whether
TYPE:
|
initial
|
Default initial state used when
TYPE:
|
default_dt
|
Step taken by
TYPE:
|
variables
|
Component names, enabling
TYPE:
|
Examples:
>>> # a plain logistic map written by hand
>>> import numpy as np
>>> def step(u, n):
... x = u[0]
... for _ in range(int(n)):
... x = 3.9 * x * (1 - x)
... return [x]
>>> sysm = WrappedSystem(step, dim=1, is_discrete=True, initial=[0.5])
>>> traj = sysm.trajectory(500)
>>> import tsdynamics as ts
>>> ts.max_lyapunov(sysm, ic=[0.3]) > 0 # chaotic
True
Source code in src/tsdynamics/families/wrapped.py
reinit
¶
reinit(
u: Any | None = None,
*,
t: float | None = None,
params: dict[str, Any] | None = None,
) -> None
Restart from state u (falls back to initial, then zeros).
Source code in src/tsdynamics/families/wrapped.py
step
¶
Advance by n_or_dt (default default_dt) and return the new state.
Source code in src/tsdynamics/families/wrapped.py
copy
¶
copy() -> WrappedSystem
Return a fresh wrapper sharing the same step rule (independent state).
Source code in src/tsdynamics/families/wrapped.py
trajectory
¶
trajectory(
n: int | None = None,
*,
transient: int = 0,
ic: Any | None = None,
final_time: float | None = None,
dt: float | None = None,
) -> Trajectory
Step the wrapper repeatedly (after transient) and collect a Trajectory.
The wrapper accepts both the map-style positional sample count n
and — for a continuous wrapper — the family-uniform final_time /
dt spelling, so a generic protocol caller (which calls
trajectory(final_time=…, dt=…)) works without raising TypeError.
Exactly one count source must be supplied: either n (the number of
samples to collect), or final_time. When final_time is given the
sample count is round(final_time / dt) and the per-step increment is
dt, which defaults to default_dt for both continuous and
discrete wrappers. (A discrete wrapper that wants the "one time unit =
one iteration" convention should construct itself with default_dt=1.0,
the constructor default.)
| PARAMETER | DESCRIPTION |
|---|---|
n
|
Number of samples to collect. Mutually exclusive with
TYPE:
|
transient
|
Number of leading samples to discard (steps taken but not recorded).
TYPE:
|
ic
|
Initial state for the run (falls back to
TYPE:
|
final_time
|
Integration horizon, an alternative to
TYPE:
|
dt
|
Per-step increment used with
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Trajectory
|
|
| RAISES | DESCRIPTION |
|---|---|
InvalidInputError
|
If neither |
Examples:
>>> import numpy as np
>>> flow = lambda u, dt: [u[0] * np.exp(0.5 * dt)]
>>> w = WrappedSystem(flow, dim=1, is_discrete=False, default_dt=0.1)
>>> traj = w.trajectory(final_time=1.0, dt=0.1) # family-uniform spelling
>>> traj.y.shape
(10, 1)
Source code in src/tsdynamics/families/wrapped.py
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | |
DerivedSystem
¶
DerivedSystem(system: Any)
Base for wrappers that present an existing system through a new lens.
A derived system implements the :class:~tsdynamics.families.System protocol
by delegating to a wrapped system, transforming what "one step" or "the
state" means (Poincaré crossings, stroboscopic samples, projections...).
Parameters and metadata are forwarded to the wrapped system, and
with_params re-parametrizes the inner system and rebuilds the
wrapper, so parameter sweeps compose: an orbit diagram over a
PoincareMap is a bifurcation diagram of the underlying flow.
Source code in src/tsdynamics/derived/_base.py
with_params
¶
with_params(**overrides: Any) -> DerivedSystem
Return a new wrapper of the same kind around a re-parametrized copy.
copy
¶
copy() -> DerivedSystem
trajectory
¶
trajectory(*args: Any, **kwargs: Any) -> Trajectory
Produce the wrapper's trajectory — subclasses implement the lens-specific collection.
run
¶
run(*args: Any, **kwargs: Any) -> Trajectory
Produce the wrapper's trajectory — the alias of :meth:trajectory.
run is the library's canonical trajectory-producer verb (a flow's
Lorenz().run(...), a map's Henon().run(...)), so a fluent
derived view reads left-to-right with the same verb at the end::
section = Rossler().poincare(section="y", at=0.0).run(steps=500)
It forwards verbatim to this wrapper's :meth:trajectory, so the two are
byte-identical and every wrapper-specific keyword (transient, ...) is
honoured. trajectory remains the member the structural System
protocol requires; run is the discoverable spelling.
Source code in src/tsdynamics/derived/_base.py
to_plot_spec
¶
to_plot_spec(kind: str | None = None) -> PlotSpec
Describe this derived view as a backend-agnostic :class:PlotSpec.
The default delegates to the wrapper's own :meth:trajectory: it
collects the lens-specific trajectory (Poincaré crossings, projected
columns, ...) and forwards to that trajectory's
:meth:~tsdynamics.data.Trajectory.to_plot_spec. Subclasses whose
natural picture is not a single trajectory line — a stroboscopic
scatter, an ensemble fan, a Lyapunov convergence curve — override this
with their own spec builder.
The :mod:tsdynamics.viz.spec import stays inside the trajectory method
(lazy), so building a spec never pulls in a plotting backend.
| PARAMETER | DESCRIPTION |
|---|---|
kind
|
Override the auto-dispatched semantic kind with any member of the
closed :class:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
PlotSpec
|
|