Skip to content

6. Constraints

This chapter checks the specimen's size against a tolerance before its strength is trusted. It shows how a rule pairs a condition with what to do when it fails, the four outcomes of checking one, and how to check several at once.

The program keeps chapter 5's two sides, and adds four rules on them and three specimens to check; it calculates no strength.

A rule the result must meet

The test method requires each side of the loaded face to measure 150 mm within 1 mm. A formula::constraint pairs a predicate, the condition that must hold, with a formula::Verdict, what to do when it does not. formula::constant<unit::Millimetre>(149) is a fixed length in the predicate, with its unit, so it is compared with a side in the same dimension:

constexpr auto sideANotTooShort = formula::constraint(var<SideA> >= formula::constant<unit::Millimetre>(149),
                                                      formula::Verdict { "reject the specimen: side a out of tolerance" });
constexpr auto sideANotTooLong = formula::constraint(var<SideA> <= formula::constant<unit::Millimetre>(151),
                                                     formula::Verdict { "reject the specimen: side a out of tolerance" });
constexpr auto sideBNotTooShort = formula::constraint(var<SideB> >= formula::constant<unit::Millimetre>(149),
                                                      formula::Verdict { "reject the specimen: side b out of tolerance" });
constexpr auto sideBNotTooLong = formula::constraint(var<SideB> <= formula::constant<unit::Millimetre>(151),
                                                     formula::Verdict { "reject the specimen: side b out of tolerance" });

A predicate compares two values; it does not combine comparisons. The tolerance on each side is therefore two rules, a lower and an upper limit, with the same verdict.

The program checks three specimens. The third has no measurement of side b:

auto const withinTolerance =
    formula::environment(formula::Measured<SideA> { 150.2_r }, formula::Measured<SideB> { 149.8_r });
auto const sideBTooLong =
    formula::environment(formula::Measured<SideA> { 150.2_r }, formula::Measured<SideB> { 152.5_r });
auto const sideBMissing = formula::environment(formula::Measured<SideA> { 150.2_r }, formula::Measured<SideB>::absent());

formula::check(constraint, environment) checks one rule and returns a formula::ConstraintOutcome. The program checks the upper limit on side b against the second specimen, whose side b measures 152.5 mm:

std::println("specimen 2, one rule:");
if (!print_outcome("b <= 151 mm", formula::check(sideBNotTooLong, sideBTooLong)))
    return 1;

The rule does not hold, so the outcome is violated and carries the rule's verdict.

Four outcomes, not two

Checking a rule has four outcomes, and kind() names which one it reached:

  • satisfied -- the predicate held.
  • violated -- the predicate did not hold; verdict() returns the rule's verdict.
  • not checked -- the predicate never resolved, because a value it reads was never measured. An unmeasured side is never reported as satisfied: a record saying the specimen was verified when nothing verified it is worse than no check at all.
  • invalid -- checking itself failed, because evaluating the predicate raised an arithmetic error; error() returns it. See A fourth state in the guide.

ConstraintOutcome has no conversion to bool, because any answer it gave for not checked or invalid would be wrong. The program prints each outcome by asking for the verdict and the error, which are present only for violated and invalid:

// Prints one rule's outcome on a line of its own. Returns false if checking
// the rule failed, after printing why.
bool print_outcome(std::string_view rule, formula::ConstraintOutcome const& outcome)
{
    if (auto const verdict = outcome.verdict())
        std::println("  {}: {} ({})", rule, outcome.kind(), verdict->label);
    else if (auto const error = outcome.error())
    {
        std::println("  {}: {} ({})", rule, outcome.kind(), *error);
        return false;
    }
    else
        std::println("  {}: {}", rule, outcome.kind());
    return true;
}

None of the rules here can fail to evaluate, so an invalid outcome would be a fault: the program prints it and exits with an error.

Checking several rules

formula::constraints(...) bundles the rules into a set, and the program names each rule for printing, in the same order:

constexpr auto sideTolerances = formula::constraints(sideANotTooShort, sideANotTooLong, sideBNotTooShort, sideBNotTooLong);
constexpr std::array<std::string_view, 4> ruleNames { "a >= 149 mm", "a <= 151 mm", "b >= 149 mm", "b <= 151 mm" };

formula::check_all(set, environment) checks every rule in the set and returns one outcome per rule, at the index the rule was given. It checks every rule without stopping at the first failure: a specimen can fail two rules at once, and a report naming only the first would send it back for a second round of testing.

auto const checkAndPrint = [](std::string_view name, auto const& specimen) {
    std::array<formula::ConstraintOutcome, 4> const outcomes = formula::check_all(sideTolerances, specimen);
    std::println("{}:", name);
    bool checked = true;
    for (std::size_t index = 0; index < outcomes.size(); ++index)
        checked = print_outcome(ruleNames[index], outcomes[index]) && checked;
    return checked;
};
if (!checkAndPrint("specimen 1, 150.2 mm by 149.8 mm", withinTolerance)
    || !checkAndPrint("specimen 2, 150.2 mm by 152.5 mm", sideBTooLong)
    || !checkAndPrint("specimen 3, side b not measured", sideBMissing))
    return 1;

The first specimen meets all four rules. The second fails the upper limit on side b, and every other rule is still reported. The third meets both rules on side a; both rules on side b are not checked, since nobody measured it.

Output

specimen 2, one rule:
  b <= 151 mm: violated (reject the specimen: side b out of tolerance)
specimen 1, 150.2 mm by 149.8 mm:
  a >= 149 mm: satisfied
  a <= 151 mm: satisfied
  b >= 149 mm: satisfied
  b <= 151 mm: satisfied
specimen 2, 150.2 mm by 152.5 mm:
  a >= 149 mm: satisfied
  a <= 151 mm: satisfied
  b >= 149 mm: satisfied
  b <= 151 mm: violated (reject the specimen: side b out of tolerance)
specimen 3, side b not measured:
  a >= 149 mm: satisfied
  a <= 151 mm: satisfied
  b >= 149 mm: not checked
  b <= 151 mm: not checked

Summary

  • formula::constraint(predicate, verdict) -- a rule: a condition that must hold, and what to do when it does not.
  • formula::Verdict -- what to do when a rule is violated, in words.
  • formula::constant<Unit>(value) -- a fixed value with its unit, for use in a predicate.
  • formula::check(constraint, environment) -- checks one rule; the outcome is satisfied, violated, not checked or invalid.
  • formula::check_all(set, environment) -- checks every rule in a set, without stopping at the first failure.
  • formula::constraints(...) -- bundles rules into a set for check_all.

Further reading