Week 3 Reading: State Transitions and Trajectories
Predict a trajectory from one starting point
The core task is to approximate F_Δt: current concentration and a held interval input → next concentration. Given c₀ and T₀,…,T₁₄₉, recursively compute 150 future states.
Fix Δt = 0.2 min, τ and C_Af within each trajectory. T changes between intervals. The ideal mixed state contains all three species; thermal dynamics are omitted.
Work through the balances, interval solution, data construction, and two evaluation loops before the optional sequence models.
Derive the concentration balances
For constant volume V and flow Q, τ = V/Q. Divide each species inventory balance by V. A enters at concentration C_Af and reacts into B; B reacts into C.
Adding the three equations cancels both reaction terms, giving dS/dt = (C_Af − S)/τ. Therefore S(t) = C_Af + [S(0) − C_Af] exp(−t/τ) under fixed feed.
S(0) = C_Af gives a constant sum. An arbitrary initial sum instead relaxes toward feed.
Derive the interval solution
Set z = c − c∗, where Mc∗ + b = 0. Then dz/dt = Mz. For fixed inputs within the interval, z(t+Δt) = exp(MΔt)z(t).
This formula remains valid when T changes at a boundary: compute a new M and c∗ for the next interval, retaining the boundary state.
A worked first transition
At 330 K, τ = 5 min and feed = 1.2 mol/L, a = 1/τ + k₁ = 0.4 and b = 1/τ + k₂ = 0.3 min⁻¹. The steady composition is c∗ = [0.6, 0.4, 0.2] mol/L. With Δt = 0.2 min and c₀ = [1.2, 0, 0]:
C_C,1 = S₁ − C_A,1 − C_B,1. This gives [1.153870, 0.045672, 0.000458] mol/L.
Forward Euler gives [1.152, 0.048, 0]. It differs because the rates change during the interval. When a = b, the fraction’s limit is Δt exp(−aΔt).
Turn a trajectory into learning pairs
One trajectory has states of shape (151, 3) and controls of shape (150, 3). Split by trajectory, then stack its rows:
inputs = np.column_stack((states[:-1], controls))
increments = states[1:] - states[:-1]
# inputs: (150, 6); increments: (150, 3)
x_mean = train_inputs.mean(axis=0)
x_scale = np.maximum(train_inputs.std(axis=0), 1e-8)
d_mean = train_increments.mean(axis=0)
d_scale = np.maximum(train_increments.std(axis=0), 1e-8)
The first input row contains c₀ and the first interval control. The last contains c₁₄₉ and the last control; its label is c₁₅₀. Test statistics do not enter scaling.
Restore the increment and evaluate both loops
# Reference current states at every step
one_step = dynamic_step(model, scaling,
states[:-1], controls)
# Only the initial state is supplied
recursive = rollout(model, scaling, controls, states[0])
err_one = one_step - states[1:]
err_roll = recursive[1:] - states[1:]
Restore Δc before adding it to c. Flatten all noninitial times across test trajectories for overall component RMSE; keep the horizon axis for RMSE_j,h. Both loops use the same future inputs.
Derive the error recurrence
If the learned map is L_k-Lipschitz between c_k and ĉ_k, then:
For constant L and local error bound ε, repeated substitution gives:
L < 1 limits this bound to ε/(1−L) when e₀ = 0; L = 1 gives hε. The sensitivity and error bounds must hold along the relevant states. Test RMSE alone is not that bound.
Optional: train through several transitions
A multi-step loss begins at a reference state, then feeds predicted states forward for H steps and penalizes their trajectory error. Use component scaling consistently.
Gradients pass through every unrolled transition. This can expose the model to its own states during training, but increases memory and may make training harder. Construct windows inside train trajectories only; choose H with validation trajectories.
The stored core experiment uses one-step training. A multi-step comparison is an extension to run, not an extra reported result.
Exercises with short answer checks
- Why 151 states? A 30 min horizon has 150 intervals of 0.2 min, plus its initial state.
- How many parameters? 224 + 1056 + 99 = 1379.
- What if S₀ ≠ feed? The reference sum evolves as feed + (S₀−feed)exp(−t/τ); a constant-sum check would be wrong.
- Error at h = 10 in the scalar example? 0.001(1−0.9¹⁰)/0.1 ≈ 0.006513.
- Can we run the model at Δt = 1 min by changing a variable? Its learned output still represents 0.2 min. Apply five steps or train a map for the new interval.
- What if a reference state is injected halfway through rollout? That is a forecast reset; report the reset times and evaluate the resulting protocol separately.
Optional sequence models and original sources
With incomplete observations, an RNN/LSTM hidden state can summarize available history. An encoder–decoder can convert observed history into a future sequence conditioned on the prescribed controls. Keep future measurements out of the encoder.
- Sutskever, Vinyals & Le (2014), Sections 1–2: sequence encoding and decoding. Their model is for translation.
- PyTorch RNN and LSTM: recurrent updates, inputs, hidden states, and outputs.
- SciPy matrix exponential: an alternative implementation of exp(MΔt).
The CSTR derivations and worked exercises here are original course materials.