The Principia Machina/TPC-PY/gen module corpus.py: Difference between revisions
Appearance
TPC-PY source gen_module_corpus.py (0.2.0), SHA-256 f3023791a9f78b0f; conformed TPS 0.4.0; PROPOSED, not declared (TPC-PY 0.2.0) |
m 1 revision imported |
(No difference)
| |
Latest revision as of 02:11, 30 September 2026
"""gen_module_corpus 0.2.0: builds vectors/module_corpus.json for tpc_module
(D178.1 (a)-(g); plan 0.8.0 P2-04; specification tpm_module_spec 0.2.0, which
applies the supervisor's rulings S48-S54 on RV's review 0.1.0).
VECTORS FIRST. Every expectation below is a literal written from
tpm_module_spec 0.2.0 before either residence was changed to it. The
generator never imports or runs tpc_module. It writes the corpus in the
RUN_CASE form of lint_corpus: {"corpus": "module", "version": "0.2.1",
"cases": [{"name", "fn", "args", "expected", "hand_checked"}]}, "expected"
being the rendered result (tpm_module_spec 2.3, 6.4 RENDER).
WHAT IS COMPUTED, AND WITH WHAT (tpm_module_spec section 9). Only values
that are digests are computed, and only by EXISTING, verified TPC-PY
functions over data written here:
- a module digest (S49; spec section 4): tpc_digest.digest_fields over
["TPC-MODULE", "1", T], T the canonical text built by CT() below from a
fixture dict; CT is anchored by the canonical texts of MATH 1.0.0 and of
GRAV-UNIFORM 1.0.0 written out literally below (asserted equal), and by
the escape anchors;
- a D(TPK): tpc_zone.d_tpk over the expanded TPK written literally below
(EXP_* dicts: every composed EQUATION, REFS, STATEMENT and emitted entry
typed by hand); RV's oracle values for X54 and X57 are asserted equal;
- a catalogue STATE DIGEST: tpc_digest.digest_fields.
CHECKS OF THE HAND DERIVATION (asserts; existing functions only): every
expanded TPK an expectation calls PASS runs through tpc_tests.kernel_tests
and tpc_tests.t3_consistency (S_MAX 1); every FAIL row's text is compared
with the existing function's; every eom label a refusal quotes is compared
with tpc_eom.eom_map; every canonical spelling written by hand is compared
with tpc_form.parse_form; every quoted T1 problem with
tpc_zone.t1_well_formed. The generator REFUSES TO WRITE when an assert
fails. Deterministic: no randomness, no clock.
Every physical value in the fixtures is a FIXTURE, not a declaration
(tpm_module_spec section 8): the ISO 2533:1975 values are VERIFY-HIGH (S6).
0.2.1 (increment 11, 2026-09-28): X64-X67, the base TPK read as the JSON
is written (C-MS-5); the corpus is written with LF line ends on every
platform (S65). No earlier case moves.
"""
import copy
import hashlib
import json
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import tpc_zone as Z # noqa: E402 existing: _node, _tree_tpk, d_tpk, canon_decimal
import tpc_digest as G # noqa: E402 existing: digest_fields
import tpc_form as F # noqa: E402 existing: parse_form (assert only)
import tpc_units as U # noqa: E402 existing: unit_parse (canonical unit)
import tpc_tests as K # noqa: E402 existing: kernel_tests, t3_consistency (assert only)
import tpc_eom as E # noqa: E402 existing: eom_map (assert only)
CORPUS_VERSION = "0.2.1"
CASES = []
def J(v):
"""tpm_module_spec 2.3: the rendering of a result."""
return json.dumps(v, ensure_ascii=True, separators=(",", ":"))
def OK(*items):
return J(["OK"] + list(items))
def REF(msg):
return J(["REFUSED", msg])
_HEX64 = re.compile(r"[0-9a-f]{64}")
def add(name, fn, args, expected):
"""hand_checked is false exactly where the expectation holds a digest or a
D(TPK): those are computed by existing TPC-PY functions over literal data
(the docstring); every other character of every expectation is typed here."""
c = {"name": name, "fn": fn, "args": args, "expected": expected,
"hand_checked": _HEX64.search(expected) is None}
if not c["hand_checked"]:
c["computed"] = "digests by existing TPC-PY functions over literal data"
CASES.append(c)
# ------------------------------------------------ existing-function helpers
# The canonical text of a module (tpm_module_spec 0.2.0 section 4, S49): the
# module schema's objects in FIELD ORDER, every other object by key in code
# point order, strings as tpc_zone._text escaped only for '"', '\\' and the
# controls (\u00XX lower case), integers in decimal, no white space.
FIELD_ORDER = {
"MODULE": ["ID", "VERSION", "TITLE", "SOURCE", "PREREQUISITES", "PARAMETERS", "SUMS", "TERMS",
"ASSUMPTIONS", "BOUNDARIES", "NOT_CHOSEN", "NOT_INCLUDED", "EXCLUDES",
"INTERACTS_WITH", "TEST_BINDINGS"],
"PREREQUISITES": ["SLOT", "KIND", "DIMENSION", "MIN", "MAX", "LABEL"],
"PARAMETERS": ["NAME", "DIMENSION", "SUPPLY", "LABEL", "VALUE", "UNIT", "UNCERTAINTY",
"OVERRIDABLE"],
"SUMS": ["NAME", "OVER", "EACH"],
"TERMS": ["TARGET", "SIGN", "EXPR", "LABEL"],
"ASSUMPTIONS": ["NAME", "PROPOSITION", "REFS"],
"BOUNDARIES": ["NAME", "PREDICATE", "CONDITION"],
"INTERACTS_WITH": ["WITH", "QUESTION_ID", "QUESTION", "YES_LINE", "NO_LINE"],
"TEST_BINDINGS": ["NAME", "TPK", "BIND", "VALUES", "ANSWERS"],
}
def JS(s):
out = ['"']
for ch in Z._text(s):
if ch == '"' or ch == "\\":
out.append("\\" + ch)
elif ord(ch) < 0x20:
out.append("\\u%04x" % ord(ch))
else:
out.append(ch)
return "".join(out) + '"'
def GV(v):
"""A value outside the module schema's objects: keys in code point order."""
if type(v) is str:
return JS(v)
if type(v) is int:
return str(v)
if type(v) is list:
return "[" + ",".join(GV(x) for x in v) + "]"
assert type(v) is dict, v
return "{" + ",".join(JS(k) + ":" + GV(v[k]) for k in sorted(v)) + "}"
def SO(d, order):
return "{" + ",".join(JS(k) + ":" + GV(d[k]) for k in order if k in d) + "}"
def CT(m):
parts = []
for k in FIELD_ORDER["MODULE"]:
v = m[k]
if k in FIELD_ORDER:
parts.append(JS(k) + ":[" + ",".join(SO(e, FIELD_ORDER[k]) for e in v) + "]")
else:
parts.append(JS(k) + ":" + GV(v))
return "{" + ",".join(parts) + "}"
def MDIG(m):
"""A module digest (S49): digest_fields(["TPC-MODULE", "1", CT(m)])."""
return G.digest_fields(["TPC-MODULE", "1", CT(m)])
assert JS('a"b\\c\td\ne\r\nf\u007f') == '"a\\"b\\\\c\\u0009d\\u000ae\\u000af\u007f"'
assert JS("cafe\u0301") == '"caf\u00e9"'
def DTPK(tpk):
return Z.d_tpk(tpk)
def CD(s):
r = Z.canon_decimal(s)
assert r is not None, s
return r
def CU(u):
r = U.unit_parse(u)
assert r[0] == "OK", u
return r[1]
def SD(offer):
"""STATE DIGEST of an OFFERED list (tpm_module_spec 6.8)."""
f = ["TPC-MODULE-OFFER", "1"]
for i, v, d in offer:
f += [i, v, d]
return G.digest_fields(f)
def HX(label):
"""An arbitrary 64-hex digest for lifecycle-only lines (data)."""
return hashlib.sha256(label.encode("ascii")).hexdigest()
def canon_is(text, mode):
r = F.parse_form(text, mode)
assert r == ["OK", text], (text, r)
return text
def must_pass(tpk, tag):
kt = K.kernel_tests(tpk)
assert kt[0] is True, (tag, kt)
t3 = K.t3_consistency(tpk, {"PARAMETERS": {"S_MAX": 1}})
assert t3[0] is True, (tag, t3)
def first_kt_problem(tpk):
for name, passed, probs in K.kernel_tests(tpk)[1]:
if passed is not True and passed != "NO FORM":
return name + ": " + probs[0]
return None
def label_of(tpk, q):
m = E.eom_map({"TPK": tpk})
assert m[0] == "OK", m
r = dict((row[0], row[1:]) for row in m[1])
for dof, label, law in r["MAP"][0]:
if dof == q:
return label, law
raise AssertionError(q)
def t1_first(tpk):
r = Z.t1_well_formed(tpk)
assert r[0] is False
return r[1][0]
# =================================================================== TPKs
def law(n, eq, role, refs, st):
return {"NAME": n, "STATEMENT": st, "REFS": refs, "ROLE": role, "EQUATION": eq}
def dof(n, d, role=None):
e = {"NAME": n, "DIMENSION": d}
if role:
e["ROLE"] = role
return e
P2 = {
"DOF": [dof("t", "T1"), dof("x", "L1"), dof("z", "L1"), dof("v_x", "L1 T-1"),
dof("v_z", "L1 T-1")],
"CONSTANT": [{"NAME": "m", "DIMENSION": "M1"}],
"LAW": [law("KIN_X", "D(x,t)=v_x", "EOM(x)", ["t", "v_x", "x"], "dx/dt = v_x"),
law("KIN_Z", "D(z,t)=v_z", "EOM(z)", ["t", "v_z", "z"], "dz/dt = v_z"),
law("EOM_X", "m*D(v_x,t)=0", "EOM(v_x)", ["m", "t", "v_x"], "m dv_x/dt = 0"),
law("EOM_Z", "m*D(v_z,t)=0", "EOM(v_z)", ["m", "t", "v_z"], "m dv_z/dt = 0")],
"ASSUMPTION": [{"NAME": "point_mass", "PROPOSITION": "the body is treated as a point mass",
"REFS": ["m"]}],
}
must_pass(P2, "P2")
TABLE = {
"DOF": [dof("t", "T1"), dof("x", "L1"), dof("z", "L1"), dof("v_x", "L1 T-1"),
dof("v_z", "L1 T-1")],
"CONSTANT": [{"NAME": "m", "DIMENSION": "M1"}],
"LAW": [law("KIN_X", "D(x,t)=v_x", "EOM(x)", ["t", "v_x", "x"], "dx/dt = v_x"),
law("KIN_Z", "D(z,t)=v_z", "CONSTRAINT", ["t", "v_z", "z"], "dz/dt = v_z"),
law("EOM_X", "m*D(v_x,t)=0", "EOM(v_x)", ["m", "t", "v_x"], "m dv_x/dt = 0")],
"CONSTRAINT": [{"NAME": "C_Z", "TARGET": "z", "ACTION": "FIX", "VALUE": "0", "UNIT": "m",
"STATEMENT": "the puck stays on the table top, z = 0"},
{"NAME": "C_VZ", "TARGET": "v_z", "ACTION": "FIX", "VALUE": "0",
"UNIT": "m s-1", "STATEMENT": "the puck has no vertical velocity"}],
}
must_pass(TABLE, "TABLE")
assert label_of(TABLE, "v_z") == ("FIXED", "")
SOLO = {
"DOF": [dof("t", "T1"), dof("q", "L1")],
"LAW": [law("EQ_Q", "D(q,t)=0", "EOM(q)", ["q", "t"], "dq/dt = 0")],
}
must_pass(SOLO, "SOLO")
def tpk_mod(base, laws=None, add_entries=(), drop_laws=(), extra=None):
"""Plumbing over literal values: base with some laws replaced by literal
(EQUATION, REFS, STATEMENT) triples and literal entries appended."""
t = copy.deepcopy(base)
for i, e in enumerate(t.get("LAW", [])):
if laws and e["NAME"] in laws:
eq, refs, st = laws[e["NAME"]]
t["LAW"][i] = {"NAME": e["NAME"], "STATEMENT": st, "REFS": refs, "ROLE": e["ROLE"],
"EQUATION": eq}
if drop_laws:
t["LAW"] = [e for e in t["LAW"] if e["NAME"] not in drop_laws]
for kind, e in add_entries:
t.setdefault(kind, []).append(copy.deepcopy(e))
if extra:
extra(t)
return t
# ======================================================== module fixtures
# ---- GRAV-UNIFORM 1.0.0
GRAV_TITLE = "Uniform gravity at the standard value g_n"
GRAV_SOURCE = ("g_n = 9.80665 m s-2, exact by definition: 3rd CGPM (1901), declaration on "
"the unit of mass and on the definition of weight")
L_MASS = "the mass of the body"
L_UPV = "the velocity component pointing up; its equation of motion receives the weight"
L_HEIGHT = "the position along the same upward axis"
L_GN = "the standard acceleration of gravity"
L_HMAX = "the largest height, above or below the origin, over which the field is taken as uniform"
PROP_GU = "the gravitational field is uniform over the region abs({HEIGHT}) < h_max"
PROP_GS = "the local gravitational acceleration equals the conventional standard value g_n"
PROP_FI = "the frame in which {HEIGHT} is measured is treated as inertial"
PRED_GR = "abs({HEIGHT}) < h_max"
GRAV_NC = "No gravity: no weight acts on any DOF."
GRAV_NI = ["No variation of gravity with height or latitude beyond what g_n contains.",
"No Earth rotation (Coriolis or centrifugal force) beyond what g_n contains.",
"No gravity of bodies other than the Earth."]
Q_B = "Does the air's buoyancy (upthrust) act on the body in the system you mean?"
YES_B = ("Buoyancy acts (your answer): no module adds it; it acts in this kernel only where "
"one of your own laws states it.")
NO_B = "No buoyancy: the air's upthrust on the body is not included."
PRE_MASS = {"SLOT": "MASS", "KIND": "CONSTANT", "DIMENSION": "M1", "MIN": 1, "MAX": 1,
"LABEL": L_MASS}
PRE_UPV = {"SLOT": "UP_VELOCITY", "KIND": "DOF", "DIMENSION": "L1 T-1", "MIN": 1, "MAX": 1,
"LABEL": L_UPV}
PRE_HEIGHT = {"SLOT": "HEIGHT", "KIND": "DOF", "DIMENSION": "L1", "MIN": 1, "MAX": 1,
"LABEL": L_HEIGHT}
PAR_GN = {"NAME": "g_n", "DIMENSION": "L1 T-2", "SUPPLY": "DECLARED", "VALUE": "9.80665",
"UNIT": "m s-2", "OVERRIDABLE": "NO", "LABEL": L_GN}
PAR_HMAX = {"NAME": "h_max", "DIMENSION": "L1", "SUPPLY": "AUTHOR", "LABEL": L_HMAX}
GRAV_BIND = {"HEIGHT": ["z"], "MASS": ["m"], "UP_VELOCITY": ["v_z"]}
TB_GRAV_P2 = {"NAME": "PROJECTILE_2D", "TPK": P2, "BIND": GRAV_BIND,
"VALUES": {"h_max": {"VALUE": "100", "UNIT": "m"}}, "ANSWERS": {}}
TB_GRAV_TABLE = {"NAME": "TABLE", "TPK": TABLE, "BIND": GRAV_BIND,
"VALUES": {"h_max": {"VALUE": "1", "UNIT": "m"}}, "ANSWERS": {"FIXED:v_z": "YES"}}
GRAV = {
"ID": "GRAV-UNIFORM", "VERSION": "1.0.0", "TITLE": GRAV_TITLE, "SOURCE": GRAV_SOURCE,
"PREREQUISITES": [PRE_MASS, PRE_UPV, PRE_HEIGHT],
"PARAMETERS": [PAR_GN, PAR_HMAX],
"SUMS": [],
"TERMS": [{"TARGET": "UP_VELOCITY", "SIGN": "-", "EXPR": "MASS*g_n", "LABEL": "the weight"}],
"ASSUMPTIONS": [{"NAME": "gravity_uniform", "PROPOSITION": PROP_GU,
"REFS": ["HEIGHT", "h_max"]},
{"NAME": "gravity_standard", "PROPOSITION": PROP_GS, "REFS": ["g_n"]},
{"NAME": "frame_inertial", "PROPOSITION": PROP_FI, "REFS": ["HEIGHT"]}],
"BOUNDARIES": [{"NAME": "gravity_region", "PREDICATE": PRED_GR,
"CONDITION": "abs(HEIGHT) < h_max"}],
"NOT_CHOSEN": GRAV_NC, "NOT_INCLUDED": GRAV_NI, "EXCLUDES": [],
"INTERACTS_WITH": [{"WITH": "AIR-*", "QUESTION_ID": "BUOYANCY", "QUESTION": Q_B,
"YES_LINE": YES_B, "NO_LINE": NO_B}],
"TEST_BINDINGS": [TB_GRAV_P2, TB_GRAV_TABLE],
}
assert CD("9.80665") == "980665e-5" and CU("m s-2") == "m s-2" and CD("100") == "1e2"
canon_is("MASS*g_n", "expression")
canon_is("abs(HEIGHT)<h_max", "predicate")
# ---- GRAV-UNIFORM 1.1.0 (the same physics; only TITLE revised)
GRAV11_TITLE = "Uniform gravity at the standard value g_n (text revised)"
GRAV11 = copy.deepcopy(GRAV)
GRAV11["VERSION"] = "1.1.0"
GRAV11["TITLE"] = GRAV11_TITLE
# ---- GRAV-UNIFORM 1.2.0 (one assumption added: a different expansion)
PROP_NT = "no tidal force acts on the body"
GRAV12 = copy.deepcopy(GRAV)
GRAV12["VERSION"] = "1.2.0"
GRAV12["ASSUMPTIONS"].append({"NAME": "gravity_no_tides", "PROPOSITION": PROP_NT,
"REFS": ["HEIGHT"]})
# ---- GRAV-UNIFORM 2.0.0 (a new AUTHOR parameter z_ref)
L_ZREF = "the height of the origin above sea level"
GRAV20 = copy.deepcopy(GRAV)
GRAV20["VERSION"] = "2.0.0"
GRAV20["PARAMETERS"].append({"NAME": "z_ref", "DIMENSION": "L1", "SUPPLY": "AUTHOR",
"LABEL": L_ZREF})
GRAV20["TEST_BINDINGS"][0]["VALUES"] = {"h_max": {"VALUE": "100", "UNIT": "m"}, "z_ref": {}}
GRAV20["TEST_BINDINGS"][1]["VALUES"] = {"h_max": {"VALUE": "1", "UNIT": "m"}, "z_ref": {}}
# ---- AIR-QUADRATIC 1.0.0 and AIR-LINEAR 1.0.0 (ISO 2533:1975 values VERIFY-HIGH)
AIR_SOURCE = ("ISO 2533:1975, Standard Atmosphere, sea-level values: density 1.225 kg m-3, "
"dynamic viscosity 1.7894e-5 Pa s")
AIRQ_TITLE = "Air resistance at sea level, quadratic in the speed"
AIRL_TITLE = "Air resistance at sea level, linear in the speed (Stokes drag on a sphere)"
L_VEL = "the velocity components of the body, one per axis"
L_RHO = "the density of air at sea level"
L_MU = "the dynamic viscosity of air at sea level"
L_CD = "the drag coefficient of the body"
L_AREF = "the reference area of the drag coefficient"
L_LREF = "the reference length of the Reynolds number"
L_PI = "pi, to 16 significant digits"
L_RS = "the radius of the sphere"
PROP_AS = "the air is at rest in the frame of the body's velocity"
PROP_AU = "the air density is uniform over the region of motion"
PROP_CD = "the drag coefficient C_d is constant over the motion"
PROP_NL = "the body experiences no lift force"
PROP_NM = "the body experiences no Magnus force"
PROP_SP = "the body is a rigid sphere of radius R_s"
PRED_Q = "1e3 < rho_air |v| L_ref / mu_air < 2e5: the Reynolds window of the quadratic law"
PRED_L = "2 rho_air |v| R_s / mu_air < 1: the regime of the linear law"
COND_Q = ("mu_air != 0 and SPEED2 >= 0 and rho_air*sqrt(SPEED2)*L_ref/mu_air > 1000 "
"and rho_air*sqrt(SPEED2)*L_ref/mu_air < 200000")
COND_Qc = ("mu_air!=0 and SPEED2>=0 and rho_air*sqrt(SPEED2)*L_ref/mu_air>1e3 and "
"rho_air*sqrt(SPEED2)*L_ref/mu_air<2e5")
COND_L = "mu_air != 0 and SPEED2 >= 0 and 2*rho_air*sqrt(SPEED2)*R_s/mu_air < 1"
COND_Lc = "mu_air!=0 and SPEED2>=0 and 2*rho_air*sqrt(SPEED2)*R_s/mu_air<1"
assert F.parse_form(COND_Q, "predicate") == ["OK", COND_Qc]
assert F.parse_form(COND_L, "predicate") == ["OK", COND_Lc]
EXPR_Q = "0.5*rho_air*C_d*A_ref*sqrt(SPEED2)*VELOCITY"
EXPR_Qc = "5e-1*rho_air*C_d*A_ref*sqrt(SPEED2)*VELOCITY"
assert F.parse_form(EXPR_Q, "expression") == ["OK", EXPR_Qc]
EXPR_L = "6*pi_c*mu_air*R_s*VELOCITY"
canon_is(EXPR_L, "expression")
canon_is("VELOCITY^2", "expression")
AIRQ_NC = "No quadratic air resistance."
AIRL_NC = "No linear (Stokes) air resistance."
AIR_NI_WIND = "No wind: the air is at rest."
AIR_NI_ALT = "No change of air density or viscosity with altitude or temperature."
AIRQ_NI = [AIR_NI_WIND, AIR_NI_ALT, "No lift, Magnus or added-mass force.",
"No drag outside the Reynolds window of the quadratic law."]
AIRL_NI = [AIR_NI_WIND, AIR_NI_ALT, "No drag outside the regime of the linear law."]
PRE_VEL = {"SLOT": "VELOCITY", "KIND": "DOF", "DIMENSION": "L1 T-1", "MIN": 1, "MAX": 3,
"LABEL": L_VEL}
PAR_RHO = {"NAME": "rho_air", "DIMENSION": "L-3 M1", "SUPPLY": "DECLARED", "VALUE": "1.225",
"UNIT": "kg m-3", "OVERRIDABLE": "NO", "LABEL": L_RHO}
PAR_MU = {"NAME": "mu_air", "DIMENSION": "L-1 M1 T-1", "SUPPLY": "DECLARED",
"VALUE": "1.7894e-5", "UNIT": "Pa s", "OVERRIDABLE": "NO", "LABEL": L_MU}
PAR_PI = {"NAME": "pi_c", "DIMENSION": "1", "SUPPLY": "DECLARED", "VALUE": "3.141592653589793",
"UNIT": "1", "OVERRIDABLE": "NO", "LABEL": L_PI}
SUM_S2 = {"NAME": "SPEED2", "OVER": "VELOCITY", "EACH": "VELOCITY^2"}
ASM_AS = {"NAME": "air_still", "PROPOSITION": PROP_AS, "REFS": ["VELOCITY"]}
ASM_AU = {"NAME": "air_uniform", "PROPOSITION": PROP_AU, "REFS": ["rho_air"]}
AIRQ = {
"ID": "AIR-QUADRATIC", "VERSION": "1.0.0", "TITLE": AIRQ_TITLE, "SOURCE": AIR_SOURCE,
"PREREQUISITES": [PRE_VEL],
"PARAMETERS": [PAR_RHO, PAR_MU,
{"NAME": "C_d", "DIMENSION": "1", "SUPPLY": "AUTHOR", "LABEL": L_CD},
{"NAME": "A_ref", "DIMENSION": "L2", "SUPPLY": "AUTHOR", "LABEL": L_AREF},
{"NAME": "L_ref", "DIMENSION": "L1", "SUPPLY": "AUTHOR", "LABEL": L_LREF}],
"SUMS": [SUM_S2],
"TERMS": [{"TARGET": "VELOCITY", "SIGN": "-", "EXPR": EXPR_Q, "LABEL": "the quadratic drag"}],
"ASSUMPTIONS": [ASM_AS, ASM_AU,
{"NAME": "drag_cd_constant", "PROPOSITION": PROP_CD, "REFS": ["C_d"]},
{"NAME": "drag_no_lift", "PROPOSITION": PROP_NL, "REFS": ["VELOCITY"]},
{"NAME": "drag_no_magnus", "PROPOSITION": PROP_NM, "REFS": ["VELOCITY"]}],
"BOUNDARIES": [{"NAME": "drag_quadratic_regime", "PREDICATE": PRED_Q, "CONDITION": COND_Q}],
"NOT_CHOSEN": AIRQ_NC, "NOT_INCLUDED": AIRQ_NI, "EXCLUDES": ["AIR-LINEAR"],
"INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "PROJECTILE_2D", "TPK": P2, "BIND": {"VELOCITY": ["v_x", "v_z"]},
"VALUES": {"A_ref": {}, "C_d": {}, "L_ref": {}}, "ANSWERS": {}}],
}
assert CD("1.225") == "1225e-3" and CU("kg m-3") == "m-3 kg"
assert CD("1.7894e-5") == "17894e-9" and CU("Pa s") == "s Pa"
AIRL = {
"ID": "AIR-LINEAR", "VERSION": "1.0.0", "TITLE": AIRL_TITLE, "SOURCE": AIR_SOURCE,
"PREREQUISITES": [PRE_VEL],
"PARAMETERS": [PAR_RHO, PAR_MU, PAR_PI,
{"NAME": "R_s", "DIMENSION": "L1", "SUPPLY": "AUTHOR", "LABEL": L_RS}],
"SUMS": [SUM_S2],
"TERMS": [{"TARGET": "VELOCITY", "SIGN": "-", "EXPR": EXPR_L,
"LABEL": "the linear (Stokes) drag"}],
"ASSUMPTIONS": [ASM_AS, ASM_AU,
{"NAME": "drag_sphere", "PROPOSITION": PROP_SP, "REFS": ["R_s"]}],
"BOUNDARIES": [{"NAME": "drag_linear_regime", "PREDICATE": PRED_L, "CONDITION": COND_L}],
"NOT_CHOSEN": AIRL_NC, "NOT_INCLUDED": AIRL_NI, "EXCLUDES": [], "INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "PROJECTILE_2D", "TPK": P2, "BIND": {"VELOCITY": ["v_x", "v_z"]},
"VALUES": {"R_s": {}}, "ANSWERS": {}}],
}
assert CD("3.141592653589793") == "3141592653589793e-15" and CU("1") == "1"
# ---- MATH 1.0.0
MATH_TITLE = "The mathematical constant pi, as the CONSTANT pi_c"
MATH_SOURCE = "pi to 16 significant digits, 3.141592653589793, the nearest IEEE 754 binary64 value"
MATH_NC = "No mathematical constant: pi exists only if you declare it."
MATH_NI = ["pi_c is pi rounded to 16 significant digits; no other mathematical constant is "
"declared."]
MATH = {
"ID": "MATH", "VERSION": "1.0.0", "TITLE": MATH_TITLE, "SOURCE": MATH_SOURCE,
"PREREQUISITES": [], "PARAMETERS": [PAR_PI], "SUMS": [], "TERMS": [], "ASSUMPTIONS": [],
"BOUNDARIES": [], "NOT_CHOSEN": MATH_NC, "NOT_INCLUDED": MATH_NI, "EXCLUDES": [],
"INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "PROJECTILE_2D", "TPK": P2, "BIND": {}, "VALUES": {},
"ANSWERS": {}}],
}
# ---- synthetic fixtures (lifecycle, conflict, sign, override, fail rows)
TPI_TITLE = "Test: a rough pi"
TEST_PI = {
"ID": "TEST-PI", "VERSION": "1.0.0", "TITLE": TPI_TITLE, "SOURCE": "synthetic fixture",
"PREREQUISITES": [],
"PARAMETERS": [{"NAME": "pi_c", "DIMENSION": "1", "SUPPLY": "DECLARED", "VALUE": "3.14",
"UNIT": "1", "OVERRIDABLE": "NO", "LABEL": "pi, roughly"}],
"SUMS": [], "TERMS": [], "ASSUMPTIONS": [], "BOUNDARIES": [],
"NOT_CHOSEN": "No rough pi.", "NOT_INCLUDED": [], "EXCLUDES": [], "INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "PROJECTILE_2D", "TPK": P2, "BIND": {}, "VALUES": {},
"ANSWERS": {}}],
}
assert CD("3.14") == "314e-2"
TX_TITLE = "Test interaction X"
TEST_X = {
"ID": "TEST-X", "VERSION": "1.0.0", "TITLE": TX_TITLE, "SOURCE": "synthetic fixture",
"PREREQUISITES": [],
"PARAMETERS": [{"NAME": "x_test", "DIMENSION": "1", "SUPPLY": "DECLARED", "VALUE": "1",
"UNIT": "1", "OVERRIDABLE": "NO", "LABEL": "a test constant"}],
"SUMS": [], "TERMS": [], "ASSUMPTIONS": [], "BOUNDARIES": [],
"NOT_CHOSEN": "No test interaction X.", "NOT_INCLUDED": ["Test X adds only a constant."],
"EXCLUDES": [], "INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "SOLO", "TPK": SOLO, "BIND": {}, "VALUES": {}, "ANSWERS": {}}],
}
TE_TITLE = "Test: excludes MATH, one way"
TEST_EXCL = {
"ID": "TEST-EXCL", "VERSION": "1.0.0", "TITLE": TE_TITLE, "SOURCE": "synthetic fixture",
"PREREQUISITES": [], "PARAMETERS": [], "SUMS": [], "TERMS": [], "ASSUMPTIONS": [],
"BOUNDARIES": [], "NOT_CHOSEN": "No exclusion test.", "NOT_INCLUDED": [],
"EXCLUDES": ["MATH"], "INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "PROJECTILE_2D", "TPK": P2, "BIND": {}, "VALUES": {},
"ANSWERS": {}}],
}
TP_TITLE = "Test: a push and a pull"
L_PUSHED = "the velocity that receives the push"
L_PULLED = "the velocity that receives the pull"
L_FA = "the first applied force"
L_FB = "the second applied force"
TEST_PUSH = {
"ID": "TEST-PUSH", "VERSION": "1.0.0", "TITLE": TP_TITLE, "SOURCE": "synthetic fixture",
"PREREQUISITES": [{"SLOT": "PUSHED", "KIND": "DOF", "DIMENSION": "L1 T-1", "MIN": 1, "MAX": 1,
"LABEL": L_PUSHED},
{"SLOT": "PULLED", "KIND": "DOF", "DIMENSION": "L1 T-1", "MIN": 1, "MAX": 1,
"LABEL": L_PULLED}],
"PARAMETERS": [{"NAME": "F_a", "DIMENSION": "L1 M1 T-2", "SUPPLY": "AUTHOR", "LABEL": L_FA},
{"NAME": "F_b", "DIMENSION": "L1 M1 T-2", "SUPPLY": "AUTHOR", "LABEL": L_FB}],
"SUMS": [],
"TERMS": [{"TARGET": "PUSHED", "SIGN": "+", "EXPR": "F_a+F_b", "LABEL": "the push"},
{"TARGET": "PULLED", "SIGN": "-", "EXPR": "F_a+F_b", "LABEL": "the pull"}],
"ASSUMPTIONS": [], "BOUNDARIES": [], "NOT_CHOSEN": "No push or pull.", "NOT_INCLUDED": [],
"EXCLUDES": [], "INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "PROJECTILE_2D", "TPK": P2,
"BIND": {"PULLED": ["v_z"], "PUSHED": ["v_x"]},
"VALUES": {"F_a": {}, "F_b": {}}, "ANSWERS": {}}],
}
TO_TITLE = "Test: an overridable declared value"
TEST_OVR = {
"ID": "TEST-OVR", "VERSION": "1.0.0", "TITLE": TO_TITLE, "SOURCE": "synthetic fixture",
"PREREQUISITES": [],
"PARAMETERS": [{"NAME": "k_loc", "DIMENSION": "1", "SUPPLY": "DECLARED", "VALUE": "2",
"UNIT": "1", "OVERRIDABLE": "YES", "LABEL": "a local factor"}],
"SUMS": [], "TERMS": [], "ASSUMPTIONS": [], "BOUNDARIES": [],
"NOT_CHOSEN": "No local factor.", "NOT_INCLUDED": [], "EXCLUDES": [], "INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "PROJECTILE_2D", "TPK": P2, "BIND": {}, "VALUES": {},
"ANSWERS": {}}],
}
TB_TITLE = "Test: a boundary without its guard"
TEST_BADB = {
"ID": "TEST-BADB", "VERSION": "1.0.0", "TITLE": TB_TITLE, "SOURCE": "synthetic fixture",
"PREREQUISITES": [{"SLOT": "SPEEDY", "KIND": "DOF", "DIMENSION": "L1 T-1", "MIN": 1, "MAX": 1,
"LABEL": "a velocity"}],
"PARAMETERS": [{"NAME": "k_b", "DIMENSION": "1", "SUPPLY": "AUTHOR", "LABEL": "a number"},
{"NAME": "h_b", "DIMENSION": "1", "SUPPLY": "AUTHOR", "LABEL": "a divisor"}],
"SUMS": [], "TERMS": [], "ASSUMPTIONS": [],
"BOUNDARIES": [{"NAME": "bad_region", "PREDICATE": "k_b / h_b > 1",
"CONDITION": "k_b/h_b > 1"}],
"NOT_CHOSEN": "No guard test.", "NOT_INCLUDED": [], "EXCLUDES": [], "INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "PROJECTILE_2D", "TPK": P2, "BIND": {"SPEEDY": ["v_x"]},
"VALUES": {"h_b": {}, "k_b": {}}, "ANSWERS": {}},
{"NAME": "WRONG", "TPK": P2, "BIND": {"SPEEDY": ["z"]},
"VALUES": {"h_b": {}, "k_b": {}}, "ANSWERS": {}}],
}
canon_is("k_b/h_b>1", "predicate")
# ---- fixtures of RV's review (rv_additions.py; F-9) and of S51
TEST_SUMP = {
"ID": "TEST-SUMP", "VERSION": "1.0.0", "TITLE": "Test: a sum inside a product",
"SOURCE": "synthetic fixture", "PREREQUISITES": [PRE_VEL],
"PARAMETERS": [{"NAME": "k_q", "DIMENSION": "L-1 M1", "SUPPLY": "AUTHOR",
"LABEL": "a quadratic coefficient"}],
"SUMS": [SUM_S2],
"TERMS": [{"TARGET": "VELOCITY", "SIGN": "-", "EXPR": "k_q*SPEED2", "LABEL": "the test force"}],
"ASSUMPTIONS": [], "BOUNDARIES": [], "NOT_CHOSEN": "No sum test.", "NOT_INCLUDED": [],
"EXCLUDES": [], "INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "PROJECTILE_2D", "TPK": P2, "BIND": {"VELOCITY": ["v_x", "v_z"]},
"VALUES": {"k_q": {}}, "ANSWERS": {}}],
}
TEST_NOAIR = {
"ID": "TEST-NOAIR", "VERSION": "1.0.0", "TITLE": "Test: excludes every AIR module",
"SOURCE": "synthetic fixture", "PREREQUISITES": [], "PARAMETERS": [], "SUMS": [], "TERMS": [],
"ASSUMPTIONS": [], "BOUNDARIES": [], "NOT_CHOSEN": "No pattern test.", "NOT_INCLUDED": [],
"EXCLUDES": ["AIR-*"], "INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "PROJECTILE_2D", "TPK": P2, "BIND": {}, "VALUES": {},
"ANSWERS": {}}],
}
FIX2 = {"DOF": [dof("t", "T1"), dof("x", "L1")],
"LAW": [law("EQ_X", "D(x,t)=0", "EOM(x)", ["t", "x"], "dx/dt = 0")],
"CONSTRAINT": [{"NAME": "C1", "TARGET": "x", "ACTION": "FIX", "VALUE": "0", "UNIT": "m",
"STATEMENT": "x is held at 0"},
{"NAME": "C2", "TARGET": "x", "ACTION": "FIX", "VALUE": "1", "UNIT": "m",
"STATEMENT": "x is held at 1"}]}
TEST_T3 = {
"ID": "TEST-T3", "VERSION": "1.0.0", "TITLE": "Test: a binding failing T3",
"SOURCE": "synthetic fixture", "PREREQUISITES": [],
"PARAMETERS": [{"NAME": "k_t", "DIMENSION": "1", "SUPPLY": "DECLARED", "VALUE": "1",
"UNIT": "1", "OVERRIDABLE": "NO", "LABEL": "a constant"}],
"SUMS": [], "TERMS": [], "ASSUMPTIONS": [], "BOUNDARIES": [], "NOT_CHOSEN": "No T3 test.",
"NOT_INCLUDED": [], "EXCLUDES": [], "INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "FIX2", "TPK": FIX2, "BIND": {}, "VALUES": {}, "ANSWERS": {}}],
}
TEST_SHARE = {
"ID": "TEST-SHARE", "VERSION": "1.0.0", "TITLE": "Test: an author parameter shared by name",
"SOURCE": "synthetic fixture", "PREREQUISITES": [],
"PARAMETERS": [{"NAME": "k_q", "DIMENSION": "L-1 M1", "SUPPLY": "AUTHOR",
"LABEL": "a quadratic coefficient, named by another module too"}],
"SUMS": [], "TERMS": [], "ASSUMPTIONS": [], "BOUNDARIES": [],
"NOT_CHOSEN": "No shared-name test.", "NOT_INCLUDED": [], "EXCLUDES": [], "INTERACTS_WITH": [],
"TEST_BINDINGS": [{"NAME": "PROJECTILE_2D", "TPK": P2, "BIND": {}, "VALUES": {"k_q": {}},
"ANSWERS": {}}],
}
TEST_KQ1 = copy.deepcopy(TEST_SHARE)
TEST_KQ1["ID"] = "TEST-KQ1"
TEST_KQ1["TITLE"] = "Test: an author parameter of another dimension"
TEST_KQ1["PARAMETERS"][0]["DIMENSION"] = "1"
TEST_KQ1["NOT_CHOSEN"] = "No dimension test."
# ---- texts and digests
def TXT(d):
return json.dumps(d, ensure_ascii=True)
X_GRAV, X_GRAV11, X_GRAV12, X_GRAV20 = TXT(GRAV), TXT(GRAV11), TXT(GRAV12), TXT(GRAV20)
X_AIRQ, X_AIRL, X_MATH = TXT(AIRQ), TXT(AIRL), TXT(MATH)
X_TPI, X_TX, X_TEXCL, X_TPUSH = TXT(TEST_PI), TXT(TEST_X), TXT(TEST_EXCL), TXT(TEST_PUSH)
X_TOVR, X_TBADB = TXT(TEST_OVR), TXT(TEST_BADB)
D_GRAV, D_GRAV11, D_GRAV12, D_GRAV20 = MDIG(GRAV), MDIG(GRAV11), MDIG(GRAV12), MDIG(GRAV20)
D_AIRQ, D_AIRL, D_MATH = MDIG(AIRQ), MDIG(AIRL), MDIG(MATH)
D_TPI, D_TX, D_TEXCL, D_TPUSH = MDIG(TEST_PI), MDIG(TEST_X), MDIG(TEST_EXCL), MDIG(TEST_PUSH)
D_TOVR, D_TBADB = MDIG(TEST_OVR), MDIG(TEST_BADB)
X_SUMP, X_NOAIR, X_T3, X_SHARE, X_KQ1 = (TXT(TEST_SUMP), TXT(TEST_NOAIR), TXT(TEST_T3),
TXT(TEST_SHARE), TXT(TEST_KQ1))
D_SUMP, D_NOAIR, D_T3, D_SHARE, D_KQ1 = (MDIG(TEST_SUMP), MDIG(TEST_NOAIR), MDIG(TEST_T3),
MDIG(TEST_SHARE), MDIG(TEST_KQ1))
# ---- the anchors of CT (tpm_module_spec 0.2.0 section 4), typed by hand from the spec
_DOF5 = ('"DOF":[{"DIMENSION":"T1","NAME":"t"},{"DIMENSION":"L1","NAME":"x"},'
'{"DIMENSION":"L1","NAME":"z"},{"DIMENSION":"L1 T-1","NAME":"v_x"},'
'{"DIMENSION":"L1 T-1","NAME":"v_z"}]')
_KINX = ('{"EQUATION":"D(x,t)=v_x","NAME":"KIN_X","REFS":["t","v_x","x"],"ROLE":"EOM(x)",'
'"STATEMENT":"dx/dt = v_x"}')
_EOMX = ('{"EQUATION":"m*D(v_x,t)=0","NAME":"EOM_X","REFS":["m","t","v_x"],"ROLE":"EOM(v_x)",'
'"STATEMENT":"m dv_x/dt = 0"}')
P2_CT = ('{"ASSUMPTION":[{"NAME":"point_mass","PROPOSITION":"the body is treated as a point mass",'
'"REFS":["m"]}],"CONSTANT":[{"DIMENSION":"M1","NAME":"m"}],' + _DOF5 + ',"LAW":['
+ _KINX + ',{"EQUATION":"D(z,t)=v_z","NAME":"KIN_Z","REFS":["t","v_z","z"],"ROLE":"EOM(z)",'
'"STATEMENT":"dz/dt = v_z"},' + _EOMX + ',{"EQUATION":"m*D(v_z,t)=0","NAME":"EOM_Z",'
'"REFS":["m","t","v_z"],"ROLE":"EOM(v_z)","STATEMENT":"m dv_z/dt = 0"}]}')
TABLE_CT = ('{"CONSTANT":[{"DIMENSION":"M1","NAME":"m"}],"CONSTRAINT":[{"ACTION":"FIX","NAME":"C_Z",'
'"STATEMENT":"the puck stays on the table top, z = 0","TARGET":"z","UNIT":"m",'
'"VALUE":"0"},{"ACTION":"FIX","NAME":"C_VZ","STATEMENT":"the puck has no vertical '
'velocity","TARGET":"v_z","UNIT":"m s-1","VALUE":"0"}],' + _DOF5 + ',"LAW":[' + _KINX
+ ',{"EQUATION":"D(z,t)=v_z","NAME":"KIN_Z","REFS":["t","v_z","z"],"ROLE":"CONSTRAINT",'
'"STATEMENT":"dz/dt = v_z"},' + _EOMX + ']}')
MATH_CT = ('{"ID":"MATH","VERSION":"1.0.0","TITLE":"The mathematical constant pi, as the CONSTANT '
'pi_c","SOURCE":"pi to 16 significant digits, 3.141592653589793, the nearest IEEE 754 '
'binary64 value","PREREQUISITES":[],"PARAMETERS":[{"NAME":"pi_c","DIMENSION":"1",'
'"SUPPLY":"DECLARED","LABEL":"pi, to 16 significant digits","VALUE":"3.141592653589793",'
'"UNIT":"1","OVERRIDABLE":"NO"}],"SUMS":[],"TERMS":[],"ASSUMPTIONS":[],"BOUNDARIES":[],'
'"NOT_CHOSEN":"No mathematical constant: pi exists only if you declare it.",'
'"NOT_INCLUDED":["pi_c is pi rounded to 16 significant digits; no other mathematical '
'constant is declared."],"EXCLUDES":[],"INTERACTS_WITH":[],"TEST_BINDINGS":[{"NAME":'
'"PROJECTILE_2D","TPK":' + P2_CT + ',"BIND":{},"VALUES":{},"ANSWERS":{}}]}')
_GBIND = '"BIND":{"HEIGHT":["z"],"MASS":["m"],"UP_VELOCITY":["v_z"]}'
GRAV_CT = ('{"ID":"GRAV-UNIFORM","VERSION":"1.0.0","TITLE":"Uniform gravity at the standard value '
'g_n","SOURCE":"g_n = 9.80665 m s-2, exact by definition: 3rd CGPM (1901), declaration '
'on the unit of mass and on the definition of weight","PREREQUISITES":[{"SLOT":"MASS",'
'"KIND":"CONSTANT","DIMENSION":"M1","MIN":1,"MAX":1,"LABEL":"the mass of the body"},'
'{"SLOT":"UP_VELOCITY","KIND":"DOF","DIMENSION":"L1 T-1","MIN":1,"MAX":1,"LABEL":"the '
'velocity component pointing up; its equation of motion receives the weight"},'
'{"SLOT":"HEIGHT","KIND":"DOF","DIMENSION":"L1","MIN":1,"MAX":1,"LABEL":"the position '
'along the same upward axis"}],"PARAMETERS":[{"NAME":"g_n","DIMENSION":"L1 T-2",'
'"SUPPLY":"DECLARED","LABEL":"the standard acceleration of gravity","VALUE":"9.80665",'
'"UNIT":"m s-2","OVERRIDABLE":"NO"},{"NAME":"h_max","DIMENSION":"L1","SUPPLY":"AUTHOR",'
'"LABEL":"the largest height, above or below the origin, over which the field is taken '
'as uniform"}],"SUMS":[],"TERMS":[{"TARGET":"UP_VELOCITY","SIGN":"-","EXPR":"MASS*g_n",'
'"LABEL":"the weight"}],"ASSUMPTIONS":[{"NAME":"gravity_uniform","PROPOSITION":"the '
'gravitational field is uniform over the region abs({HEIGHT}) < h_max","REFS":["HEIGHT",'
'"h_max"]},{"NAME":"gravity_standard","PROPOSITION":"the local gravitational '
'acceleration equals the conventional standard value g_n","REFS":["g_n"]},{"NAME":'
'"frame_inertial","PROPOSITION":"the frame in which {HEIGHT} is measured is treated as '
'inertial","REFS":["HEIGHT"]}],"BOUNDARIES":[{"NAME":"gravity_region","PREDICATE":'
'"abs({HEIGHT}) < h_max","CONDITION":"abs(HEIGHT) < h_max"}],"NOT_CHOSEN":"No gravity: '
'no weight acts on any DOF.","NOT_INCLUDED":["No variation of gravity with height or '
'latitude beyond what g_n contains.","No Earth rotation (Coriolis or centrifugal force) '
'beyond what g_n contains.","No gravity of bodies other than the Earth."],"EXCLUDES":[],'
'"INTERACTS_WITH":[{"WITH":"AIR-*","QUESTION_ID":"BUOYANCY","QUESTION":"Does the '
"air's buoyancy (upthrust) act on the body in the system you mean?\",\"YES_LINE\":"
'"Buoyancy acts (your answer): no module adds it; it acts in this kernel only where one '
'of your own laws states it.","NO_LINE":"No buoyancy: the air' + "'" + 's upthrust on the '
'body is not included."}],"TEST_BINDINGS":[{"NAME":"PROJECTILE_2D","TPK":' + P2_CT + ','
+ _GBIND + ',"VALUES":{"h_max":{"UNIT":"m","VALUE":"100"}},"ANSWERS":{}},{"NAME":'
'"TABLE","TPK":' + TABLE_CT + ',' + _GBIND + ',"VALUES":{"h_max":{"UNIT":"m","VALUE":'
'"1"}},"ANSWERS":{"FIXED:v_z":"YES"}}]}')
assert GV(P2) == P2_CT and GV(TABLE) == TABLE_CT
assert CT(MATH) == MATH_CT, (CT(MATH), MATH_CT)
assert CT(GRAV) == GRAV_CT
assert D_MATH == G.digest_fields(["TPC-MODULE", "1", MATH_CT])
assert D_GRAV == G.digest_fields(["TPC-MODULE", "1", GRAV_CT])
ALLD = [D_GRAV, D_GRAV11, D_GRAV12, D_GRAV20, D_AIRQ, D_AIRL, D_MATH, D_TPI, D_TX, D_TEXCL,
D_TPUSH, D_TOVR, D_TBADB, D_SUMP, D_NOAIR, D_T3, D_SHARE, D_KQ1]
assert len(set(ALLD)) == len(ALLD)
M4 = [X_AIRL, X_AIRQ, X_GRAV, X_MATH] # the first catalogue's texts
def claim(i, v, d, bind, values, answers):
return {"ID": i, "VERSION": v, "DIGEST": d, "BIND": bind, "VALUES": values,
"ANSWERS": answers}
C_GRAV = claim("GRAV-UNIFORM", "1.0.0", D_GRAV, GRAV_BIND,
{"h_max": {"VALUE": "100", "UNIT": "m"}}, {})
C_GRAV_NO = claim("GRAV-UNIFORM", "1.0.0", D_GRAV, GRAV_BIND,
{"h_max": {"VALUE": "100", "UNIT": "m"}}, {"BUOYANCY": "NO"})
C_GRAV_YES = claim("GRAV-UNIFORM", "1.0.0", D_GRAV, GRAV_BIND,
{"h_max": {"VALUE": "100", "UNIT": "m"}}, {"BUOYANCY": "YES"})
C_AIRQ = claim("AIR-QUADRATIC", "1.0.0", D_AIRQ, {"VELOCITY": ["v_x", "v_z"]},
{"A_ref": {}, "C_d": {}, "L_ref": {}}, {})
C_AIRL = claim("AIR-LINEAR", "1.0.0", D_AIRL, {"VELOCITY": ["v_x", "v_z"]}, {"R_s": {}}, {})
C_MATH = claim("MATH", "1.0.0", D_MATH, {}, {}, {})
C_TPI = claim("TEST-PI", "1.0.0", D_TPI, {}, {}, {})
C_TX = claim("TEST-X", "1.0.0", D_TX, {}, {}, {})
C_TEXCL = claim("TEST-EXCL", "1.0.0", D_TEXCL, {}, {}, {})
C_TPUSH = claim("TEST-PUSH", "1.0.0", D_TPUSH, {"PULLED": ["v_z"], "PUSHED": ["v_x"]},
{"F_a": {}, "F_b": {}}, {})
# ================================================== the expanded TPKs (literal)
E_GN = {"NAME": "g_n", "DIMENSION": "L1 T-2", "VALUE": "9.80665", "UNIT": "m s-2"}
E_HMAX100 = {"NAME": "h_max", "DIMENSION": "L1", "VALUE": "100", "UNIT": "m"}
E_HMAX1 = {"NAME": "h_max", "DIMENSION": "L1", "VALUE": "1", "UNIT": "m"}
E_HMAXS = {"NAME": "h_max", "DIMENSION": "L1"}
E_GU = {"NAME": "gravity_uniform",
"PROPOSITION": "the gravitational field is uniform over the region abs(z) < h_max",
"REFS": ["h_max", "z"]}
E_GS = {"NAME": "gravity_standard", "PROPOSITION": PROP_GS, "REFS": ["g_n"]}
E_FI = {"NAME": "frame_inertial",
"PROPOSITION": "the frame in which z is measured is treated as inertial", "REFS": ["z"]}
E_GR = {"NAME": "gravity_region", "PREDICATE": "abs(z) < h_max", "REFS": ["h_max", "z"],
"CONDITION": "abs(z)<h_max"}
E_NT = {"NAME": "gravity_no_tides", "PROPOSITION": PROP_NT, "REFS": ["z"]}
E_ZREF = {"NAME": "z_ref", "DIMENSION": "L1"}
E_FIXVZ = {"NAME": "fixed_v_z_balanced",
"PROPOSITION": "a constraint force along v_z balances every other force along v_z",
"REFS": ["v_z"]}
GRAV_EMIT_100 = [("CONSTANT", E_GN), ("CONSTANT", E_HMAX100), ("ASSUMPTION", E_GU),
("ASSUMPTION", E_GS), ("ASSUMPTION", E_FI), ("BOUNDARY", E_GR)]
E_RHO = {"NAME": "rho_air", "DIMENSION": "L-3 M1", "VALUE": "1.225", "UNIT": "kg m-3"}
E_MU = {"NAME": "mu_air", "DIMENSION": "L-1 M1 T-1", "VALUE": "1.7894e-5", "UNIT": "Pa s"}
E_PI = {"NAME": "pi_c", "DIMENSION": "1", "VALUE": "3.141592653589793", "UNIT": "1"}
E_PIR = {"NAME": "pi_c", "DIMENSION": "1", "VALUE": "3.14", "UNIT": "1"}
E_CD = {"NAME": "C_d", "DIMENSION": "1"}
E_AREF = {"NAME": "A_ref", "DIMENSION": "L2"}
E_LREF = {"NAME": "L_ref", "DIMENSION": "L1"}
E_RS = {"NAME": "R_s", "DIMENSION": "L1"}
E_AS2 = {"NAME": "air_still", "PROPOSITION": PROP_AS, "REFS": ["v_x", "v_z"]}
E_AU = {"NAME": "air_uniform", "PROPOSITION": PROP_AU, "REFS": ["rho_air"]}
E_DCD = {"NAME": "drag_cd_constant", "PROPOSITION": PROP_CD, "REFS": ["C_d"]}
E_NL2 = {"NAME": "drag_no_lift", "PROPOSITION": PROP_NL, "REFS": ["v_x", "v_z"]}
E_NM2 = {"NAME": "drag_no_magnus", "PROPOSITION": PROP_NM, "REFS": ["v_x", "v_z"]}
E_SPH = {"NAME": "drag_sphere", "PROPOSITION": PROP_SP, "REFS": ["R_s"]}
COND_Q2 = ("mu_air!=0 and v_x^2+v_z^2>=0 and rho_air*sqrt(v_x^2+v_z^2)*L_ref/mu_air>1e3 and "
"rho_air*sqrt(v_x^2+v_z^2)*L_ref/mu_air<2e5")
COND_L2 = "mu_air!=0 and v_x^2+v_z^2>=0 and 2*rho_air*sqrt(v_x^2+v_z^2)*R_s/mu_air<1"
canon_is(COND_Q2, "predicate")
canon_is(COND_L2, "predicate")
E_BQ2 = {"NAME": "drag_quadratic_regime", "PREDICATE": PRED_Q,
"REFS": ["L_ref", "mu_air", "rho_air", "v_x", "v_z"], "CONDITION": COND_Q2}
E_BL2 = {"NAME": "drag_linear_regime", "PREDICATE": PRED_L,
"REFS": ["R_s", "mu_air", "rho_air", "v_x", "v_z"], "CONDITION": COND_L2}
AIRQ_EMIT_2 = [("CONSTANT", E_RHO), ("CONSTANT", E_MU), ("CONSTANT", E_CD), ("CONSTANT", E_AREF),
("CONSTANT", E_LREF), ("ASSUMPTION", E_AS2), ("ASSUMPTION", E_AU),
("ASSUMPTION", E_DCD), ("ASSUMPTION", E_NL2), ("ASSUMPTION", E_NM2),
("BOUNDARY", E_BQ2)]
AIRL_EMIT_2 = [("CONSTANT", E_RHO), ("CONSTANT", E_MU), ("CONSTANT", E_PI), ("CONSTANT", E_RS),
("ASSUMPTION", E_AS2), ("ASSUMPTION", E_AU), ("ASSUMPTION", E_SPH),
("BOUNDARY", E_BL2)]
EQ_GZ = "m*D(v_z,t)=-m*g_n"
EQ_QX = "m*D(v_x,t)=-5e-1*rho_air*C_d*A_ref*sqrt(v_x^2+v_z^2)*v_x"
EQ_QZ = "m*D(v_z,t)=-5e-1*rho_air*C_d*A_ref*sqrt(v_x^2+v_z^2)*v_z"
EQ_QZG = "m*D(v_z,t)=-5e-1*rho_air*C_d*A_ref*sqrt(v_x^2+v_z^2)*v_z-m*g_n"
EQ_LX = "m*D(v_x,t)=-6*pi_c*mu_air*R_s*v_x"
EQ_LZ = "m*D(v_z,t)=-6*pi_c*mu_air*R_s*v_z"
EQ_LZG = "m*D(v_z,t)=-6*pi_c*mu_air*R_s*v_z-m*g_n"
for _eq in (EQ_GZ, EQ_QX, EQ_QZ, EQ_QZG, EQ_LX, EQ_LZ, EQ_LZG):
canon_is(_eq, "equation")
TQ_X = "-5e-1*rho_air*C_d*A_ref*sqrt(v_x^2+v_z^2)*v_x"
TQ_Z = "-5e-1*rho_air*C_d*A_ref*sqrt(v_x^2+v_z^2)*v_z"
R_QX = ["A_ref", "C_d", "m", "rho_air", "t", "v_x", "v_z"]
R_QZ = ["A_ref", "C_d", "m", "rho_air", "t", "v_x", "v_z"]
R_QZG = ["A_ref", "C_d", "g_n", "m", "rho_air", "t", "v_x", "v_z"]
R_LX = ["R_s", "m", "mu_air", "pi_c", "t", "v_x"]
R_LZ = ["R_s", "m", "mu_air", "pi_c", "t", "v_z"]
R_LZG = ["R_s", "g_n", "m", "mu_air", "pi_c", "t", "v_z"]
S_W = "; with the weight: -m*g_n"
S_QX = "; with the quadratic drag: " + TQ_X
S_QZ = "; with the quadratic drag: " + TQ_Z
S_LX = "; with the linear (Stokes) drag: -6*pi_c*mu_air*R_s*v_x"
S_LZ = "; with the linear (Stokes) drag: -6*pi_c*mu_air*R_s*v_z"
EXP_GRAV = tpk_mod(P2, {"EOM_Z": (EQ_GZ, ["g_n", "m", "t", "v_z"], "m dv_z/dt = 0" + S_W)},
GRAV_EMIT_100)
EXP_AIRQ = tpk_mod(P2, {"EOM_X": (EQ_QX, R_QX, "m dv_x/dt = 0" + S_QX),
"EOM_Z": (EQ_QZ, R_QZ, "m dv_z/dt = 0" + S_QZ)}, AIRQ_EMIT_2)
EXP_AIRL = tpk_mod(P2, {"EOM_X": (EQ_LX, R_LX, "m dv_x/dt = 0" + S_LX),
"EOM_Z": (EQ_LZ, R_LZ, "m dv_z/dt = 0" + S_LZ)}, AIRL_EMIT_2)
EXP_MATH = tpk_mod(P2, None, [("CONSTANT", E_PI)])
EXP_GRAV_AIRQ = tpk_mod(P2, {"EOM_X": (EQ_QX, R_QX, "m dv_x/dt = 0" + S_QX),
"EOM_Z": (EQ_QZG, R_QZG, "m dv_z/dt = 0" + S_QZ + S_W)},
AIRQ_EMIT_2 + GRAV_EMIT_100)
EXP_GRAV_AIRL = tpk_mod(P2, {"EOM_X": (EQ_LX, R_LX, "m dv_x/dt = 0" + S_LX),
"EOM_Z": (EQ_LZG, R_LZG, "m dv_z/dt = 0" + S_LZ + S_W)},
AIRL_EMIT_2 + GRAV_EMIT_100)
EXP_GRAV_MATH = tpk_mod(P2, {"EOM_Z": (EQ_GZ, ["g_n", "m", "t", "v_z"], "m dv_z/dt = 0" + S_W)},
GRAV_EMIT_100 + [("CONSTANT", E_PI)])
EXP_AIRQ_MATH = tpk_mod(P2, {"EOM_X": (EQ_QX, R_QX, "m dv_x/dt = 0" + S_QX),
"EOM_Z": (EQ_QZ, R_QZ, "m dv_z/dt = 0" + S_QZ)},
AIRQ_EMIT_2 + [("CONSTANT", E_PI)])
EXP_GRAV_AIRL_MATH = EXP_GRAV_AIRL # pi_c merges: one entry
EXP_TABLE = tpk_mod(TABLE, None, [("CONSTANT", E_GN), ("CONSTANT", E_HMAX1), ("ASSUMPTION", E_GU),
("ASSUMPTION", E_GS), ("ASSUMPTION", E_FI), ("BOUNDARY", E_GR),
("ASSUMPTION", E_FIXVZ)])
EXP_TPI = tpk_mod(P2, None, [("CONSTANT", E_PIR)])
EXP_AIRQ_TPI = tpk_mod(P2, {"EOM_X": (EQ_QX, R_QX, "m dv_x/dt = 0" + S_QX),
"EOM_Z": (EQ_QZ, R_QZ, "m dv_z/dt = 0" + S_QZ)},
AIRQ_EMIT_2 + [("CONSTANT", E_PIR)])
EXP_GRAV_TPI = tpk_mod(P2, {"EOM_Z": (EQ_GZ, ["g_n", "m", "t", "v_z"], "m dv_z/dt = 0" + S_W)},
GRAV_EMIT_100 + [("CONSTANT", E_PIR)])
EXP_TX = tpk_mod(SOLO, None, [("CONSTANT", {"NAME": "x_test", "DIMENSION": "1", "VALUE": "1",
"UNIT": "1"})])
EXP_GRAV12 = tpk_mod(P2, {"EOM_Z": (EQ_GZ, ["g_n", "m", "t", "v_z"], "m dv_z/dt = 0" + S_W)},
GRAV_EMIT_100[:5] + [("ASSUMPTION", E_NT), ("BOUNDARY", E_GR)])
for _t, _n in [(EXP_GRAV, "EXP_GRAV"), (EXP_AIRQ, "EXP_AIRQ"), (EXP_AIRL, "EXP_AIRL"),
(EXP_MATH, "EXP_MATH"), (EXP_GRAV_AIRQ, "EXP_GRAV_AIRQ"),
(EXP_GRAV_AIRL, "EXP_GRAV_AIRL"), (EXP_GRAV_MATH, "EXP_GRAV_MATH"),
(EXP_AIRQ_MATH, "EXP_AIRQ_MATH"), (EXP_TABLE, "EXP_TABLE"), (EXP_TPI, "EXP_TPI"),
(EXP_AIRQ_TPI, "EXP_AIRQ_TPI"), (EXP_GRAV_TPI, "EXP_GRAV_TPI"), (EXP_TX, "EXP_TX"),
(EXP_GRAV12, "EXP_GRAV12")]:
must_pass(_t, _n)
def SRC(*mods):
return list(mods)
G1 = "GRAV-UNIFORM 1.0.0"
AQ1 = "AIR-QUADRATIC 1.0.0"
AL1 = "AIR-LINEAR 1.0.0"
MA1 = "MATH 1.0.0"
def rows_of(emit, src):
return [[k, e["NAME"], [src]] for k, e in emit]
def XR(tpk, composed, emitted, dropped):
"""The render of module_expand (tpm_module_spec 6.4 RENDER)."""
return OK(["D(TPK)", DTPK(tpk)], ["COMPOSED", composed], ["EMITTED", emitted],
["DROPPED", dropped])
R_GRAV = XR(EXP_GRAV, [["EOM_Z", EQ_GZ]], rows_of(GRAV_EMIT_100, G1), [])
R_AIRQ = XR(EXP_AIRQ, [["EOM_X", EQ_QX], ["EOM_Z", EQ_QZ]], rows_of(AIRQ_EMIT_2, AQ1), [])
R_AIRL = XR(EXP_AIRL, [["EOM_X", EQ_LX], ["EOM_Z", EQ_LZ]], rows_of(AIRL_EMIT_2, AL1), [])
R_MATH = XR(EXP_MATH, [], [["CONSTANT", "pi_c", [MA1]]], [])
R_GRAV_AIRQ = XR(EXP_GRAV_AIRQ, [["EOM_X", EQ_QX], ["EOM_Z", EQ_QZG]],
rows_of(AIRQ_EMIT_2, AQ1) + rows_of(GRAV_EMIT_100, G1), [])
_airl_math_rows = rows_of(AIRL_EMIT_2, AL1)
_airl_math_rows[2] = ["CONSTANT", "pi_c", [AL1, MA1]]
R_AIRL_MATH = XR(EXP_AIRL, [["EOM_X", EQ_LX], ["EOM_Z", EQ_LZ]], _airl_math_rows, [])
R_GRAV_AIRL_MATH = XR(EXP_GRAV_AIRL_MATH, [["EOM_X", EQ_LX], ["EOM_Z", EQ_LZG]],
_airl_math_rows + rows_of(GRAV_EMIT_100, G1), [])
# ============================================================ module_parse
def SUMMARY_GRAV():
return OK("GRAV-UNIFORM", "1.0.0",
[["MASS", "CONSTANT", "M1", 1, 1], ["UP_VELOCITY", "DOF", "L1 T-1", 1, 1],
["HEIGHT", "DOF", "L1", 1, 1]],
[["g_n", "DECLARED", "L1 T-2"], ["h_max", "AUTHOR", "L1"]],
[], [["UP_VELOCITY", "-", "MASS*g_n"]],
["gravity_uniform", "gravity_standard", "frame_inertial"],
[["gravity_region", "abs(HEIGHT)<h_max"]], [], [["AIR-*", "BUOYANCY"]],
["PROJECTILE_2D", "TABLE"])
add("P01 GRAV-UNIFORM 1.0.0 parses", "module_parse", {"text": X_GRAV}, SUMMARY_GRAV())
add("P02 AIR-QUADRATIC 1.0.0 parses; the template spelled canonically", "module_parse",
{"text": X_AIRQ},
OK("AIR-QUADRATIC", "1.0.0", [["VELOCITY", "DOF", "L1 T-1", 1, 3]],
[["rho_air", "DECLARED", "L-3 M1"], ["mu_air", "DECLARED", "L-1 M1 T-1"],
["C_d", "AUTHOR", "1"], ["A_ref", "AUTHOR", "L2"], ["L_ref", "AUTHOR", "L1"]],
[["SPEED2", "VELOCITY", "VELOCITY^2"]], [["VELOCITY", "-", EXPR_Qc]],
["air_still", "air_uniform", "drag_cd_constant", "drag_no_lift", "drag_no_magnus"],
[["drag_quadratic_regime", COND_Qc]], ["AIR-LINEAR"], [], ["PROJECTILE_2D"]))
add("P03 AIR-LINEAR 1.0.0 parses", "module_parse", {"text": X_AIRL},
OK("AIR-LINEAR", "1.0.0", [["VELOCITY", "DOF", "L1 T-1", 1, 3]],
[["rho_air", "DECLARED", "L-3 M1"], ["mu_air", "DECLARED", "L-1 M1 T-1"],
["pi_c", "DECLARED", "1"], ["R_s", "AUTHOR", "L1"]],
[["SPEED2", "VELOCITY", "VELOCITY^2"]], [["VELOCITY", "-", EXPR_L]],
["air_still", "air_uniform", "drag_sphere"], [["drag_linear_regime", COND_Lc]], [], [],
["PROJECTILE_2D"]))
add("P04 MATH 1.0.0 parses", "module_parse", {"text": X_MATH},
OK("MATH", "1.0.0", [], [["pi_c", "DECLARED", "1"]], [], [], [], [], [], [],
["PROJECTILE_2D"]))
add("P05 GRAV pretty-printed: white space outside strings changes nothing", "module_parse",
{"text": json.dumps(GRAV, indent=2)}, SUMMARY_GRAV())
_dimvar = copy.deepcopy(GRAV)
_dimvar["PREREQUISITES"][1]["DIMENSION"] = "T-1 L1"
add("P06 a DIMENSION written T-1 L1 is kept normalized", "module_parse", {"text": TXT(_dimvar)},
SUMMARY_GRAV())
add("P07 the text is not a string", "module_parse", {"text": GRAV},
REF("MODULE: the text is not a string"))
add("P08 a text of 262145 bytes", "module_parse", {"text_parts": [["x", 262145]]},
REF("MODULE: the text is longer than 262144 bytes"))
add("P09 131073 two-byte letters: 262146 bytes", "module_parse",
{"text_parts": [["\u00e9", 131073]]}, REF("MODULE: the text is longer than 262144 bytes"))
add("P10 exactly 262144 bytes, an array: read, then not an object", "module_parse",
{"text_parts": [["[", 1], [" ", 262142], ["]", 1]]},
REF("MODULE: the text is not a JSON object"))
NOTJSON = REF("MODULE: the text is not JSON (RFC 8259, strict)")
add("P11 a trailing comma", "module_parse", {"text": '{"ID": "MATH",}'}, NOTJSON)
add("P12 NaN", "module_parse", {"text": '{"ID": NaN}'}, NOTJSON)
add("P13 a duplicate key", "module_parse", {"text": '{"ID": "MATH", "ID": "MATH"}'}, NOTJSON)
add("P14 a duplicate key after NFC", "module_parse",
{"text": '{"caf\\u00e9": 1, "cafe\\u0301": 2}'}, NOTJSON)
add("P15 a lone surrogate escape", "module_parse", {"text": '{"TITLE": "a\\ud800b"}'}, NOTJSON)
add("P16 a byte-order mark before the object", "module_parse", {"text": "\ufeff{}"}, NOTJSON)
add("P17 a raw control character in a string", "module_parse", {"text": '{"TITLE": "a\u0001b"}'},
NOTJSON)
add("P18 65 nested arrays: depth 65 refused", "module_parse",
{"text": '{"ID": ' + "[" * 65 + "]" * 65 + "}"}, NOTJSON)
add("P19 64 nested arrays are read; then the first missing field", "module_parse",
{"text": '{"ID": ' + "[" * 64 + "]" * 64 + "}"}, REF("MODULE: missing field VERSION"))
add("P20 an empty text", "module_parse", {"text": ""}, NOTJSON)
def mut(base, fn):
d = copy.deepcopy(base)
fn(d)
return TXT(d)
def P(name, base, fn, msg):
add(name, "module_parse", {"text": mut(base, fn)}, REF(msg))
P("P21 an unknown key (the least named)", GRAV,
lambda d: d.update({"LAWS": [], "DOF": []}), "MODULE: unknown field DOF")
P("P22 a missing key", GRAV, lambda d: d.pop("TEST_BINDINGS"),
"MODULE: missing field TEST_BINDINGS")
P("P23 ID in lower case", GRAV, lambda d: d.update({"ID": "grav-uniform"}),
"MODULE: ID grav-uniform is not a module ID")
P("P24 ID ending in a hyphen", GRAV, lambda d: d.update({"ID": "GRAV-"}),
"MODULE: ID GRAV- is not a module ID")
P("P25 ID of 33 characters", GRAV, lambda d: d.update({"ID": "A" * 33}),
"MODULE: ID " + "A" * 33 + " is not a module ID")
P("P26 VERSION of two parts", GRAV, lambda d: d.update({"VERSION": "1.0"}),
"MODULE: VERSION 1.0 is not a version")
P("P27 VERSION with a leading zero", GRAV, lambda d: d.update({"VERSION": "1.01.0"}),
"MODULE: VERSION 1.01.0 is not a version")
P("P28 VERSION with a five-digit part", GRAV, lambda d: d.update({"VERSION": "10000.0.0"}),
"MODULE: VERSION 10000.0.0 is not a version")
P("P29 TITLE blank", GRAV, lambda d: d.update({"TITLE": " \t "}), "MODULE: field TITLE is blank")
P("P30 TITLE with a bidi override", GRAV, lambda d: d.update({"TITLE": "g\u202en"}),
"MODULE: field TITLE contains disallowed character U+202E")
P("P31 TITLE a number", GRAV, lambda d: d.update({"TITLE": 7}),
"MODULE: field TITLE has the wrong type")
P("P32 PREREQUISITES holding a string", GRAV,
lambda d: d.update({"PREREQUISITES": ["MASS"]}), "MODULE: field PREREQUISITES has the wrong type")
P("P33 PREREQUISITES of 17 entries", GRAV,
lambda d: d.update({"PREREQUISITES": [PRE_MASS] * 17}),
"MODULE: field PREREQUISITES has more than 16 entries")
P("P34 a slot name in lower case", GRAV,
lambda d: d["PREREQUISITES"][0].update({"SLOT": "mass"}),
"PREREQUISITE:mass: SLOT mass is not a slot name")
P("P35 KIND not DOF or CONSTANT", GRAV,
lambda d: d["PREREQUISITES"][0].update({"KIND": "ENTRY"}),
"PREREQUISITE:MASS: KIND ENTRY is not DOF or CONSTANT")
P("P36 a DIMENSION that does not parse", GRAV,
lambda d: d["PREREQUISITES"][0].update({"DIMENSION": "Q1"}),
"PREREQUISITE:MASS: DIMENSION Q1 is not a dimension")
P("P37 MIN 0", GRAV, lambda d: d["PREREQUISITES"][0].update({"MIN": 0}),
"PREREQUISITE:MASS: MIN is not an integer from 1 to 16")
P("P38 MIN a string", GRAV, lambda d: d["PREREQUISITES"][0].update({"MIN": "1"}),
"PREREQUISITE:MASS: MIN is not an integer from 1 to 16")
assert '"MIN": 1, "MAX": 1, "LABEL": "the mass' in X_GRAV
add("P39 MIN written 1.0 (not an integer in the strict reader)", "module_parse",
{"text": X_GRAV.replace('"MIN": 1, "MAX": 1, "LABEL": "the mass', '"MIN": 1.0, "MAX": 1, '
'"LABEL": "the mass', 1)},
REF("PREREQUISITE:MASS: MIN is not an integer from 1 to 16"))
add("P40 MAX written 1e0", "module_parse",
{"text": X_GRAV.replace('"MAX": 1, "LABEL": "the mass', '"MAX": 1e0, "LABEL": "the mass', 1)},
REF("PREREQUISITE:MASS: MAX is not an integer from 1 to 16"))
add("P41 MIN true", "module_parse",
{"text": X_GRAV.replace('"MIN": 1, "MAX": 1, "LABEL": "the mass', '"MIN": true, "MAX": 1, '
'"LABEL": "the mass', 1)},
REF("PREREQUISITE:MASS: MIN is not an integer from 1 to 16"))
P("P42 MIN greater than MAX", GRAV,
lambda d: d["PREREQUISITES"][1].update({"MIN": 2}),
"PREREQUISITE:UP_VELOCITY: MIN is greater than MAX")
P("P43 a CONSTANT slot of MAX 2", GRAV,
lambda d: d["PREREQUISITES"][0].update({"MAX": 2}),
"PREREQUISITE:MASS: a CONSTANT slot takes exactly one name")
P("P44 an unknown field in a prerequisite", GRAV,
lambda d: d["PREREQUISITES"][0].update({"ROLE": "INPUT"}),
"PREREQUISITE:MASS: unknown field ROLE")
P("P45 a prerequisite without LABEL", GRAV, lambda d: d["PREREQUISITES"][0].pop("LABEL"),
"PREREQUISITE:MASS: missing field LABEL")
P("P46 a duplicate slot", GRAV,
lambda d: d["PREREQUISITES"].append(copy.deepcopy(PRE_MASS)), "MODULE: duplicate slot MASS")
P("P47 a parameter name without a lower-case letter", GRAV,
lambda d: d["PARAMETERS"][0].update({"NAME": "G_N"}),
"PARAMETER:G_N: NAME G_N is not an entry name (an identifier with a lower-case letter)")
P("P48 SUPPLY not DECLARED or AUTHOR", GRAV,
lambda d: d["PARAMETERS"][0].update({"SUPPLY": "FIXED"}),
"PARAMETER:g_n: SUPPLY FIXED is not DECLARED or AUTHOR")
P("P49 DECLARED without OVERRIDABLE", GRAV, lambda d: d["PARAMETERS"][0].pop("OVERRIDABLE"),
"PARAMETER:g_n: DECLARED requires OVERRIDABLE")
P("P50 AUTHOR with a VALUE", GRAV, lambda d: d["PARAMETERS"][1].update({"VALUE": "100"}),
"PARAMETER:h_max: AUTHOR forbids VALUE")
P("P51 a VALUE that is not a decimal", GRAV,
lambda d: d["PARAMETERS"][0].update({"VALUE": "9,80665"}),
"PARAMETER:g_n: VALUE 9,80665 is not a decimal")
P("P52 a UNIT of another dimension", GRAV,
lambda d: d["PARAMETERS"][0].update({"UNIT": "m s-3"}),
"PARAMETER:g_n: UNIT m s-3 has dimension L1 T-3, not L1 T-2")
assert U.unit_parse("m/s2") == ["REFUSED", "UNIT m/s2 is not a unit"]
P("P53 a UNIT that does not parse (tpc_units' reason)", GRAV,
lambda d: d["PARAMETERS"][0].update({"UNIT": "m/s2"}), "PARAMETER:g_n: UNIT m/s2 is not a unit")
P("P54 a negative UNCERTAINTY", GRAV,
lambda d: d["PARAMETERS"][0].update({"UNCERTAINTY": "-0.1"}),
"PARAMETER:g_n: UNCERTAINTY -0.1 is negative")
P("P55 OVERRIDABLE in lower case", GRAV,
lambda d: d["PARAMETERS"][0].update({"OVERRIDABLE": "yes"}),
"PARAMETER:g_n: OVERRIDABLE yes is not YES or NO")
P("P56 a duplicate parameter", GRAV, lambda d: d["PARAMETERS"].append(copy.deepcopy(PAR_GN)),
"MODULE: duplicate parameter g_n")
P("P57 a sum over a name that is no slot", AIRQ, lambda d: d["SUMS"][0].update({"OVER": "V"}),
"SUM:SPEED2: OVER V is not a DOF slot")
P("P58 a sum naming another name", AIRQ,
lambda d: d["SUMS"][0].update({"EACH": "VELOCITY^2+HEIGHT"}),
"SUM:SPEED2: EACH names HEIGHT, which is not VELOCITY or a parameter")
P("P59 a sum using D", AIRQ, lambda d: d["SUMS"][0].update({"EACH": "D(VELOCITY,VELOCITY)"}),
"SUM:SPEED2: EACH uses D")
P("P60 a sum named as a slot", AIRQ, lambda d: d["SUMS"][0].update({"NAME": "VELOCITY"}),
"SUM:VELOCITY: NAME VELOCITY is the name of a slot")
P("P61 a term on a CONSTANT slot", GRAV, lambda d: d["TERMS"][0].update({"TARGET": "MASS"}),
"TERM:1: TARGET MASS is not a DOF slot")
P("P62 a SIGN that is neither + nor -", GRAV, lambda d: d["TERMS"][0].update({"SIGN": "*"}),
"TERM:1: SIGN * is not + or -")
assert F.parse_form("MASS*", "expression") == ["REFUSED", "unexpected end"]
P("P63 an EXPR that does not parse (tpc_form's reason)", GRAV,
lambda d: d["TERMS"][0].update({"EXPR": "MASS*"}), "TERM:1: EXPR does not parse: unexpected end")
P("P64 an EXPR beginning with a minus sign", GRAV,
lambda d: d["TERMS"][0].update({"EXPR": "-MASS*g_n"}),
"TERM:1: EXPR begins with a minus sign; the sign is SIGN's")
P("P65 an EXPR using D", GRAV,
lambda d: d["TERMS"][0].update({"EXPR": "MASS*D(HEIGHT,UP_VELOCITY)"}), "TERM:1: EXPR uses D")
P("P66 an EXPR naming an unknown name (its own unknown reference)", GRAV,
lambda d: d["TERMS"][0].update({"EXPR": "MASS*g"}),
"TERM:1: EXPR names g, which is not a slot, parameter or sum of this module")
def _other(d):
d["PREREQUISITES"].append({"SLOT": "OTHER", "KIND": "DOF", "DIMENSION": "L1 T-1", "MIN": 1,
"MAX": 3, "LABEL": "other velocities"})
d["TERMS"][0]["EXPR"] = "0.5*rho_air*C_d*A_ref*sqrt(SPEED2)*OTHER"
P("P67 an EXPR naming a list slot outside its own TERM", AIRQ, _other,
"TERM:1: EXPR names the list slot OTHER outside its own TERM")
P("P68 an assumption name without a lower-case letter", GRAV,
lambda d: d["ASSUMPTIONS"][0].update({"NAME": "GRAVITY"}),
"ASSUMPTION:GRAVITY: NAME GRAVITY is not an entry name (an identifier with a lower-case letter)")
P("P69 a placeholder naming no slot", GRAV,
lambda d: d["ASSUMPTIONS"][0].update({"PROPOSITION": "uniform near {MASSES}"}),
"ASSUMPTION:gravity_uniform: PROPOSITION: {MASSES} is not a slot of this module")
P("P70 a stray closing brace", GRAV,
lambda d: d["ASSUMPTIONS"][0].update({"PROPOSITION": "a brace } here"}),
"ASSUMPTION:gravity_uniform: PROPOSITION: a brace outside a {SLOT} placeholder")
P("P71 an unclosed placeholder", GRAV,
lambda d: d["ASSUMPTIONS"][0].update({"PROPOSITION": "uniform over {HEIGHT"}),
"ASSUMPTION:gravity_uniform: PROPOSITION: a brace outside a {SLOT} placeholder")
P("P72 REFS naming no slot or parameter", GRAV,
lambda d: d["ASSUMPTIONS"][0].update({"REFS": ["HEIGHT", "g"]}),
"ASSUMPTION:gravity_uniform: REFS g is not a slot or parameter of this module")
P("P73 duplicate REFS", GRAV, lambda d: d["ASSUMPTIONS"][0].update({"REFS": ["h_max", "h_max"]}),
"ASSUMPTION:gravity_uniform: field REFS has duplicate entries")
P("P74 a CONDITION that does not parse", GRAV,
lambda d: d["BOUNDARIES"][0].update({"CONDITION": "abs(HEIGHT) <"}),
"BOUNDARY:gravity_region: CONDITION does not parse: unexpected end")
P("P75 a CONDITION naming a list slot outside a sum", AIRQ,
lambda d: d["BOUNDARIES"][0].update({"CONDITION": "VELOCITY > 0 and mu_air != 0"}),
"BOUNDARY:drag_quadratic_regime: CONDITION names the list slot VELOCITY outside a sum")
P("P76 a CONDITION naming an unknown name", GRAV,
lambda d: d["BOUNDARIES"][0].update({"CONDITION": "abs(HEIGHT) < h_min"}),
"BOUNDARY:gravity_region: CONDITION names h_min, which is not a slot, parameter or sum of "
"this module")
P("P77 an assumption named as a parameter", GRAV,
lambda d: d["ASSUMPTIONS"][1].update({"NAME": "g_n"}), "MODULE: duplicate entry name g_n")
P("P78 NOT_CHOSEN blank", GRAV, lambda d: d.update({"NOT_CHOSEN": ""}),
"MODULE: field NOT_CHOSEN is blank")
P("P79 a blank NOT_INCLUDED line", GRAV, lambda d: d.update({"NOT_INCLUDED": ["a", " "]}),
"MODULE: NOT_INCLUDED 2 is blank")
P("P80 duplicate NOT_INCLUDED lines", GRAV, lambda d: d.update({"NOT_INCLUDED": ["a", "a"]}),
"MODULE: field NOT_INCLUDED has duplicate entries")
P("P81 a zero-width space in a NOT_INCLUDED line", GRAV,
lambda d: d.update({"NOT_INCLUDED": ["a", "b\u200bc"]}),
"MODULE: field NOT_INCLUDED contains disallowed character U+200B")
P("P82 an EXCLUDES pattern without its hyphen", AIRQ, lambda d: d.update({"EXCLUDES": ["AIR*"]}),
"MODULE: EXCLUDES AIR* is not a module ID or pattern")
P("P83 EXCLUDES naming the module itself", AIRQ,
lambda d: d.update({"EXCLUDES": ["AIR-QUADRATIC"]}),
"MODULE: EXCLUDES AIR-QUADRATIC matches the module's own ID")
P("P84 an EXCLUDES pattern matching the module itself", AIRQ,
lambda d: d.update({"EXCLUDES": ["AIR-*"]}), "MODULE: EXCLUDES AIR-* matches the module's own ID")
P("P85 INTERACTS_WITH matching the module itself", GRAV,
lambda d: d["INTERACTS_WITH"][0].update({"WITH": "GRAV-*"}),
"INTERACTS_WITH:1: WITH GRAV-* matches the module's own ID")
P("P86 a question ID in lower case", GRAV,
lambda d: d["INTERACTS_WITH"][0].update({"QUESTION_ID": "buoyancy"}),
"INTERACTS_WITH:1: QUESTION_ID buoyancy is not a question ID")
P("P87 a duplicate question", GRAV,
lambda d: d["INTERACTS_WITH"].append(copy.deepcopy(d["INTERACTS_WITH"][0])),
"MODULE: duplicate question BUOYANCY")
P("P88 no test binding", GRAV, lambda d: d.update({"TEST_BINDINGS": []}),
"MODULE: field TEST_BINDINGS is empty (a module declares at least one test binding)")
P("P89 nine test bindings", MATH, lambda d: d.update({"TEST_BINDINGS": [
dict(d["TEST_BINDINGS"][0], NAME="B%d" % i) for i in range(9)]}),
"MODULE: field TEST_BINDINGS has more than 8 entries")
_badtpk = copy.deepcopy(P2)
_badtpk["LAW"][3]["REFS"] = ["t", "v_z"]
assert t1_first(_badtpk) == "LAW:EOM_Z: EQUATION names m, which is not in REFS"
P("P90 a test binding whose TPK fails T1 (T1's first problem)", GRAV,
lambda d: d["TEST_BINDINGS"][0].update({"TPK": _badtpk}),
"TEST_BINDING:PROJECTILE_2D: TPK fails T1: LAW:EOM_Z: EQUATION names m, which is not in REFS")
P("P91 a test binding with a slot unbound", GRAV,
lambda d: d["TEST_BINDINGS"][0].update({"BIND": {"MASS": ["m"], "UP_VELOCITY": ["v_z"]}}),
"TEST_BINDING:PROJECTILE_2D: BIND: slot HEIGHT is not bound")
P("P92 a test binding with an unknown slot", GRAV,
lambda d: d["TEST_BINDINGS"][0]["BIND"].update({"WEIGHT": ["m"]}),
"TEST_BINDING:PROJECTILE_2D: BIND: unknown slot WEIGHT")
P("P93 a test binding with an unknown parameter", GRAV,
lambda d: d["TEST_BINDINGS"][0]["VALUES"].update({"g": {}}),
"TEST_BINDING:PROJECTILE_2D: VALUES: unknown parameter g")
P("P94 a test binding value without UNIT", GRAV,
lambda d: d["TEST_BINDINGS"][0].update({"VALUES": {"h_max": {"VALUE": "100"}}}),
"TEST_BINDING:PROJECTILE_2D: VALUES: h_max: VALUE requires UNIT")
P("P95 an answer key that is no question", GRAV,
lambda d: d["TEST_BINDINGS"][1].update({"ANSWERS": {"FIXED:v z": "YES"}}),
"TEST_BINDING:TABLE: ANSWERS: FIXED:v z is not a question")
P("P96 an answer that is neither YES nor NO", GRAV,
lambda d: d["TEST_BINDINGS"][0].update({"ANSWERS": {"BUOYANCY": "yes"}}),
"TEST_BINDING:PROJECTILE_2D: ANSWERS: BUOYANCY is not YES or NO")
P("P97 a duplicate test binding", GRAV,
lambda d: d["TEST_BINDINGS"].append(copy.deepcopy(TB_GRAV_P2)),
"MODULE: duplicate test binding PROJECTILE_2D")
P("P98 a binding name in lower case", GRAV,
lambda d: d["TEST_BINDINGS"][0].update({"NAME": "projectile"}),
"TEST_BINDING:projectile: NAME projectile is not a binding name")
P("P99 LAWS key: a module emits no law (B2)", MATH, lambda d: d.update({"LAWS": []}),
"MODULE: unknown field LAWS")
# ---- RV's vectors (review 0.1.0 F-9): refusals without a case in 0.1.0
_dmsg = F.parse_form("D(HEIGHT,UP_VELOCITY) < h_max", "predicate")
assert _dmsg == ["REFUSED", "D is not permitted in a CONDITION at 1"], _dmsg
P("P100 a CONDITION using D: tpc_form's own refusal (S53, F-6)", GRAV,
lambda d: d["BOUNDARIES"][0].update({"CONDITION": "D(HEIGHT,UP_VELOCITY) < h_max"}),
"BOUNDARY:gravity_region: CONDITION does not parse: D is not permitted in a CONDITION at 1")
P("P101 an UNCERTAINTY that is not a decimal", GRAV,
lambda d: d["PARAMETERS"][0].update({"UNCERTAINTY": "0.1.2"}),
"PARAMETER:g_n: UNCERTAINTY 0.1.2 is not a decimal")
P("P102 a SUM NAME that is not a slot name", AIRQ, lambda d: d["SUMS"][0].update({"NAME": "speed2"}),
"SUM:speed2: NAME speed2 is not a slot name")
_emsg = F.parse_form("VELOCITY^", "expression")
assert _emsg == ["REFUSED", "^ takes an integer or a parenthesized rational exponent at 9"], _emsg
P("P103 a SUM EACH that does not parse", AIRQ, lambda d: d["SUMS"][0].update({"EACH": "VELOCITY^"}),
"SUM:SPEED2: EACH does not parse: ^ takes an integer or a parenthesized rational exponent at 9")
P("P104 a duplicate sum", AIRQ, lambda d: d["SUMS"].append(copy.deepcopy(d["SUMS"][0])),
"MODULE: duplicate sum SPEED2")
P("P105 duplicate EXCLUDES", AIRQ, lambda d: d.update({"EXCLUDES": ["AIR-LINEAR", "AIR-LINEAR"]}),
"MODULE: field EXCLUDES has duplicate entries")
P("P106 a WITH that is no ID or pattern", GRAV,
lambda d: d["INTERACTS_WITH"][0].update({"WITH": "air-*"}),
"INTERACTS_WITH:1: WITH air-* is not a module ID or pattern")
P("P107 a PREDICATE placeholder naming no slot", GRAV,
lambda d: d["BOUNDARIES"][0].update({"PREDICATE": "abs({HIGHT}) < h_max"}),
"BOUNDARY:gravity_region: PREDICATE: {HIGHT} is not a slot of this module")
P("P108 a test binding's BIND slot of the wrong type", GRAV,
lambda d: d["TEST_BINDINGS"][0]["BIND"].update({"MASS": "m"}),
"TEST_BINDING:PROJECTILE_2D: BIND: slot MASS has the wrong type")
# ============================================================ module_digest
add("G01 GRAV-UNIFORM 1.0.0", "module_digest", {"text": X_GRAV}, OK(D_GRAV))
add("G02 AIR-QUADRATIC 1.0.0", "module_digest", {"text": X_AIRQ}, OK(D_AIRQ))
add("G03 AIR-LINEAR 1.0.0", "module_digest", {"text": X_AIRL}, OK(D_AIRL))
add("G04 MATH 1.0.0", "module_digest", {"text": X_MATH}, OK(D_MATH))
add("G05 white space outside strings: one digest", "module_digest",
{"text": json.dumps(GRAV, indent=2)}, OK(D_GRAV))
_rev = dict(reversed(list(GRAV.items())))
add("G06 key order: one digest", "module_digest", {"text": TXT(_rev)}, OK(D_GRAV))
_spell = copy.deepcopy(GRAV)
_spell["PREREQUISITES"][1]["DIMENSION"] = "T-1 L1"
_spell["PARAMETERS"][0]["VALUE"] = "9.806650"
_spell["TERMS"][0]["EXPR"] = "MASS * g_n"
_spell["BOUNDARIES"][0]["CONDITION"] = "(abs(HEIGHT) < h_max)"
_spell["TEST_BINDINGS"][0]["VALUES"]["h_max"]["VALUE"] = "1e2"
assert CD("9.806650") == "980665e-5" and CD("1e2") == "1e2"
assert F.parse_form("(abs(HEIGHT) < h_max)", "predicate") == ["OK", "abs(HEIGHT)<h_max"]
D_SPELL = MDIG(_spell)
assert D_SPELL != D_GRAV
add("G07 a DIMENSION, decimal and forms spelled otherwise: another digest (verbatim, S49)",
"module_digest", {"text": TXT(_spell)}, OK(D_SPELL))
add("G08 GRAV-UNIFORM 1.1.0 (TITLE revised)", "module_digest", {"text": X_GRAV11}, OK(D_GRAV11))
_v101 = copy.deepcopy(GRAV)
_v101["VERSION"] = "1.0.1"
add("G09 the same text at another VERSION: another digest", "module_digest",
{"text": TXT(_v101)}, OK(MDIG(_v101)))
assert MDIG(_v101) != D_GRAV
_ws = copy.deepcopy(GRAV)
_ws["TITLE"] = "Uniform gravity at the standard value g_n"
add("G10 white space inside a string is text: another digest", "module_digest",
{"text": TXT(_ws)}, OK(MDIG(_ws)))
assert MDIG(_ws) != D_GRAV
_nfc1 = copy.deepcopy(TEST_X)
_nfc1["TITLE"] = "Test interaction X, caf\u00e9"
_nfc2 = copy.deepcopy(TEST_X)
_nfc2["TITLE"] = "Test interaction X, cafe\u0301"
assert MDIG(_nfc1) == MDIG(_nfc2) and CT(_nfc1) == CT(_nfc2)
add("G11 a precomposed letter", "module_digest", {"text": TXT(_nfc1)}, OK(MDIG(_nfc1)))
add("G12 the decomposed spelling: one digest (NFC)", "module_digest", {"text": TXT(_nfc2)},
OK(MDIG(_nfc1)))
add("G13 a refused text: module_parse's refusal", "module_digest", {"text": '{"ID": 1'}, NOTJSON)
add("G14 GRAV-UNIFORM 1.2.0", "module_digest", {"text": X_GRAV12}, OK(D_GRAV12))
add("G15 GRAV-UNIFORM 2.0.0", "module_digest", {"text": X_GRAV20}, OK(D_GRAV20))
add("G16 TEST-PUSH 1.0.0", "module_digest", {"text": X_TPUSH}, OK(D_TPUSH))
_esc = copy.deepcopy(TEST_X)
_esc["TITLE"] = 'Test "X", a\\b\tc'
_esc["NOT_INCLUDED"] = ["one line\nand the next"]
assert '"TITLE":"Test \\"X\\", a\\\\b\\u0009c"' in CT(_esc)
assert '"NOT_INCLUDED":["one line\\u000aand the next"]' in CT(_esc)
add("G17 a quotation mark, a backslash, a TAB and an LF inside strings: escaped (section 4)",
"module_digest", {"text": TXT(_esc)}, OK(MDIG(_esc)))
_crlf = copy.deepcopy(_esc)
_crlf["NOT_INCLUDED"] = ["one line\r\nand the next"]
assert CT(_crlf) == CT(_esc)
add("G18 CRLF inside a string: the digest of LF (2.5)", "module_digest", {"text": TXT(_crlf)},
OK(MDIG(_esc)))
_refs = copy.deepcopy(GRAV)
_refs["ASSUMPTIONS"][0]["REFS"] = ["h_max", "HEIGHT"]
assert MDIG(_refs) != D_GRAV
add("G19 REFS in another order: another digest (lists keep the author's order)", "module_digest",
{"text": TXT(_refs)}, OK(MDIG(_refs)))
# ============================================================== module_bind
B_OK = OK([["SLOT", "MASS", ["m"]], ["SLOT", "UP_VELOCITY", ["v_z"]], ["SLOT", "HEIGHT", ["z"]]],
[["PARAMETER", "g_n", "DECLARED", "9.80665", "m s-2", ""],
["PARAMETER", "h_max", "AUTHOR", "100", "m", ""]])
def B(name, bind, values, expected, text=X_GRAV, tpk=P2):
add(name, "module_bind", {"tpk": tpk, "text": text, "bind": bind, "values": values}, expected)
H100 = {"h_max": {"VALUE": "100", "UNIT": "m"}}
B("N01 GRAV bound on the projectile", GRAV_BIND, H100, B_OK)
B("N02 an author value left symbolic", GRAV_BIND, {"h_max": {}},
OK([["SLOT", "MASS", ["m"]], ["SLOT", "UP_VELOCITY", ["v_z"]], ["SLOT", "HEIGHT", ["z"]]],
[["PARAMETER", "g_n", "DECLARED", "9.80665", "m s-2", ""],
["PARAMETER", "h_max", "SYMBOLIC", "", "", ""]]))
B("N03 an author value with its uncertainty", GRAV_BIND,
{"h_max": {"VALUE": "100", "UNIT": "m", "UNCERTAINTY": "0.5"}},
OK([["SLOT", "MASS", ["m"]], ["SLOT", "UP_VELOCITY", ["v_z"]], ["SLOT", "HEIGHT", ["z"]]],
[["PARAMETER", "g_n", "DECLARED", "9.80665", "m s-2", ""],
["PARAMETER", "h_max", "AUTHOR", "100", "m", "0.5"]]))
_p2bad = copy.deepcopy(P2)
_p2bad["LAW"][3] = law("EOM_Z", "m*D(v_z,t)=-m*g_n", "EOM(v_z)", ["g_n", "m", "t", "v_z"], "x")
assert t1_first(_p2bad) == "LAW:EOM_Z: REFS g_n does not resolve"
B("N04 a base TPK failing T1", GRAV_BIND, H100,
REF("BASE: the TPK fails T1: LAW:EOM_Z: REFS g_n does not resolve"), tpk=_p2bad)
B("N05 bind not an object", [["m"]], H100, REF("BIND: not an object"))
B("N06 an unknown slot", dict(GRAV_BIND, SPEED=["v_x"]), H100, REF("BIND: unknown slot SPEED"))
B("N07 a slot unbound (declared order)", {"MASS": ["m"], "UP_VELOCITY": ["v_z"]}, H100,
REF("BIND: slot HEIGHT is not bound"))
B("N08 a slot bound to a string", dict(GRAV_BIND, MASS="m"), H100,
REF("BIND: slot MASS has the wrong type"))
B("N09 a slot bound to no name", dict(GRAV_BIND, MASS=[]), H100,
REF("BIND: slot MASS takes from 1 to 1 names, not 0"))
B("N10 a scalar slot bound to two names", dict(GRAV_BIND, MASS=["m", "m2"]), H100,
REF("BIND: slot MASS takes from 1 to 1 names, not 2"))
B("N11 a CONSTANT slot bound to a DOF", dict(GRAV_BIND, MASS=["x"]), H100,
REF("BIND: slot MASS: x is not a CONSTANT of the TPK"))
B("N12 a DOF slot bound to a CONSTANT", dict(GRAV_BIND, UP_VELOCITY=["m"]), H100,
REF("BIND: slot UP_VELOCITY: m is not a DOF of the TPK"))
B("N13 a DOF of another dimension", dict(GRAV_BIND, UP_VELOCITY=["z"]), H100,
REF("BIND: slot UP_VELOCITY: z has dimension L1, not L1 T-1"))
B("N14 a name the TPK lacks", dict(GRAV_BIND, UP_VELOCITY=["w"]), H100,
REF("BIND: slot UP_VELOCITY: w is not a DOF of the TPK"))
AQV = {"A_ref": {}, "C_d": {}, "L_ref": {}}
B("N15 list names out of code point order", {"VELOCITY": ["v_z", "v_x"]}, AQV,
REF("BIND: slot VELOCITY: the names are not in code point order without repeats"), text=X_AIRQ)
B("N16 list names repeated", {"VELOCITY": ["v_x", "v_x"]}, AQV,
REF("BIND: slot VELOCITY: the names are not in code point order without repeats"), text=X_AIRQ)
B("N17 a list slot of four names", {"VELOCITY": ["a", "b", "c", "d"]}, AQV,
REF("BIND: slot VELOCITY takes from 1 to 3 names, not 4"), text=X_AIRQ)
B("N18 a list slot bound", {"VELOCITY": ["v_x", "v_z"]}, AQV,
OK([["SLOT", "VELOCITY", ["v_x", "v_z"]]],
[["PARAMETER", "rho_air", "DECLARED", "1.225", "kg m-3", ""],
["PARAMETER", "mu_air", "DECLARED", "1.7894e-5", "Pa s", ""],
["PARAMETER", "C_d", "SYMBOLIC", "", "", ""], ["PARAMETER", "A_ref", "SYMBOLIC", "", "", ""],
["PARAMETER", "L_ref", "SYMBOLIC", "", "", ""]]), text=X_AIRQ)
B("N19 one name in two slots", {"PULLED": ["v_x"], "PUSHED": ["v_x"]}, {"F_a": {}, "F_b": {}},
REF("BIND: v_x is bound to two slots, PUSHED and PULLED"), text=X_TPUSH)
B("N20 values not an object", GRAV_BIND, [], REF("VALUES: not an object"))
B("N21 an unknown parameter", GRAV_BIND, dict(H100, g={}), REF("VALUES: unknown parameter g"))
B("N22 a value for a parameter that is not OVERRIDABLE", GRAV_BIND,
dict(H100, g_n={"VALUE": "9.81", "UNIT": "m s-2"}), REF("VALUES: g_n is not OVERRIDABLE"))
B("N23 an author parameter without a value", GRAV_BIND, {},
REF("VALUES: h_max is author supplied: give a value, or {} for a symbolic constant"))
B("N24 a value that is not an object", GRAV_BIND, {"h_max": "100"},
REF("VALUES: h_max has the wrong type"))
B("N25 VALUE without UNIT", GRAV_BIND, {"h_max": {"VALUE": "100"}},
REF("VALUES: h_max: VALUE requires UNIT"))
B("N26 UNIT without VALUE", GRAV_BIND, {"h_max": {"UNIT": "m"}},
REF("VALUES: h_max: UNIT requires VALUE"))
B("N27 UNCERTAINTY alone", GRAV_BIND, {"h_max": {"UNCERTAINTY": "1"}},
REF("VALUES: h_max: UNCERTAINTY requires VALUE"))
B("N28 an unknown field in a value", GRAV_BIND,
{"h_max": {"VALUE": "100", "UNIT": "m", "NOTE": "x"}}, REF("VALUES: h_max: unknown field NOTE"))
B("N29 a VALUE that is not a decimal", GRAV_BIND, {"h_max": {"VALUE": "1OO", "UNIT": "m"}},
REF("VALUES: h_max: VALUE 1OO is not a decimal"))
B("N30 a UNIT of another dimension", GRAV_BIND, {"h_max": {"VALUE": "100", "UNIT": "s"}},
REF("VALUES: h_max: UNIT s has dimension T1, not L1"))
assert U.unit_parse("furlong") == ["REFUSED", "UNIT furlong: furlong is not a unit symbol"]
B("N31 a UNIT that does not parse (tpc_units' reason)", GRAV_BIND,
{"h_max": {"VALUE": "100", "UNIT": "furlong"}},
REF("VALUES: h_max: UNIT furlong: furlong is not a unit symbol"))
B("N32 a negative UNCERTAINTY", GRAV_BIND,
{"h_max": {"VALUE": "100", "UNIT": "m", "UNCERTAINTY": "-1"}},
REF("VALUES: h_max: UNCERTAINTY -1 is negative"))
B("N33 a VALUE given as a number", GRAV_BIND, {"h_max": {"VALUE": 100, "UNIT": "m"}},
REF("VALUES: h_max: field VALUE has the wrong type"))
B("N34 a bidi override in a VALUE", GRAV_BIND, {"h_max": {"VALUE": "1\u202e00", "UNIT": "m"}},
REF("VALUES: h_max: field VALUE contains disallowed character U+202E"))
B("N35 an OVERRIDABLE value overridden", {}, {"k_loc": {"VALUE": "3", "UNIT": "1"}},
OK([], [["PARAMETER", "k_loc", "OVERRIDDEN", "3", "1", ""]]), text=X_TOVR)
B("N36 an OVERRIDABLE value kept", {}, {},
OK([], [["PARAMETER", "k_loc", "DECLARED", "2", "1", ""]]), text=X_TOVR)
B("N37 an empty override", {}, {"k_loc": {}},
REF("VALUES: k_loc: an override needs VALUE and UNIT"), text=X_TOVR)
B("N38 a module text that is not JSON", GRAV_BIND, H100, NOTJSON, text="{")
B("N39 an author UNCERTAINTY that is not a decimal (RV F-9)", GRAV_BIND,
{"h_max": {"VALUE": "100", "UNIT": "m", "UNCERTAINTY": "x"}},
REF("VALUES: h_max: UNCERTAINTY x is not a decimal"))
# ============================================================ module_expand
def X(name, draft, claims, modules, expected):
add(name, "module_expand", {"draft": draft, "claims": claims, "modules": modules}, expected)
D_P2 = {"TPK": P2}
X("X01 GRAV-UNIFORM on the projectile: the weight composed into EOM_Z", D_P2, [C_GRAV], M4,
R_GRAV)
X("X02 AIR-QUADRATIC: one term per member, the speed a sum", D_P2, [C_AIRQ], M4, R_AIRQ)
X("X03 AIR-LINEAR", D_P2, [C_AIRL], M4, R_AIRL)
X("X04 MATH: a constant only; no structure check needed", D_P2, [C_MATH], M4, R_MATH)
X("X05 GRAV and AIR-QUADRATIC, buoyancy NO: terms in module-ID order", D_P2,
[C_GRAV_NO, C_AIRQ], M4, R_GRAV_AIRQ)
X("X06 the other selection order: one D(TPK)", D_P2, [C_AIRQ, C_GRAV_NO], M4, R_GRAV_AIRQ)
X("X07 buoyancy YES changes no expansion (RD-MS-7)", D_P2, [C_GRAV_YES, C_AIRQ], M4, R_GRAV_AIRQ)
X("X08 buoyancy not answered", D_P2, [C_GRAV, C_AIRQ], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: question BUOYANCY is not answered"))
X("X09 AIR-LINEAR and MATH: pi_c emitted identically merges", D_P2, [C_AIRL, C_MATH], M4,
R_AIRL_MATH)
X("X10 the other order", D_P2, [C_MATH, C_AIRL], M4, R_AIRL_MATH)
X("X11 three modules (buoyancy NO)", D_P2, [C_MATH, C_GRAV_NO, C_AIRL], M4, R_GRAV_AIRL_MATH)
X("X12 pi_c emitted differently is refused", D_P2, [C_TPI, C_MATH], M4 + [X_TPI],
REF("EXPAND: MATH 1.0.0 and TEST-PI 1.0.0 both emit pi_c, differently"))
X("X13 AIR-QUADRATIC EXCLUDES AIR-LINEAR", D_P2, [C_AIRQ, C_AIRL], M4,
REF("CLAIMS: AIR-LINEAR 1.0.0 and AIR-QUADRATIC 1.0.0 exclude each other (EXCLUDES)"))
X("X14 EXCLUDES is symmetric: MATH names nothing, TEST-EXCL names MATH", D_P2,
[C_MATH, C_TEXCL], M4 + [X_TEXCL],
REF("CLAIMS: MATH 1.0.0 and TEST-EXCL 1.0.0 exclude each other (EXCLUDES)"))
C_TAB_YES = claim("GRAV-UNIFORM", "1.0.0", D_GRAV, GRAV_BIND,
{"h_max": {"VALUE": "1", "UNIT": "m"}}, {"FIXED:v_z": "YES"})
C_TAB_NO = claim("GRAV-UNIFORM", "1.0.0", D_GRAV, GRAV_BIND,
{"h_max": {"VALUE": "1", "UNIT": "m"}}, {"FIXED:v_z": "NO"})
C_TAB_NONE = claim("GRAV-UNIFORM", "1.0.0", D_GRAV, GRAV_BIND,
{"h_max": {"VALUE": "1", "UNIT": "m"}}, {})
D_TAB = {"TPK": TABLE}
X("X15 weight on a FIXED velocity, support answered YES: dropped, the balance assumed", D_TAB,
[C_TAB_YES], M4,
XR(EXP_TABLE, [], rows_of(GRAV_EMIT_100[:1], G1)
+ [["CONSTANT", "h_max", [G1]]] + rows_of(GRAV_EMIT_100[2:], G1)
+ [["ASSUMPTION", "fixed_v_z_balanced", [G1]]], [[G1, "v_z"]]))
X("X16 the support-force question answered NO", D_TAB, [C_TAB_NO], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: TERM 1: v_z is FIXED and the answer to FIXED:v_z is NO: a force "
"along a fixed DOF with no constraint force balancing it contradicts the constraint"))
X("X17 the support-force question not answered", D_TAB, [C_TAB_NONE], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: question FIXED:v_z is not answered (v_z is FIXED: does a "
"constraint force balance the forces this module adds along it?)"))
X("X18 a FIXED answer where nothing is fixed", D_P2,
[claim("GRAV-UNIFORM", "1.0.0", D_GRAV, GRAV_BIND, H100, {"FIXED:v_z": "YES"})], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: answer FIXED:v_z answers no question raised"))
X("X19 a buoyancy answer without an air module", D_P2, [C_GRAV_NO], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: answer BUOYANCY answers no question raised"))
X("X20 an answer neither YES nor NO", D_P2,
[claim("GRAV-UNIFORM", "1.0.0", D_GRAV, GRAV_BIND, H100, {"BUOYANCY": "MAYBE"}), C_AIRQ], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: answer BUOYANCY is not YES or NO"))
X("X21 an answer key that is no question", D_P2,
[claim("GRAV-UNIFORM", "1.0.0", D_GRAV, GRAV_BIND, H100, {"buoyancy": "NO"}), C_AIRQ], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: answer buoyancy is not a question"))
_p2rm = tpk_mod(P2, extra=lambda t: t.update({"CONSTRAINT": [
{"NAME": "C_RVZ", "TARGET": "v_z", "ACTION": "REMOVE", "STATEMENT": "v_z is not modelled"}]}))
assert Z.t1_well_formed(_p2rm)[0] and label_of(_p2rm, "v_z")[0] == "REMOVED"
X("X22 a REMOVED target", {"TPK": _p2rm}, [C_GRAV], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: TERM 1: v_z is REMOVED; no module is bound to a removed DOF"))
_p2in = copy.deepcopy(P2)
_p2in["DOF"][4]["ROLE"] = "INPUT"
assert Z.t1_well_formed(_p2in)[0] and label_of(_p2in, "v_z")[0] == "INPUT"
X("X23 an INPUT target", {"TPK": _p2in}, [C_GRAV], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: TERM 1: v_z is an INPUT DOF and has no equation of motion"))
_puw = {"DOF": [dof("u", "L1 T-1"), dof("w", "L1 T-1")],
"LAW": [law("EQ_W", "D(w,u)=0", "EOM(w)", ["u", "w"], "dw/du = 0")]}
assert Z.t1_well_formed(_puw)[0] and label_of(_puw, "u")[0] == "INDEPENDENT"
X("X24 the independent variable as a target", {"TPK": _puw},
[claim("TEST-PUSH", "1.0.0", D_TPUSH, {"PULLED": ["w"], "PUSHED": ["u"]},
{"F_a": {}, "F_b": {}}, {})], [X_TPUSH],
REF("CLAIM TEST-PUSH 1.0.0: TERM 1: u is the independent variable"))
_p2noeom = tpk_mod(P2, drop_laws=("EOM_Z",))
assert Z.t1_well_formed(_p2noeom)[0] and label_of(_p2noeom, "v_z") == ("NO LAW", "")
X("X25 no equation of motion for the target (NO LAW)", {"TPK": _p2noeom}, [C_GRAV], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: TERM 1: v_z has no equation of motion (NO LAW); write it, then "
"bind the module"))
_p2prose = copy.deepcopy(P2)
for _l in _p2prose["LAW"]:
del _l["EQUATION"]
assert Z.t1_well_formed(_p2prose)[0] and label_of(_p2prose, "v_z") == ("NO LAW", "")
X("X26 laws in prose only (analysis NO FORM: NO LAW)", {"TPK": _p2prose}, [C_GRAV], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: TERM 1: v_z has no equation of motion (NO LAW); write it, "
"then bind the module"))
_p2pde = copy.deepcopy(P2)
_p2pde["PARAMETERS"] = {"D_VARIABLES": "SEVERAL"}
_p2pde["DOF"].append(dof("s", "T1"))
_p2pde["LAW"][0] = law("KIN_X", "D(x,s)=v_x", "EOM(x)", ["s", "v_x", "x"], "dx/ds = v_x")
assert Z.t1_well_formed(_p2pde)[0] and label_of(_p2pde, "v_z") == ("NOT ANALYZED", "")
X("X26b two D variables (analysis PDE: NOT ANALYZED)", {"TPK": _p2pde}, [C_GRAV], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: TERM 1: v_z has no equation of motion (NOT ANALYZED); write "
"it, then bind the module"))
_p2role = copy.deepcopy(P2)
_p2role["LAW"][3]["ROLE"] = "CONSTITUTIVE"
assert Z.t1_well_formed(_p2role)[0] and label_of(_p2role, "v_z") == ("DETERMINED", "EOM_Z")
X("X27 the determining law's ROLE is not EOM(v_z)", {"TPK": _p2role}, [C_GRAV], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: TERM 1: v_z's equation of motion LAW EOM_Z has ROLE "
"CONSTITUTIVE, not EOM(v_z)"))
_p2both = copy.deepcopy(P2)
_p2both["CONSTANT"].append({"NAME": "c_v", "DIMENSION": "M1"})
_p2both["LAW"][3] = law("EOM_Z", "m*D(v_z,t)=c_v*D(v_z,t)", "EOM(v_z)", ["c_v", "m", "t", "v_z"],
"m dv_z/dt = c_v dv_z/dt")
assert Z.t1_well_formed(_p2both)[0] and label_of(_p2both, "v_z") == ("DETERMINED", "EOM_Z")
X("X28 both sides of the equation-of-motion shape", {"TPK": _p2both}, [C_GRAV], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: TERM 1: LAW EOM_Z has no single side of the equation-of-motion "
"shape for v_z"))
_p2right = copy.deepcopy(P2)
_p2right["LAW"][3] = law("EOM_Z", "0 = m*D(v_z, t)", "EOM(v_z)", ["m", "t", "v_z"], "0 = m dv_z/dt")
assert label_of(_p2right, "v_z") == ("DETERMINED", "EOM_Z")
_EXP_RIGHT = tpk_mod(_p2right, {"EOM_Z": (canon_is("-m*g_n=m*D(v_z,t)", "equation"),
["g_n", "m", "t", "v_z"], "0 = m dv_z/dt" + S_W)},
GRAV_EMIT_100)
must_pass(_EXP_RIGHT, "_EXP_RIGHT")
X("X29 the D side on the right: the force side is the left", {"TPK": _p2right}, [C_GRAV], M4,
XR(_EXP_RIGHT, [["EOM_Z", "-m*g_n=m*D(v_z,t)"]], rows_of(GRAV_EMIT_100, G1), []))
SPRING = {"DOF": [dof("t", "T1"), dof("z", "L1"), dof("v_z", "L1 T-1")],
"CONSTANT": [{"NAME": "m", "DIMENSION": "M1"}, {"NAME": "k_s", "DIMENSION": "M1 T-2"}],
"LAW": [law("KIN", "D(z,t)=v_z", "EOM(z)", ["t", "v_z", "z"], "dz/dt = v_z"),
law("N2", "m*D(v_z,t)=-k_s*z", "EOM(v_z)", ["k_s", "m", "t", "v_z", "z"],
"m dv_z/dt = -k_s z")],
"ASSUMPTION": [{"NAME": "linear_spring", "PROPOSITION": "the spring force is linear in z",
"REFS": ["z"]}]}
must_pass(SPRING, "SPRING")
_EXP_SPRING = tpk_mod(SPRING, {"N2": (canon_is("m*D(v_z,t)=-k_s*z-m*g_n", "equation"),
["g_n", "k_s", "m", "t", "v_z", "z"],
"m dv_z/dt = -k_s z" + S_W)},
[("CONSTANT", E_GN), ("CONSTANT", E_HMAXS), ("ASSUMPTION", E_GU),
("ASSUMPTION", E_GS), ("ASSUMPTION", E_FI), ("BOUNDARY", E_GR)])
must_pass(_EXP_SPRING, "_EXP_SPRING")
X("X30 a force side that is not 0: the term appended; h_max symbolic", {"TPK": SPRING},
[claim("GRAV-UNIFORM", "1.0.0", D_GRAV, GRAV_BIND, {"h_max": {}}, {})], M4,
XR(_EXP_SPRING, [["N2", "m*D(v_z,t)=-k_s*z-m*g_n"]], rows_of(GRAV_EMIT_100, G1), []))
_EXP_PUSH = tpk_mod(P2, {"EOM_X": (canon_is("m*D(v_x,t)=F_a+F_b", "equation"),
["F_a", "F_b", "m", "t", "v_x"],
"m dv_x/dt = 0; with the push: +(F_a+F_b)"),
"EOM_Z": (canon_is("m*D(v_z,t)=-(F_a+F_b)", "equation"),
["F_a", "F_b", "m", "t", "v_z"],
"m dv_z/dt = 0; with the pull: -(F_a+F_b)")},
[("CONSTANT", {"NAME": "F_a", "DIMENSION": "L1 M1 T-2"}),
("CONSTANT", {"NAME": "F_b", "DIMENSION": "L1 M1 T-2"})])
must_pass(_EXP_PUSH, "_EXP_PUSH")
TP1 = "TEST-PUSH 1.0.0"
X("X31 SIGN +, and a sum wrapped in parentheses under SIGN -", D_P2, [C_TPUSH], [X_TPUSH],
XR(_EXP_PUSH, [["EOM_X", "m*D(v_x,t)=F_a+F_b"], ["EOM_Z", "m*D(v_z,t)=-(F_a+F_b)"]],
[["CONSTANT", "F_a", [TP1]], ["CONSTANT", "F_b", [TP1]]], []))
_p2damp = copy.deepcopy(P2)
_p2damp["CONSTANT"].append({"NAME": "c_x", "DIMENSION": "M1 T-1"})
_p2damp["LAW"][2] = law("EOM_X", "m*D(v_x,t)=-c_x*v_x", "EOM(v_x)", ["c_x", "m", "t", "v_x"],
"m dv_x/dt = -c_x v_x")
_p2damp["LAW"][3] = law("EOM_Z", "m*D(v_z,t)=-c_x*v_z", "EOM(v_z)", ["c_x", "m", "t", "v_z"],
"m dv_z/dt = -c_x v_z")
must_pass(_p2damp, "_p2damp")
_EXP_DAMP = tpk_mod(_p2damp, {"EOM_X": (canon_is("m*D(v_x,t)=-c_x*v_x+(F_a+F_b)", "equation"),
["F_a", "F_b", "c_x", "m", "t", "v_x"],
"m dv_x/dt = -c_x v_x; with the push: +(F_a+F_b)"),
"EOM_Z": (canon_is("m*D(v_z,t)=-c_x*v_z-(F_a+F_b)", "equation"),
["F_a", "F_b", "c_x", "m", "t", "v_z"],
"m dv_z/dt = -c_x v_z; with the pull: -(F_a+F_b)")},
[("CONSTANT", {"NAME": "F_a", "DIMENSION": "L1 M1 T-2"}),
("CONSTANT", {"NAME": "F_b", "DIMENSION": "L1 M1 T-2"})])
must_pass(_EXP_DAMP, "_EXP_DAMP")
X("X32 appended to a force side that is not 0: +(...) and -(...)", {"TPK": _p2damp}, [C_TPUSH],
[X_TPUSH],
XR(_EXP_DAMP, [["EOM_X", "m*D(v_x,t)=-c_x*v_x+(F_a+F_b)"],
["EOM_Z", "m*D(v_z,t)=-c_x*v_z-(F_a+F_b)"]],
[["CONSTANT", "F_a", [TP1]], ["CONSTANT", "F_b", [TP1]]], []))
FALL = {"DOF": [dof("t", "T1"), dof("z", "L1"), dof("v_z", "L1 T-1")],
"CONSTANT": [{"NAME": "m", "DIMENSION": "M1"}],
"LAW": [law("KIN_Z", "D(z,t)=v_z", "EOM(z)", ["t", "v_z", "z"], "dz/dt = v_z"),
law("EOM_Z", "m*D(v_z,t)=0", "EOM(v_z)", ["m", "t", "v_z"], "m dv_z/dt = 0")]}
_TQ1 = "-5e-1*rho_air*C_d*A_ref*sqrt(v_z^2)*v_z"
_COND1 = ("mu_air!=0 and v_z^2>=0 and rho_air*sqrt(v_z^2)*L_ref/mu_air>1e3 and "
"rho_air*sqrt(v_z^2)*L_ref/mu_air<2e5")
canon_is(_COND1, "predicate")
_EXP_FALL = tpk_mod(FALL, {"EOM_Z": (canon_is("m*D(v_z,t)=" + _TQ1, "equation"),
["A_ref", "C_d", "m", "rho_air", "t", "v_z"],
"m dv_z/dt = 0; with the quadratic drag: " + _TQ1)},
[("CONSTANT", E_RHO), ("CONSTANT", E_MU), ("CONSTANT", E_CD),
("CONSTANT", E_AREF), ("CONSTANT", E_LREF),
("ASSUMPTION", {"NAME": "air_still", "PROPOSITION": PROP_AS, "REFS": ["v_z"]}),
("ASSUMPTION", E_AU), ("ASSUMPTION", E_DCD),
("ASSUMPTION", {"NAME": "drag_no_lift", "PROPOSITION": PROP_NL,
"REFS": ["v_z"]}),
("ASSUMPTION", {"NAME": "drag_no_magnus", "PROPOSITION": PROP_NM,
"REFS": ["v_z"]}),
("BOUNDARY", {"NAME": "drag_quadratic_regime", "PREDICATE": PRED_Q,
"REFS": ["L_ref", "mu_air", "rho_air", "v_z"],
"CONDITION": _COND1})])
must_pass(_EXP_FALL, "_EXP_FALL")
X("X33 a list slot of one member: the sum of one square", {"TPK": FALL},
[claim("AIR-QUADRATIC", "1.0.0", D_AIRQ, {"VELOCITY": ["v_z"]}, AQV, {})], M4,
XR(_EXP_FALL, [["EOM_Z", "m*D(v_z,t)=" + _TQ1]], rows_of(AIRQ_EMIT_2, AQ1), []))
_p2gn = copy.deepcopy(P2)
_p2gn["CONSTANT"].append(copy.deepcopy(E_GN))
X("X34 an emitted name the author's TPK already has", {"TPK": _p2gn}, [C_GRAV], M4,
REF("EXPAND: GRAV-UNIFORM 1.0.0 emits CONSTANT g_n, but the TPK already has an entry named g_n"))
X("X35 a claim whose DIGEST is not its text's", D_P2,
[dict(C_GRAV, DIGEST=D_GRAV11)], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: DIGEST does not match the module text"))
X("X36 a claimed version with no text", D_P2,
[dict(C_GRAV, VERSION="1.1.0", DIGEST=D_GRAV11)], M4,
REF("CLAIM GRAV-UNIFORM 1.1.0: no module text"))
X("X37 two claims of one module", D_P2, [C_GRAV, C_GRAV], M4,
REF("CLAIMS: duplicate module GRAV-UNIFORM"))
X("X38 claims not a list", D_P2, C_GRAV, M4, REF("CLAIMS: not a list"))
_cm = dict(C_GRAV)
del _cm["ANSWERS"]
X("X39 a claim without ANSWERS", D_P2, [_cm], M4, REF("CLAIM 1: missing field ANSWERS"))
X("X40 a claim with an unknown field", D_P2, [dict(C_GRAV, NOTE="x")], M4,
REF("CLAIM 1: unknown field NOTE"))
X("X41 a claim whose DIGEST is not a digest", D_P2, [dict(C_GRAV, DIGEST="XYZ")], M4,
REF("CLAIM 1: DIGEST is not a digest"))
X("X42 the draft not an object", [P2], [C_GRAV], M4, REF("DRAFT: not an object"))
X("X43 an unknown zone", {"TPK": P2, "RR": {}}, [C_GRAV], M4, REF("DRAFT: unknown zone RR"))
X("X44 no TPK", {"TMS": {}}, [C_GRAV], M4, REF("DRAFT: missing zone TPK"))
X("X45 a base TPK failing T1", {"TPK": _p2bad}, [C_GRAV], M4,
REF("BASE: the TPK fails T1: LAW:EOM_Z: REFS g_n does not resolve"))
X("X46 modules not a list", D_P2, [C_GRAV], X_GRAV, REF("MODULES: not a list of texts"))
X("X47 a module text that is not JSON", D_P2, [C_GRAV], [X_GRAV, "{"],
REF("MODULES 2: MODULE: the text is not JSON (RFC 8259, strict)"))
X("X48 one text twice", D_P2, [C_GRAV], [X_GRAV, json.dumps(GRAV, indent=1)],
REF("MODULES: duplicate text of GRAV-UNIFORM 1.0.0"))
X("X49 no claim: the base unchanged (the NONE answer)", D_P2, [], M4,
XR(P2, [], [], []))
X("X50 a binding refused inside the expansion", D_P2,
[dict(C_GRAV, BIND=dict(GRAV_BIND, UP_VELOCITY=["z"]))], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: BIND: slot UP_VELOCITY: z has dimension L1, not L1 T-1"))
_long = "m*D(v_z,t)=" + "-c_1*v_z" * 510
assert len(_long) == 4091
_p2long = copy.deepcopy(P2)
_p2long["CONSTANT"].append({"NAME": "c_1", "DIMENSION": "M1 T-1"})
_p2long["LAW"][3] = law("EOM_Z", _long, "EOM(v_z)", ["c_1", "m", "t", "v_z"], "a long force side")
assert Z.t1_well_formed(_p2long)[0] and label_of(_p2long, "v_z") == ("DETERMINED", "EOM_Z")
assert F.parse_form(_long + "-m*g_n", "equation") == [
"REFUSED", "the form is longer than 4096 code points"]
X("X51 a composed EQUATION over 4096 code points (D186.1)", {"TPK": _p2long}, [C_GRAV], M4,
REF("EXPAND: LAW EOM_Z: the composed EQUATION is refused: the form is longer than 4096 code "
"points"))
X("X52 one name bound to two slots, inside the expansion", D_P2,
[claim("TEST-PUSH", "1.0.0", D_TPUSH, {"PULLED": ["v_x"], "PUSHED": ["v_x"]},
{"F_a": {}, "F_b": {}}, {})], [X_TPUSH],
REF("CLAIM TEST-PUSH 1.0.0: BIND: v_x is bound to two slots, PUSHED and PULLED"))
X("X53 TrM and TMS in the draft: the expansion as X01 (the RENDER shows D(TPK) only)",
{"TPK": P2, "TrM": {"HEADER": {}}, "TMS": {"PARAMETERS": {}}}, [C_GRAV], M4, R_GRAV)
# ---- RV's vectors (review 0.1.0 F-9; rv_additions.py), expectations re-derived here
TP1_ = "TEST-PUSH 1.0.0"
C_TPUSH_TAB = claim("TEST-PUSH", "1.0.0", D_TPUSH, {"PULLED": ["v_z"], "PUSHED": ["v_x"]},
{"F_a": {}, "F_b": {}}, {"FIXED:v_z": "YES"})
E_FA = {"NAME": "F_a", "DIMENSION": "L1 M1 T-2"}
E_FB = {"NAME": "F_b", "DIMENSION": "L1 M1 T-2"}
_EXP_TAB2 = tpk_mod(TABLE, {"EOM_X": ("m*D(v_x,t)=F_a+F_b", ["F_a", "F_b", "m", "t", "v_x"],
"m dv_x/dt = 0; with the push: +(F_a+F_b)")},
[("CONSTANT", E_GN), ("CONSTANT", E_HMAX1), ("ASSUMPTION", E_GU),
("ASSUMPTION", E_GS), ("ASSUMPTION", E_FI), ("BOUNDARY", E_GR),
("ASSUMPTION", E_FIXVZ), ("CONSTANT", E_FA), ("CONSTANT", E_FB)])
must_pass(_EXP_TAB2, "_EXP_TAB2")
assert DTPK(_EXP_TAB2) == "4b845b45a26745ae30d77643b7a7fb4270259c8febcc096d32cdec4ddefdd382"
X("X54 two modules drop terms on one FIXED DOF: the balance assumption merges", D_TAB,
[C_TPUSH_TAB, C_TAB_YES], M4 + [X_TPUSH],
XR(_EXP_TAB2, [["EOM_X", "m*D(v_x,t)=F_a+F_b"]],
rows_of(GRAV_EMIT_100[:1], G1) + [["CONSTANT", "h_max", [G1]]]
+ rows_of(GRAV_EMIT_100[2:], G1) + [["ASSUMPTION", "fixed_v_z_balanced", [G1, TP1_]],
["CONSTANT", "F_a", [TP1_]], ["CONSTANT", "F_b", [TP1_]]],
[[G1, "v_z"], [TP1_, "v_z"]]))
_und = copy.deepcopy(P2)
_und["DOF"].append(dof("w", "L1 M1 T-2"))
_und["LAW"][3] = law("EOM_Z", "m*D(v_z,t)=w", "EOM(v_z)", ["m", "t", "v_z", "w"], "m dv_z/dt = w")
assert Z.t1_well_formed(_und)[0] and label_of(_und, "v_z")[0] == "UNDER"
X("X55 the target in the UNDER part", {"TPK": _und}, [C_GRAV], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: TERM 1: v_z's equation of motion is not determined (UNDER); "
"resolve it, then bind the module"))
_ovr = copy.deepcopy(P2)
_ovr["LAW"].append(law("EOM_Z2", "m*D(v_z,t)=0", "EOM(v_z)", ["m", "t", "v_z"],
"m dv_z/dt = 0, again"))
assert Z.t1_well_formed(_ovr)[0] and label_of(_ovr, "v_z")[0] == "OVER"
X("X56 two equations of motion for the target (OVER)", {"TPK": _ovr}, [C_GRAV], M4,
REF("CLAIM GRAV-UNIFORM 1.0.0: TERM 1: v_z's equation of motion is not determined (OVER); "
"resolve it, then bind the module"))
SUMP1 = "TEST-SUMP 1.0.0"
C_SUMP = claim("TEST-SUMP", "1.0.0", D_SUMP, {"VELOCITY": ["v_x", "v_z"]}, {"k_q": {}}, {})
E_KQ = {"NAME": "k_q", "DIMENSION": "L-1 M1"}
_EQ_SX = canon_is("m*D(v_x,t)=-k_q*(v_x^2+v_z^2)", "equation")
_EQ_SZ = canon_is("m*D(v_z,t)=-k_q*(v_x^2+v_z^2)", "equation")
_EXP_SUMP = tpk_mod(P2, {"EOM_X": (_EQ_SX, ["k_q", "m", "t", "v_x", "v_z"],
"m dv_x/dt = 0; with the test force: -k_q*(v_x^2+v_z^2)"),
"EOM_Z": (_EQ_SZ, ["k_q", "m", "t", "v_x", "v_z"],
"m dv_z/dt = 0; with the test force: -k_q*(v_x^2+v_z^2)")},
[("CONSTANT", E_KQ)])
must_pass(_EXP_SUMP, "_EXP_SUMP")
assert DTPK(_EXP_SUMP) == "e1cff39ece3129f139b8b74d9e94d7e33718d2a94920165f2f5b6cd471acf187"
X("X57 a sum inside a product keeps its parentheses (3.4)", D_P2, [C_SUMP], [X_SUMP],
XR(_EXP_SUMP, [["EOM_X", _EQ_SX], ["EOM_Z", _EQ_SZ]], [["CONSTANT", "k_q", [SUMP1]]], []))
X("X58 a claim that is not an object", D_P2, ["GRAV-UNIFORM"], M4, REF("CLAIM 1: not an object"))
X("X59 a claim ID that is not a module ID", D_P2, [dict(C_GRAV, ID="grav")], M4,
REF("CLAIM 1: ID is not a module ID"))
X("X60 a claim VERSION that is not a version", D_P2, [dict(C_GRAV, VERSION="1")], M4,
REF("CLAIM 1: VERSION is not a version"))
C_NOAIR = claim("TEST-NOAIR", "1.0.0", D_NOAIR, {}, {}, {})
X("X61 EXCLUDES by pattern (AIR-*)", D_P2, [C_NOAIR, C_AIRQ], M4 + [X_NOAIR],
REF("CLAIMS: AIR-QUADRATIC 1.0.0 and TEST-NOAIR 1.0.0 exclude each other (EXCLUDES)"))
# ---- S51: same-named AUTHOR parameters left symbolic by two modules merge
SHARE1 = "TEST-SHARE 1.0.0"
C_SHARE = claim("TEST-SHARE", "1.0.0", D_SHARE, {}, {"k_q": {}}, {})
X("X62 two same-named AUTHOR parameters left symbolic: one CONSTANT (S51)", D_P2,
[C_SUMP, C_SHARE], [X_SUMP, X_SHARE],
XR(_EXP_SUMP, [["EOM_X", _EQ_SX], ["EOM_Z", _EQ_SZ]], [["CONSTANT", "k_q", [SHARE1, SUMP1]]], []))
X("X63 the same, one given a value: they differ, refused", D_P2,
[C_SUMP, dict(C_SHARE, VALUES={"k_q": {"VALUE": "2", "UNIT": "kg m-1"}})], [X_SUMP, X_SHARE],
REF("EXPAND: TEST-SHARE 1.0.0 and TEST-SUMP 1.0.0 both emit k_q, differently"))
# 0.2.1 (C-MS-5, increment 11): the base TPK reaches T1 as the JSON reads, {} apart from []
# (tpm_module_spec C-MS-5: TPC-PHP's bridge into the zone functions now keeps the distinction)
_X_DOF = {"NAME": "x", "DIMENSION": "L1"}
assert t1_first({"DOF": {}}) == "TPK kind DOF is not a list"
X("X64 a base TPK kind that is a JSON {} is not a list (C-MS-5)", {"TPK": {"DOF": {}}}, [], [],
REF("BASE: the TPK fails T1: TPK kind DOF is not a list"))
X("X65 a base TPK kind keyed 0 is not a list (C-MS-5)", {"TPK": {"DOF": {"0": _X_DOF}}}, [], [],
REF("BASE: the TPK fails T1: TPK kind DOF is not a list"))
X("X66 an empty PARAMETERS object in the base is valid, D_VARIABLES ONE (C-MS-5)",
{"TPK": {"PARAMETERS": {}, "DOF": [_X_DOF]}}, [], [],
XR({"DOF": [_X_DOF]}, [], [], []))
X("X67 an entry that is a JSON [] is not an object (C-MS-5)", {"TPK": {"DOF": [[]]}}, [], [],
REF("BASE: the TPK fails T1: TPK kind DOF: entry is not an object"))
# ============================================================== catalogue lines
DAY1, DAY2, DAY3, DAY4, DAY5 = "2026-10-01", "2026-10-02", "2026-10-03", "2026-10-04", "2026-10-05"
def LADD(i, v, d, date=DAY1):
return {"ACT": "MODULE ADD", "ID": i, "VERSION": v, "DIGEST": d, "DATE": date}
def LVER(i, v, d, date):
return {"ACT": "MODULE VERSION", "ID": i, "VERSION": v, "DIGEST": d, "DATE": date}
def LRET(i, v, date):
return {"ACT": "MODULE RETIRE", "ID": i, "VERSION": v, "DATE": date}
def LREI(i, v, date):
return {"ACT": "MODULE REINSTATE", "ID": i, "VERSION": v, "DATE": date}
def LWD(i, v, reason, date, repl=None):
d = {"ACT": "MODULE WITHDRAW", "ID": i, "VERSION": v, "REASON": reason, "DATE": date}
if repl is not None:
d["REPLACED_BY"] = repl
return d
GU, AQ, AL, MA, TX = "GRAV-UNIFORM", "AIR-QUADRATIC", "AIR-LINEAR", "MATH", "TEST-X"
L4 = [LADD(AL, "1.0.0", D_AIRL), LADD(AQ, "1.0.0", D_AIRQ), LADD(GU, "1.0.0", D_GRAV),
LADD(MA, "1.0.0", D_MATH)]
OF_AL, OF_AQ, OF_GU, OF_MA = ([AL, "1.0.0", D_AIRL], [AQ, "1.0.0", D_AIRQ], [GU, "1.0.0", D_GRAV],
[MA, "1.0.0", D_MATH])
OFFER4 = [OF_AL, OF_AQ, OF_GU, OF_MA]
OF_TX = [TX, "1.0.0", D_TX]
OF_GU11 = [GU, "1.1.0", D_GRAV11]
L5X = L4 + [LADD(TX, "1.0.0", D_TX, DAY2)]
L5V = L4 + [LVER(GU, "1.1.0", D_GRAV11, DAY2)]
L5R = L4 + [LRET(MA, "1.0.0", DAY2)]
REASON = "the weight's sign was reversed in the binding page's preview"
L5W = L4 + [LWD(GU, "1.0.0", REASON, DAY2)]
def VROW(i, v, d, st, date, note=""):
return [i, v, d, st, date, note]
def ST(n_offers):
return [[n + 1, SD(o)] for n, o in enumerate(n_offers)]
def CS(name, lines, expected):
add(name, "catalogue_state", {"lines": lines}, expected)
ROWS4 = [VROW(AL, "1.0.0", D_AIRL, "OFFERED", DAY1), VROW(AQ, "1.0.0", D_AIRQ, "OFFERED", DAY1),
VROW(GU, "1.0.0", D_GRAV, "OFFERED", DAY1), VROW(MA, "1.0.0", D_MATH, "OFFERED", DAY1)]
STATES4 = ST([[OF_AL], [OF_AL, OF_AQ], [OF_AL, OF_AQ, OF_GU], OFFER4])
CS("S01 no line: an empty catalogue", [], OK(["VERSIONS", []], ["OFFERED", []], ["STATES", []]))
CS("S02 the first catalogue: four MODULE ADD lines", L4,
OK(["VERSIONS", ROWS4], ["OFFERED", OFFER4], ["STATES", STATES4]))
CS("S03 MODULE VERSION: the previous version SUPERSEDED in the same act", L5V,
OK(["VERSIONS", ROWS4[:2] + [VROW(GU, "1.0.0", D_GRAV, "SUPERSEDED", DAY2),
VROW(GU, "1.1.0", D_GRAV11, "OFFERED", DAY2), ROWS4[3]]],
["OFFERED", [OF_AL, OF_AQ, OF_GU11, OF_MA]],
["STATES", STATES4 + [[5, SD([OF_AL, OF_AQ, OF_GU11, OF_MA])]]]))
CS("S04 MODULE RETIRE", L5R,
OK(["VERSIONS", ROWS4[:3] + [VROW(MA, "1.0.0", D_MATH, "RETIRED", DAY2)]],
["OFFERED", [OF_AL, OF_AQ, OF_GU]],
["STATES", STATES4 + [[5, SD([OF_AL, OF_AQ, OF_GU])]]]))
CS("S05 MODULE REINSTATE of a RETIRED version; equal offers, equal state digests",
L5R + [LREI(MA, "1.0.0", DAY3)],
OK(["VERSIONS", ROWS4[:3] + [VROW(MA, "1.0.0", D_MATH, "OFFERED", DAY3)]],
["OFFERED", OFFER4],
["STATES", STATES4 + [[5, SD([OF_AL, OF_AQ, OF_GU])], [6, SD(OFFER4)]]]))
CS("S06 MODULE REINSTATE of a SUPERSEDED version: the OFFERED one SUPERSEDED",
L5V + [LREI(GU, "1.0.0", DAY3)],
OK(["VERSIONS", ROWS4[:2] + [VROW(GU, "1.0.0", D_GRAV, "OFFERED", DAY3),
VROW(GU, "1.1.0", D_GRAV11, "SUPERSEDED", DAY3), ROWS4[3]]],
["OFFERED", OFFER4],
["STATES", STATES4 + [[5, SD([OF_AL, OF_AQ, OF_GU11, OF_MA])], [6, SD(OFFER4)]]]))
CS("S07 MODULE WITHDRAW of the OFFERED version: the ID offers nothing", L5W,
OK(["VERSIONS", ROWS4[:2] + [VROW(GU, "1.0.0", D_GRAV, "WITHDRAWN", DAY2, REASON), ROWS4[3]]],
["OFFERED", [OF_AL, OF_AQ, OF_MA]],
["STATES", STATES4 + [[5, SD([OF_AL, OF_AQ, OF_MA])]]]))
CS("S08 MODULE WITHDRAW of a SUPERSEDED version", L5V + [LWD(GU, "1.0.0", REASON, DAY3)],
OK(["VERSIONS", ROWS4[:2] + [VROW(GU, "1.0.0", D_GRAV, "WITHDRAWN", DAY3, REASON),
VROW(GU, "1.1.0", D_GRAV11, "OFFERED", DAY2), ROWS4[3]]],
["OFFERED", [OF_AL, OF_AQ, OF_GU11, OF_MA]],
["STATES", STATES4 + [[5, SD([OF_AL, OF_AQ, OF_GU11, OF_MA])],
[6, SD([OF_AL, OF_AQ, OF_GU11, OF_MA])]]]))
CS("S09 MODULE WITHDRAW naming REPLACED_BY a SUPERSEDED version",
L5V + [LWD(GU, "1.1.0", REASON, DAY3, "1.0.0")],
OK(["VERSIONS", ROWS4[:2] + [VROW(GU, "1.0.0", D_GRAV, "OFFERED", DAY3),
VROW(GU, "1.1.0", D_GRAV11, "WITHDRAWN", DAY3, REASON), ROWS4[3]]],
["OFFERED", OFFER4],
["STATES", STATES4 + [[5, SD([OF_AL, OF_AQ, OF_GU11, OF_MA])], [6, SD(OFFER4)]]]))
_L_ret_ver = L4 + [LRET(GU, "1.0.0", DAY2), LVER(GU, "1.1.0", D_GRAV11, DAY3)]
CS("S10 MODULE VERSION of an ID whose versions are all RETIRED", _L_ret_ver,
OK(["VERSIONS", ROWS4[:2] + [VROW(GU, "1.0.0", D_GRAV, "RETIRED", DAY2),
VROW(GU, "1.1.0", D_GRAV11, "OFFERED", DAY3), ROWS4[3]]],
["OFFERED", [OF_AL, OF_AQ, OF_GU11, OF_MA]],
["STATES", STATES4 + [[5, SD([OF_AL, OF_AQ, OF_MA])],
[6, SD([OF_AL, OF_AQ, OF_GU11, OF_MA])]]]))
_L_repl_ret = L4 + [LVER(GU, "1.1.0", D_GRAV11, DAY2), LRET(GU, "1.1.0", DAY3),
LREI(GU, "1.0.0", DAY4), LWD(GU, "1.0.0", REASON, DAY5, "1.1.0")]
CS("S11 REPLACED_BY a RETIRED version", _L_repl_ret,
OK(["VERSIONS", ROWS4[:2] + [VROW(GU, "1.0.0", D_GRAV, "WITHDRAWN", DAY5, REASON),
VROW(GU, "1.1.0", D_GRAV11, "OFFERED", DAY5), ROWS4[3]]],
["OFFERED", [OF_AL, OF_AQ, OF_GU11, OF_MA]],
["STATES", STATES4 + [[5, SD([OF_AL, OF_AQ, OF_GU11, OF_MA])],
[6, SD([OF_AL, OF_AQ, OF_MA])], [7, SD(OFFER4)],
[8, SD([OF_AL, OF_AQ, OF_GU11, OF_MA])]]]))
CS("S12 MODULE ADD of an ID already declared", L4 + [LADD(GU, "2.0.0", HX("a"), DAY2)],
REF("LINE 5: MODULE ADD of GRAV-UNIFORM, an ID already declared (no ID is reissued)"))
CS("S13 MODULE ADD of an ID whose only version is RETIRED",
L4 + [LRET(GU, "1.0.0", DAY2), LADD(GU, "1.0.0", HX("b"), DAY3)],
REF("LINE 6: MODULE ADD of GRAV-UNIFORM, an ID already declared (no ID is reissued)"))
CS("S14 MODULE VERSION of an ID never added", [LVER(GU, "1.0.0", HX("c"), DAY1)],
REF("LINE 1: MODULE VERSION of GRAV-UNIFORM, an ID never added"))
CS("S15 MODULE VERSION reissuing a version", L4 + [LVER(GU, "1.0.0", HX("d"), DAY2)],
REF("LINE 5: GRAV-UNIFORM 1.0.0 is already declared (no version is reissued)"))
CS("S16 MODULE VERSION not greater", L4 + [LVER(GU, "0.9.0", HX("e"), DAY2)],
REF("LINE 5: GRAV-UNIFORM 0.9.0 is not greater than GRAV-UNIFORM 1.0.0"))
CS("S17 greater as integers: 1.10.0 after 1.9.0",
[LADD("TEST-V", "1.9.0", HX("v19")), LVER("TEST-V", "1.10.0", HX("v110"), DAY2)],
OK(["VERSIONS", [VROW("TEST-V", "1.10.0", HX("v110"), "OFFERED", DAY2),
VROW("TEST-V", "1.9.0", HX("v19"), "SUPERSEDED", DAY2)]],
["OFFERED", [["TEST-V", "1.10.0", HX("v110")]]],
["STATES", [[1, SD([["TEST-V", "1.9.0", HX("v19")]])],
[2, SD([["TEST-V", "1.10.0", HX("v110")]])]]]))
CS("S18 MODULE REINSTATE of a WITHDRAWN version", L5W + [LREI(GU, "1.0.0", DAY3)],
REF("LINE 6: MODULE REINSTATE of GRAV-UNIFORM 1.0.0, which is WITHDRAWN (WITHDRAWN is final)"))
CS("S19 MODULE REINSTATE of the OFFERED version", L4 + [LREI(GU, "1.0.0", DAY2)],
REF("LINE 5: MODULE REINSTATE of GRAV-UNIFORM 1.0.0, which is already OFFERED"))
CS("S20 MODULE RETIRE of a SUPERSEDED version", L5V + [LRET(GU, "1.0.0", DAY3)],
REF("LINE 6: MODULE RETIRE of GRAV-UNIFORM 1.0.0, which is SUPERSEDED, not OFFERED"))
CS("S21 MODULE RETIRE of a version never declared", L4 + [LRET(GU, "3.0.0", DAY2)],
REF("LINE 5: GRAV-UNIFORM 3.0.0 is not declared"))
_nor = LWD(GU, "1.0.0", REASON, DAY2)
del _nor["REASON"]
CS("S22 MODULE WITHDRAW without a reason", L4 + [_nor],
REF("LINE 5: MODULE WITHDRAW without a reason"))
CS("S23 MODULE WITHDRAW with a blank reason", L4 + [LWD(GU, "1.0.0", " ", DAY2)],
REF("LINE 5: MODULE WITHDRAW without a reason"))
CS("S24 WITHDRAWN is final: withdrawn twice", L5W + [LWD(GU, "1.0.0", REASON, DAY3)],
REF("LINE 6: GRAV-UNIFORM 1.0.0 is already WITHDRAWN (WITHDRAWN is final)"))
CS("S25 REPLACED_BY when the withdrawn version is not OFFERED",
L5V + [LWD(GU, "1.0.0", REASON, DAY3, "1.1.0")],
REF("LINE 6: REPLACED_BY needs GRAV-UNIFORM 1.0.0 OFFERED; it is SUPERSEDED"))
CS("S26 REPLACED_BY a version never declared", L4 + [LWD(GU, "1.0.0", REASON, DAY2, "0.5.0")],
REF("LINE 5: REPLACED_BY GRAV-UNIFORM 0.5.0 is not declared"))
CS("S27 REPLACED_BY a WITHDRAWN version",
L5V + [LWD(GU, "1.0.0", REASON, DAY3), LWD(GU, "1.1.0", REASON, DAY4, "1.0.0")],
REF("LINE 7: REPLACED_BY GRAV-UNIFORM 1.0.0 is WITHDRAWN, not SUPERSEDED or RETIRED"))
CS("S28 an act that is not a module act",
[{"ACT": "MODULE DELETE", "ID": GU, "VERSION": "1.0.0", "DATE": DAY1}],
REF("LINE 1: ACT MODULE DELETE is not a module act"))
CS("S29 an unknown field", [dict(LADD(GU, "1.0.0", D_GRAV), NOTE="x")],
REF("LINE 1: unknown field NOTE"))
CS("S30 a field of another act: DIGEST on RETIRE", L4 + [dict(LRET(GU, "1.0.0", DAY2),
DIGEST=D_GRAV)],
REF("LINE 5: unknown field DIGEST"))
CS("S31 no ACT", [{"ID": GU, "VERSION": "1.0.0", "DATE": DAY1}], REF("LINE 1: missing field ACT"))
CS("S32 ACT not a string", [{"ACT": 1, "ID": GU}], REF("LINE 1: field ACT has the wrong type"))
_nod = LADD(GU, "1.0.0", D_GRAV)
del _nod["DIGEST"]
CS("S33 MODULE ADD without DIGEST", [_nod], REF("LINE 1: missing field DIGEST"))
CS("S34 a DIGEST in upper case", [LADD(GU, "1.0.0", D_GRAV.upper())],
REF("LINE 1: DIGEST is not a digest"))
CS("S35 a DIGEST already declared", [LADD(AL, "1.0.0", D_AIRL), LADD(AQ, "1.0.0", D_AIRL)],
REF("LINE 2: DIGEST already declared for AIR-LINEAR 1.0.0"))
CS("S36 a month 13", [LADD(GU, "1.0.0", D_GRAV, "2026-13-01")],
REF("LINE 1: DATE 2026-13-01 is not a date"))
CS("S37 29 February of a common year", [LADD(GU, "1.0.0", D_GRAV, "2026-02-29")],
REF("LINE 1: DATE 2026-02-29 is not a date"))
CS("S38 29 February of a leap year", [LADD(GU, "1.0.0", D_GRAV, "2028-02-29")],
OK(["VERSIONS", [VROW(GU, "1.0.0", D_GRAV, "OFFERED", "2028-02-29")]], ["OFFERED", [OF_GU]],
["STATES", [[1, SD([OF_GU])]]]))
CS("S39 a line dated before the previous one",
[LADD(AL, "1.0.0", D_AIRL, DAY2), LADD(AQ, "1.0.0", D_AIRQ, DAY1)],
REF("LINE 2: DATE 2026-10-01 is before the previous line's"))
CS("S40 a VERSION of two parts", [LADD(GU, "1.0", D_GRAV)],
REF("LINE 1: VERSION 1.0 is not a version"))
CS("S41 an ID in lower case", [LADD("grav", "1.0.0", D_GRAV)],
REF("LINE 1: ID grav is not a module ID"))
CS("S42 lines not a list", {"ACT": "MODULE ADD"}, REF("LINES: not a list"))
CS("S43 a line not an object", [["MODULE ADD"]], REF("LINE 1: not an object"))
CS("S44 a VERSION given as a number", [dict(LADD(GU, "1.0.0", D_GRAV), VERSION=1)],
REF("LINE 1: field VERSION has the wrong type"))
CS("S45 a REASON with a bidi override", L4 + [LWD(GU, "1.0.0", "bad \u202e sign", DAY2)],
REF("LINE 5: field REASON contains disallowed character U+202E"))
CS("S46 a REPLACED_BY that is not a version", L4 + [LWD(GU, "1.0.0", REASON, DAY2, "one")],
REF("LINE 5: REPLACED_BY one is not a version"))
# ---- RV's vectors (review 0.1.0 F-9)
CS("S47 MODULE WITHDRAW of a RETIRED version", L5R + [LWD(MA, "1.0.0", REASON, DAY3)],
OK(["VERSIONS", ROWS4[:3] + [VROW(MA, "1.0.0", D_MATH, "WITHDRAWN", DAY3, REASON)]],
["OFFERED", [OF_AL, OF_AQ, OF_GU]],
["STATES", STATES4 + [[5, SD([OF_AL, OF_AQ, OF_GU])], [6, SD([OF_AL, OF_AQ, OF_GU])]]]))
CS("S48 MODULE VERSION after the OFFERED version was WITHDRAWN (J F-4's repair)",
L5W + [LVER(GU, "1.1.0", D_GRAV11, DAY3)],
OK(["VERSIONS", ROWS4[:2] + [VROW(GU, "1.0.0", D_GRAV, "WITHDRAWN", DAY2, REASON),
VROW(GU, "1.1.0", D_GRAV11, "OFFERED", DAY3), ROWS4[3]]],
["OFFERED", [OF_AL, OF_AQ, OF_GU11, OF_MA]],
["STATES", STATES4 + [[5, SD([OF_AL, OF_AQ, OF_MA])],
[6, SD([OF_AL, OF_AQ, OF_GU11, OF_MA])]]]))
CS("S49 MODULE WITHDRAW of a version never declared", L4 + [LWD(GU, "3.0.0", REASON, DAY2)],
REF("LINE 5: GRAV-UNIFORM 3.0.0 is not declared"))
CS("S50 MODULE REINSTATE of a version never declared", L4 + [LREI(MA, "2.0.0", DAY2)],
REF("LINE 5: MATH 2.0.0 is not declared"))
CS("S51 MODULE VERSION whose DIGEST is already declared", L4 + [LVER(GU, "1.1.0", D_GRAV, DAY3)],
REF("LINE 5: DIGEST already declared for GRAV-UNIFORM 1.0.0"))
CS("S52 REPLACED_BY naming the withdrawn version itself",
L4 + [LWD(GU, "1.0.0", REASON, DAY2, "1.0.0")],
REF("LINE 5: REPLACED_BY GRAV-UNIFORM 1.0.0 is OFFERED, not SUPERSEDED or RETIRED"))
# ============================================================ interview_check
def IC(name, answer, claims, lines, modules, carried, expected, modified=()):
add(name, "interview_check", {"answer": answer, "claims": claims, "lines": lines,
"modules": modules, "carried": carried,
"modified": list(modified)}, expected)
def RND(at, covered, chosen, reselect=None):
r = {"AT": at, "COVERED": covered, "CHOSEN": chosen}
if reselect is not None:
r["RESELECT"] = reselect
return r
A_NONE4 = [RND(4, OFFER4, "NONE")]
A_GRAV4 = [RND(4, OFFER4, [GU])]
A_GQ4 = [RND(4, OFFER4, [AQ, GU])]
IC("I01 NONE over the first catalogue", A_NONE4, [], L4, M4, [],
OK(["ASK", []], ["CLAIMS", []]))
IC("I02 GRAV-UNIFORM chosen and claimed: PINNED", A_GRAV4, [C_GRAV], L4, M4, [],
OK(["ASK", []], ["CLAIMS", [[GU, "1.0.0", "PINNED"]]]))
IC("I03 two chosen, buoyancy answered", A_GQ4, [C_GRAV_NO, C_AIRQ], L4, M4, [],
OK(["ASK", []], ["CLAIMS", [[AQ, "1.0.0", "PINNED"], [GU, "1.0.0", "PINNED"]]]))
M5X = M4 + [X_TX]
IC("I04 a module offered after the answer is asked about (the delta)", A_NONE4, [], L5X, M5X, [],
OK(["ASK", [OF_TX]], ["CLAIMS", []]))
IC("I05 the delta answered in a second round", A_NONE4 + [RND(5, [OF_TX], "NONE")], [], L5X,
M5X, [], OK(["ASK", []], ["CLAIMS", []]))
IC("I06 a second round covering the whole offer", A_NONE4 + [RND(5, OFFER4 + [OF_TX], "NONE")], [],
L5X, M5X, [], REF("ANSWER ROUND 2: COVERED is not the offer the round was given over"))
IC("I07 a second round not after the first", A_NONE4 + [RND(4, [OF_TX], "NONE")], [], L5X, M5X,
[], REF("ANSWER ROUND 2: AT is not after the previous round's"))
M5V = M4 + [X_GRAV11]
IC("I08 a second round with nothing new (a new VERSION is no new module)",
A_NONE4 + [RND(5, [], "NONE")], [], L5V, M5V, [],
REF("ANSWER ROUND 2: no module was added since the previous round"))
IC("I09 AT beyond the lines", [RND(7, OFFER4, "NONE")], [], L4, M4, [],
REF("ANSWER ROUND 1: AT is not a line count from 0 to 4"))
IC("I10 no round: the interaction question not answered", [], [], L4, M4, [],
REF("INTERACTIONS: not answered"))
IC("I11 answer not a list", RND(4, OFFER4, "NONE"), [], L4, M4, [], REF("ANSWER: not a list"))
IC("I12 CHOSEN empty", [RND(4, OFFER4, [])], [], L4, M4, [],
REF("ANSWER ROUND 1: CHOSEN is empty; the exclusive answer is NONE"))
IC("I13 CHOSEN out of order", [RND(4, OFFER4, [MA, GU])], [C_GRAV, C_MATH], L4, M4, [],
REF("ANSWER ROUND 1: CHOSEN is not in code point order without repeats"))
IC("I14 CHOSEN an ID the round did not cover", [RND(4, OFFER4, [TX])], [], L4, M4, [],
REF("ANSWER ROUND 1: CHOSEN TEST-X is not in COVERED"))
IC("I15 CHOSEN a word other than NONE", [RND(4, OFFER4, "ALL")], [], L4, M4, [],
REF("ANSWER ROUND 1: field CHOSEN has the wrong type"))
IC("I16 NONE, and a claim", A_NONE4, [C_GRAV], L4, M4, [],
REF("CLAIM GRAV-UNIFORM 1.0.0: the answer did not choose GRAV-UNIFORM"))
IC("I17 chosen but not claimed", A_GRAV4, [], L4, M4, [],
REF("ANSWER: GRAV-UNIFORM is chosen but not claimed"))
IC("I18 a pinned claim whose version was SUPERSEDED after the answer: kept", A_GRAV4, [C_GRAV],
L5V, M5V, [], OK(["ASK", []], ["CLAIMS", [[GU, "1.0.0", "PINNED"]]]))
OFFER5V = [OF_AL, OF_AQ, OF_GU11, OF_MA]
IC("I19 a new claim of a version not OFFERED is refused", [RND(5, OFFER5V, [GU])], [C_GRAV], L5V,
M5V, [], REF("CLAIM GRAV-UNIFORM 1.0.0: not OFFERED when chosen (a new claim names the OFFERED "
"version)"))
IC("I20 the same claim carried by COPY-FORWARD is admitted", [RND(5, OFFER5V, [GU])], [C_GRAV],
L5V, M5V, [OF_GU], OK(["ASK", []], ["CLAIMS", [[GU, "1.0.0", "CARRIED"]]]))
OFFER5R = [OF_AL, OF_AQ, OF_GU]
IC("I21 a carried claim of an ID no longer offered", [RND(5, OFFER5R, "NONE")], [C_MATH], L5R, M4,
[OF_MA], OK(["ASK", []], ["CLAIMS", [[MA, "1.0.0", "CARRIED"]]]))
IC("I22 the same claim not carried", [RND(5, OFFER5R, "NONE")], [C_MATH], L5R, M4, [],
REF("CLAIM MATH 1.0.0: MATH is not in the answer"))
IC("I23 two excluding modules chosen", [RND(4, OFFER4, [AL, AQ])], [C_AIRQ, C_AIRL], L4, M4, [],
REF("CLAIMS: AIR-LINEAR 1.0.0 and AIR-QUADRATIC 1.0.0 exclude each other (EXCLUDES)"))
IC("I24 the buoyancy question unanswered", A_GQ4, [C_GRAV, C_AIRQ], L4, M4, [],
REF("CLAIM GRAV-UNIFORM 1.0.0: question BUOYANCY is not answered"))
IC("I25 a buoyancy answer with no air module", A_GRAV4, [C_GRAV_NO], L4, M4, [],
REF("CLAIM GRAV-UNIFORM 1.0.0: answer BUOYANCY answers no question raised"))
IC("I26 a FIXED answer is the expansion's to judge", A_GRAV4,
[claim(GU, "1.0.0", D_GRAV, GRAV_BIND, H100, {"FIXED:v_z": "YES"})], L4, M4, [],
OK(["ASK", []], ["CLAIMS", [[GU, "1.0.0", "PINNED"]]]))
IC("I27 a claim DIGEST not the declared one", A_GRAV4, [dict(C_GRAV, DIGEST=D_GRAV11)], L4, M4, [],
REF("CLAIM GRAV-UNIFORM 1.0.0: DIGEST does not match the declaration"))
IC("I28 carried not a list of triples", A_NONE4, [], L4, M4, [GU],
REF("CARRIED: not a list of [ID, VERSION, DIGEST]"))
IC("I29 carried naming a version not declared", A_NONE4, [], L4, M4, [[GU, "9.0.0", D_GRAV]],
REF("CARRIED GRAV-UNIFORM 9.0.0: not declared"))
IC("I30 COVERED in another order", [RND(4, [OF_AQ, OF_AL, OF_GU, OF_MA], "NONE")], [], L4, M4, [],
REF("ANSWER ROUND 1: COVERED is not the offer the round was given over"))
IC("I31 an empty catalogue answered NONE at AT 0", [RND(0, [], "NONE")], [], [], [], [],
OK(["ASK", []], ["CLAIMS", []]))
IC("I32 answered before any module: every module asked", [RND(0, [], "NONE")], [], L4, M4, [],
OK(["ASK", OFFER4], ["CLAIMS", []]))
IC("I33 a pinned claim whose version was WITHDRAWN after the answer: kept", A_GRAV4, [C_GRAV],
L5W, M4, [], OK(["ASK", []], ["CLAIMS", [[GU, "1.0.0", "PINNED"]]]))
IC("I34 a claim with no module text", A_GRAV4, [C_GRAV], L4, [X_AIRL, X_AIRQ, X_MATH], [],
REF("CLAIM GRAV-UNIFORM 1.0.0: no module text"))
IC("I35 a text of a version not declared", A_NONE4, [], L4, M4 + [X_GRAV11], [],
REF("MODULES: GRAV-UNIFORM 1.1.0 is not declared"))
IC("I36 a catalogue that is refused", A_NONE4, [], [["x"]], M4, [],
REF("CATALOGUE: LINE 1: not an object"))
IC("I37 a claim of a version never declared", A_GRAV4, [dict(C_GRAV, VERSION="1.0.5")], L4, M4, [],
REF("CLAIM GRAV-UNIFORM 1.0.5: not declared"))
# ---- RV's vectors (review 0.1.0 F-9)
L5RG_ = L4 + [LRET(GU, "1.0.0", DAY2)]
IC("I38 a pinned claim whose version was RETIRED after the answer: kept", A_GRAV4, [C_GRAV],
L5RG_, M4, [], OK(["ASK", []], ["CLAIMS", [[GU, "1.0.0", "PINNED"]]]))
_bad_cov = copy.deepcopy(A_GRAV4)
_bad_cov[0]["COVERED"] = "all"
IC("I39 COVERED of the wrong type", _bad_cov, [C_GRAV], L4, M4, [],
REF("ANSWER ROUND 1: field COVERED has the wrong type"))
IC("I40 CARRIED with a DIGEST not the declared one", A_GRAV4, [C_GRAV], L4, M4,
[[GU, "1.0.0", D_MATH]], REF("CARRIED GRAV-UNIFORM 1.0.0: DIGEST does not match the declaration"))
# ---- S50: RESELECT (RV F-2, RD-RV-3)
C_GRAV11 = claim(GU, "1.1.0", D_GRAV11, GRAV_BIND, H100, {})
A_RESEL = A_GRAV4 + [RND(5, [OF_GU11], [GU], [GU])]
IC("I41 a re-selection round takes the OFFERED version: PINNED to it", A_RESEL, [C_GRAV11], L5V,
M5V, [], OK(["ASK", []], ["CLAIMS", [[GU, "1.1.0", "PINNED"]]]))
IC("I42 the same round, the claim still on the old version: refused (DR-7)", A_RESEL, [C_GRAV],
L5V, M5V, [], REF("CLAIM GRAV-UNIFORM 1.0.0: not OFFERED when chosen (a new claim names the "
"OFFERED version)"))
IC("I43 RESELECT of an ID no earlier round covered", A_NONE4 + [RND(5, [OF_TX], "NONE", [TX])],
[], L5X, M5X, [], REF("ANSWER ROUND 2: RESELECT TEST-X was not covered before"))
# ---- S50: MODIFIED (RV F-3, C-RV-2)
IC("I44 a chosen module the author edited by hand: MODIFIED, not claimed", A_GRAV4, [], L4, M4, [],
OK(["ASK", []], ["CLAIMS", []]), modified=[[GU, "1.0.0"]])
IC("I45 MODIFIED not a list of pairs", A_GRAV4, [], L4, M4, [],
REF("MODIFIED: not a list of [ID, VERSION]"), modified=[[GU, "1.0.0", D_GRAV]])
IC("I46 MODIFIED naming a version never declared", A_GRAV4, [], L4, M4, [],
REF("MODIFIED GRAV-UNIFORM 9.0.0: not declared"), modified=[[GU, "9.0.0"]])
IC("I47 MODIFIED and claimed at once", A_GRAV4, [C_GRAV], L4, M4, [],
REF("MODIFIED GRAV-UNIFORM 1.0.0: the ID is also claimed"), modified=[[GU, "1.0.0"]])
IC("I48 MODIFIED naming one module twice", A_GRAV4, [], L4, M4, [],
REF("MODIFIED: duplicate module GRAV-UNIFORM"), modified=[[GU, "1.0.0"], [GU, "1.0.0"]])
# ---- RESELECT's own refusals, and the round's shape (5.4)
IC("I49 RESELECT of the wrong type", A_GRAV4 + [RND(5, [OF_GU11], [GU], GU)], [C_GRAV11], L5V,
M5V, [], REF("ANSWER ROUND 2: field RESELECT has the wrong type"))
IC("I50 RESELECT out of order", A_NONE4 + [RND(5, OFFER5V, "NONE", [MA, GU])], [], L5V, M5V, [],
REF("ANSWER ROUND 2: RESELECT is not in code point order without repeats"))
L6WV = L5V + [LWD(GU, "1.1.0", REASON, DAY3)]
IC("I51 RESELECT of an ID no longer offered", A_GRAV4 + [RND(6, [], "NONE", [GU])], [C_GRAV],
L6WV, M5V, [], REF("ANSWER ROUND 2: RESELECT GRAV-UNIFORM is not offered at AT"))
IC("I52 a round that is not an object", [OFFER4], [], L4, M4, [],
REF("ANSWER ROUND 1: not an object"))
IC("I53 a round with an unknown field", [dict(RND(4, OFFER4, "NONE"), NOTE="x")], [], L4, M4, [],
REF("ANSWER ROUND 1: unknown field NOTE"))
_nochosen = RND(4, OFFER4, "NONE")
del _nochosen["CHOSEN"]
IC("I54 a round without CHOSEN", [_nochosen], [], L4, M4, [],
REF("ANSWER ROUND 1: missing field CHOSEN"))
IC("I55 a re-selection round answered NONE: the module dropped, nothing claimed", A_GRAV4
+ [RND(5, [OF_GU11], "NONE", [GU])], [], L5V, M5V, [], OK(["ASK", []], ["CLAIMS", []]))
# ========================================================= not_included_lines
def NI(name, answer, claims, lines, modules, carried, expected, modified=()):
add(name, "not_included_lines", {"answer": answer, "claims": claims, "lines": lines,
"modules": modules, "carried": carried,
"modified": list(modified)}, expected)
CATCH = ["-", "Any interaction not listed under INCLUDED is absent from this kernel."]
NC_AL, NC_AQ, NC_GU, NC_MA = ([AL1, AIRL_NC], [AQ1, AIRQ_NC], [G1, GRAV_NC], [MA1, MATH_NC])
NI("K01 NONE: one NOT_CHOSEN line per covered module, then the closing line", A_NONE4, [], L4, M4,
[], OK([NC_AL, NC_AQ, NC_GU, NC_MA, CATCH]))
NI("K02 GRAV-UNIFORM chosen: its NOT_INCLUDED lines", A_GRAV4, [C_GRAV], L4, M4, [],
OK([NC_AL, NC_AQ, NC_MA] + [[G1, x] for x in GRAV_NI] + [CATCH]))
NI("K03 GRAV-UNIFORM and AIR-QUADRATIC, buoyancy NO", A_GQ4, [C_GRAV_NO, C_AIRQ], L4, M4, [],
OK([NC_AL, NC_MA] + [[AQ1, x] for x in AIRQ_NI] + [[G1, x] for x in GRAV_NI]
+ [[G1, NO_B], CATCH]))
NI("K04 the same, buoyancy YES", A_GQ4, [C_GRAV_YES, C_AIRQ], L4, M4, [],
OK([NC_AL, NC_MA] + [[AQ1, x] for x in AIRQ_NI] + [[G1, x] for x in GRAV_NI]
+ [[G1, YES_B], CATCH]))
NI("K05 a module RETIRED after the answer keeps its line", A_NONE4, [], L5R, M4, [],
OK([NC_AL, NC_AQ, NC_GU, NC_MA, CATCH]))
NI("K06 a module offered after the answer has no line until answered", A_NONE4, [], L5X, M5X, [],
OK([NC_AL, NC_AQ, NC_GU, NC_MA, CATCH]))
TX1 = "TEST-X 1.0.0"
NI("K07 answered in a second round: its line, in ID order", A_NONE4 + [RND(5, [OF_TX], "NONE")],
[], L5X, M5X, [], OK([NC_AL, NC_AQ, NC_GU, NC_MA, [TX1, "No test interaction X."], CATCH]))
NI("K08 chosen in the second round", A_NONE4 + [RND(5, [OF_TX], [TX])], [C_TX], L5X, M5X, [],
OK([NC_AL, NC_AQ, NC_GU, NC_MA, [TX1, "Test X adds only a constant."], CATCH]))
NI("K09 the interview refused: its refusal", [], [], L4, M4, [],
REF("INTERACTIONS: not answered"))
NI("K10 a carried claim of an ID no longer offered keeps its lines", [RND(5, OFFER5R, "NONE")],
[C_MATH], L5R, M4, [OF_MA], OK([NC_AL, NC_AQ, NC_GU] + [[MA1, x] for x in MATH_NI] + [CATCH]))
NI("K11 a covered module without its text", A_NONE4, [], L4, [X_AIRL, X_AIRQ, X_GRAV], [],
REF("MODULES: no text of MATH 1.0.0, covered by the answer"))
# ---- S50: MODIFIED and RESELECT in the check page's lines
_MODL = [G1, "Modified from GRAV-UNIFORM 1.0.0: its entries are the author's own."]
NI("K12 a module the author edited by hand: its Modified line after the claims' lines", A_GQ4,
[C_AIRQ], L4, M4, [], OK([NC_AL, NC_MA] + [[AQ1, x] for x in AIRQ_NI] + [_MODL, CATCH]),
modified=[[GU, "1.0.0"]])
NI("K13 a re-selection round answered NONE: the latest round's line", A_GRAV4
+ [RND(5, [OF_GU11], "NONE", [GU])], [], L5V, M5V, [],
OK([NC_AL, NC_AQ, ["GRAV-UNIFORM 1.1.0", GRAV_NC], NC_MA, CATCH]))
NI("K14 a re-selection round choosing the new version: its lines", A_RESEL, [C_GRAV11], L5V, M5V,
[], OK([NC_AL, NC_AQ, NC_MA] + [["GRAV-UNIFORM 1.1.0", x] for x in GRAV_NI] + [CATCH]))
# ============================================================== module_verify
def V(name, base, claims, modules, lines, draft, expected):
add(name, "module_verify", {"base": base, "claims": claims, "modules": modules,
"lines": lines, "draft": draft}, expected)
W_GRAV = DTPK(EXP_GRAV)
V("V01 the draft carries the expansion: MATCH, OFFERED", D_P2, [C_GRAV], M4, L4,
{"TPK": EXP_GRAV}, OK("MATCH", W_GRAV, W_GRAV, [[GU, "1.0.0", "OFFERED"]]))
V("V02 re-expanded whatever the status: SUPERSEDED", D_P2, [C_GRAV], M5V, L5V, {"TPK": EXP_GRAV},
OK("MATCH", W_GRAV, W_GRAV, [[GU, "1.0.0", "SUPERSEDED"]]))
L5RG = L4 + [LRET(GU, "1.0.0", DAY2)]
V("V03 RETIRED", D_P2, [C_GRAV], M4, L5RG, {"TPK": EXP_GRAV},
OK("MATCH", W_GRAV, W_GRAV, [[GU, "1.0.0", "RETIRED"]]))
V("V04 WITHDRAWN", D_P2, [C_GRAV], M4, L5W, {"TPK": EXP_GRAV},
OK("MATCH", W_GRAV, W_GRAV, [[GU, "1.0.0", "WITHDRAWN"]]))
_ed = copy.deepcopy(EXP_GRAV)
_ed["CONSTANT"][2]["VALUE"] = "200"
assert _ed["CONSTANT"][2]["NAME"] == "h_max"
V("V05 an emitted value edited by hand: MISMATCH", D_P2, [C_GRAV], M4, L4, {"TPK": _ed},
OK("MISMATCH", W_GRAV, DTPK(_ed), [[GU, "1.0.0", "OFFERED"]]))
_noterm = tpk_mod(P2, None, GRAV_EMIT_100)
V("V06 the entries without the composed term: MISMATCH", D_P2, [C_GRAV], M4, L4,
{"TPK": _noterm}, OK("MISMATCH", W_GRAV, DTPK(_noterm), [[GU, "1.0.0", "OFFERED"]]))
_nwf = copy.deepcopy(EXP_GRAV)
_nwf["LAW"][3]["REFS"] = ["m", "t", "v_z"]
assert Z.t1_well_formed(_nwf)[0] is False
V("V07 a draft TPK failing T1: NOT WELL-FORMED", D_P2, [C_GRAV], M4, L4, {"TPK": _nwf},
OK("MISMATCH", W_GRAV, "NOT WELL-FORMED", [[GU, "1.0.0", "OFFERED"]]))
V("V08 a claim of a version never declared", D_P2, [dict(C_GRAV, VERSION="1.1.0",
DIGEST=D_GRAV11)], M4, L4,
{"TPK": EXP_GRAV}, REF("CLAIM GRAV-UNIFORM 1.1.0: not declared"))
V("V09 a claim DIGEST not the declared one", D_P2, [dict(C_GRAV, DIGEST=HX("z"))], M4, L4,
{"TPK": EXP_GRAV}, REF("CLAIM GRAV-UNIFORM 1.0.0: DIGEST does not match the declaration"))
V("V10 a catalogue refused", D_P2, [C_GRAV], M4, [["x"]], {"TPK": EXP_GRAV},
REF("CATALOGUE: LINE 1: not an object"))
V("V11 a draft without TPK", D_P2, [C_GRAV], M4, L4, {"TMS": {}}, REF("DRAFT: missing zone TPK"))
W_GQ = DTPK(EXP_GRAV_AIRQ)
V("V12 two claims in either order: MATCH", D_P2, [C_AIRQ, C_GRAV_NO], M4, L4,
{"TPK": EXP_GRAV_AIRQ},
OK("MATCH", W_GQ, W_GQ, [[AQ, "1.0.0", "OFFERED"], [GU, "1.0.0", "OFFERED"]]))
V("V13 the expansion refused: its refusal", D_P2, [C_GRAV, C_AIRQ], M4, L4,
{"TPK": EXP_GRAV_AIRQ}, REF("CLAIM GRAV-UNIFORM 1.0.0: question BUOYANCY is not answered"))
_L4bad = copy.deepcopy(L4)
_L4bad[0]["DIGEST"] = HX("other")
V("V14 a text whose digest is not its declaration's", D_P2, [C_GRAV], M4, _L4bad,
{"TPK": EXP_GRAV},
REF("MODULES: AIR-LINEAR 1.0.0: the text's digest does not match the declaration"))
V("V15 no claim: the base itself", D_P2, [], M4, L4, D_P2,
OK("MATCH", DTPK(P2), DTPK(P2), []))
# ======================================================= draft_module_status
def DS(name, base, claims, modules, lines, expected):
add(name, "draft_module_status", {"base": base, "claims": claims, "modules": modules,
"lines": lines}, expected)
L_SUP_DIFF = L4 + [LVER(GU, "1.2.0", D_GRAV12, DAY2)]
L_SUP_NE = L4 + [LVER(GU, "2.0.0", D_GRAV20, DAY2)]
L_SUP_NONE = L5V + [LRET(GU, "1.1.0", DAY3)]
GATE_Q = ("This draft uses GRAV-UNIFORM 1.0.0, withdrawn on 2026-10-02 for: " + REASON
+ ". Declare with it?")
assert DTPK(EXP_GRAV12) != W_GRAV
DS("D01 every claim OFFERED", D_P2, [C_GRAV], M4, L4,
OK(["CLAIMS", [[GU, "1.0.0", "OFFERED"]]], ["GATE", []]))
DS("D02 SUPERSEDED by a version that expands alike: SAME", D_P2, [C_GRAV], M5V, L5V,
OK(["CLAIMS", [[GU, "1.0.0", "SUPERSEDED", "1.1.0", "SAME"]]], ["GATE", []]))
DS("D03 SUPERSEDED by a version that expands otherwise: DIFFERS", D_P2, [C_GRAV],
M4 + [X_GRAV12], L_SUP_DIFF,
OK(["CLAIMS", [[GU, "1.0.0", "SUPERSEDED", "1.2.0", "DIFFERS"]]], ["GATE", []]))
DS("D04 SUPERSEDED by a version this draft's binding cannot expand", D_P2, [C_GRAV],
M4 + [X_GRAV20], L_SUP_NE,
OK(["CLAIMS", [[GU, "1.0.0", "SUPERSEDED", "2.0.0",
"NOT EXPANDED: CLAIM GRAV-UNIFORM 2.0.0: VALUES: z_ref is author supplied: "
"give a value, or {} for a symbolic constant"]]], ["GATE", []]))
DS("D05 SUPERSEDED, and no version OFFERED now", D_P2, [C_GRAV], M5V, L_SUP_NONE,
OK(["CLAIMS", [[GU, "1.0.0", "SUPERSEDED", "NONE OFFERED"]]], ["GATE", []]))
DS("D06 RETIRED", D_P2, [C_GRAV], M4, L5RG,
OK(["CLAIMS", [[GU, "1.0.0", "RETIRED", DAY2]]], ["GATE", []]))
DS("D07 WITHDRAWN: the line for H-DECL's one question", D_P2, [C_GRAV], M4, L5W,
OK(["CLAIMS", [[GU, "1.0.0", "WITHDRAWN", DAY2, REASON]]], ["GATE", [GATE_Q]]))
DS("D08 two claims, one WITHDRAWN", D_P2, [C_GRAV_NO, C_AIRQ], M4, L5W,
OK(["CLAIMS", [[AQ, "1.0.0", "OFFERED"], [GU, "1.0.0", "WITHDRAWN", DAY2, REASON]]],
["GATE", [GATE_Q]]))
DS("D09 the OFFERED successor's text missing", D_P2, [C_GRAV], M4, L5V,
REF("MODULES: no text of GRAV-UNIFORM 1.1.0 (OFFERED)"))
DS("D10 the draft's own expansion refused: its refusal", D_P2,
[dict(C_GRAV, BIND=dict(GRAV_BIND, MASS=["x"]))], M4, L4,
REF("CLAIM GRAV-UNIFORM 1.0.0: BIND: slot MASS: x is not a CONSTANT of the TPK"))
DS("D11 a catalogue refused", D_P2, [C_GRAV], M4, "L4", REF("CATALOGUE: LINES: not a list"))
# ============================================================ module_use_view
def UV(name, certificates, lines, expected):
add(name, "module_use_view", {"certificates": certificates, "lines": lines}, expected)
def CERT(p, used):
return {"PREFIX": p, "MODULES_USED": used}
CERTS = [CERT("RR", [OF_GU, OF_AQ]), CERT("QQ", [OF_MA]), CERT("AA", [OF_GU])]
UV("U01 rows by ID, VERSION, PREFIX", CERTS, L4,
OK(["ROWS", [[AQ, "1.0.0", "RR", "OFFERED"], [GU, "1.0.0", "AA", "OFFERED"],
[GU, "1.0.0", "RR", "OFFERED"], [MA, "1.0.0", "QQ", "OFFERED"]]],
["UWCS ON WITHDRAWN", 0]))
UV("U02 a version WITHDRAWN: the certificates are unchanged, the view shows it", CERTS, L5W,
OK(["ROWS", [[AQ, "1.0.0", "RR", "OFFERED"], [GU, "1.0.0", "AA", "WITHDRAWN"],
[GU, "1.0.0", "RR", "WITHDRAWN"], [MA, "1.0.0", "QQ", "OFFERED"]]],
["UWCS ON WITHDRAWN", 2]))
_LV = [LADD("TEST-V", "1.2.0", HX("v12")), LVER("TEST-V", "1.10.0", HX("v110"), DAY2)]
UV("U03 versions in code point order: 1.10.0 before 1.2.0",
[CERT("BB", [["TEST-V", "1.2.0", HX("v12")]]), CERT("CC", [["TEST-V", "1.10.0", HX("v110")]])],
_LV, OK(["ROWS", [["TEST-V", "1.10.0", "CC", "OFFERED"],
["TEST-V", "1.2.0", "BB", "SUPERSEDED"]]],
["UWCS ON WITHDRAWN", 0]))
UV("U04 no certificate", [], L4, OK(["ROWS", []], ["UWCS ON WITHDRAWN", 0]))
UV("U05 a version never declared", [CERT("RR", [[GU, "9.9.9", D_GRAV]])], L4,
REF("CERTIFICATE RR: GRAV-UNIFORM 9.9.9 is not declared"))
UV("U06 a digest not the declared one", [CERT("RR", [[GU, "1.0.0", D_GRAV11]])], L4,
REF("CERTIFICATE RR: GRAV-UNIFORM 1.0.0: DIGEST does not match the declaration"))
UV("U07 a prefix twice", [CERT("RR", [OF_GU]), CERT("RR", [OF_MA])], L4,
REF("CERTIFICATES: duplicate prefix RR"))
UV("U08 a module twice in one certificate", [CERT("RR", [OF_GU, OF_GU])], L4,
REF("CERTIFICATE RR: duplicate module GRAV-UNIFORM"))
UV("U09 certificates not a list", CERT("RR", []), L4, REF("CERTIFICATES: not a list"))
UV("U10 a certificate without MODULES_USED", [{"PREFIX": "RR"}], L4,
REF("CERTIFICATE 1: missing field MODULES_USED"))
UV("U11 a prefix that is no identifier", [CERT("R-R", [])], L4,
REF("CERTIFICATE 1: PREFIX R-R is not a prefix"))
UV("U12 MODULES_USED not a list of triples", [CERT("RR", [GU])], L4,
REF("CERTIFICATE 1: field MODULES_USED has the wrong type"))
UV("U13 a catalogue refused", CERTS, [["x"]], REF("CATALOGUE: LINE 1: not an object"))
UV("U14 a certificate that is not an object (RV F-9)", ["RR"], L4,
REF("CERTIFICATE 1: not an object"))
UV("U15 a certificate with an unknown field (RV F-9)",
[{"PREFIX": "RR", "MODULES_USED": [], "NOTE": "x"}], L4, REF("CERTIFICATE 1: unknown field NOTE"))
# =============================================================== module_check
def MC(name, text, act, date, lines, modules, expected):
add(name, "module_check", {"text": text, "act": act, "date": date, "lines": lines,
"modules": modules}, expected)
def PASSROWS(dg, refs, bindings, pairs, atomicity):
rows = [["DIGEST", dg], ["REFERENCES", refs]] + bindings + pairs + [["ATOMICITY", atomicity]]
bad = any(r[-1] not in ("PASS", "EXCLUDED") for r in rows[1:-1])
return OK("FAIL" if bad else "PASS", rows)
ATOM_AL = ["air_still", "air_uniform", "drag_sphere"]
ATOM_AQ = ["air_still", "air_uniform", "drag_cd_constant", "drag_no_lift", "drag_no_magnus"]
ATOM_GU = ["gravity_uniform", "gravity_standard", "frame_inertial"]
L1_ = L4[:1]
L2_ = L4[:2]
L3_ = L4[:3]
MC("M01 ADD AIR-LINEAR to an empty catalogue: PASS", X_AIRL, "MODULE ADD", DAY1, [], [],
PASSROWS(D_AIRL, "PASS", [["BINDING", "PROJECTILE_2D", "PASS"]], [], ATOM_AL))
MC("M02 ADD AIR-QUADRATIC: EXCLUDES a declared ID; the excluded pair not combined", X_AIRQ,
"MODULE ADD", DAY1, L1_, [X_AIRL],
PASSROWS(D_AIRQ, "PASS", [["BINDING", "PROJECTILE_2D", "PASS"]],
[["PAIR", AL, "1.0.0", "EXCLUDED"]], ATOM_AQ))
MC("M03 ADD GRAV-UNIFORM: both bindings, both air pairs on the shared binding", X_GRAV,
"MODULE ADD", DAY1, L2_, [X_AIRL, X_AIRQ],
PASSROWS(D_GRAV, "PASS", [["BINDING", "PROJECTILE_2D", "PASS"], ["BINDING", "TABLE", "PASS"]],
[["PAIR", AL, "1.0.0", "PROJECTILE_2D", "PASS"],
["PAIR", AQ, "1.0.0", "PROJECTILE_2D", "PASS"]], ATOM_GU))
MC("M04 ADD MATH: combined with every offered module (the pi_c merge with AIR-LINEAR)", X_MATH,
"MODULE ADD", DAY1, L3_, [X_AIRL, X_AIRQ, X_GRAV],
PASSROWS(D_MATH, "PASS", [["BINDING", "PROJECTILE_2D", "PASS"]],
[["PAIR", AL, "1.0.0", "PROJECTILE_2D", "PASS"],
["PAIR", AQ, "1.0.0", "PROJECTILE_2D", "PASS"],
["PAIR", GU, "1.0.0", "PROJECTILE_2D", "PASS"]], []))
MC("M05 VERSION GRAV-UNIFORM 1.1.0: never paired with its own ID", X_GRAV11, "MODULE VERSION",
DAY2, L4, M4,
PASSROWS(D_GRAV11, "PASS", [["BINDING", "PROJECTILE_2D", "PASS"], ["BINDING", "TABLE", "PASS"]],
[["PAIR", AL, "1.0.0", "PROJECTILE_2D", "PASS"],
["PAIR", AQ, "1.0.0", "PROJECTILE_2D", "PASS"],
["PAIR", MA, "1.0.0", "PROJECTILE_2D", "PASS"]], ATOM_GU))
MC("M06 ADD GRAV-UNIFORM before any air module: WITH matches nothing", X_GRAV, "MODULE ADD", DAY1,
[], [],
PASSROWS(D_GRAV, "WITH AIR-* matches no declared module",
[["BINDING", "PROJECTILE_2D", "PASS"], ["BINDING", "TABLE", "PASS"]], [], ATOM_GU))
MC("M07 ADD AIR-QUADRATIC before AIR-LINEAR: EXCLUDES matches nothing", X_AIRQ, "MODULE ADD", DAY1,
[], [],
PASSROWS(D_AIRQ, "EXCLUDES AIR-LINEAR matches no declared module",
[["BINDING", "PROJECTILE_2D", "PASS"]], [], ATOM_AQ))
MC("M08 ADD TEST-PI: two pairs refused (pi_c differently)", X_TPI, "MODULE ADD", DAY2, L4, M4,
PASSROWS(D_TPI, "PASS", [["BINDING", "PROJECTILE_2D", "PASS"]],
[["PAIR", AL, "1.0.0", "PROJECTILE_2D",
"EXPAND: AIR-LINEAR 1.0.0 and TEST-PI 1.0.0 both emit pi_c, differently"],
["PAIR", AQ, "1.0.0", "PROJECTILE_2D", "PASS"],
["PAIR", GU, "1.0.0", "PROJECTILE_2D", "PASS"],
["PAIR", MA, "1.0.0", "PROJECTILE_2D",
"EXPAND: MATH 1.0.0 and TEST-PI 1.0.0 both emit pi_c, differently"]], []))
MC("M09 ADD TEST-X, whose one binding no offered module shares", X_TX, "MODULE ADD", DAY2, L4, M4,
PASSROWS(D_TX, "PASS", [["BINDING", "SOLO", "PASS"]],
[["PAIR", AL, "1.0.0", "-", "NO SHARED BINDING"],
["PAIR", AQ, "1.0.0", "-", "NO SHARED BINDING"],
["PAIR", GU, "1.0.0", "-", "NO SHARED BINDING"],
["PAIR", MA, "1.0.0", "-", "NO SHARED BINDING"]], []))
_badb_p2 = tpk_mod(P2, None, [("CONSTANT", {"NAME": "k_b", "DIMENSION": "1"}),
("CONSTANT", {"NAME": "h_b", "DIMENSION": "1"}),
("BOUNDARY", {"NAME": "bad_region", "PREDICATE": "k_b / h_b > 1",
"REFS": ["h_b", "k_b"], "CONDITION": "k_b/h_b>1"})])
_badb_row = "T6: BOUNDARY:bad_region: CONDITION uses / on h_b with no guard h_b != 0 (T6)"
assert first_kt_problem(_badb_p2) == _badb_row
MC("M10 a binding failing T6, a binding failing its BIND", X_TBADB, "MODULE ADD", DAY1, [], [],
PASSROWS(D_TBADB, "PASS",
[["BINDING", "PROJECTILE_2D", _badb_row],
["BINDING", "WRONG",
"CLAIM TEST-BADB 1.0.0: BIND: slot SPEEDY: z has dimension L1, not L1 T-1"]], [],
[]))
MC("M11 ADD of an ID already declared", X_GRAV, "MODULE ADD", DAY2, L4, M4,
REF("CATALOGUE: LINE 5: MODULE ADD of GRAV-UNIFORM, an ID already declared (no ID is "
"reissued)"))
MC("M12 VERSION reissuing a version", X_GRAV, "MODULE VERSION", DAY2, L4, M4,
REF("CATALOGUE: LINE 5: GRAV-UNIFORM 1.0.0 is already declared (no version is reissued)"))
MC("M13 an act that is neither ADD nor VERSION", X_GRAV, "MODULE RETIRE", DAY2, L4, M4,
REF("ACT: MODULE RETIRE is not MODULE ADD or MODULE VERSION"))
MC("M14 a proposal that does not parse", "{", "MODULE ADD", DAY2, L4, M4, NOTJSON)
MC("M15 a text missing for an OFFERED version", X_TPI, "MODULE ADD", DAY2, L4,
[X_AIRL, X_AIRQ, X_GRAV], REF("MODULES: no text of MATH 1.0.0 (OFFERED)"))
MC("M16 a catalogue refused", X_TPI, "MODULE ADD", DAY2, [["x"]], M4,
REF("CATALOGUE: LINE 1: not an object"))
MC("M17 a date that is no date", X_TPI, "MODULE ADD", "2026-99-01", L4, M4,
REF("CATALOGUE: LINE 5: DATE 2026-99-01 is not a date"))
MC("M18 a date before the last line's", X_TPI, "MODULE ADD", "2026-09-30", L4, M4,
REF("CATALOGUE: LINE 5: DATE 2026-09-30 is before the previous line's"))
MC("M19 ADD TEST-PUSH: SIGN + and a wrapped sum pass on the shared binding", X_TPUSH,
"MODULE ADD", DAY2, L4, M4,
PASSROWS(D_TPUSH, "PASS", [["BINDING", "PROJECTILE_2D", "PASS"]],
[["PAIR", AL, "1.0.0", "PROJECTILE_2D", "PASS"],
["PAIR", AQ, "1.0.0", "PROJECTILE_2D", "PASS"],
["PAIR", GU, "1.0.0", "PROJECTILE_2D", "PASS"],
["PAIR", MA, "1.0.0", "PROJECTILE_2D", "PASS"]], []))
# ---- RV's vector (review 0.1.0 F-9): a binding failing T3 at depth 1
_exp_t3 = tpk_mod(FIX2, None, [("CONSTANT", {"NAME": "k_t", "DIMENSION": "1", "VALUE": "1",
"UNIT": "1"})])
assert K.kernel_tests(_exp_t3)[0] is True
_t3row = "T3: CONSTRAINT:C1 and CONSTRAINT:C2: x fixed at 0 m and at 1 m (T3)"
assert "T3: " + K.t3_consistency(_exp_t3, {"PARAMETERS": {"S_MAX": 1}})[1][0] == _t3row
MC("M20 a binding failing T3 at depth 1", X_T3, "MODULE ADD", DAY1, [], [],
PASSROWS(D_T3, "PASS", [["BINDING", "FIX2", _t3row]], [], []))
# ---- S51: a SHARED PARAMETER row; differing DIMENSIONs refuse the pair
L_SUMP = [LADD("TEST-SUMP", "1.0.0", D_SUMP)]
_exp_share = tpk_mod(P2, None, [("CONSTANT", E_KQ)])
must_pass(_exp_share, "_exp_share")
_exp_kq1 = tpk_mod(P2, None, [("CONSTANT", {"NAME": "k_q", "DIMENSION": "1"})])
must_pass(_exp_kq1, "_exp_kq1")
MC("M21 ADD TEST-SHARE: an AUTHOR parameter named as TEST-SUMP's: the question for H-DECL",
X_SHARE, "MODULE ADD", DAY2, L_SUMP, [X_SUMP],
OK("PASS", [["DIGEST", D_SHARE], ["REFERENCES", "PASS"], ["BINDING", "PROJECTILE_2D", "PASS"],
["PAIR", "TEST-SUMP", "1.0.0", "PROJECTILE_2D", "PASS"],
["SHARED PARAMETER", "TEST-SHARE", "1.0.0", "TEST-SUMP", "1.0.0", "k_q"],
["ATOMICITY", []]]))
MC("M22 ADD TEST-KQ1: the same name, another DIMENSION: the pair refused (S51)", X_KQ1,
"MODULE ADD", DAY2, L_SUMP, [X_SUMP],
OK("FAIL", [["DIGEST", D_KQ1], ["REFERENCES", "PASS"], ["BINDING", "PROJECTILE_2D", "PASS"],
["PAIR", "TEST-SUMP", "1.0.0", "-", "SHARED PARAMETER k_q: DIMENSION 1 in TEST-KQ1 "
"1.0.0, L-1 M1 in TEST-SUMP 1.0.0"],
["ATOMICITY", []]]))
# hand derivation of the PASS pairs: every pair expansion, both orders one TPK, passes
_PAIR_TPKS = {
("AIR-LINEAR", "GRAV-UNIFORM"): EXP_GRAV_AIRL, ("AIR-QUADRATIC", "GRAV-UNIFORM"): EXP_GRAV_AIRQ,
("AIR-LINEAR", "MATH"): EXP_AIRL, ("AIR-QUADRATIC", "MATH"): EXP_AIRQ_MATH,
("GRAV-UNIFORM", "MATH"): EXP_GRAV_MATH, ("AIR-QUADRATIC", "TEST-PI"): EXP_AIRQ_TPI,
("GRAV-UNIFORM", "TEST-PI"): EXP_GRAV_TPI}
_PAIR_TPKS[("TEST-SHARE", "TEST-SUMP")] = _EXP_SUMP
for _k, _t in _PAIR_TPKS.items():
must_pass(_t, str(_k))
_PUSH_PAIRS = {
"AIR-LINEAR": tpk_mod(P2, {"EOM_X": ("m*D(v_x,t)=-6*pi_c*mu_air*R_s*v_x+(F_a+F_b)",
["F_a", "F_b", "R_s", "m", "mu_air", "pi_c", "t", "v_x"],
"m dv_x/dt = 0" + S_LX + "; with the push: +(F_a+F_b)"),
"EOM_Z": ("m*D(v_z,t)=-6*pi_c*mu_air*R_s*v_z-(F_a+F_b)",
["F_a", "F_b", "R_s", "m", "mu_air", "pi_c", "t", "v_z"],
"m dv_z/dt = 0" + S_LZ + "; with the pull: -(F_a+F_b)")},
AIRL_EMIT_2 + [("CONSTANT", {"NAME": "F_a", "DIMENSION": "L1 M1 T-2"}),
("CONSTANT", {"NAME": "F_b", "DIMENSION": "L1 M1 T-2"})]),
"GRAV-UNIFORM": tpk_mod(P2, {"EOM_X": ("m*D(v_x,t)=F_a+F_b", ["F_a", "F_b", "m", "t", "v_x"],
"m dv_x/dt = 0; with the push: +(F_a+F_b)"),
"EOM_Z": ("m*D(v_z,t)=-m*g_n-(F_a+F_b)",
["F_a", "F_b", "g_n", "m", "t", "v_z"],
"m dv_z/dt = 0" + S_W + "; with the pull: -(F_a+F_b)")},
GRAV_EMIT_100 + [("CONSTANT", {"NAME": "F_a",
"DIMENSION": "L1 M1 T-2"}),
("CONSTANT", {"NAME": "F_b",
"DIMENSION": "L1 M1 T-2"})]),
}
_PUSH_PAIRS["AIR-QUADRATIC"] = tpk_mod(
P2, {"EOM_X": (EQ_QX + "+(F_a+F_b)", ["F_a", "F_b"] + R_QX,
"m dv_x/dt = 0" + S_QX + "; with the push: +(F_a+F_b)"),
"EOM_Z": (EQ_QZ + "-(F_a+F_b)", ["F_a", "F_b"] + R_QZ,
"m dv_z/dt = 0" + S_QZ + "; with the pull: -(F_a+F_b)")},
AIRQ_EMIT_2 + [("CONSTANT", {"NAME": "F_a", "DIMENSION": "L1 M1 T-2"}),
("CONSTANT", {"NAME": "F_b", "DIMENSION": "L1 M1 T-2"})])
_PUSH_PAIRS["MATH"] = tpk_mod(_EXP_PUSH, None, [("CONSTANT", E_PI)])
for _k, _t in _PUSH_PAIRS.items():
for _l in _t["LAW"]:
canon_is(_l["EQUATION"], "equation")
must_pass(_t, "push+" + _k)
# ---------------------------------------------------------------- write
names = [c["name"] for c in CASES]
assert len(set(names)) == len(names), "duplicate case names"
for c in CASES:
json.loads(c["expected"]) # every expectation is JSON text
assert all(ord(ch) < 128 for ch in c["expected"])
counts = {}
for c in CASES:
counts[c["fn"]] = counts.get(c["fn"], 0) + 1
out = {"corpus": "module", "version": CORPUS_VERSION,
"spec": "tpm_module_spec 0.2.0",
"note": ("vectors first: every expectation a literal of the generator; digests and D(TPK) "
"computed by existing TPC-PY functions over literal data (see the generator's "
"docstring); no tpc_module exists"),
"cases": CASES}
with open(os.path.join(HERE, "vectors", "module_corpus.json"), "w", encoding="utf-8", newline="\n") as f:
json.dump(out, f, indent=1, sort_keys=True, ensure_ascii=True)
f.write("\n")
print("wrote %d module cases" % len(CASES))
for fn in sorted(counts):
print(" %-20s %d" % (fn, counts[fn]))