Books / Constructions / Chapter 17

Part V · Chapter 17

A Check That Tells Models Apart

8 min read

Contents of Constructions

In Part I we recorded two pieces of information about water and derived amphiproticity. Then we removed one piece, and the conclusion disappeared. These two cases check different sides of one promise: two premises suffice, and one does not.

Now imagine an error slipping into the rule — say, the proton-acceptance condition is dropped by accident. The two-premise scenario still gives the right answer. So one lucky example is not enough: we need a case where the right and the wrong rules behave differently.

First name the promise

Let us return to the familiar rule:

pub rule AmphiproticDonatesAndAccepts strict {
    label ru unofficial "способная и отдать, и принять протон частица амфипротна (14.1)";
    label en unofficial "a species capable of either donating or accepting protons is amphiprotic (14.1)";
    for s: Species;
    when donates_proton(s) and accepts_proton(s);
    then amphiprotic(s);
}

This rule carries at least three checkable requirements: both properties are needed; they must concern one particle; to test the inference, the conclusion must be derived from the inputs through this rule, rather than supplied in the case.

For each requirement we can pick a small change to the model that breaks it:

Requirement Error the scenario must notice
Amphiproticity follows from two pieces of information The rule is removed
Both pieces of information are needed One condition is dropped from the rule
The information concerns one particle The second premise names another variable

That states in advance what each check case is for. The expectation comes from the rule’s meaning and the source. The run then tests that expectation — the run’s own output must not become the expectation’s sole ground.

What the two-premise example notices

Scenario 14 of the chemical vocabulary reports both properties of water and expects TRUE_ONLY. If the rule AmphiproticDonatesAndAccepts is removed, the same information yields NEITHER. The scenario catches the lost conclusion.

Scenario 15 reports only proton donation and expects NEITHER. It passes both on the original model and on the model without the rule. The outcome matches, but the reasons differ: in one case a premise is missing; in the other, the very path to the conclusion is gone.

That does not make scenario 15 useless. It catches a different error: dropping the proton-acceptance condition. After that edit, a report of proton donation alone suffices, and TRUE_ONLY arrives instead of the expected NEITHER. One scenario may catch one error and miss another.

A woman stays dry under an ordinary umbrella while a man gets wet under a colander umbrella; rain falls over both.

For experiments we can use the law engine mutate tool. It proposes changes to the program, for example remove_rule, drop_conjunct, and freshen_var_occurrence. The last replaces one occurrence of a variable with a fresh one. Such altered variants are usually called mutants[^chapter-17-mutation].

Here they serve as deliberately wrong models to compare against the original. If a change makes the model invalid, the language check catches it. The harder case is what matters here: the model stays valid, but the reasoning changes.

An error the earlier examples missed

Imagine that instead of one variable in two premises, the rule requires proton donation from a particle s and acceptance from some particle t. That is already a different statement: the properties may belong to different objects.

In the original water example the error stays invisible. Water both donates and accepts a proton, so either variable can bind to it. What is needed is a case that splits the properties.

Below is the author’s check based on scenario 15. One piece of information about another object is added. The header and context are kept so the example can be run as a separate .lawtest:

language "law.core" version "0.2";
package openstax.chemistry_acids version "0.1.0";
namespace "urn:openstax:clir:chemistry-acids";

test "urn:query:openstax-chem-acids-15-book-water-one-role-no-conclusion" {
    given {
        context {
            decision_time @2026-09-06T12:00:00Z;
            knowledge_time @2026-09-06T12:00:00Z;
            legal_time @2026-09-06;
            timezone "UTC";
        }
        assert donates_proton(entity_ref("urn:case:chem:h2o")) { id "k15-1"; origin case_input; }
        assert accepts_proton(entity_ref("urn:case:book:other")) { id "book-other-accepts"; origin case_input; }
    }
    evaluate truth(amphiprotic(entity_ref("urn:case:chem:h2o")));
    expect truth_status == NEITHER;
    expect evaluation_status == COMPUTED;
}

Donating belongs to h2o, accepting to other. We ask about h2o‘s amphiproticity. On the original model the answer is NEITHER: information about another object does not satisfy the premise about water.

On the altered model, where the acceptance premise carries a substituted variable, the answer becomes TRUE_ONLY and the test fails. That is exactly the difference we wanted to catch.

All 22 earlier scenarios still pass on that altered rule. The new case exposes a specific coverage gap: none of them splits the properties across objects in a way that fits this error. A count of passing checks alone does not reveal that gap.

For the symmetric error a mirror check is needed: water accepts a proton, and another object donates a proton. Then substituting the variable in the donation premise becomes visible too.

The same answer may come another way

Scenario 14 has one more expectation:

expect applied(AmphiproticDonatesAndAccepts);

