"""Validate the requested output time grid before starting the C worker.""" from __future__ import annotations from math import floor, isfinite from sys import maxsize from app.simulation.config import SolveIVPConfig class SimulationSampleTimeError(ValueError): """Stable failure contract for an unsafe or unrepresentable sample grid.""" def __init__(self, code: str, message: str) -> None: super().__init__(message) self.code = code def simulation_sample_times( config: SolveIVPConfig, step: float, ) -> list[float]: t_start = float(config.t_start) t_stop = float(config.t_stop) if not isfinite(t_start) or not isfinite(t_stop): raise SimulationSampleTimeError( "SIMULATION_VALUE_NOT_FINITE", "Simulation start and stop times must be finite.", ) if step <= 0.0 or not isfinite(step): raise SimulationSampleTimeError( "SIMULATION_SAMPLE_STEP_INVALID", "Simulation sample step must be finite and greater than zero.", ) duration = t_stop - t_start if not isfinite(duration): raise SimulationSampleTimeError( "SIMULATION_TIME_SPAN_NOT_FINITE", "Simulation time span must be finite.", ) if duration <= 0.0: raise SimulationSampleTimeError( "SIMULATION_TIME_RANGE_INVALID", "Simulation stop time must be greater than start time.", ) ratio = duration / step if not isfinite(ratio): raise SimulationSampleTimeError( "SIMULATION_SAMPLE_COUNT_UNREPRESENTABLE", "Simulation sample count cannot be represented by this runtime; " "increase sampleStep.", ) interval_count = floor(ratio) # There is no product-level point cap. Still reject a collection that the # Python runtime cannot index before multiplying by the potentially huge # interval count or allocating the output grid. if interval_count > maxsize - 2: raise SimulationSampleTimeError( "SIMULATION_SAMPLE_COUNT_UNREPRESENTABLE", "Simulation sample count cannot be represented by this runtime; " "increase sampleStep.", ) last_regular_time = t_start + interval_count * step append_stop = last_regular_time < t_stop requested_point_count = interval_count + 1 + int(append_stop) if requested_point_count > maxsize: raise SimulationSampleTimeError( "SIMULATION_SAMPLE_COUNT_UNREPRESENTABLE", "Simulation sample count cannot be represented by this runtime; " "increase sampleStep.", ) times = [t_start] for index in range(1, interval_count + 1): candidate = t_start + index * step if not isfinite(candidate): raise SimulationSampleTimeError( "SIMULATION_SAMPLE_TIME_UNREPRESENTABLE", "Simulation sampleStep cannot be represented over the requested " "absolute time range.", ) if candidate >= t_stop: candidate = t_stop if candidate <= times[-1]: raise SimulationSampleTimeError( "SIMULATION_SAMPLE_TIME_UNREPRESENTABLE", "Simulation sampleStep is too small to advance floating-point " "time over the requested absolute time range.", ) times.append(candidate) if candidate == t_stop: break if times[-1] < t_stop: times.append(t_stop) if len(times) < 2 or any( current >= following for current, following in zip(times, times[1:]) ): raise SimulationSampleTimeError( "SIMULATION_SAMPLE_TIME_UNREPRESENTABLE", "Simulation sample times must contain at least two strictly " "increasing values.", ) return times