Reactivity Rocket Project
  • Home
  • Overview
  • Engineering
    • Interface Control Document
    • Safety Case
    • Computational Engineering Static Test
    • Phase Transition Gate (YAML)
  • Teams
    • Rocket Chemistry
    • Materials
    • Manufacturing
    • Computational Engineering
    • Heat Exchanger
  • Class Experiments
    • Class Experiments — Overview
    • Lab 1 · H₂O₂ Rates Calibration
    • Lab 2 · Ethanol & Methane Calorimetry
  • Assessment
    • Lab Report Scaffold
    • Syllabus Mapping
  • Lessons
    • W1L1 — Project Brief Slides

On this page

  • 1 Conservation laws — invariants
  • 2 State envelope — guard predicates
  • 3 Action legs — what may happen
  • 4 Forbidden transitions — explicit non-actions
  • 5 Lean proof obligations
  • 6 Rust test layer
  • 7 What this gives the prac

Prac Safety Case as a Governed Process — VSL Sketch

Helios-style four-layer specification of the H₂O₂ bench prac as a chemical reactor

Published

May 12, 2026

Year 10 Chemistry · Reactivity Rocket Project · Engineering Reasoning · Simulation companion

A Helios-style specification of the KMnO₄ + H₂O₂ Year 10 prac, treating the bench setup as an instance of a chemical reactor and applying the four-layer assurance pattern. This is illustrative — the formal grammar is approximate and intended to make the engineering reasoning concrete rather than to compile.

1 Conservation laws — invariants

These hold at every timestep of the physical process and at every state transition in the specification.

invariant mass_balance:
  ∀ t. Σ_species mass(species, t) = mass_total_initial

invariant energy_balance:
  ∀ t. ΔU_internal(t) + Q_lost(t) + W_steam(t)
        = Σ_reactions r(t) · ΔH_reaction · dt

invariant species_balance_h2o2:
  ∀ t. n_H2O2(t) = n_H2O2(0) - ∫₀ᵗ r(τ) · V dτ
       where r(τ) = k(T(τ)) · [KMnO4](τ) · [H2O2](τ)
                  · A · exp(-Ea / (R · T(τ)))

invariant stoichiometry_o2:
  ∀ t. n_O2_produced(t) = 0.5 · (n_H2O2(0) - n_H2O2(t))

2 State envelope — guard predicates

The reactor is only permitted to operate inside these bounds. Any actuator that would push state outside the envelope is denied.

predicate safe_temperature:
  T_peak(reactor, t) < T_critical - ΔT_spread_max
                                  - ΔT_measurement
                                  - ΔT_uncertainty
  where T_critical = 343.15 K        // 70 °C, autocatalytic onset
        ΔT_spread_max = 15.0 K       // unstirred test tube bound
        ΔT_measurement = 3.0 K       // probe placement + reading
        ΔT_uncertainty = 7.0 K       // batch and concentration variation

predicate safe_concentration:
  [H2O2]_initial ≤ 0.06 (w/w)        // 6% maximum
  ∧ [KMnO4]_initial ≤ 0.01 mol/L

predicate adequate_venting:
  V_vessel_headspace ≥ 5 · V_O2_stoichiometric(T_max)

predicate stirring_active:
  ω_stir(t) ≥ ω_min  ∀ t ∈ [t_initiation, t_completion]
  where ω_min sufficient to maintain ΔT_spread < ΔT_spread_max

predicate operator_present:
  supervisor_in_room(t) = true  ∀ t ∈ [t_setup, t_disposal]

3 Action legs — what may happen

In Helios terms, every state transition is an ActionLeg with explicit pre- and post-conditions. The full set for this prac is small:

action_leg add_kmno4:
  precondition:
    state = reagents_prepared
    ∧ safe_temperature ∧ safe_concentration
    ∧ adequate_venting ∧ stirring_active
    ∧ operator_present
  effect:
    [KMnO4](t⁺) = [KMnO4]_target
    state → reacting
  postcondition:
    safe_temperature still holds

action_leg sample_temperature:
  precondition:
    state ∈ {reacting, completing}
  effect:
    record(T_reading, t)
    if T_reading > 45 °C then emit warning_event

action_leg sample_volume:
  precondition:
    state ∈ {reacting, completing}
  effect:
    record(V_O2_reading, t)

action_leg emergency_quench:
  precondition:
    warning_event_active ∨ T_reading > 50 °C
  effect:
    transfer_to_cold_bath
    state → quenched
  postcondition:
    T_bulk → T_ambient within Δt_quench

