use super::config::RadauMatrixLayout;
use super::error::{RadauError, RadauUnsupportedRoute};
use super::linear::{DenseLinearWorkspace, RadauLinearWorkspace, SparseLinearWorkspace};
use super::telemetry::RadauTelemetry;
#[derive(Debug, Default)]
pub(crate) struct RadauWorkspace {
pub telemetry: RadauTelemetry,
pub stage_states: Vec<f64>,
pub stage_rhs: Vec<f64>,
pub correction: Vec<f64>,
pub scale: Vec<f64>,
pub jacobian: Vec<f64>,
pub linear: RadauLinearWorkspace,
pub rollback_state: Vec<f64>,
pub base_rhs: Vec<f64>,
base_rhs_preloaded: bool,
pub real_rhs: Vec<f64>,
pub complex_rhs_real: Vec<f64>,
pub complex_rhs_imag: Vec<f64>,
pub transformed_state: Vec<f64>,
pub transformed_correction: Vec<f64>,
pub dense_output_q: Vec<f64>,
pub dense_output_y_old: Vec<f64>,
pub dense_output_t_old: f64,
pub dense_output_h: f64,
pub dense_output_valid: bool,
pub pending_dense_output_q: Vec<f64>,
pub pending_dense_output_y_old: Vec<f64>,
pub pending_dense_output_t_old: f64,
pub pending_dense_output_h: f64,
pub pending_dense_output_valid: bool,
jacobian_ready: bool,
jacobian_current: bool,
factor_valid: bool,
factor_h: f64,
jacobian_generation: u64,
factor_generation: u64,
callback_generation: Option<u64>,
}
impl RadauWorkspace {
pub(crate) fn resize_for_dimension(&mut self, dimension: usize) -> Result<(), RadauError> {
self.resize_for_layout(dimension, RadauMatrixLayout::Dense)
}
pub(crate) fn resize_for_layout(
&mut self,
dimension: usize,
layout: RadauMatrixLayout,
) -> Result<(), RadauError> {
self.resize_for_layout_with_pattern(dimension, layout, &[])
}
pub(crate) fn resize_for_layout_with_pattern(
&mut self,
dimension: usize,
layout: RadauMatrixLayout,
sparse_pattern: &[(usize, usize)],
) -> Result<(), RadauError> {
let started = (self.telemetry.mode() == super::telemetry::RadauTelemetryMode::Timings)
.then(std::time::Instant::now);
self.telemetry.count_workspace_resize();
let result = self.resize_for_layout_inner(dimension, layout, sparse_pattern);
if let Some(started) = started {
self.telemetry
.add_workspace_timing_ms(started.elapsed().as_secs_f64() * 1_000.0);
}
result
}
fn resize_for_layout_inner(
&mut self,
dimension: usize,
layout: RadauMatrixLayout,
sparse_pattern: &[(usize, usize)],
) -> Result<(), RadauError> {
self.resize_buffers_with_pattern(dimension, layout, sparse_pattern)?;
match layout {
RadauMatrixLayout::Dense => {
if !matches!(self.linear, RadauLinearWorkspace::Dense(_)) {
self.linear = RadauLinearWorkspace::Dense(DenseLinearWorkspace::default());
}
if let RadauLinearWorkspace::Dense(linear) = &mut self.linear {
linear.resize_for_dimension(dimension)?;
}
}
RadauMatrixLayout::Banded { lower, upper } => {
let replace = match &self.linear {
RadauLinearWorkspace::Banded(linear) => {
linear.dimension != dimension
|| linear.lower != lower
|| linear.upper != upper
}
_ => true,
};
if replace {
self.linear = RadauLinearWorkspace::Banded(
super::linear::BandedLinearWorkspace::new(dimension, lower, upper)?,
);
}
}
RadauMatrixLayout::Sparse => {
let expected_pattern_len =
SparseLinearWorkspace::from_pattern(dimension, sparse_pattern)?
.row_indices
.len();
let replace = match &self.linear {
RadauLinearWorkspace::Sparse(linear) => {
linear.dimension != dimension
|| linear.row_indices.len() != expected_pattern_len
}
_ => true,
};
if replace {
self.linear = RadauLinearWorkspace::Sparse(
SparseLinearWorkspace::from_pattern(dimension, sparse_pattern)?,
);
}
}
}
Ok(())
}
pub(crate) fn resize_buffers(
&mut self,
dimension: usize,
layout: RadauMatrixLayout,
) -> Result<(), RadauError> {
self.resize_buffers_with_pattern(dimension, layout, &[])
}
pub(crate) fn resize_buffers_with_pattern(
&mut self,
dimension: usize,
layout: RadauMatrixLayout,
sparse_pattern: &[(usize, usize)],
) -> Result<(), RadauError> {
let capacities_before =
(self.telemetry.mode() != super::telemetry::RadauTelemetryMode::Off).then(|| {
[
self.stage_states.capacity(),
self.stage_rhs.capacity(),
self.correction.capacity(),
self.scale.capacity(),
self.jacobian.capacity(),
self.rollback_state.capacity(),
self.base_rhs.capacity(),
self.real_rhs.capacity(),
self.complex_rhs_real.capacity(),
self.complex_rhs_imag.capacity(),
self.transformed_state.capacity(),
self.transformed_correction.capacity(),
]
});
let stage_len = dimension
.checked_mul(3)
.ok_or(RadauError::WorkspaceSizeOverflow { dimension })?;
self.stage_states.resize(stage_len, 0.0);
self.stage_rhs.resize(stage_len, 0.0);
self.correction.resize(stage_len, 0.0);
self.scale.resize(dimension, 0.0);
let jacobian_len = match layout {
RadauMatrixLayout::Dense => dimension
.checked_mul(dimension)
.ok_or(RadauError::WorkspaceSizeOverflow { dimension })?,
RadauMatrixLayout::Banded { lower, upper } => lower
.checked_add(upper)
.and_then(|value| value.checked_add(1))
.and_then(|slots| slots.checked_mul(dimension))
.ok_or(RadauError::WorkspaceSizeOverflow { dimension })?,
RadauMatrixLayout::Sparse => {
SparseLinearWorkspace::from_pattern(dimension, sparse_pattern)?
.jacobian
.len()
}
};
self.jacobian.resize(jacobian_len, 0.0);
self.rollback_state.resize(dimension, 0.0);
self.base_rhs.resize(dimension, 0.0);
self.real_rhs.resize(dimension, 0.0);
self.complex_rhs_real.resize(dimension, 0.0);
self.complex_rhs_imag.resize(dimension, 0.0);
self.transformed_state.resize(stage_len, 0.0);
self.transformed_correction.resize(stage_len, 0.0);
let dense_output_len = dimension
.checked_mul(3)
.ok_or(RadauError::WorkspaceSizeOverflow { dimension })?;
self.dense_output_q.resize(dense_output_len, 0.0);
self.dense_output_y_old.resize(dimension, 0.0);
self.pending_dense_output_q.resize(dense_output_len, 0.0);
self.pending_dense_output_y_old.resize(dimension, 0.0);
self.jacobian_ready = false;
self.jacobian_current = false;
self.factor_valid = false;
self.factor_h = 0.0;
self.jacobian_generation = 0;
self.factor_generation = 0;
self.callback_generation = None;
self.base_rhs_preloaded = false;
self.dense_output_valid = false;
self.pending_dense_output_valid = false;
if let Some(capacities_before) = capacities_before {
let capacities_after = [
self.stage_states.capacity(),
self.stage_rhs.capacity(),
self.correction.capacity(),
self.scale.capacity(),
self.jacobian.capacity(),
self.rollback_state.capacity(),
self.base_rhs.capacity(),
self.real_rhs.capacity(),
self.complex_rhs_real.capacity(),
self.complex_rhs_imag.capacity(),
self.transformed_state.capacity(),
self.transformed_correction.capacity(),
];
for (before, after) in capacities_before.into_iter().zip(capacities_after) {
if after > before {
self.telemetry.count_allocation();
}
}
}
Ok(())
}
pub(crate) fn dimension(&self) -> usize {
self.rollback_state.len()
}
pub(crate) fn linear_layout(&self) -> RadauMatrixLayout {
match &self.linear {
RadauLinearWorkspace::Dense(_) => RadauMatrixLayout::Dense,
RadauLinearWorkspace::Banded(linear) => RadauMatrixLayout::Banded {
lower: linear.lower,
upper: linear.upper,
},
RadauLinearWorkspace::Sparse(_) => RadauMatrixLayout::Sparse,
}
}
pub(crate) fn factor_valid_for(&self, h: f64) -> bool {
self.factor_valid
&& self.factor_generation == self.jacobian_generation
&& self.factor_h == h
}
pub(crate) fn mark_jacobian_evaluated(&mut self) {
self.jacobian_ready = true;
self.jacobian_current = true;
self.factor_valid = false;
self.jacobian_generation = self.jacobian_generation.wrapping_add(1);
}
pub(crate) fn mark_jacobian_stale(&mut self) {
if self.jacobian_ready {
self.jacobian_current = false;
}
}
pub(crate) fn invalidate_jacobian(&mut self) {
self.jacobian_ready = false;
self.jacobian_current = false;
self.factor_valid = false;
self.base_rhs_preloaded = false;
}
pub(crate) fn preload_base_rhs(&mut self) {
self.base_rhs_preloaded = true;
}
pub(crate) fn clear_base_rhs_preload(&mut self) {
self.base_rhs_preloaded = false;
}
pub(crate) fn base_rhs_preloaded(&self) -> bool {
self.base_rhs_preloaded
}
pub(crate) fn sync_callback_generation(&mut self, generation: u64) {
if self.callback_generation != Some(generation) {
self.invalidate_jacobian();
self.callback_generation = Some(generation);
}
}
pub(crate) fn jacobian_ready(&self) -> bool {
self.jacobian_ready
}
pub(crate) fn jacobian_current(&self) -> bool {
self.jacobian_current
}
pub(crate) fn mark_factorized(&mut self, h: f64) {
self.factor_valid = true;
self.factor_h = h;
self.factor_generation = self.jacobian_generation;
}
pub(crate) fn invalidate_factor(&mut self) {
self.factor_valid = false;
}
pub(crate) fn prepare_dense_output(
&mut self,
coefficients: &super::coefficients::RadauIia5,
t: f64,
h: f64,
y: &[f64],
) {
let dimension = y.len();
self.pending_dense_output_y_old.copy_from_slice(y);
for index in 0..dimension {
let z = [
self.stage_states[index] - y[index],
self.stage_states[dimension + index] - y[index],
self.stage_states[2 * dimension + index] - y[index],
];
for power in 0..3 {
self.pending_dense_output_q[power * dimension + index] = (0..3)
.map(|stage| z[stage] * coefficients.p[stage][power])
.sum();
}
}
self.pending_dense_output_t_old = t;
self.pending_dense_output_h = h;
self.pending_dense_output_valid = true;
}
pub(crate) fn commit_dense_output(&mut self) {
if !self.pending_dense_output_valid {
return;
}
self.dense_output_q
.copy_from_slice(&self.pending_dense_output_q);
self.dense_output_y_old
.copy_from_slice(&self.pending_dense_output_y_old);
self.dense_output_t_old = self.pending_dense_output_t_old;
self.dense_output_h = self.pending_dense_output_h;
self.dense_output_valid = true;
self.pending_dense_output_valid = false;
}
pub(crate) fn dense_output_segment(
&self,
) -> Result<Option<super::dense_output::RadauDenseOutputSegment>, RadauError> {
if !self.dense_output_valid {
return Ok(None);
}
Ok(Some(super::dense_output::RadauDenseOutputSegment::new(
self.dense_output_t_old,
self.dense_output_h,
self.dense_output_y_old.clone(),
self.dense_output_q.clone(),
)?))
}
pub(crate) fn discard_dense_output(&mut self) {
self.pending_dense_output_valid = false;
}
pub(crate) fn predict_stage_states(
&mut self,
coefficients: &super::coefficients::RadauIia5,
t: f64,
h: f64,
y: &[f64],
) {
let dimension = y.len();
if !self.dense_output_valid {
for stage in 0..3 {
self.stage_states[stage * dimension..(stage + 1) * dimension].copy_from_slice(y);
}
return;
}
for stage in 0..3 {
let x = (t + coefficients.c[stage] * h - self.dense_output_t_old) / self.dense_output_h;
let powers = [x, x * x, x * x * x];
for index in 0..dimension {
let offset = stage * dimension + index;
self.stage_states[offset] = self.dense_output_y_old[index]
+ (0..3)
.map(|power| self.dense_output_q[power * dimension + index] * powers[power])
.sum::<f64>();
}
}
}
pub(crate) fn dense_linear(&self) -> Result<&DenseLinearWorkspace, RadauError> {
match &self.linear {
RadauLinearWorkspace::Dense(linear) => Ok(linear),
_ => Err(RadauUnsupportedRoute::DenseWorkspaceRequired.into()),
}
}
pub(crate) fn dense_linear_mut(&mut self) -> Result<&mut DenseLinearWorkspace, RadauError> {
match &mut self.linear {
RadauLinearWorkspace::Dense(linear) => Ok(linear),
_ => Err(RadauUnsupportedRoute::DenseWorkspaceRequired.into()),
}
}
}