use super::error::{RadauConfigError, RadauError, RadauStage, RadauUnsupportedRoute};
use super::{
aot::{AotPlan, AotWorkspace},
atom_native::{AtomNativePlan, AtomNativeWorkspace},
config::{RadauAssembly, RadauMatrixLayout},
lambdify::{LambdifyPlan, LambdifyWorkspace},
telemetry::{RadauFrontendStage, RadauTelemetry, RadauTelemetryMode},
};
use crate::symbolic::ivp_telemetry::IvpLambdifyExecutionPolicy;
use crate::symbolic::symbolic_engine::Expr;
pub(crate) enum PreparedSymbolicCallbacks {
ExprLegacy(LambdifyPlan),
AtomViewNative(AtomNativePlan),
Aot(AotPlan),
}
pub(crate) enum SymbolicCallbackWorkspace {
ExprLegacy(LambdifyWorkspace),
AtomViewNative(AtomNativeWorkspace),
Aot(AotWorkspace),
}
pub(crate) struct SymbolicCallbackSession<'a> {
callbacks: &'a PreparedSymbolicCallbacks,
workspace: SymbolicCallbackWorkspace,
jacobian_pattern: Vec<(usize, usize)>,
parameters: Vec<f64>,
generation: u64,
telemetry: RadauTelemetry,
execution_policy: IvpLambdifyExecutionPolicy,
}
impl PreparedSymbolicCallbacks {
pub(crate) fn prepare(
assembly: RadauAssembly,
residual: Vec<Expr>,
jacobian: Option<Vec<Expr>>,
independent_variable: &str,
variables: &[&str],
parameters: &[&str],
) -> Result<Self, RadauError> {
let mut telemetry = RadauTelemetry::new(Default::default());
Self::prepare_with_telemetry(
assembly,
residual,
jacobian,
independent_variable,
variables,
parameters,
&mut telemetry,
)
}
pub(crate) fn prepare_with_telemetry(
assembly: RadauAssembly,
residual: Vec<Expr>,
jacobian: Option<Vec<Expr>>,
independent_variable: &str,
variables: &[&str],
parameters: &[&str],
telemetry: &mut RadauTelemetry,
) -> Result<Self, RadauError> {
Self::prepare_with_telemetry_and_policy(
assembly,
residual,
jacobian,
independent_variable,
variables,
parameters,
telemetry,
IvpLambdifyExecutionPolicy::Sequential,
)
}
pub(crate) fn prepare_with_telemetry_and_policy(
assembly: RadauAssembly,
residual: Vec<Expr>,
jacobian: Option<Vec<Expr>>,
independent_variable: &str,
variables: &[&str],
parameters: &[&str],
telemetry: &mut RadauTelemetry,
execution_policy: IvpLambdifyExecutionPolicy,
) -> Result<Self, RadauError> {
match assembly {
RadauAssembly::ExprLegacy => {
telemetry.measure_frontend(RadauFrontendStage::ExprLegacyPrepare, || {
LambdifyPlan::from_expressions_with_policy(
residual,
jacobian,
independent_variable,
variables,
parameters,
execution_policy,
)
.map(Self::ExprLegacy)
})
}
RadauAssembly::AtomViewNative => {
let telemetry_mode = telemetry.mode();
let result = telemetry.measure_frontend(RadauFrontendStage::AtomConversion, || {
AtomNativePlan::from_expressions_with_telemetry_and_policy(
residual,
jacobian,
independent_variable,
variables,
parameters,
telemetry_mode,
execution_policy,
)
});
result.map(|plan| {
plan.absorb_preparation_telemetry(telemetry);
Self::AtomViewNative(plan)
})
}
}
}
pub(crate) fn workspace(&self) -> SymbolicCallbackWorkspace {
self.workspace_with_telemetry(RadauTelemetryMode::Off)
}
pub(crate) fn workspace_with_telemetry(
&self,
mode: RadauTelemetryMode,
) -> SymbolicCallbackWorkspace {
match self {
Self::ExprLegacy(plan) => SymbolicCallbackWorkspace::ExprLegacy(plan.workspace()),
Self::AtomViewNative(plan) => {
SymbolicCallbackWorkspace::AtomViewNative(plan.workspace())
}
Self::Aot(plan) => SymbolicCallbackWorkspace::Aot(plan.workspace_with_telemetry(mode)),
}
}
pub(crate) fn execution_policy(&self) -> IvpLambdifyExecutionPolicy {
match self {
Self::ExprLegacy(plan) => plan.execution_policy(),
Self::AtomViewNative(plan) => plan.execution_policy(),
Self::Aot(plan) => plan.execution_policy(),
}
}
pub(crate) fn session(&self) -> SymbolicCallbackSession<'_> {
self.session_with_telemetry(RadauTelemetryMode::Off)
}
pub(crate) fn session_with_telemetry(
&self,
mode: RadauTelemetryMode,
) -> SymbolicCallbackSession<'_> {
let parameter_count = match self {
Self::ExprLegacy(plan) => plan.parameter_count(),
Self::AtomViewNative(plan) => plan.parameter_count(),
Self::Aot(plan) => plan.parameter_count(),
};
SymbolicCallbackSession {
callbacks: self,
workspace: self.workspace_with_telemetry(mode),
jacobian_pattern: self.jacobian_pattern(),
parameters: vec![0.0; parameter_count],
generation: 1,
telemetry: RadauTelemetry::new(mode),
execution_policy: self.execution_policy(),
}
}
pub(crate) fn evaluate_residual(
&self,
t: f64,
state: &[f64],
parameters: &[f64],
output: &mut [f64],
workspace: &mut SymbolicCallbackWorkspace,
) -> Result<(), RadauError> {
let mut telemetry = RadauTelemetry::new(RadauTelemetryMode::Off);
self.evaluate_residual_with_telemetry(
t,
state,
parameters,
output,
workspace,
&mut telemetry,
)
}
pub(crate) fn evaluate_residual_with_telemetry(
&self,
t: f64,
state: &[f64],
parameters: &[f64],
output: &mut [f64],
workspace: &mut SymbolicCallbackWorkspace,
telemetry: &mut RadauTelemetry,
) -> Result<(), RadauError> {
match (self, workspace) {
(Self::ExprLegacy(plan), SymbolicCallbackWorkspace::ExprLegacy(workspace)) => plan
.evaluate_residual_with_telemetry(
t, state, parameters, output, workspace, telemetry,
),
(Self::AtomViewNative(plan), SymbolicCallbackWorkspace::AtomViewNative(workspace)) => {
plan.evaluate_residual_with_telemetry(
t, state, parameters, output, workspace, telemetry,
)
}
(Self::Aot(plan), SymbolicCallbackWorkspace::Aot(workspace)) => {
plan.evaluate_residual(t, state, parameters, output, workspace, telemetry)
}
_ => Err(RadauConfigError::CallbackFrontendMismatch.into()),
}
}
pub(crate) fn evaluate_jacobian(
&self,
t: f64,
state: &[f64],
parameters: &[f64],
output: &mut [f64],
workspace: &mut SymbolicCallbackWorkspace,
) -> Result<(), RadauError> {
let mut telemetry = RadauTelemetry::new(RadauTelemetryMode::Off);
self.evaluate_jacobian_with_telemetry(
t,
state,
parameters,
output,
workspace,
&mut telemetry,
)
}
pub(crate) fn evaluate_jacobian_with_telemetry(
&self,
t: f64,
state: &[f64],
parameters: &[f64],
output: &mut [f64],
workspace: &mut SymbolicCallbackWorkspace,
telemetry: &mut RadauTelemetry,
) -> Result<(), RadauError> {
match (self, workspace) {
(Self::ExprLegacy(plan), SymbolicCallbackWorkspace::ExprLegacy(workspace)) => plan
.evaluate_jacobian_with_telemetry(
t, state, parameters, output, workspace, telemetry,
),
(Self::AtomViewNative(plan), SymbolicCallbackWorkspace::AtomViewNative(workspace)) => {
plan.evaluate_jacobian_with_telemetry(
t, state, parameters, output, workspace, telemetry,
)
}
(Self::Aot(plan), SymbolicCallbackWorkspace::Aot(workspace)) => plan.evaluate_jacobian(
t,
state,
parameters,
RadauMatrixLayout::Dense,
output,
workspace,
telemetry,
),
_ => Err(RadauConfigError::CallbackFrontendMismatch.into()),
}
}
pub(crate) fn jacobian_pattern(&self) -> Vec<(usize, usize)> {
match self {
Self::ExprLegacy(plan) => plan.jacobian_pattern().to_vec(),
Self::AtomViewNative(plan) => plan.jacobian_pattern(),
Self::Aot(plan) => plan.jacobian_pattern().to_vec(),
}
}
pub(crate) fn evaluate_jacobian_layout(
&self,
t: f64,
state: &[f64],
parameters: &[f64],
layout: RadauMatrixLayout,
output: &mut [f64],
workspace: &mut SymbolicCallbackWorkspace,
) -> Result<(), RadauError> {
let mut telemetry = RadauTelemetry::new(RadauTelemetryMode::Off);
self.evaluate_jacobian_layout_with_telemetry(
t,
state,
parameters,
layout,
output,
workspace,
&mut telemetry,
)
}
pub(crate) fn evaluate_jacobian_layout_with_telemetry(
&self,
t: f64,
state: &[f64],
parameters: &[f64],
layout: RadauMatrixLayout,
output: &mut [f64],
workspace: &mut SymbolicCallbackWorkspace,
telemetry: &mut RadauTelemetry,
) -> Result<(), RadauError> {
match (self, workspace) {
(Self::ExprLegacy(plan), SymbolicCallbackWorkspace::ExprLegacy(workspace)) => plan
.evaluate_jacobian_layout_with_telemetry(
t, state, parameters, layout, output, workspace, telemetry,
),
(Self::AtomViewNative(plan), SymbolicCallbackWorkspace::AtomViewNative(workspace)) => {
plan.evaluate_jacobian_layout_with_telemetry(
t, state, parameters, layout, output, workspace, telemetry,
)
}
(Self::Aot(plan), SymbolicCallbackWorkspace::Aot(workspace)) => {
plan.evaluate_jacobian(t, state, parameters, layout, output, workspace, telemetry)
}
_ => Err(RadauConfigError::CallbackFrontendMismatch.into()),
}
}
}
impl<'a> SymbolicCallbackSession<'a> {
pub(crate) fn telemetry(&self) -> &RadauTelemetry {
&self.telemetry
}
pub(crate) fn telemetry_mut(&mut self) -> &mut RadauTelemetry {
&mut self.telemetry
}
pub(crate) fn parameters(&self) -> &[f64] {
&self.parameters
}
pub(crate) fn generation(&self) -> u64 {
self.generation
}
pub(crate) fn jacobian_pattern(&self) -> &[(usize, usize)] {
&self.jacobian_pattern
}
pub(crate) fn workspace_capacity(&self) -> usize {
match &self.workspace {
SymbolicCallbackWorkspace::ExprLegacy(workspace) => workspace.argument_capacity(),
SymbolicCallbackWorkspace::AtomViewNative(workspace) => workspace.value_capacity(),
SymbolicCallbackWorkspace::Aot(workspace) => workspace.argument_capacity(),
}
}
pub(crate) fn rebind_parameters(&mut self, values: &[f64]) -> Result<(), RadauError> {
if values.len() != self.parameters.len() {
return Err(RadauError::ShapeMismatch {
stage: RadauStage::Preparation,
expected: self.parameters.len(),
actual: values.len(),
});
}
self.telemetry.measure_callback(
super::telemetry::RadauCallbackStage::ParameterRebind,
|| self.parameters.copy_from_slice(values),
);
self.telemetry.count_copy();
self.generation = self.generation.saturating_add(1);
Ok(())
}
pub(crate) fn evaluate_residual(
&mut self,
t: f64,
state: &[f64],
output: &mut [f64],
) -> Result<(), RadauError> {
self.telemetry.count_stage(RadauStage::Residual);
self.telemetry
.record_policy_dispatch(self.execution_policy, output.len(), output.len());
self.callbacks.evaluate_residual_with_telemetry(
t,
state,
&self.parameters,
output,
&mut self.workspace,
&mut self.telemetry,
)
}
pub(crate) fn evaluate_jacobian(
&mut self,
t: f64,
state: &[f64],
output: &mut [f64],
) -> Result<(), RadauError> {
self.telemetry.count_stage(RadauStage::Jacobian);
self.telemetry
.record_policy_dispatch(self.execution_policy, output.len(), output.len());
self.callbacks.evaluate_jacobian_with_telemetry(
t,
state,
&self.parameters,
output,
&mut self.workspace,
&mut self.telemetry,
)
}
pub(crate) fn evaluate_jacobian_layout(
&mut self,
t: f64,
state: &[f64],
layout: RadauMatrixLayout,
output: &mut [f64],
) -> Result<(), RadauError> {
self.telemetry.count_stage(RadauStage::Jacobian);
self.telemetry
.record_policy_dispatch(self.execution_policy, output.len(), output.len());
self.callbacks.evaluate_jacobian_layout_with_telemetry(
t,
state,
&self.parameters,
layout,
output,
&mut self.workspace,
&mut self.telemetry,
)
}
pub(crate) fn absorb_runtime_telemetry(&mut self) {
if let (PreparedSymbolicCallbacks::Aot(plan), SymbolicCallbackWorkspace::Aot(workspace)) =
(self.callbacks, &self.workspace)
{
plan.absorb_runtime_telemetry(workspace, &mut self.telemetry);
}
}
}
pub(crate) trait ResidualCallback {
fn eval(&mut self, t: f64, y: &[f64], out: &mut [f64]) -> Result<(), RadauError>;
}
pub(crate) trait DenseJacobianCallback {
fn eval_into(&mut self, t: f64, y: &[f64], out: &mut [f64]) -> Result<(), RadauError>;
}
pub(crate) trait RadauStepCallbacks {
fn eval_residual(&mut self, t: f64, y: &[f64], out: &mut [f64]) -> Result<(), RadauError>;
fn eval_jacobian(&mut self, t: f64, y: &[f64], out: &mut [f64]) -> Result<(), RadauError>;
fn eval_jacobian_layout(
&mut self,
t: f64,
y: &[f64],
layout: RadauMatrixLayout,
out: &mut [f64],
) -> Result<(), RadauError> {
if layout == RadauMatrixLayout::Dense {
self.eval_jacobian(t, y, out)
} else {
Err(RadauUnsupportedRoute::StructuredJacobianCallbackRequired.into())
}
}
}
impl<'a> RadauStepCallbacks for SymbolicCallbackSession<'a> {
fn eval_residual(&mut self, t: f64, y: &[f64], out: &mut [f64]) -> Result<(), RadauError> {
self.evaluate_residual(t, y, out)
}
fn eval_jacobian(&mut self, t: f64, y: &[f64], out: &mut [f64]) -> Result<(), RadauError> {
self.evaluate_jacobian(t, y, out)
}
fn eval_jacobian_layout(
&mut self,
t: f64,
y: &[f64],
layout: RadauMatrixLayout,
out: &mut [f64],
) -> Result<(), RadauError> {
self.evaluate_jacobian_layout(t, y, layout, out)
}
}
impl<F> ResidualCallback for F
where
F: FnMut(f64, &[f64], &mut [f64]) -> Result<(), RadauError>,
{
fn eval(&mut self, t: f64, y: &[f64], out: &mut [f64]) -> Result<(), RadauError> {
self(t, y, out)
}
}
impl<F> DenseJacobianCallback for F
where
F: FnMut(f64, &[f64], &mut [f64]) -> Result<(), RadauError>,
{
fn eval_into(&mut self, t: f64, y: &[f64], out: &mut [f64]) -> Result<(), RadauError> {
self(t, y, out)
}
}
pub(crate) fn validate_callback_output(
stage: RadauStage,
expected: usize,
values: &[f64],
) -> Result<(), RadauError> {
if values.len() != expected {
return Err(RadauError::ShapeMismatch {
stage,
expected,
actual: values.len(),
});
}
if values.iter().any(|value| !value.is_finite()) {
return Err(RadauError::NonFiniteCallback { stage });
}
Ok(())
}