PyDynSys.core Module
The PyDynSys.core module provides the foundational classes and utilities for working with Euclidean dynamical systems.
Submodules
- Core.Euclidean Module - Euclidean dynamical systems and related types
Main Classes and Types
Dynamical Systems
- EuclideanDS - Abstract base class for Euclidean dynamical systems
- AutonomousEuclideanDS - Systems where dx/dt = F(x)
- NonAutonomousEuclideanDS - Systems where dx/dt = F(x, t)
Trajectory Types
- EuclideanTrajectorySegment - Single trajectory segment
- EuclideanTrajectory - Composed trajectory over multiple segments
Phase Space and Time
- PhaseSpace - Phase space representation with symbolic and callable constraints
- TimeHorizon - Time domain representation
System Builder
- SymbolicSystemBuilder - Build systems from symbolic ODEs
Type Definitions
AutonomousVectorField- Vector field type for autonomous systemsNonAutonomousVectorField- Vector field type for non-autonomous systemsVectorField- Union type for any vector fieldSymbolicODE- Symbolic ODE representationSystemParameters- Parameter substitution dictionaryTrajectoryCacheKey- Cache key for trajectory solutionsSciPyIvpSolution- SciPy IVP solution wrapperTrajectorySegmentMergePolicy- Policy for merging trajectory segments
Full Docs
PyDynSys.core
Core module for PyFlow dynamical systems library.
AutonomousVectorField = Callable[[NDArray[np.float64]], NDArray[np.float64]]
module-attribute
Vector field for autonomous systems: F: R^n → R^n Maps state vector x to derivative dx/dt = F(x)
NonAutonomousVectorField = Callable[[NDArray[np.float64], float], NDArray[np.float64]]
module-attribute
Vector field for non-autonomous systems: F: R^n × R → R^n Maps (state x, time t) to derivative dx/dt = F(x, t)
SymbolicODE = Union[List[syp.Expr], syp.Expr, str, List[str]]
module-attribute
Union type for symbolic ODE representations. Accepts: single expression, list of expressions, or string forms. Expected format: d(x_i)/dt - F_i(x, t) = 0 (we drop the "= 0")
SystemParameters = Union[Dict[str, float], Dict[syp.Symbol, float]]
module-attribute
Parameter substitution dictionary for symbolic systems. Keys: parameter names (str) or SymPy symbols Values: numerical parameter values NOTE: All keys in a single dict must be same type (all str or all Symbol)
TrajectorySegmentMergePolicy = Literal['average', 'left', 'right', 'stitch']
module-attribute
Strategy for merging overlapping trajectory segments in EuclideanTrajectory.from_segments().
WHEN OVERLAPS OCCUR:
Overlapping domains arise when the same physical trajectory is computed multiple times over intersecting time intervals. Common scenarios: 1. Re-solving for comparison: Solve [0,1.5] with RK45, then [0.5,2] with DOP853 2. Patching numerical errors: Re-solve unstable region with tighter tolerances 3. Composing from cache: Merging cached trajectories to avoid recomputation
MERGE POLICIES:
When two segments overlap on [a, b], we must decide: - Which y values to use at shared evaluation points? - Which interpolant to use for continuous evaluation in [a, b]?
Available policies: - 'average' (DEFAULT): Average y values at shared evaluation points in overlap region. Takes midpoint between competing numerical approximations. Interpolant set to None (limitation of current implementation). Use case: Equal trust in both segments, want best estimate.
-
'left': Prioritize left segment's values and interpolant in overlap region. Use case: Left segment has higher accuracy (tighter tolerance, better method). Status: Not yet implemented (raises NotImplementedError).
-
'right': Prioritize right segment's values and interpolant in overlap region. Use case: Right segment has higher accuracy or is more recent computation. Status: Not yet implemented (raises NotImplementedError).
-
'stitch': Use left segment's interpolant until overlap midpoint, then right's. Creates continuous transition across overlap region. Use case: Both segments equally valid, want smooth transition. Status: Not yet implemented (raises NotImplementedError).
TANGENT DOMAINS (Special Case):
When domains touch at exactly one point (e.g., [0,1] + [1,2]), the "overlap" is just the boundary. The average policy automatically handles this by averaging the single shared point, which is correct for tangent segments from bidirectional integration where both segments share x(t_0) at the tangent point.
Note: Only 'average' is implemented in current version. Others raise NotImplementedError.
VectorField = Union[AutonomousVectorField, NonAutonomousVectorField]
module-attribute
Union type for any vector field representation. NOTE: For type safety, prefer specific types in implementations.
SymbolicSystemBuilder
Converts symbolic ODE representations to numerical vector fields.
Handles: - Parsing various symbolic input formats (str, syp.Expr, lists) - Parameter substitution - First-order system validation - Autonomous vs non-autonomous detection - Compilation to efficient numerical functions via syp.lambdify
Source code in src/PyDynSys/core/sym_utils.py
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 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 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 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 | |
build_vector_field(equations, variables, parameters=None)
staticmethod
Convert symbolic ODE system to numerical vector field.
Auto-detects whether system is autonomous by checking if time t
appears in any derivative after solving for dx/dt.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
equations
|
SymbolicODE
|
Symbolic system expressed as d(x_i)/dt - F_i(x, t), i=1,...,n -> SymbolicODE = Union[List[syp.Expr], syp.Expr, str, List[str]] -> str use is highly experimental and not recommended. |
required |
variables
|
List[Function]
|
List of dependent variables as SymPy Function objects -> support for strings may be supported later, but not a pressing matter. |
required |
parameters
|
SystemParameters
|
Optional parameter substitution dict -> SystemParameters = Union[Dict[str, float], Dict[syp.Symbol, float]] |
None
|
Returns:
| Type | Description |
|---|---|
SymbolicToVectorFieldResult
|
SymbolicToVectorFieldResult wrapper, which itself contains: - vector_field: Callable with appropriate signature - dimension: Phase space dimension n - is_autonomous: True <==> partial(vector_field, t) = 0 |
Raises:
| Type | Description |
|---|---|
ValueError
|
If system is not first-order |
Example
t = syp.symbols('t') x, y = syp.symbols('x y', cls=syp.Function) x, y = x(t), y(t) eqs = [syp.diff(x, t) - y, syp.diff(y, t) + x] result = SymbolicSystemBuilder.build_vector_field(eqs, [x, y]) result.is_autonomous # True (no explicit t dependence) True
Source code in src/PyDynSys/core/sym_utils.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 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 | |
SymbolicToVectorFieldResult
dataclass
Result of converting symbolic ODE to numerical vector field.
Encapsulates all information about the conversion process. Frozen to ensure immutability and hashability (if needed for caching).
Fields
vector_field: Callable vector field with appropriate signature on is_autonomous bool. dimension: dimension of the phase space (not as linear subspace), i.e. #state_components is_autonomous: True <==> partial(vector_field, t) == 0
Future Extensions
- conversion_method: Details on nth → 1st order reduction (when supported)
- symbolic_derivatives: Cached symbolic forms for Jacobian
- parameter_values: Substituted parameters for reference
Source code in src/PyDynSys/core/sym_utils.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
TrajectoryCacheKey
dataclass
Immutable cache key for trajectory solutions.
DESIGN RATIONALE - Why This Key Structure:
Caches by initial conditions and evaluation domain, NOT by method. This allows retrieval regardless of solver used. Note: multi-method trajectories (segments solved with different methods) are valid.
Why method is NOT in the cache key: - Removed to support multi-method trajectories (e.g., bidirectional integration where backward uses RK45 and forward uses DOP853) - Same IVP solved with different methods should give similar results (modulo numerical error), so caching without method is reasonable - Simplifies cache logic and enables trajectory composition
Why initial_time IS in the cache key: - For autonomous systems (dx/dt = F(x)): initial_time doesn't affect trajectory SHAPE in phase space, only the parameterization. However, we store it for consistency and potential future time-shifted cache lookups. - For non-autonomous systems (dx/dt = F(x,t)): initial_time is CRITICAL! The same initial state x_0 at different times evolves completely differently due to time-dependent forcing. Example: - System: dx/dt = -x + sin(t) - x(0) = 1.0 at t_0=0: affected by sin(0)=0 - x(0) = 1.0 at t_0=π/2: affected by sin(π/2)=1 These produce entirely different trajectories despite same x_0!
For autonomous systems: initial_time is conventionally t_span[0] For non-autonomous systems: initial_time matters and varies
Fields
initial_conditions: x(t_0) as hashable tuple initial_time: t_0 (critical for non-autonomous, stored for autonomous too) t_eval_tuple: Evaluation time points as hashable tuple
Future enhancements (smart caching):
- Smart cache lookup: merge cached [0,2] + [1,5] to get requested [1,3] (use EuclideanTrajectory.from_segments to compose cached segments)
- Cache slicing: slice cached [1,3] to get requested [1,2] (extract subset of evaluation points from cached trajectory)
Source code in src/PyDynSys/core/types.py
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | |