113 3D: Prepared spatial ODE trajectories and vector fields
3D reference map · First 3D picture
Prepared spatial trajectories are immutable data with event-aware preparation, explicit stopping policies, display-ready adaptive streamlines, and deterministic seed sets. prepare-ode-trajectory3d records dense RK4 or Dormand–Prince segments once; subsequent position, tangent, arc-length, and event-hit lookup accepts any supported time in any order and never calls the author field. A field accepts either (field x y z) or (field time x y z) and must return exactly one finite vec3. Use ode-field3d when the field needs an explicit cache identity or when its autonomous status matters to later flow analysis.
An ode-event3d evaluates a finite scalar on the stored trajectory while it is prepared. Its procedure accepts either (event point) or (event time point). A sign-changing root is found by deterministic bisection of the dense Hermite segment; a zero at a shared accepted node is reported once. Event directions always mean increasing physical time, including when the requested range runs backward. A terminal event becomes a canonical end node and shortens the relevant side of the returned range; nonterminal hits remain in the immutable hit vector.
trajectory-termination3d makes every other stopping condition explicit: a symmetric time budget about the seed, an AABB exit, an accumulated arc-length budget, a low-speed threshold, a maximum step count, policy-owned events, and a choice between reporting or tolerating an author-field failure. Terminal events are checked on every accepted solver step: the first root clips that step and prevents later steps from being accepted. Other termination policies are resolved on the retained dense Hermite segments and recorded as immutable termination hits. An AABB exit reports the outward face normal. On one branch, simultaneous candidates are ordered as field failure, terminal event in declaration order, bounds exit, arc-length limit, low speed, maximum steps, then the requested time limit. The returned range ends exactly at the selected dense point when that condition can be located inside a segment.
flow-particle3d is a semantic spatial relation. Before an image or preview worker resolves it, Animate samples its requested phase values into an immutable table. Thus worker rendering reads positions and tangents only; it does not evaluate the field procedure. The fixed solver’s #:checkpoint-every value is diagnostic preparation metadata; it does not change lookup cost.
prepare-streamline3d uses the same dense prepared trajectory but adds an explicit choice of parameterization. 'time means dp/ds = F(p), preserving the field’s speed. 'arc-length means dp/ds = F(p)/||F(p)||; it therefore requires an autonomous field and ends at an equilibrium rather than inventing a direction. The prepared display samples are recursively subdivided in world coordinates until the chord error, tangent turn, and segment length satisfy an immutable streamline-sample-policy3d. Camera zoom does not change their points. For a bidirectional streamline, the returned samples run from the backward end through one copy of the seed to the forward end; the seed index and per-branch diagnostics remain inspectable.
(define lorenz-path (prepare-ode-trajectory3d (ode-field3d (lambda (x y z) (vec3 (* 10 (- y x)) (- (* x (- 28 z)) y) (- (* x y) (* 8/3 z)))) #:cache-key 'lorenz-10-28-8/3) (vec3 0 1 21/20) #:time-range (cons 0 20) #:solver (adaptive-rk45-solver3d #:relative-tolerance 1e-6))) (define phase (parameter 'time 0)) (flow-particle3d lorenz-path phase #:id 'particle #:tangent-length 1)
procedure
(prepare-ode-trajectory3d field seed #:time-range time-range [ #:step-size step-size #:checkpoint-every checkpoint-every #:solver solver #:events events #:termination termination #:cancellation-token cancellation-token]) → ode-trajectory3d? field : any/c seed : vec3? time-range : (cons/c finite-real? finite-real?) step-size : (and/c finite-real? positive?) = 1/20 checkpoint-every : exact-positive-integer? = 16 solver : any/c = #f events : list? = '() termination : (or/c false/c trajectory-termination3d?) = #f cancellation-token : any/c = #f
procedure
(ode-field3d procedure [ #:cache-key cache-key #:autonomous? autonomous? #:parallel-safe? parallel-safe?]) → any/c procedure : procedure? cache-key : any/c = #f autonomous? : (or/c boolean? 'auto) = 'auto parallel-safe? : boolean? = #f
procedure
(fixed-rk4-solver3d [#:step-size step-size]) → any/c
step-size : positive? = 1/20
procedure
(adaptive-rk45-solver3d [ #:relative-tolerance relative-tolerance #:absolute-tolerance absolute-tolerance #:initial-step initial-step #:minimum-step minimum-step #:maximum-step maximum-step #:maximum-steps maximum-steps]) → any/c relative-tolerance : positive? = 1e-6 absolute-tolerance : positive? = 1e-9 initial-step : positive? = 1/20 minimum-step : positive? = 1e-9 maximum-step : positive? = 1 maximum-steps : exact-positive-integer? = 100000
procedure
(ode-event3d #:id id #:function function [ #:direction direction #:terminal? terminal? #:value-tolerance value-tolerance #:time-tolerance time-tolerance #:maximum-iterations maximum-iterations #:cache-key cache-key #:parallel-safe? parallel-safe? #:root-kind root-kind #:initial-subdivisions initial-subdivisions #:maximum-depth maximum-depth]) → any/c id : symbol? function : procedure? direction : (or/c 'any 'increasing 'decreasing) = 'any terminal? : boolean? = #t value-tolerance : positive? = 1e-9 time-tolerance : positive? = 1e-9 maximum-iterations : exact-positive-integer? = 64 cache-key : any/c = #f parallel-safe? : boolean? = #f root-kind : (or/c 'crossing 'touching 'both) = 'crossing initial-subdivisions : exact-positive-integer? = 4 maximum-depth : exact-nonnegative-integer? = 12
procedure
(ode-event-hit3d? value) → boolean?
value : any/c
procedure
(trajectory-termination3d [ #:time-limit time-limit #:arc-length-limit arc-length-limit #:bounds bounds #:minimum-speed minimum-speed #:maximum-steps maximum-steps #:events events #:on-field-error on-field-error]) → trajectory-termination3d? time-limit : (or/c false/c nonnegative-real?) = #f arc-length-limit : (or/c false/c nonnegative-real?) = #f bounds : (or/c false/c aabb3?) = #f minimum-speed : (or/c false/c nonnegative-real?) = #f maximum-steps : exact-positive-integer? = 100000 events : list? = '() on-field-error : (or/c 'error 'terminate) = 'error
procedure
(trajectory-termination3d? value) → boolean?
value : any/c
procedure
(trajectory-termination-hit3d? value) → boolean?
value : any/c
procedure
(streamline-sample-policy3d [ #:maximum-chord-error maximum-chord-error #:maximum-turn-angle maximum-turn-angle #:maximum-segment-length maximum-segment-length #:minimum-segment-length minimum-segment-length]) → streamline-sample-policy3d? maximum-chord-error : nonnegative-real? = 1/100 maximum-turn-angle : nonnegative-real? = (/ pi 12) maximum-segment-length : positive? = 1/4 minimum-segment-length : positive? = 1/2000
procedure
(prepare-streamline3d field seed [ #:direction direction #:parameterization parameterization #:solver solver #:termination termination #:sample-policy sample-policy #:cancellation-token cancellation-token]) → prepared-streamline3d? field : any/c seed : vec3? direction : (or/c 'forward 'backward 'both) = 'forward parameterization : (or/c 'time 'arc-length) = 'time solver : any/c = (adaptive-rk45-solver3d) termination : (or/c false/c trajectory-termination3d?) = #f
sample-policy : streamline-sample-policy3d? = (streamline-sample-policy3d) cancellation-token : any/c = #f
procedure
(prepared-streamline3d? value) → boolean?
value : any/c
procedure
(adaptive-streamline3d prepared #:id id #:style style [ #:opacity opacity]) → spatial-visual? prepared : prepared-streamline3d? id : symbol? style : any/c opacity : real? = 1