It checks that the named rule took part. To see its use, take a temporary copy of the model. Give the original rule an extra condition that the case leaves unknown, and add a second rule deriving amphiproticity from the same two premises. Before running the changed model, predict the truth status and the rule-application check separately.

The water question still yields TRUE_ONLY, so the answer-only check passes. But the applied(AmphiproticDonatesAndAccepts) expectation fails: the conclusion came through a different rule. The explanation exposes the substitution.

At the same time, applied does not prove the named rule was the answer’s only ground, nor that its premises faithfully express the source. To assess that, we must read the rule itself and the matching branch of the explanation. The expectation has one particular job[^chapter-17-applied]; do not stretch its meaning further.

The same holds when no conclusion is established. On NEITHER, check what was missing: information, a reading choice as in Chapter 14, an applicable edition as in Chapter 13, or a rule. A matching status alone does not mean matching reasons.

Check the intermediate steps

The model on Gauss’s Disquisitiones Arithmeticae checks a supplied trace — a step-by-step record of a modular exponentiation calculation: raising a number to a power and finding its remainder after division by a fixed number. To follow the example, it is enough to know that a remainder is what is left after division.

The case reports base 2, divisor (or modulus) 341, and several “exponent — remainder” pairs. The model checks the links between them: when the exponent doubles, the remainder must match the square of the previous remainder taken modulo the same modulus.

In scenario 10, exponent 5 is recorded with remainder 33, and exponent 10 with remainder 1. Let us check the pair independently:

33² = 1089
1089 = 3 · 341 + 66

The independent calculation gives remainder 66, not 1.

(Author’s paraphrase of the reported engine result; the full output is not reproduced here.) The rule VestigiumFalsumInDuplicatione detects the inconsistency and derives vestigium_falsum. The query yields TRUE_ONLY, COMPUTED, and the explanation points to these two recorded steps.

The model also checks adjacent exponents: when the exponent increases by one, multiply the previous remainder by the base and take the remainder modulo the same divisor. This explains the second error, from remainder 16 at exponent 4 to remainder 33 at exponent 5: 16 · 2 = 32, already below 341, so the next remainder must be 32. The rule VestigiumFalsumInGradu checks that link.

Scenario 12 removes a link needed to build the checked chain. Here it is not enough to check equalities for the pairs at hand: we must also establish that the chain reached the declared final exponent 340.

(Author’s paraphrase of the reported engine result; the full output is not reproduced here.) A separate rule does this job. It reads the declared limit and not_known(catena(...)): no checked chain reaching it is established. The result is vestigium_ruptum: the trace breaks. Here 340 is the exponent the checked chain must reach.

That is how the check tells apart a wrong link and a missing path to the goal. Neither result on its own proves Fermat’s little theorem or that 341 is prime. The check’s subject is narrower: the consistency of the presented computation trace[^chapter-17-trace].

What repeating a calculation checks

So far we asked whether the model behaves as its stated rules require. One question remains: did the recipient repeat the calculation they were handed?

The calculation document holds the input manifest, the answers, and the explanation graph. The resultHash field serves as the fingerprint of its canonical contents. Comparing fingerprints catches a difference between documents even when both show TRUE_ONLY.

But a matching fingerprint does not check whether the author read the source rightly. It establishes which particular calculation was reproduced. Substantive checks and repetition checks solve different problems; the last chapter runs both.

Try picking the missing check

Work with a copy of the chemical package. First predict the outcome of scenarios 14 and 15 if accepts_proton(s) is removed from AmphiproticDonatesAndAccepts. Then make that edit and prepare the program with the law engine lower command and run the unchanged tests with law engine test --program. Scenario 14 must pass, and 15 must catch the error.

Then take the two-object check above and swap the relations: water accepts, another object donates. Confirm the explanation above by checking which of the two variable substitutions this case catches.

Finally, for one rule of your own, write down its promise and two examples: where it must produce a conclusion and where a nearby case must not yield that conclusion. If both examples pass after removing an essential condition, the check needs refining.

The final scenario set does not prove the model complete. It shows which differences we can detect. Hand over that knowledge with the model too: what was checked, by which case, and which boundary stays unchecked so far.

[^chapter-17-mutation]: The CLI’s mutation notes applies local changes to the CLIR program and checks that the result is valid. The original chemical set holds 22 scenarios. The count of passed cases concerns a particular mutation, not the model’s completeness. [^chapter-17-applied]: Scenario 14 separately checks truth_status and applied. The original rule links both premises with one variable; the alternative rule in the experiment is a study change to a temporary copy. [^chapter-17-trace]: The trace-check rules, scenario 10, and scenario 12. The first rule compares the presented remainders, the second checks the absence of an established catena up to the declared limit.

Contents · PDF · EPUB · Original text under CC BY 4.0 · Source-material terms

Constructions

Contents

You’re reading · Part V

Assemble and Hand Over

Reading settings

Text size
Appearance

Preferences and your reading place are saved only in this browser.