Transformer emergency loading — overload only with every fan running
A large power transformer dies slowly and then suddenly: every hour spent above its thermal rating cooks the paper insulation (IEEE C57.91 calls it loss-of-life), until one hot afternoon the winding faults and forty tons of mineral oil have an ignition source. Grid operators DO run transformers past nameplate during emergencies — that is what the ONAF cooling stages are for: first fan bank buys you 115%, second bank 125%, but only while the fans are actually spinning. The classic failure is the quiet one: a fan contactor died months ago, the SCADA point was never wired, and the bank takes emergency load with cooling it does not have. Here the cooling stage is a state machine and the loading ceiling is derived FROM the state: `emergency_load` is a state reachable only from `fans_full` — soma verify proves overload cannot be entered with idle fans. The MVA slot's invariant is the absolute thermal wall at 125.
A Soma program from the energy part of the verified corpus: 134 lines, cells Transformer, OverloadTests. It passes soma check with soma 3.2.4.
How it is built
The cell Transformer declares 6 signals in its face: start_fan_stage(xf: String) -> Map, enter_emergency(xf: String) -> Map, end_emergency(xf: String) -> Map, apply_mva(xf: String, mva: Int) -> Map, cooling_state(xf: String) -> String and mva_of(xf: String) -> Int; keeps 1 memory slot: bank_mva (Map<String, Int>, persistent and consistent) under the invariant bank_mva >= 0 && bank_mva <= 125. Its state machine cooling starts in onan and allows 6 transitions: onan -> fans_stage1, fans_stage1 -> fans_full, fans_full -> emergency_load, emergency_load -> fans_full, fans_full -> fans_stage1 and fans_stage1 -> onan. Handlers: stage_ceiling (line 49), start_fan_stage (line 56), enter_emergency (line 65), end_emergency (line 73), apply_mva (line 83), cooling_state (line 95) and mva_of (line 96).
The file also holds 1 test cell (OverloadTests) with 19 assertions, run by soma test.
The source
transformer_overload.cell
// ============================================================================
// TRANSFORMER EMERGENCY LOADING — overload only with every fan running
// ============================================================================
//
// A large power transformer dies slowly and then suddenly: every hour
// spent above its thermal rating cooks the paper insulation (IEEE C57.91
// calls it loss-of-life), until one hot afternoon the winding faults and
// forty tons of mineral oil have an ignition source. Grid operators DO
// run transformers past nameplate during emergencies — that is what the
// ONAF cooling stages are for: first fan bank buys you 115%, second bank
// 125%, but only while the fans are actually spinning. The classic
// failure is the quiet one: a fan contactor died months ago, the SCADA
// point was never wired, and the bank takes emergency load with cooling
// it does not have.
//
// Here the cooling stage is a state machine and the loading ceiling is
// derived FROM the state: `emergency_load` is a state reachable only from
// `fans_full` — soma verify proves overload cannot be entered with idle
// fans. The MVA slot's invariant is the absolute thermal wall at 125.
// ============================================================================
cell Transformer {
face {
signal start_fan_stage(xf: String) -> Map
signal enter_emergency(xf: String) -> Map
signal end_emergency(xf: String) -> Map
signal apply_mva(xf: String, mva: Int) -> Map
signal cooling_state(xf: String) -> String
signal mva_of(xf: String) -> Int
}
memory {
// loading in MVA on a 100 MVA bank; 125 is the absolute thermal wall
bank_mva: Map<String, Int> [persistent, consistent]
invariant bank_mva >= 0 && bank_mva <= 125
}
state cooling {
initial: onan // natural circulation only
onan -> fans_stage1 // first fan bank proven spinning
fans_stage1 -> fans_full // second fan bank proven spinning
fans_full -> emergency_load // ONLY here may load exceed 100 MVA
emergency_load -> fans_full // emergency over — back below 100 first
fans_full -> fans_stage1 // a fan bank lost: shed to its ceiling
fans_stage1 -> onan
}
// ceiling follows the PROVEN cooling, never an assumed one
on stage_ceiling(st: String) {
if st == "emergency_load" { return 125 }
if st == "fans_full" { return 100 }
if st == "fans_stage1" { return 85 }
return 70 // onan: natural circulation
}
on start_fan_stage(xf: String) {
let st = get_status(xf)
let target = "fans_stage1"
if st == "fans_stage1" { target = "fans_full" }
let r = try { transition(xf, target) }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "state", get_status(xf))
}
on enter_emergency(xf: String) {
let r = try { transition(xf, "emergency_load") }
if r.error != () {
return map("ok", false, "reason", "cooling interlock: {r.error}")
}
return map("ok", true, "state", get_status(xf))
}
on end_emergency(xf: String) {
if mva_of(xf) > 100 {
return map("ok", false, "reason",
"shed below 100 MVA before leaving emergency cooling")
}
let r = try { transition(xf, "fans_full") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "state", get_status(xf))
}
on apply_mva(xf: String, mva: Int) {
if mva < 0 { return map("ok", false, "reason", "load cannot be negative") }
let ceiling = stage_ceiling(get_status(xf))
if mva > ceiling {
return map("ok", false, "reason",
"{mva} MVA exceeds the {ceiling} MVA ceiling for state {get_status(xf)}")
}
let w = try { bank_mva.set(xf, mva) }
if w.error != () { return map("ok", false, "reason", "thermal wall: {w.error}") }
return map("ok", true, "mva", mva, "ceiling", ceiling)
}
on cooling_state(xf: String) { return get_status(xf) }
on mva_of(xf: String) { return bank_mva.get(xf) ?? 0 }
}
cell test OverloadTests {
rules {
// natural circulation: 70 MVA is the line
assert apply_mva("x1", 65).ok == true
assert apply_mva("x1", 80).ok == false
// THE DEAD-FAN OVERLOAD: emergency loading with no fans proven.
// emergency_load is not reachable from onan — the transition raises.
assert_fails transition("x1", "emergency_load")
assert enter_emergency("x1").ok == false
// prove the fans, stage by stage; ceilings follow
assert start_fan_stage("x1").ok == true // -> fans_stage1
assert apply_mva("x1", 85).ok == true
assert apply_mva("x1", 95).ok == false
assert start_fan_stage("x1").ok == true // -> fans_full
assert apply_mva("x1", 100).ok == true
// still no overload outside the declared emergency state
assert apply_mva("x1", 110).ok == false
assert enter_emergency("x1").ok == true
assert apply_mva("x1", 120).ok == true
// the ABSOLUTE thermal wall: 125 MVA, in any state, ever
assert apply_mva("x1", 130).ok == false
assert_fails bank_mva.set("x1", 126)
assert_fails bank_mva.set("x1", -1)
// leaving emergency requires shedding below nameplate first
assert end_emergency("x1").ok == false
assert apply_mva("x1", 95).ok == true
assert end_emergency("x1").ok == true
assert cooling_state("x1") == "fans_full"
}
}What the toolchain says
$ soma check transformer_overload.cell
✓ All checks passed.$ soma test transformer_overload.cell
test OverloadTests ...
✓ assert apply_mva("x1", 65).ok == true
✓ assert apply_mva("x1", 80).ok == false
✓ assert_fails transition("x1", "emergency_load") — raised invalid transition: onan → emergency_load. Current state: 'onan'. Valid targets: [fans_stage1]
✓ assert enter_emergency("x1").ok == false
✓ assert start_fan_stage("x1").ok == true
✓ assert apply_mva("x1", 85).ok == true
✓ assert apply_mva("x1", 95).ok == false
✓ assert start_fan_stage("x1").ok == true
✓ assert apply_mva("x1", 100).ok == true
✓ assert apply_mva("x1", 110).ok == false
✓ assert enter_emergency("x1").ok == true
✓ assert apply_mva("x1", 120).ok == true
✓ assert apply_mva("x1", 130).ok == false
✓ assert_fails bank_mva.set("x1", 126) — raised memory invariant violated on 'bank_mva': bank_mva >= 0 && bank_mva <= 125 — rejected write of 126 (key "x1"); the slot is unchanged
✓ assert_fails bank_mva.set("x1", -1) — raised memory invariant violated on 'bank_mva': bank_mva >= 0 && bank_mva <= 125 — rejected write of -1 (key "x1"); the slot is unchanged
✓ assert end_emergency("x1").ok == false
✓ assert apply_mva("x1", 95).ok == true
✓ assert end_emergency("x1").ok == true
✓ assert cooling_state("x1") == "fans_full"
19 tests: 19 passed, 0 failed$ soma verify transformer_overload.cell
Verifying transformer_overload.cell...
State machine 'cooling': 4 states, initial 'onan'
States: [emergency_load, fans_full, fans_stage1, onan]
✓ 4 states, 6 transitions
✓ all states reachable from 'onan'
✓ reactive machine: no terminal state, and every reachable state can return to 'onan'
✓ no deadlocks
✓ liveness: every reachable state can return to 'onan' (reactive machine)
⚠ dynamic transition targets: handler `start_fan_stage` uses a dynamic (computed) transition target — the state these handlers move to is computed at run time (from input or an LLM answer), so the machine's safety is checked at run time for them, not proven
✓ termination: all 7 handlers structurally terminate
⚠ refinement (partial — dynamic targets fall back to runtime check): handler `start_fan_stage` calls transition() with a computed target — which edge it takes is checked at run time (invalid_transition), not proven; write the target as a literal state to have it verified
✓ refinement (partial — dynamic targets fall back to runtime check): handler `start_fan_stage` ⟶ <dynamic>
✓ refinement (partial — dynamic targets fall back to runtime check): handler `enter_emergency` ⟶ {emergency_load}
✓ refinement (partial — dynamic targets fall back to runtime check): handler `end_emergency` ⟶ {fans_full}
· transition sources: `enter_emergency`, `end_emergency` call transition(id, target) without declaring the source — the runtime refuses a move the graph forbids, but the proof does not see where the handler starts; `transition(id, from, to)` names the edge (checked by `soma check`, refused at run time from any other state)
Transformer/invariants:
✓ invariant bank_mva >= 0 && bank_mva <= 125 — writer 'apply_mva' proven by induction (writes mva)
10 passed, 2 warnings, 0 failures
Temporal properties for 'cooling':
✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK