Calculations are named, type-checked OCL computation primitives. They
are referenced from form fields via the
calculation
property and the
calculation_bindings
block.
Without a calculation registry, OCL formulas are inlined wherever needed. For any standard with non-trivial calculations (OIML R 60 has ~30), this leads to:
The calculation registry solves all five problems.
| Construct | Relationship |
|---|---|
symbol |
Calculations reference symbols in their
expression
. |
form |
Form fields invoke calculations via `calculation` and `calculation_bindings`. |
measurement |
Calculations are similar to `measurement DERIVED` but with typed inputs and named reference. Use `measurement DERIVED` for inline shorthand; use `calculation` for reusable typed primitives. |
validate_measurement |
A `validate_measurement` expression is an inline anonymous calculation with boolean output. It can be expressed as a named calculation when reuse is desired. |
Calculation identifiers should be verbs or verb phrases that describe
the operation:
loadCellError
,
mean
,
repeatabilityError
,
humidityCHmin
. They are PascalCase by convention.
A calculation declaration uses the
calculation
keyword followed by an
identifier and a brace-delimited body.
calculation <CalculationID> {
name "Display name"
description "..."
inputs {
<param> : <type> {
unit "<unit>"
description "..."
}
}
output : <type> {
unit "<unit>"
}
expression "ocl{...}"
reference { <ReferenceID> }
}
| Property | Required | Description |
|---|---|---|
name |
yes | Display name. |
description |
no | Free-text description. |
inputs |
no | Typed input parameters. Zero or more. |
output |
yes | Typed output. |
expression |
yes | OCL expression that computes the output from the inputs. |
reference |
no | Normative references for traceability. |
calculation loadCellError {
name "Load cell error"
description "Signed difference between observed and reference indication, divided by conversion factor."
inputs {
avgIndication : number { unit "counts" }
referenceIndication : number { unit "counts" }
conversionFactor : number { unit "counts/v" }
}
output : number { unit "v" }
expression "ocl{(avgIndication - referenceIndication) / conversionFactor}"
reference { R60-3-2-1-2-2 }
}
calculation repeatabilityError {
name "Repeatability error"
description "Max minus min of a list of values."
inputs {
values : list<number> { unit "1" }
}
output : number { unit "1" }
expression "ocl{values->max() - values->min()}"
}
calculation creep {
name "Creep error"
description "Change in indication from initial D_max loading, divided by conversion factor."
inputs {
indicationAtTime : number { unit "counts" }
initialIndication : number { unit "counts" }
conversionFactor : number { unit "counts/v" }
}
output : number { unit "v" }
expression "ocl{(indicationAtTime - initialIndication) / conversionFactor}"
reference { R60-3-2-1-5 }
}
A calculation may have zero inputs. This represents a typed constant:
calculation halfVFixed {
name "Fixed 0.5 v threshold"
description "Dead load output return limit per R 60-1 §5.5.2."
output : number { unit "v" }
expression "ocl{0.5}"
reference { R60-1-5-5-2 }
}
Calculation inputs and outputs are typed parameters with optional unit annotations and descriptions.
inputs {
<param> : <type> {
unit "<unit>"
description "..."
default <value>
}
}
output : <type> {
unit "<unit>"
}
Output is singular (one per calculation).
At parse time, the parser validates:
At parse time, the parser validates:
Inputs and outputs can be list-typed:
inputs {
values : list<number> { unit "counts" }
threshold : number { unit "v" }
}
Use list types for aggregations (mean, max, min, sum, count).
If an input has a
default
value, the binding is optional at the call
site. Otherwise, all inputs must be bound.
inputs {
required_param : number { unit "v" }
optional_param : number { unit "1" default 1.0 }
}
At the call site:
calculation_bindings {
required_param: some_field // required
// optional_param omitted → uses default 1.0
}
Calculation
expression
values are written in a subset of Object
Constraint Language (OCL). This section documents the supported subset.
expression "ocl{42}" // numeric literal
expression "ocl{3.14}"
expression "ocl{true}"
expression "ocl{false}"
expression "ocl{\"hello\"}" // string literal (escaped quotes)
Identifiers inside an OCL expression reference either:
expression "ocl{(avgIndication - referenceIndication) / conversionFactor}"
// ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
// inputs
expression "ocl{[E_L] <= [MPE]}"
// ^^^^ ^^^^
// symbols
Expr = Expr "+" Expr
| Expr "-" Expr
| Expr "*" Expr
| Expr "/" Expr
| Expr "mod" Expr
| Expr "div" Expr
| "-" Expr
| Atom ;
Expr = Expr "<" Expr
| Expr "<=" Expr
| Expr ">" Expr
| Expr ">=" Expr
| Expr "=" Expr
| Expr "<>" Expr ;
Expr = Expr "and" Expr
| Expr "or" Expr
| Expr "xor" Expr
| "not" Expr
| Expr "implies" Expr ;
expression "ocl{runs.indication}" // array of indications
expression "ocl{runs->first().indication}" // first run's indication
expression "ocl{runs->last().time}" // last run's time
Expr = Expr "->" "size" "(" ")"
| Expr "->" "sum" "(" ")"
| Expr "->" "mean" "(" ")"
| Expr "->" "min" "(" ")"
| Expr "->" "max" "(" ")"
| Expr "->" "count" "(" ")"
| Expr "->" "first" "(" ")"
| Expr "->" "last" "(" ")"
| Expr "->" "forAll" "(" Var "|" Expr ")"
| Expr "->" "exists" "(" Var "|" Expr ")"
| Expr "->" "collect" "(" Var "|" Expr ")"
| Expr "->" "select" "(" Var "|" Expr ")"
| Expr "->" "reject" "(" Var "|" Expr ")" ;
Expr = "abs" "(" Expr ")"
| "sqrt" "(" Expr ")"
| "pow" "(" Expr "," Expr ")"
| "min" "(" Expr "," Expr ")"
| "max" "(" Expr "," Expr ")"
| "mean" "(" Expr { "," Expr } ")" ;
Expr = "if" Expr "then" Expr "else" Expr "endif" ;
expression "ocl{if values->size() > 0 then values->sum() / values->size() else 0 endif}"
The following OCL features are NOT required for Primmel 1.0:
These may be added in future versions if use cases emerge.
When a form field references a calculation via the
calculation
property, the
calculation_bindings
block maps the calculation's named
inputs to form fields or sub-paths.
field error_EL : number {
unit "v"
measurement_method computed
calculation loadCellError
calculation_bindings {
avgIndication: average_indication
referenceIndication: reference_indication
conversionFactor: f
}
}
Each binding entry is
<input_name>: <path_expression>
. The
<input_name>
must match a declared input on the calculation.
A path expression is
ID { "." ID }
— one or more identifiers joined
by dots.
At parse time, the parser validates:
Units are compatible when:
Otherwise the binding is rejected.
If a calculation input has no binding and no default, the parser emits an error:
calculation loadCellError input "conversionFactor" has no binding
and no default
If an input has a
default
, the binding is optional. The default is
used when no binding is provided.
Given the calculation:
calculation loadCellError {
inputs {
avgIndication : number { unit "counts" }
referenceIndication : number { unit "counts" }
conversionFactor : number { unit "counts/v" }
}
output : number { unit "v" }
expression "ocl{(avgIndication - referenceIndication) / conversionFactor}"
}
And the form:
field f : number {
unit "counts/v"
measurement_method declared
}
field average_indication : number {
unit "counts"
measurement_method derived
derivation "ocl{mean(runs.indication)}"
}
field reference_indication : number {
unit "counts"
measurement_method computed
calculation referenceIndication
calculation_bindings { /* ... */ }
}
field error_EL : number {
unit "v"
measurement_method computed
calculation loadCellError
calculation_bindings {
avgIndication: average_indication
referenceIndication: reference_indication
conversionFactor: f
}
}
The parser:
1. Looks up
loadCellError
— found
2. Validates bindings:
avgIndication
,
referenceIndication
,
conversionFactor
all declared inputs
3. Validates types: all
number
(matches)
4. Validates units:
counts
/
counts
/
counts-v
(matches
counts
/
counts
/
counts-v
)
5. Field
error_EL
gets type
number
, unit
v
from calculation output
Primmel ships with a standard library of calculations available to all models without explicit declaration. These correspond to OCL operators and provide common aggregates.
| Calculation | Signature | Description |
|---|---|---|
size |
list<T> → integer |
Number of items in the list. |
sum |
list<number> → number |
Sum of all items. |
mean |
list<number> → number |
Arithmetic mean (sum / size). |
min |
list<number> → number |
Minimum value. |
max |
list<number> → number |
Maximum value. |
count |
list<T> → integer |
Same as
size
(alias). |
first |
list<T> → T |
First item. |
last |
list<T> → T |
Last item. |
These are accessed via the
->
operator on collections:
expression "ocl{runs.indication->mean()}"
expression "ocl{runs.indication->max() - runs.indication->min()}"
| Function | Signature | Description |
|---|---|---|
abs |
number → number |
Absolute value. |
sqrt |
number → number |
Square root. |
pow |
(number, number) → number |
Exponentiation. |
min |
(number, number) → number |
Minimum of two values. |
max |
(number, number) → number |
Maximum of two values. |
These are accessed as function calls:
expression "ocl{abs(error_EL)}"
expression "ocl{pow(conversionFactor, 2)}"
| Quantifier | Signature | Description |
|---|---|---|
forAll |
(list<T>, T → boolean) → boolean |
True if predicate holds for every item. |
exists |
(list<T>, T → boolean) → boolean |
True if predicate holds for at least one item. |
collect |
(list<T>, T → U) → list<U> |
Map each item through a function. |
select |
(list<T>, T → boolean) → list<T> |
Filter items by predicate. |
reject |
(list<T>, T → boolean) → list<T> |
Inverse of
select
. |
expression "ocl{runs->forAll(r | r.indication <= threshold)}"
expression "ocl{runs->exists(r | r.indication > threshold)}"
expression "ocl{runs->collect(r | r.indication)->sum()}"
A model may declare its own calculation with the same name as a built-in. The model's declaration takes precedence within that model. A warning is emitted.
CalculationDecl = "calculation" ID "{" { CalculationProperty } "}" ;
CalculationProperty = "name" STRING
| "description" STRING
| "inputs" "{" { InputDecl } "}"
| "output" ":" TypeSpec "{" { OutputProperty } "}"
| "expression" STRING
| "reference" "{" { ID } "}" ;
InputDecl = ID ":" TypeSpec "{" { InputProperty } "}" ;
InputProperty = "unit" STRING
| "description" STRING
| "default" Value ;
OutputProperty = "unit" STRING
| "description" STRING ;
TypeSpec = BaseType [ "<" TypeSpec ">" ]
| "list" "<" TypeSpec ">" ;
BaseType = "number" | "integer" | "string" | "boolean" | "enum" | ID ;
As per the forms grammar (see formal-grammar-forms ).