action_leg complete:
  precondition:
    dV_O2/dt < 0.3 mL/s for 30 s
  effect:
    state → completed
    disposal_required = true

Notice that emergency_quench is itself in the action set with formal pre- and post-conditions. Safety actions are first-class, not afterthoughts.

4 Forbidden transitions — explicit non-actions

Equally important: the actions that must never occur, with their refusal conditions.

forbidden cap_vessel:
  ∀ t ∈ [t_setup, t_completion]. ¬ vessel_sealed(t)

forbidden increase_concentration_during_run:
  ∀ t > t_initiation. d[H2O2]/dt ≤ 0 ∧ d[KMnO4]/dt ≤ 0

forbidden remove_from_bath_while_hot:
  ¬ (T_bulk > T_ambient + 5 K ∧ remove_from_bath_action)

forbidden add_organic_material:
  ∀ t. organic_introduction(t) = ∅

5 Lean proof obligations

The conservation invariants compose into theorems the prac is designed to satisfy. These are the safety theorems that the entire engineering controls assembly is meant to make true:

theorem safety_temperature_bound :
  ∀ (s : ReactorState),
    s.initial_concentration ≤ 0.06 →
    s.stirring = active →
    s.heat_loss_coefficient ≥ h_min →
    ∀ t, s.T_peak(t) < T_critical

theorem completion_terminates :
  ∀ (s : ReactorState),
    s.initial_concentration ≤ 0.06 →
    ∃ t_final, t_final < t_max ∧
      s.completion_predicate(t_final) = true

theorem quench_is_effective :
  ∀ (s : ReactorState),
    s.T_peak < T_critical →
    quench_action(s).T_bulk(t + Δt_quench) ≤ T_ambient + 5 K

The first theorem says: if you start within the concentration envelope and maintain stirring and heat loss, peak temperature never reaches the autocatalytic threshold. This is what makes the prac safe by construction rather than by hope.

6 Rust test layer

The simulation models you have are the executable manifestation of this specification. The 0-D model is an instance of the energy-balance invariant; the 2D model is an instance of the spread-bound predicate. Property-based tests in Rust would generate random valid input states (concentrations, volumes, temperatures, stirring intensities within envelope) and assert:

proptest! {
  #[test]
  fn safe_envelope_implies_safe_peak(
    h2o2_pct in 1.0..6.0_f64,
    kmno4_mM in 1.0..10.0_f64,
    vol_mL in 10.0..200.0_f64,
    T_start_C in 5.0..30.0_f64,
    stir in 0.5..1.0_f64,
  ) {
    let result = simulate(h2o2_pct, kmno4_mM, vol_mL, T_start_C, stir);
    prop_assert!(result.T_peak_C < 70.0);
    prop_assert!(result.completed);
    prop_assert!(result.O2_volume_mL > 0.0);
  }

  #[test]
  fn unsafe_envelope_caught(
    h2o2_pct in 20.0..50.0_f64,
    kmno4_mM in 50.0..100.0_f64,
    vol_mL in 50.0..500.0_f64,
    stir in 0.0..0.2_f64,
  ) {
    let result = simulate(h2o2_pct, kmno4_mM, vol_mL, 25.0, stir);
    let envelope_check = check_safety_envelope(h2o2_pct, kmno4_mM, vol_mL, stir);
    prop_assert!(!envelope_check.passes);
  }
}

7 What this gives the prac

Three useful artifacts emerge from this exercise:

The first is a written safety case rather than a list of safety rules. The case is: given conservation laws, given the kinetic constants, given the envelope, the safety theorems hold. The rules (“wear glasses”, “use the water bath”) are derived from the theorems, not asserted independently.

The second is traceability. If a student asks “why 6% not 9%?”, the answer is in the envelope predicate, which traces to the temperature bound, which traces to the kinetic constants and the conservation laws. The prac’s design decisions are auditable.

The third is transferability. The same specification pattern, with different numbers, describes industrial H₂O₂ handling, propellant systems, and pulp-bleaching plants. The Year 10 prac is a small instance of a structure that scales to defence-grade safety cases. Students who internalise this pattern have learned something about how engineering works, not just something about one reaction.

This is also why the Helios architectural pattern — typed graph as engine, ActionLegs as trust primitive, governed transitions — applies as cleanly to a chemistry prac as to a software system. Both are processes with conservation laws, envelopes, and consequences for envelope violation. The reactor and the runtime are isomorphic.