Problem Statement
Saving a simulation fails outright if it contains any custom callable. _serialize_value recognizes only the built-in "sine" and "uniform" spacing functions and raises a ValueError for anything else. This affects three cases: custom spacing functions on the movement classes, the custom second-derivative callables in AeroelasticWingMovement's spacingAnglesSecondDerivative_Gs_to_Wn_ixyz slot, and external_loads_fn on FreeFlightUnsteadyProblem. An aeroelastic simulation with custom flapping spacing or a free-flight simulation with a custom loads function cannot be saved at all, even though the load path never invokes these callables for a solved simulation. The movement histories are reconstructed by aliasing into the serialized steady problems, and the MuJoCo engine is rebuilt from its stored XML, so the callables in a saved solve are pure definition metadata.
Location(s): pterasoftware/_serialization.py, pterasoftware/problems.py, pterasoftware/movements/aeroelastic_wing_movement.py
Proposed Solution
- Serialize an unrecognized callable as an inert marker instead of raising. The marker records the callable's qualified name, its source text when
inspect.getsource can retrieve it, and a hash of that source. Nothing stored in the file is ever executed, so the no-code-execution guarantee of the JSON format is unchanged.
- Deserialize the marker to a sentinel object that behaves like the missing callable's placeholder: loading always succeeds, and the sentinel raises a descriptive error only if it is actually invoked, naming the original function and printing the stored source so the user can reconstruct it.
- Add a rebind mechanism so a user who wants to re-solve or regenerate motion after loading can supply the original functions again, with a warning if a rebound function's source hash differs from the recorded one.
- Update the tests that currently pin the raise-on-save behavior for custom spacing functions and
external_loads_fn to pin the new marker and sentinel behavior instead.
Additional Context
This interacts with #240 (pause and resume unsteady simulations) and should land first. Resume re-invokes exactly the callables this issue makes deferrable: external_loads_fn runs every free-flight time step, the second-derivative callables run every aeroelastic time step via _generate_inertial_moment_function, and spacing functions run when a resumed simulation is extended with new time steps. Without this change, a mid-run aeroelastic or free-flight checkpoint with custom callables could not even be saved, so #240 could not cover all three unsteady solvers. The resume entry point designed under #240 should accept rebind arguments for these callables, detect sentinels eagerly at the resume call rather than failing mid-step, and use the stored source hash to warn when a rebound function differs from the one the checkpoint was created with.
Problem Statement
Saving a simulation fails outright if it contains any custom callable.
_serialize_valuerecognizes only the built-in "sine" and "uniform" spacing functions and raises aValueErrorfor anything else. This affects three cases: custom spacing functions on the movement classes, the custom second-derivative callables inAeroelasticWingMovement'sspacingAnglesSecondDerivative_Gs_to_Wn_ixyzslot, andexternal_loads_fnonFreeFlightUnsteadyProblem. An aeroelastic simulation with custom flapping spacing or a free-flight simulation with a custom loads function cannot be saved at all, even though the load path never invokes these callables for a solved simulation. The movement histories are reconstructed by aliasing into the serialized steady problems, and the MuJoCo engine is rebuilt from its stored XML, so the callables in a saved solve are pure definition metadata.Location(s):
pterasoftware/_serialization.py,pterasoftware/problems.py,pterasoftware/movements/aeroelastic_wing_movement.pyProposed Solution
inspect.getsourcecan retrieve it, and a hash of that source. Nothing stored in the file is ever executed, so the no-code-execution guarantee of the JSON format is unchanged.external_loads_fnto pin the new marker and sentinel behavior instead.Additional Context
This interacts with #240 (pause and resume unsteady simulations) and should land first. Resume re-invokes exactly the callables this issue makes deferrable:
external_loads_fnruns every free-flight time step, the second-derivative callables run every aeroelastic time step via_generate_inertial_moment_function, and spacing functions run when a resumed simulation is extended with new time steps. Without this change, a mid-run aeroelastic or free-flight checkpoint with custom callables could not even be saved, so #240 could not cover all three unsteady solvers. The resume entry point designed under #240 should accept rebind arguments for these callables, detect sentinels eagerly at the resume call rather than failing mid-step, and use the stored source hash to warn when a rebound function differs from the one the checkpoint was created with.