Check that requirements hold
syside solver check answers a yes/no question: does a condition hold in every
situation the model allows? It reports which truth values the condition can take. If
false is impossible, the condition holds. If false is possible, the Solver hands
you a witness for it, a concrete assignment of values under which the condition
fails.
“Every situation the model allows” matters when the model leaves values open. If an attribute has no fixed value, the Solver does not pick one for you: the condition only holds if it holds no matter what that value turns out to be.
Checking the assertions written in the model
The most common way to use check is to write the conditions into the model as
assert constraint elements. Create pump.sysml:
package Pump {
private import ScalarValues::Integer;
part pump [1] {
attribute flowRate : Integer = 40;
attribute pressure;
}
assert constraint flowOk { pump.flowRate <= 50 }
assert constraint pressureOk { pump.pressure <= 10 }
}
Note the difference between the two attributes: flowRate is fixed at 40, but
pressure is left open. The model says the pump has a pressure, not what it is.
Run:
syside solver check -i pump.sysml
{
"result": {
"subject": "Pump",
"expression": "assertions[2]",
"determination": "determined",
"outcome": {
"shape": "assertions",
"assertions": [
{
"name": "Pump::flowOk",
"outcome": {
"shape": "attainable_set",
"attainable": {
"values": [
{
"value": "true",
"witness": {
"values": [["Pump::pump.flowRate", "40"]],
"value": "true"
}
}
],
"exhaustive": "true"
},
"unattainable": { "values": ["false"], "exhaustive": "true" }
}
},
{
"name": "Pump::pressureOk",
"outcome": {
"shape": "attainable_set",
"attainable": {
"values": [
{
"value": "false",
"witness": {
"values": [["Pump::pump.pressure", "11"]],
"value": "false"
}
},
{
"value": "true",
"witness": {
"values": [["Pump::pump.pressure", "0"]],
"value": "true"
}
}
],
"exhaustive": "true"
},
"unattainable": { "values": [], "exhaustive": "true" }
}
}
]
},
"causes": []
},
"warnings": [],
"fail_when": "false_attainable",
"exit_code": 1
}
flowOkholds:falseis unattainable, because 40 is at most 50, always.pressureOkcan fail:falseis attainable. Since the model never fixespressure, nothing stops it from being 11, and the witness offalsesays exactly that.trueis attainable too (a pressure of 0), so the condition is not necessarily violated either.
This is the check doing its job. A human reader might assume the pressure will be fine.
The Solver points out that the model, as written, does not guarantee it. To fix it you
would either give pressure a value in the model or constrain its range.
Changed in version 1.0.0: Before 1.0.0, each assertion carried a status (satisfied or violated) and a
counterexample instead of its own outcome. See Read the solver’s output.
Checking one assertion, or an ad-hoc condition
To check a single assertion by its qualified name:
syside solver check -i pump.sysml --name Pump::flowOk
To check a condition you have not written into the model, pass it inline with
--predicate:
syside solver check -i pump.sysml --predicate "Pump::pump::flowRate < 45"
With --predicate, result.outcome is the condition’s own attainable set over
true and false, rather than a list of assertions.
--name and --predicate cannot be combined. Without either, every in-model
assertion is checked.
A condition that reads an attribute written more than once by assign (in an action
that runs its assignments in sequence) has no single truth value, so --predicate
refuses it with exit code 2:
{
"error": { "code": "Lowering", "span": null, "message": "the checked predicate reads the written scalar(s) Valve::cycle.opening bare, which has no single truth value across snapshots; wrap it in `snapshots->forAll { in s; ... }` (must hold at EVERY snapshot) or `snapshots->exists { in s; ... }` (must hold at SOME snapshot)" },
"exit_code": 2
}
Write the condition into the model instead, as an assert constraint inside the
action that says which snapshots it covers:
package Valve {
private import ScalarValues::Integer;
private import ControlFunctions::*;
action cycle {
attribute opening : Integer;
assign opening := 0;
assign opening := 80;
assert constraint withinRange {
snapshots->forAll { in s; (s as cycle).opening <= 100 }
}
}
}
Checked as written, this assertion lists false as attainable and exits 1: by
default, the specification lets a snapshot between the two writes hold a value the
model never states. Add --alt-semantics sticky-writes (part of free, see
Add assumptions) to make each written value hold until the next write, and
withinRange then holds.
Which assertions are checked New in v1.0.0
Without --name or --predicate, check reports every assertion it finds in the
model:
assert constraintwith a body, at package level or inside a definition or usage.assert constrainttyped by a constraint definition, such asassert constraint typedOk : FlowLimit { in limit = 50; }. The Solver checks the definition’s expression with the parameters the usage binds. A parameter the usage leaves unbound is open.assert not constraint, which must be false.assert constraintinside a requirement definition, reported once for that definition and once for every requirement definition that specializes it.An assertion declared in a part definition, reported once for the definition and once for every specializing definition and every part usage typed by it, each named by its context. For example, an assertion
flowOkinpart def Pump, inherited bypart bigPump : Pump [1], appears as bothPumps::Pump::flowOkandPumps::bigPump.flowOk, and--name Pumps::bigPump.flowOkchecks only the second.
An assertion inside a state definition or a state is reported with an exhaustive of
"unknown" and the reason “asserts inside state definitions are not yet supported”,
and listed in warnings. Any other assertion the Solver cannot analyze is reported
the same way, with its own reason, while every other assertion in the run is still
checked (see Handle unsupported syntax).
Choosing what fails the check New in v1.0.0
By default check fails when false is attainable, that is, when the condition can
be violated. --fail-when picks a different rule:
false-attainable(the default): fails when the condition can be violated.not-exactly-true: fails when the condition is not known to hold in every situation the model allows, including when the Solver leaves a truth value undecided and when the model is inconsistent.true-unattainable: fails only when the condition is necessarily violated, sotrueis impossible.true-not-proven-attainable: fails when the condition is not known to be possible, including when the Solver leaves a truth value undecided and when the model is inconsistent.
For example, pressureOk above can fail, but it is not necessarily violated:
syside solver check -i pump.sysml --name Pump::pressureOk \
--fail-when true-unattainable
This exits 0, and the output records the mode as "fail_when": "true_unattainable".
Use not-exactly-true in CI when an assertion the Solver could not analyze should
fail the build as well (see Handle unsupported syntax).
Exit codes
check is built to run in scripts and CI pipelines:
0: no checked condition fails the
--fail-whenmode1: at least one checked condition fails the
--fail-whenmode2: the model or the query could not be loaded
An assertion the Solver could not analyze does not by itself exit 1 under the default mode (see Handle unsupported syntax).
Options
check and evaluate share these options:
-i PATH,--input PATH: a SysML v2 model file, or a directory that contributes every.sysmland.kermlfile at or below it. Repeat the option to load several inputs. All inputs load as one model.-a LIST,--alt-semantics LIST: comma-separated assumptions (see Add assumptions).-d N,--depth N: exploration depth for a model with an unbounded loop (see Explore state machine reachability).--solver-limit N: the amount of work each solver step may use before the Solver stops it (see below).--timeout SECONDS: a time limit for the whole run (see below).
check adds -n (--name), -p (--predicate) and -f (--fail-when).
syside solver check --help lists them all.
Limiting how long a run takes New in v1.0.0
Some questions take the Solver a long time, or more work than it can finish. Two
options stop it, and both apply to check and evaluate:
--solver-limit Ncaps the work of each individual solver step. The default is 10000000, and0removes the limit. The limit counts work, not time, so the same model and command give the same answers on any machine, for a given Solver release.--timeout SECONDScaps the wall-clock time of the whole run, in seconds with up to three decimals (at least0.001). Without it, there is no time limit. Results can differ between faster and slower machines, or on a machine under load.
When both are given, each step stops at whichever is reached first: its own
--solver-limit, or what remains of the --timeout.
A step that is stopped never turns into a value. The listings it would have decided get
an exhaustive of "unknown", with a reason naming the limit:
syside solver check -i pump.sysml --solver-limit 1
{
"name": "Pump::flowOk",
"outcome": {
"shape": "attainable_set",
"attainable": {
"values": [],
"exhaustive": "unknown",
"reason": "solver limit exhausted: the check exceeded --solver-limit 1"
},
"unattainable": {
"values": [],
"exhaustive": "unknown",
"reason": "solver limit exhausted: the check exceeded --solver-limit 1"
}
}
}
(Output truncated to the first assertion.)
With --timeout, the reason reads time limit of 0.001 s reached, with the time you
gave. As for any assertion the Solver could not decide, the exit code stays 0 under the
default --fail-when mode, and --fail-when not-exactly-true turns it into exit 1.
A value the options cannot take is a usage error with exit code 2, for example:
syside solver check: error: argument --timeout: must be at least 0.001 (1 ms); omit --timeout for no time limit
“Can it fail?” versus “which values are possible?”
check and evaluate report the same attainable set for a boolean expression. The
difference is the exit code: evaluate exits 0 either way, while check exits by its
--fail-when mode. Use evaluate to explore what is possible, and check to gate a
script on it. See Evaluate possible values.