1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//! Initial Value Problem (IVP) solving infrastructure for ODE-based components.
//!
//! Many climate model components are formulated as systems of Ordinary Differential
//! Equations (ODEs). This module provides the infrastructure to solve these ODEs
//! as initial value problems using numerical integration methods.
//!
//! # Overview
//!
//! The IVP module wraps the `ode_solvers` crate, providing:
//! - The [`IVP`] trait for components that define ODE systems
//! - The [`IVPBuilder`] struct for constructing and solving IVPs
//! - Integration with the component state system via [`InputState`]
//!
//! # Example
//!
//! ```ignore
//! use std::sync::Arc;
//! use nalgebra::Vector2;
//! use rscm_core::ivp::{IVP, IVPBuilder, get_last_step};
//! use rscm_core::component::InputState;
//!
//! // Define a component that implements IVP
//! struct TwoBoxModel {
//! heat_capacity_1: f64, // J / (m^2 K)
//! heat_capacity_2: f64,
//! heat_exchange: f64, // W / (m^2 K)
//! }
//!
//! impl IVP<f64, Vector2<f64>> for TwoBoxModel {
//! fn calculate_dy_dt(
//! &self,
//! t: f64,
//! input_state: &InputState,
//! y: &Vector2<f64>,
//! dy_dt: &mut Vector2<f64>,
//! ) {
//! // Get forcing from input state
//! let erf = input_state.get_scalar_window("Effective Radiative Forcing").at(t);
//!
//! // Calculate temperature derivatives
//! let (t1, t2) = (y[0], y[1]);
//! dy_dt[0] = (erf - self.heat_exchange * (t1 - t2)) / self.heat_capacity_1;
//! dy_dt[1] = self.heat_exchange * (t1 - t2) / self.heat_capacity_2;
//! }
//! }
//!
//! // Solve the IVP
//! fn solve_component(
//! component: Arc<TwoBoxModel>,
//! input_state: &InputState,
//! y0: Vector2<f64>,
//! t_start: f64,
//! t_end: f64,
//! ) -> Vector2<f64> {
//! let builder = IVPBuilder::new(component, input_state, y0);
//! let mut solver = builder.to_rk4(t_start, t_end, 0.1);
//! let result = solver.integrate();
//! get_last_step(&result, t_end).clone()
//! }
//! ```
use crateInputState;
use crateTime;
use Allocator;
use ;
use ;
use *;
use Arc;
/// Threshold for validating that the solver reached the expected end time.
/// If the final time differs from the expected time by more than this value,
/// the solver result is considered invalid.
const T_THRESHOLD: Time = 5e-3;
/// Extracts the final state vector from an ODE solver result.
///
/// This function validates that the solver reached the expected end time
/// (within a small threshold) and returns the state vector at the final timestep.
///
/// # Arguments
///
/// * `results` - The solver result containing time and state history
/// * `t_expected` - The expected end time of the integration
///
/// # Panics
///
/// Panics if:
/// - The result contains fewer than 2 timesteps
/// - The final time differs from `t_expected` by more than the threshold (5e-3)
/// Trait for components that define an ODE system.
///
/// Implement this trait to define the differential equations for a component
/// that will be solved numerically. The trait is generic over the time type `T`
/// and the state vector type `S`.
///
/// # Type Parameters
///
/// * `T` - The time type (typically `f64`)
/// * `S` - The state vector type (e.g., `nalgebra::Vector2<f64>` for a 2-variable system)
///
/// # Example
///
/// ```ignore
/// use nalgebra::Vector1;
/// use rscm_core::ivp::IVP;
/// use rscm_core::component::InputState;
///
/// struct SimpleDecay {
/// decay_rate: f64, // per year
/// }
///
/// impl IVP<f64, Vector1<f64>> for SimpleDecay {
/// fn calculate_dy_dt(
/// &self,
/// _t: f64,
/// _input_state: &InputState,
/// y: &Vector1<f64>,
/// dy_dt: &mut Vector1<f64>,
/// ) {
/// // Exponential decay: dy/dt = -k * y
/// dy_dt[0] = -self.decay_rate * y[0];
/// }
/// }
/// ```
/// Builder for constructing and solving initial value problems.
///
/// `IVPBuilder` wraps a component that implements [`IVP`] and provides methods
/// to construct ODE solvers. It connects the component's differential equations
/// to the numerical integration routines from the `ode_solvers` crate.
///
/// # Type Parameters
///
/// * `'a` - Lifetime of the input state reference
/// * `C` - The component type (must implement [`IVP`])
/// * `S` - The state vector type
///
/// # Example
///
/// ```ignore
/// use std::sync::Arc;
/// use nalgebra::Vector2;
/// use rscm_core::ivp::{IVPBuilder, get_last_step};
///
/// // Create the builder
/// let builder = IVPBuilder::new(
/// Arc::new(my_component),
/// &input_state,
/// Vector2::new(initial_temp_1, initial_temp_2),
/// );
///
/// // Create an RK4 solver and integrate
/// let mut solver = builder.to_rk4(t_start, t_end, step_size);
/// let result = solver.integrate();
/// let final_state = get_last_step(&result, t_end);
/// ```