solve-nivp 0.1.3.dev0__tar.gz → 0.2.0.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {solve_nivp-0.1.3.dev0/solve_nivp.egg-info → solve_nivp-0.2.0.dev1}/PKG-INFO +1 -1
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/pyproject.toml +1 -1
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/setup.py +1 -1
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/ODESolver.py +102 -36
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/__init__.py +44 -4
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/adaptive_integrator.py +4 -0
- solve_nivp-0.2.0.dev1/solve_nivp/block_system.py +408 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/contact.py +2 -1
- solve_nivp-0.2.0.dev1/solve_nivp/desaxce_contact.py +1874 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/integrations.py +997 -50
- solve_nivp-0.2.0.dev1/solve_nivp/macklin_contact.py +276 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/ncp_contact.py +413 -13
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/nonlinear_solvers.py +20 -4
- solve_nivp-0.2.0.dev1/solve_nivp/pcr.py +127 -0
- solve_nivp-0.2.0.dev1/solve_nivp/projected_radau_contact.py +1697 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/projections.py +11 -5
- solve_nivp-0.2.0.dev1/solve_nivp/rattle_contact.py +1750 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1/solve_nivp.egg-info}/PKG-INFO +1 -1
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp.egg-info/SOURCES.txt +18 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_3d_and_anisotropic_contact.py +0 -113
- solve_nivp-0.2.0.dev1/tests/test_block_system.py +90 -0
- solve_nivp-0.2.0.dev1/tests/test_bouncing_ball_schur_comparison.py +150 -0
- solve_nivp-0.2.0.dev1/tests/test_desaxce_contact.py +453 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_dilatancy.py +0 -70
- solve_nivp-0.2.0.dev1/tests/test_general_moreau_projection.py +74 -0
- solve_nivp-0.2.0.dev1/tests/test_macklin_contact.py +49 -0
- solve_nivp-0.2.0.dev1/tests/test_ncp_schur_integration.py +275 -0
- solve_nivp-0.2.0.dev1/tests/test_pcr.py +92 -0
- solve_nivp-0.2.0.dev1/tests/test_projected_radau_contact.py +732 -0
- solve_nivp-0.2.0.dev1/tests/test_radau_iia.py +502 -0
- solve_nivp-0.2.0.dev1/tests/test_rattle_integrator.py +551 -0
- solve_nivp-0.2.0.dev1/tests/test_rattle_local_slider.py +248 -0
- solve_nivp-0.2.0.dev1/tests/test_t_eval.py +95 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/LICENSE +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/README.md +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/setup.cfg +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/ODESystem.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/_numba_accel.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/_selftest.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/alart_curnier_contact.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/rl/__init__.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/rl/callbacks.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/rl/dependency.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp/rl/env.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp.egg-info/dependency_links.txt +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp.egg-info/entry_points.txt +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp.egg-info/requires.txt +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/solve_nivp.egg-info/top_level.txt +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/__init__.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_active_set_filter.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_alart_curnier_contact.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_algebraic_constraint_projection.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_anisotropic_soc_projection.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_auto_h0_and_dae_weighting.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_build_impulse_contact.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_c_extract_contact.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_composite_contact_projection.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_coulomb_projection.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_coulomb_projection_jacobian.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_error_predictive_rejection.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_globalization.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_identity_newton_linear_solver.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_import_and_api.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_integrators_added.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_jacobian_scaling.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_large_scale_solver_fixes.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_mu_scaled_soc_projection.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_ncp_contact.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_nl_recovery_cap.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_nonlinear_solvers_added.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_per_dof_tolerances.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_prestress_soc.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_prestressed_fault_dynamic_helper.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_projection_batch_equivalence.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_projections_added.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_sdirk2.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_sdirk2_soc_contact.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_sparse_semismooth_newton.py +0 -0
- {solve_nivp-0.1.3.dev0 → solve_nivp-0.2.0.dev1}/tests/test_threading_time_and_fk.py +0 -0
|
@@ -50,7 +50,7 @@ class BuildSphinx(Command):
|
|
|
50
50
|
|
|
51
51
|
setup(
|
|
52
52
|
name="solve_nivp",
|
|
53
|
-
version="0.
|
|
53
|
+
version="0.2.0.dev1",
|
|
54
54
|
packages=find_packages(), # automatically discovers packages
|
|
55
55
|
description="A solver package for implicit ODEs and projection-based solvers",
|
|
56
56
|
cmdclass={'build_sphinx': BuildSphinx},
|
|
@@ -40,10 +40,11 @@ class ODESolver:
|
|
|
40
40
|
store_fk: bool = True,
|
|
41
41
|
gc_interval: int = 0,
|
|
42
42
|
abort_on_fixed_failure: bool = True,
|
|
43
|
+
t_eval: Optional[np.ndarray] = None,
|
|
43
44
|
):
|
|
44
45
|
"""
|
|
45
46
|
Initialize the ODESolver.
|
|
46
|
-
|
|
47
|
+
|
|
47
48
|
Parameters:
|
|
48
49
|
system: The ODE system to be integrated.
|
|
49
50
|
t_span: A tuple (t0, tf) specifying the start and end times.
|
|
@@ -59,15 +60,57 @@ class ODESolver:
|
|
|
59
60
|
nonlinear failure instead of marching forward with the failed
|
|
60
61
|
state. The failed attempt is still recorded in
|
|
61
62
|
``error_estimates``.
|
|
63
|
+
t_eval: Strictly increasing array of times in ``[t0, tf]`` that
|
|
64
|
+
must be evaluated. When provided, the time loop clips the
|
|
65
|
+
step so it lands exactly on each ``t_eval`` entry, and the
|
|
66
|
+
returned histories contain only those entries (matches the
|
|
67
|
+
scipy ``solve_ivp`` convention). Pass ``None`` to keep the
|
|
68
|
+
adaptive/fixed grid as the output.
|
|
62
69
|
"""
|
|
63
70
|
self.system = system
|
|
64
71
|
self.t0, self.tf = t_span
|
|
65
72
|
self.h_initial = h
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
self.
|
|
73
|
+
|
|
74
|
+
# ---- t_eval validation and bookkeeping ----
|
|
75
|
+
self._use_t_eval: bool = False
|
|
76
|
+
self._t_eval: Optional[np.ndarray] = None
|
|
77
|
+
self._t_eval_idx: int = 0
|
|
78
|
+
if t_eval is not None:
|
|
79
|
+
t_eval_arr = np.asarray(t_eval, dtype=float).reshape(-1)
|
|
80
|
+
if t_eval_arr.size > 0:
|
|
81
|
+
if np.any(np.diff(t_eval_arr) <= 0.0):
|
|
82
|
+
raise ValueError("t_eval must be strictly increasing")
|
|
83
|
+
tf_eps = 1.0e-12 * max(abs(self.t0), abs(self.tf), 1.0)
|
|
84
|
+
if (t_eval_arr[0] < self.t0 - tf_eps
|
|
85
|
+
or t_eval_arr[-1] > self.tf + tf_eps):
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"t_eval out of range [{self.t0}, {self.tf}]: "
|
|
88
|
+
f"got [{t_eval_arr[0]}, {t_eval_arr[-1]}]"
|
|
89
|
+
)
|
|
90
|
+
self._t_eval = np.clip(t_eval_arr, self.t0, self.tf)
|
|
91
|
+
self._use_t_eval = True
|
|
92
|
+
|
|
93
|
+
if self._use_t_eval:
|
|
94
|
+
self.t_values: List[float] = []
|
|
95
|
+
self.y_values: List[np.ndarray] = []
|
|
96
|
+
self.h_values: List[float] = []
|
|
97
|
+
self.fk: List[Any] = []
|
|
98
|
+
# If t_eval[0] coincides with t0, record the initial state.
|
|
99
|
+
te0 = float(self._t_eval[0])
|
|
100
|
+
t0_eps = 1.0e-12 * max(abs(self.t0), 1.0)
|
|
101
|
+
if abs(te0 - self.t0) <= t0_eps:
|
|
102
|
+
self.t_values.append(te0)
|
|
103
|
+
self.y_values.append(self.system.current_y.copy())
|
|
104
|
+
self.h_values.append(h)
|
|
105
|
+
self.fk.append(None)
|
|
106
|
+
self._t_eval_idx = 1
|
|
107
|
+
else:
|
|
108
|
+
self.t_values = [self.t0]
|
|
109
|
+
self.y_values = [self.system.current_y.copy()]
|
|
110
|
+
self.h_values = [h]
|
|
111
|
+
self.fk = []
|
|
112
|
+
|
|
69
113
|
self.error_estimates: List[Tuple[Any, bool, int]] = []
|
|
70
|
-
self.fk: List[Any] = []
|
|
71
114
|
# Memory-saving options
|
|
72
115
|
self.thin_output = max(1, int(thin_output))
|
|
73
116
|
self.store_fk = bool(store_fk)
|
|
@@ -123,9 +166,38 @@ class ODESolver:
|
|
|
123
166
|
# fail, falsely reporting "reached minimum step size".
|
|
124
167
|
_tf_eps = 4.0 * np.finfo(float).eps * max(abs(self.t0), abs(self.tf), 1.0)
|
|
125
168
|
|
|
169
|
+
# Helper to record a stored sample (handles store_fk + fk fallback).
|
|
170
|
+
def _record(t_store, y, fk_val, h_taken, errinfo):
|
|
171
|
+
self.t_values.append(t_store)
|
|
172
|
+
self.y_values.append(y.copy())
|
|
173
|
+
if self.store_fk:
|
|
174
|
+
self.fk.append(fk_val.copy() if fk_val is not None else None)
|
|
175
|
+
else:
|
|
176
|
+
self.fk.append(None)
|
|
177
|
+
self.h_values.append(h_taken)
|
|
178
|
+
self.error_estimates.append(errinfo)
|
|
179
|
+
|
|
180
|
+
# Helper to drain any t_eval points that the latest accepted step
|
|
181
|
+
# advanced over (catches both exact landings and tiny float drift).
|
|
182
|
+
def _drain_t_eval(t_now, y_now, fk_val, h_taken, errinfo):
|
|
183
|
+
if not self._use_t_eval:
|
|
184
|
+
return
|
|
185
|
+
te_eps = 1.0e-9 * max(abs(t_now), 1.0)
|
|
186
|
+
while (self._t_eval_idx < len(self._t_eval)
|
|
187
|
+
and self._t_eval[self._t_eval_idx] <= t_now + te_eps):
|
|
188
|
+
te = float(self._t_eval[self._t_eval_idx])
|
|
189
|
+
_record(te, y_now, fk_val, h_taken, errinfo)
|
|
190
|
+
self._t_eval_idx += 1
|
|
191
|
+
|
|
126
192
|
while self.tf - t > _tf_eps:
|
|
127
193
|
# Ensure we do not overshoot the final time.
|
|
128
194
|
h_step = min(h, self.tf - t)
|
|
195
|
+
# When t_eval is given, also clip the step so it lands exactly
|
|
196
|
+
# on the next required output time.
|
|
197
|
+
if self._use_t_eval and self._t_eval_idx < len(self._t_eval):
|
|
198
|
+
next_te = float(self._t_eval[self._t_eval_idx])
|
|
199
|
+
if next_te > t:
|
|
200
|
+
h_step = min(h_step, next_te - t)
|
|
129
201
|
if self.system.adaptive:
|
|
130
202
|
# Adaptive stepping returns:
|
|
131
203
|
# (y_new, fk_new, h_new, E, success, solver_error, iterations)
|
|
@@ -133,17 +205,15 @@ class ODESolver:
|
|
|
133
205
|
if success:
|
|
134
206
|
t += h_step
|
|
135
207
|
_step_count += 1
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
self.h_values.append(h_step)
|
|
146
|
-
self.error_estimates.append((solver_error, success, iterations))
|
|
208
|
+
if self._use_t_eval:
|
|
209
|
+
_drain_t_eval(t, y_new, fk_new, h_step,
|
|
210
|
+
(solver_error, success, iterations))
|
|
211
|
+
else:
|
|
212
|
+
# Thin output: only store every Nth step (always store last)
|
|
213
|
+
_is_last = (t >= self.tf - 1e-14 * abs(self.tf))
|
|
214
|
+
if _step_count % _thin == 0 or _is_last:
|
|
215
|
+
_record(t, y_new, fk_new, h_step,
|
|
216
|
+
(solver_error, success, iterations))
|
|
147
217
|
self.system.current_y = y_new
|
|
148
218
|
h = h_new # Update step size for next iteration.
|
|
149
219
|
# Periodic garbage collection for large problems
|
|
@@ -162,16 +232,14 @@ class ODESolver:
|
|
|
162
232
|
if success:
|
|
163
233
|
t += h_step
|
|
164
234
|
_step_count += 1
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
self.h_values.append(h_step)
|
|
174
|
-
self.error_estimates.append((solver_error, success, iterations))
|
|
235
|
+
if self._use_t_eval:
|
|
236
|
+
_drain_t_eval(t, y_new, fk_new, h_step,
|
|
237
|
+
(solver_error, success, iterations))
|
|
238
|
+
else:
|
|
239
|
+
_is_last = (t >= self.tf - 1e-14 * abs(self.tf))
|
|
240
|
+
if _step_count % _thin == 0 or _is_last:
|
|
241
|
+
_record(t, y_new, fk_new, h_step,
|
|
242
|
+
(solver_error, success, iterations))
|
|
175
243
|
self.system.current_y = y_new
|
|
176
244
|
if _gc_iv > 0 and _step_count % _gc_iv == 0:
|
|
177
245
|
gc.collect()
|
|
@@ -187,16 +255,14 @@ class ODESolver:
|
|
|
187
255
|
break
|
|
188
256
|
t += h_step
|
|
189
257
|
_step_count += 1
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
self.h_values.append(h_step)
|
|
199
|
-
self.error_estimates.append((solver_error, success, iterations))
|
|
258
|
+
if self._use_t_eval:
|
|
259
|
+
_drain_t_eval(t, y_new, fk_new, h_step,
|
|
260
|
+
(solver_error, success, iterations))
|
|
261
|
+
else:
|
|
262
|
+
_is_last = (t >= self.tf - 1e-14 * abs(self.tf))
|
|
263
|
+
if _step_count % _thin == 0 or _is_last:
|
|
264
|
+
_record(t, y_new, fk_new, h_step,
|
|
265
|
+
(solver_error, success, iterations))
|
|
200
266
|
self.system.current_y = y_new
|
|
201
267
|
if _gc_iv > 0 and _step_count % _gc_iv == 0:
|
|
202
268
|
gc.collect()
|
|
@@ -62,7 +62,8 @@ from .projections import (
|
|
|
62
62
|
CompositeContactProjection,
|
|
63
63
|
)
|
|
64
64
|
from .nonlinear_solvers import ImplicitEquationSolver, UMFPACK_AVAILABLE, PETSC_AVAILABLE
|
|
65
|
-
from .integrations import BackwardEuler, Trapezoidal, ThetaMethod, CompositeMethod, EmbeddedBETR, SDIRK2 # , BDFMethod
|
|
65
|
+
from .integrations import BackwardEuler, BackwardEulerSchur, RadauIIASchur, Trapezoidal, ThetaMethod, CompositeMethod, EmbeddedBETR, SDIRK2, RadauIIA # , BDFMethod
|
|
66
|
+
from .block_system import SchurComplementSolver, BlockStructuredSystem
|
|
66
67
|
from .ODESystem import ODESystem
|
|
67
68
|
from .ODESolver import ODESolver
|
|
68
69
|
from .contact import build_impulse_contact, ContactSystem
|
|
@@ -73,6 +74,24 @@ from .alart_curnier_contact import (
|
|
|
73
74
|
from .ncp_contact import (
|
|
74
75
|
build_ncp_contact,
|
|
75
76
|
build_dynamic_ncp_contact,
|
|
77
|
+
build_ncp_contact_blocked,
|
|
78
|
+
)
|
|
79
|
+
from .desaxce_contact import (
|
|
80
|
+
build_dynamic_desaxce_contact,
|
|
81
|
+
build_dynamic_desaxce_projected_contact,
|
|
82
|
+
build_dynamic_desaxce_residual_contact,
|
|
83
|
+
)
|
|
84
|
+
from .rattle_contact import (
|
|
85
|
+
build_dynamic_rattle_contact,
|
|
86
|
+
solve_dynamic_rattle_contact,
|
|
87
|
+
build_rattle_system,
|
|
88
|
+
RattleMechanicalSystem,
|
|
89
|
+
RattleContactSpec,
|
|
90
|
+
RattleBilateralSpec,
|
|
91
|
+
RattleAlgebraicSpec,
|
|
92
|
+
RattleContactSystem,
|
|
93
|
+
RattleSolveResult,
|
|
94
|
+
RattleSolver,
|
|
76
95
|
)
|
|
77
96
|
|
|
78
97
|
# Curated public API
|
|
@@ -83,7 +102,7 @@ __all__ = [
|
|
|
83
102
|
# Nonlinear solver
|
|
84
103
|
'ImplicitEquationSolver',
|
|
85
104
|
# Integrators
|
|
86
|
-
'BackwardEuler', 'Trapezoidal', 'ThetaMethod', 'CompositeMethod', 'EmbeddedBETR', 'SDIRK2',
|
|
105
|
+
'BackwardEuler', 'BackwardEulerSchur', 'RadauIIASchur', 'Trapezoidal', 'ThetaMethod', 'CompositeMethod', 'EmbeddedBETR', 'SDIRK2', 'RadauIIA',
|
|
87
106
|
# Projections
|
|
88
107
|
'Projection',
|
|
89
108
|
'CoulombProjection', 'SignProjection', 'IdentityProjection',
|
|
@@ -93,7 +112,22 @@ __all__ = [
|
|
|
93
112
|
# Contact helpers
|
|
94
113
|
'build_impulse_contact', 'build_alart_curnier_contact',
|
|
95
114
|
'build_dynamic_alart_curnier_contact', 'build_ncp_contact',
|
|
96
|
-
'build_dynamic_ncp_contact', '
|
|
115
|
+
'build_dynamic_ncp_contact', 'build_ncp_contact_blocked',
|
|
116
|
+
'SchurComplementSolver', 'BlockStructuredSystem',
|
|
117
|
+
'build_dynamic_desaxce_contact',
|
|
118
|
+
'build_dynamic_desaxce_projected_contact',
|
|
119
|
+
'build_dynamic_desaxce_residual_contact',
|
|
120
|
+
'build_dynamic_rattle_contact',
|
|
121
|
+
'solve_dynamic_rattle_contact',
|
|
122
|
+
'build_rattle_system',
|
|
123
|
+
'ContactSystem',
|
|
124
|
+
'RattleMechanicalSystem',
|
|
125
|
+
'RattleContactSpec',
|
|
126
|
+
'RattleBilateralSpec',
|
|
127
|
+
'RattleAlgebraicSpec',
|
|
128
|
+
'RattleContactSystem',
|
|
129
|
+
'RattleSolveResult',
|
|
130
|
+
'RattleSolver',
|
|
97
131
|
]
|
|
98
132
|
|
|
99
133
|
|
|
@@ -126,6 +160,7 @@ def solve_nivp(
|
|
|
126
160
|
abort_on_fixed_failure=True,
|
|
127
161
|
jacobian_scaling=None,
|
|
128
162
|
active_set_filter=False,
|
|
163
|
+
t_eval=None,
|
|
129
164
|
):
|
|
130
165
|
"""Integrate an ODE / simple index–1 DAE with optional nonsmooth projection.
|
|
131
166
|
|
|
@@ -140,7 +175,9 @@ def solve_nivp(
|
|
|
140
175
|
Initial state.
|
|
141
176
|
method : str, default 'composite'
|
|
142
177
|
Time stepping scheme: ``'backward_euler'``, ``'trapezoidal'``, ``'theta'``,
|
|
143
|
-
``'composite'`` (TR-BE like second order), ``'embedded_betr'``, ``'sdirk2'
|
|
178
|
+
``'composite'`` (TR-BE like second order), ``'embedded_betr'``, ``'sdirk2'``,
|
|
179
|
+
``'radau_iia'`` (L-stable, stiffly accurate, order 3 or 5; stages controlled
|
|
180
|
+
via ``integrator_opts={'stages': 2}`` or ``{'stages': 3}``).
|
|
144
181
|
projection : str or Projection or None, default 'identity'
|
|
145
182
|
Name of projection to build: ``'coulomb'``, ``'sign'``, ``'identity'`` or
|
|
146
183
|
``None``. At the high-level API, ``None`` is promoted to the identity
|
|
@@ -348,6 +385,8 @@ def solve_nivp(
|
|
|
348
385
|
integrator = EmbeddedBETR(solver=solver_instance, A=A)
|
|
349
386
|
elif m == 'sdirk2':
|
|
350
387
|
integrator = SDIRK2(solver=solver_instance, A=A, **_integrator_opts)
|
|
388
|
+
elif m in ('radau_iia', 'radau'):
|
|
389
|
+
integrator = RadauIIA(solver=solver_instance, A=A, **_integrator_opts)
|
|
351
390
|
# elif m == 'bdf':
|
|
352
391
|
# integrator = BDFMethod(solver=solver_instance, atol=atol, rtol=rtol)
|
|
353
392
|
else:
|
|
@@ -466,6 +505,7 @@ def solve_nivp(
|
|
|
466
505
|
store_fk=store_fk,
|
|
467
506
|
gc_interval=gc_interval,
|
|
468
507
|
abort_on_fixed_failure=abort_on_fixed_failure,
|
|
508
|
+
t_eval=t_eval,
|
|
469
509
|
)
|
|
470
510
|
return solver_obj.solve(return_attempts=return_attempts)
|
|
471
511
|
|
|
@@ -416,6 +416,10 @@ class AdaptiveStepping:
|
|
|
416
416
|
# ------------------------------------------------------------------
|
|
417
417
|
|
|
418
418
|
def _infer_method_order(self, integrator) -> int:
|
|
419
|
+
p_emb = getattr(integrator, 'embedded_order', None)
|
|
420
|
+
if isinstance(p_emb, (int, float)) and p_emb > 0:
|
|
421
|
+
return int(p_emb)
|
|
422
|
+
|
|
419
423
|
p = getattr(integrator, 'order', None)
|
|
420
424
|
if isinstance(p, (int, float)) and p > 0:
|
|
421
425
|
return int(p)
|