Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

delivered energy on the heat generator

This notebook introduces deliveredEnergy, a calc on HeatGenerator bounded by a real efficiency constraint; after running it you can query the model’s own energy relation instead of recomputing it in Python.

The functional layer states an energy balance (ApplyHeat’s own balance constraint) but never characterizes how much energy a real conversion actually delivers: that characterization depends on a mechanism’s efficiency, a logical commitment, not something every solution shares. HeatGenerator, the logical carrier Chapter 6 built, is where that characterization belongs. This notebook builds it there for the first time, with efficiency as a bounded slot instead of an unconstrained input.

    attribute efficiency : DimensionOneValue;
    assert constraint efficiencyBounded {
        0.0 <= efficiency and efficiency <= 1.0
    }

efficiency is a bounded slot, not a free value: a conversion cannot deliver a negative fraction of the energy it is supplied, and it cannot deliver more than all of it. The bound is a real constraint on the model, checked the same way ApplyHeat’s own balance constraint is.

    calc deliveredEnergy {
        in power : ISQ::PowerValue;
        in duration : ISQ::DurationValue;
        return : ISQ::EnergyValue = power * duration * efficiency;
    }

deliveredEnergy characterizes what a heat generator actually delivers: a queried power and duration, scaled by its own efficiency. efficiency is not one of its parameters: it is this carrier’s own bound feature, resolved from whichever concrete usage the calc is queried through. power and duration stay free, queryable inputs, but there is no way to pass an arbitrary, unchecked efficiency into it: every efficiency value that flows through this relation is a real candidate’s own bound value, the same value efficiencyBounded checks.

rated, the candidate from Chapter 6 that already satisfies HeatGenerationReq, also needs a concrete efficiency to query. Its declaration is likewise reprinted in full below, with the new value added.

abstract part def HeatGenerator {
    perform action generateHeat : GenerateHeat;
    port energyIn : ~EnergyPort;
    attribute power : ISQ::PowerValue;
    attribute efficiency : DimensionOneValue;
    assert constraint efficiencyBounded {
        0.0 <= efficiency and efficiency <= 1.0
    }
    calc deliveredEnergy {
        in power : ISQ::PowerValue;
        in duration : ISQ::DurationValue;
        return : ISQ::EnergyValue = power * duration * efficiency;
    }
}
part rated : ResistanceCoil {
    attribute :>> efficiency = 0.7 [MeasurementReferences::one];
    assert satisfy heatGenerationReq by rated;
}

rated’s efficiency (0.7) is this chapter’s own assumed value for the worked example: nothing in Chapters 1-6 derives it. It sits alongside rated’s Chapter 6 power rating (800 W), the same assumed operating point this notebook queries below.

HeatGenerator::deliveredEnergy: kind='calcUsage', id='ToasterDemo::HeatGenerator::deliveredEnergy'

HeatGenerator::deliveredEnergy is now part of the loaded model, confirmed by model.find. A constraint that references an attribute the definition never declares fails to load. The negative control below asserts a bound on a name Widget does not have.

Negative control ok: bad.ok=False

The diagnostic reports an unresolved reference: undeclaredAttribute was never declared, so the constraint cannot type-check.

rated.deliveredEnergy(rated.power, 120 s) = 67200 [MeasurementReferences::one*SI::'kg⋅m²⋅s⁻²']

The model itself computes 67200 J (the OpenSysML runtime prints this as the unsimplified 67200 [MeasurementReferences::one*SI::'kg⋅m²⋅s⁻²'], dimensionally equivalent to J but not folded back to that symbol or freed of the identity MeasurementReferences::one factor -- a display quirk, DEFERRED.md D-033, not a modeling error) from rated’s own 800 W rating, read from the model rather than retyped, and this chapter’s own assumed 120 s duration, scaled by rated’s own 0.7 efficiency: a value the calc reads from rated, not one this notebook passes in as a free argument.

✓ constraint ToasterDemo::HeatGenerator::efficiencyBounded holds (on ToasterDemo::rated ID: 1) - observed by run

engine="run" evaluates the constraint against rated’s own bound value and reports whether it holds; it is claim evaluation, not model checking (it observes one candidate, not every possible one). The next cell probes the same bound against a candidate the real model never commits to, appended to a scratch copy of the loaded source, and confirms the bound flags it.

✗ constraint ToasterDemo::HeatGenerator::efficiencyBounded fails (on ToasterDemo::overEfficient ID: 1): condition evaluated to false: 0.0 <= efficiency and efficiency <= 1.0 - witnessed by run

efficiencyBounded fails, witnessed by evaluation against the 1.5 value. The model still loads overEfficient cleanly (assert constraint is not an eager, load-time validator); the bound only does its checking work when a caller actually evaluates it, the same way HeatGenerationReq’s own assert satisfy claims do. deliveredEnergy has no separate in efficiency parameter to bypass this with: overEfficient.deliveredEnergy(800.0 [SI::W], 120.0 [SI::s]) would compute a numerically equivalent 144000 J (again printed as the same unsimplified compound unit, D-033), a real violation of conservation, but only by reading overEfficient’s own value, which efficiencyBounded already flags. overEfficient exists only in this scratch copy, never in the committed model.

The definitions printed above loaded without error, and the two verify_constraint calls show the bound doing real work: holding for rated’s own value and failing for one outside it, both against the constraint the model itself declares.

Try the chapter exercise in exercises/ch07/exercise.ipynb: add a bounded transferEfficiency slot and a deliveredMass calc to the coffee maker’s WaterMover carrier, mirroring efficiency/deliveredEnergy exactly, and query it through model.eval.