soma.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.

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.

Generated from soma describe on this file.

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

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ 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

Builtins used

get_status, map, transition

Source file: /corpus/energy/transformer_overload.cell · examples/corpus/energy/transformer_overload.cell