API reference¶
Generated directly from docstrings, so it never drifts from what the code actually does.
mathema¶
mathema
¶
mathema: Claim-Driven Development, turning software intent into verifiable evidence.
import mathema
r = mathema.check(my_function, claims=["f(-x) == -f(x)"])
print(r)
mathema.write_spec(my_function, claims=[...]) # also writes the record
State a claim, verify it against the real function, keep the record. Every
claim is adjudicated on one of two evidence routes: derive lifts the
function's body to a sympy expression and decides the claim algebraically
(proven is exact, not sampled, see symbolic.py), or probe calls the
real function on sampled/seeded inputs and checks the claim numerically
(holds (n=...)/falsified, see probing.py). A function that can't be
lifted (a loop, a non-scalar parameter, ...) still gets probe-route
coverage; derive is strictly stronger evidence where it applies, not a
replacement for probe. Every claim binds to the function's identity hashes
so a later change is caught, not silently inherited.
A bare string claim (as above) takes the best route: a symbolic proof
is attempted first, and a claim the proof cannot decide falls through to
probing, so the returned entry's route names the mechanism that settled
it. To pin the route, build the claim with route="derive" or
route="probe" and pass it in the same claims= list; mathema.check()
accepts a pre-built claim object exactly as it accepts a string, so
nothing else about the call changes:
r = mathema.check(my_function,
claims=[mathema.claims.claim("f(-x) == -f(x)", route="derive")])
A proven verdict routed derive means the proof decided it, never
sampling. When the proof cannot decide, the claim still falls through to
probing, and Probe.meta["mathema.derive_status"] on the returned entry
("unliftable" vs. "undecided") records why the proof stopped;
see authoring.md's own claim-grammar section for the full grammar
(d(...)/lim(...)/integrate(...)/Sum(...), domain quantifiers,
raises(...)) route="derive" understands. mathema.claims is this same
claim()/check_conjectures() pair exposed directly, for calling the
derive route standalone without check()'s own built-in-law probing:
results = mathema.claims.check_conjectures(
my_function, [mathema.claims.claim("f(-x) == -f(x)", route="derive")])
This package implements the Claim-Driven Development spec: see the sibling
claim-driven-development repo's v0.2.0/ for the workflow definition and
the record schema this package reads and writes (SPEC_VERSION below
states which version).
Record
dataclass
¶
The result of checking a function's claims: the facts read off it, and one Probe per claim adjudicated (built-in laws plus any claims passed in). See the claim-driven-development repo's v0.2.0/record-schema.md for the YAML shape this turns into via to_spec()/save_spec().
Source code in mathema/__init__.py
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | |
save_spec(path)
¶
Write the decoupled spec: intent, identity, claims, references, and the reasoning chain, as standalone YAML bound to the identity hashes.
Source code in mathema/__init__.py
201 202 203 204 205 206 | |
check(fn, claims=None, domain=None, trials=None, trials_scale=1.0, extensive=False, declared=None, known_premises=None)
¶
Verify a function's claims, each adjudicated against the real function.
mathema.check(ema) # + suggested claims
mathema.check(ema, claims=["f(x, 1.0) == x[-1]"]) # add a claim
mathema.check(ema, claims=[]) # declared surfaces only
mathema.check(ema, domain={"alpha": (0, 1)})
mathema.check(ema, declared=mathema.retrieve(ema)) # + file claims
check is IO-free: it reads only what travels with the function
object (decorator, docstring, type markers) and never touches the
filesystem. The file-declared layer, the highest-precedence
authoring surface, therefore reaches it only through declared=,
a retrieved entry from mathema.retrieve(fn, root). Call-site
claims= still wins per claim name over everything retrieved.
write_spec(), the CLI, and the MCP tools do this join for you;
a bare check(fn) adjudicates the function-attached surfaces only.
claims omitted (None) defaults to suggest_claims(fn)'s
own proposals, monotonicity/affine-ness/convexity, symmetry,
idempotence, commutativity/associativity, raises(...) guards, each
adjudicated at the best evidence level actually available (a proof
when the function lifts, probing otherwise, see suggest_claims()'s
own docstring). claims=[] (an explicit empty list, not merely
omitted) means declared surfaces only: no suggested claims are
added, while the built-in structural probes and the function's own
decorator/docstring/type-marker claims still run, and so does a
declared= entry when one is passed. Any other explicit claims=[...] is
checked as given, unioned with whatever the function's own decorator
or docstring declares.
domain declares parameter limits: algebraic probes sample inside
the declared domain. Whether the code REJECTS out-of-domain input
is its own declared claim, excluded_outside_domain(p), state it
explicitly, opt in with the excluding keyword, or get it
auto-declared by @enforce_domain (the decorator that makes it
true). There is no adjudication mode: a declared exclusion that
trials find unenforced is falsified with the accepted value as
witness; an undeclared one reports nothing. See
claim-driven-development's v0.2.0/claim-anatomy.md, "Domain:
declared vs enforced".
known_premises is caller-supplied context for assuming <name>
holds references that resolve nowhere in the batch: a mapping of
claim name to a human line (usually from
compendium.external_premises) that the missing-prerequisite note
then includes. Data only; it never satisfies a premise, and
check() itself stays IO-free.
extensive reaches every route this call touches: probe()'s own
critical-point sampling hints and domain_safe[...] probe, and a
route="derive" claim's case-split fallback. Default False
everywhere; real, opt-in cost when set.
A parameter or return type hinted with a mathema type marker
(Annotated[float, Probability], Annotated[list, Shape("m", "n")],
see types.py) contributes its own claims automatically: a domain
marker merges into domain (an explicitly passed bound for the same
parameter wins), and a Shape marker adds a structural probe. Purely
additive, a function with no markers behaves exactly as before.
Domains are per claim. The signature markers and domain= together
are the function-level parent domain; each claim is adjudicated over
that parent with its own for bindings overriding it per parameter,
and one claim's bindings never reach another claim. A claim's record
states its own bindings in domain/condition and whatever the
parent supplied in meta["mathema.parent_domain"].
A @claims_decorator(...)-tagged function, or a docstring Claims:
block (see authoring.py), also contributes its claims automatically,
same as the type markers above; claims= passed here is unioned
with those, winning per claim name on a collision (an explicit call-
site claim is the most deliberate of the three; see
authoring.declared_from_function's own decorator-over-docstring rule
for the same reasoning one layer in).
Source code in mathema/__init__.py
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 | |
analyze(fn)
¶
Machine-derived facts about a function: parameters, purity, guards,
structural shape. Pure ast, no probing, no claim verification.
Falls back to a documentation-only record (tier 0) for callables with no retrievable Python source (builtins, C extensions): the docstring states the intent, and probing still runs against the live callable.
Source code in mathema/__init__.py
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | |
track_claims(fn)
¶
Optional bare tag: track this function's claims over time.
Entirely optional. Without it, everything still works: check, write_spec, claims.check, and the CLI operate on any function. What an untracked function loses is visibility in mathema.status(), the fresh-versus-stale sweep, because status can only report on functions it knows exist. Tag the functions whose claims you want watched; leave the rest alone.
Mechanics, precisely:
- runs once, at decoration time; the function object is returned
unchanged (no wrapper, zero call overhead, stack traces untouched)
- stamps fn.__mathema__ = {"key": ...} so tools and readers can see
the tag by introspection
- registers the function under its dotted key (module.qualname) in a
weak registry, so mathema.tagged() and mathema.status() can find
it; redefining the function in a notebook simply re-registers the key
The tag carries no spec content: intent and claims live in the sidecar
spec store (see mathema.spec), keeping code untouched by design.
Source code in mathema/__init__.py
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 | |
resolve(target, root='.', *, skipped=None)
¶
Resolve a target spelling to its functions, keyed by canonical
dotted name. See the module docstring for the accepted spellings.
skipped, when given, collects (submodule, error) pairs for
package submodules that failed to import (also carried on the
returned Target).
Raises:
| Type | Description |
|---|---|
TargetError
|
nothing resolvable at this spelling; the message is printable as-is. |
Source code in mathema/targets.py
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | |
resolve_function(target, root='.')
¶
Resolve a target that must name exactly one function, returning
(key, fn).
Raises:
| Type | Description |
|---|---|
TargetError
|
the target resolves to nothing, or to more than one function (the message lists candidates). |
Source code in mathema/targets.py
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | |
gate(claims, *, strict, accepted_risk=frozenset(), unresolved=())
¶
Apply the one gate policy to a set of adjudicated claims (live Probes or stored claim dicts, mixed freely).
Rules: a falsified or invalidated claim fails in every mode; an
unknown claim fails unless its name is in accepted_risk (then it
is owned, and only strict refuses it); strict additionally fails
skipped claims; an unresolved global name fails in every mode.
Suggestions mathema volunteered and foreign-grammar claims never
gate (foreign ones are collected on the report for the caller to
surface).
Source code in mathema/verify.py
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
verify_project(root='.', *, all=False, strict=True, trials_scale=1.0, only=None)
¶
The test-runner sweep as a library call: for every key the
declared/verified stores know, re-adjudicate if the function's form
hash, claims fingerprint, or a dependency changed (all=True
forces everything), rewrite the record, and gate each key's
adjudicated claims through gate. only restricts the sweep to the
given dotted keys (the single-key re-verify the reconcile workflow
points at); None sweeps the whole population.
Notes
dependencies_current claims are settled AFTER the whole sweep
has written its records, so a caller adjudicated before its
callee in the same run still reads the callee's fresh record;
the sweep's outcome does not depend on key order.
Source code in mathema/verify.py
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 | |
mathema.reason_codes¶
mathema.reason_codes
¶
The public reason-code registry: a stable, versioned vocabulary for
why a function didn't lift (ReasonCode/Category) and for why
one claim's own adjudication was skipped (ClaimReasonCode), plus
build_issue_record()/issue(), which build a real CDD spec record
(spec.to_spec()) around whichever of those applies and attach the
rest of the diagnostic depth under the record's own meta field.
This does not introduce a new vocabulary for either registry.
inventory.derivability_report() already computes exactly
ReasonCode's eight blocker values, symbolic.NotSymbolic already
tags Category's finer-grained values, and conjecture.check_conjectures()
already has its own closed set of skip sites, two of them already
tagged in Probe.meta (mathema.foreign_grammar, mathema.derive_status),
the rest identifiable from the exact note text it already writes.
ClaimReasonCode names what's already there.
Once released, these names are public interface: never renamed, and never repointed at a different meaning. A new value is added as a new name; an existing one is never redefined underneath code that already depends on it.
ReasonCode
¶
The eight values inventory.derivability_report()'s own
blocker field can take, exactly as it already writes them,
named here, not reinvented.
Source code in mathema/reason_codes.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |
Category
¶
symbolic.NotSymbolic.category's own vocabulary, only
meaningful alongside ReasonCode.UNSUPPORTED_CONSTRUCT, naming
which specific unsupported-syntax shape was hit. UNSUPPORTED_SYNTAX
is NotSymbolic's own default when no more specific category is
given; the rest are the specific categories currently raised
anywhere in the derive-route lifting code.
Source code in mathema/reason_codes.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
ClaimReasonCode
¶
Why one claim's own adjudication came back skipped (blocked,
with reason) or stayed unknown (attempted, undecided),
formalizes conjecture.check_conjectures()'s own existing skip
sites, not a new taxonomy. Meaningful mainly alongside verdict
skipped. An ordinary falsified claim's sketch/counterexample is
the full story and carries no code; the one exception is a
falsification whose stratum pins the failure on the implementation
(meta["mathema.stratum"]), which carries its implementation:
cause so tooling can separate a broken carrier from broken
mathematics without parsing prose.
DERIVE_TIMEOUT is the one real code change behind this registry,
not just a name: a derive-route wall-clock cutoff
(symbolic._proof_support._prove_relation's own _with_timeout)
used to come back indistinguishable from sympy simply failing to
decide on its own; both were a bare undecided. It now carries
meta["mathema.timeout"] ("fast" or "extensive", the tier that
was in effect), propagated through to the claim's own Probe.meta
alongside the existing mathema.derive_status.
NO_EVALUABLE_INPUTS is the one code here that's genuinely
probe-route-specific (every synthesized trial raised before a
comparison was ever made); everything else is checked before, or
independent of, the route split.
Source code in mathema/reason_codes.py
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | |
LoopReason
¶
The fold recognizer's own decline slugs (symbolic.diagnose_fold
and _lift_fold_impl's reason field), exactly as they are
already written, named here, not reinvented. Rendered composed as
loop:<value> by blocked_code(). A loop decline can also carry a
Category value when one of the loop's own pieces hit an
unsupported construct.
Source code in mathema/reason_codes.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | |
BranchReason
¶
Why one branch condition can or cannot be pruned
(symbolic._explain_branch's code field). NEEDS_DOMAIN is the
resolvable kind, pruning settles the branch once a claim declares
a specific-enough domain for the named parameters; the rest are the
structurally blocked kinds. Rendered composed as branch:<value>
(branch:needs-domain(scale) names the parameters) by
blocked_code().
Source code in mathema/reason_codes.py
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | |
blocked_code(report)
¶
The compact underivability code for one
inventory.derivability_report() dict: stateful, recursion,
no-parameters, non-scalar-parameters, internal-error,
loop:<LoopReason>, branch:<BranchReason> (with
(param, ...) naming the domains to declare for the resolvable
kind, and a +N suffix when several distinct blocked kinds occur),
or unsupported:<Category>. None for a derivable function or a
missing report. The lookup for every code is describe_code().
Source code in mathema/reason_codes.py
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
describe_code(code)
¶
The CODE_TABLE entry for one compact code, tolerant of the
composed decorations blocked_code() adds: a parameter list
(branch:needs-domain(scale)) and a +N multiplicity suffix are
stripped before lookup. A loop:<Category> code (a loop whose own
pieces hit an unsupported construct) falls back to the matching
unsupported:<Category> entry. None for an unknown code.
Source code in mathema/reason_codes.py
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 | |
claim_reason_code(probe)
¶
ClaimReasonCode for a skipped or unknown Probe, read off whatever
check_conjectures() already recorded for it, meta tags where
they exist, the note text otherwise (not every skip site is
meta-tagged today). None for any other verdict, or a skip this
function doesn't recognize.
Source code in mathema/reason_codes.py
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 | |
mathema.conjecture¶
mathema.conjecture
¶
The conjecture pipeline: externally proposed claims, machine-adjudicated.
A conjecture is a claim stated by someone other than the analyzer: a human in
a review, or a model acting as a hypothesis generator. It enters as
conjectured, and the verifier decides what it becomes: holds (n=…) with
seeded probing, or falsified with the counterexample kept. The proposer
never adjudicates its own claims.
Laws are written over the function name f and its parameter names, with any
extra free names treated as auxiliary real variables (sampled alongside the
inputs):
Conjecture("odd", lhs="f(-x)", rhs="-f(x)", relation="==")
Conjecture("bounded", lhs="min(x)", rhs="f(x, alpha)", relation="<=")
Conjecture("shift", lhs="f(x + c)", rhs="f(x) + c", relation="==")
Expressions are validated against a strict AST whitelist before evaluation: this pipeline accepts untrusted proposals, so nothing outside arithmetic, comparisons handled by the relation, and a fixed set of safe calls can run.
Conjecture
dataclass
¶
One parsed claim, ready to be adjudicated: a relation between
lhs and rhs over the function under test (f), the evidence
route it's checked on, and everything a proof/probe attempt needs
(declared domain, tolerance, prior counterexamples to replay, extra
functions a multi-function law refers to). Built by claim(), which
parses a claim's law text into these fields.
Source code in mathema/conjecture.py
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 | |
claim(law, name=None, source='user', route='best', grammar=GRAMMAR, funcs=None, tolerance=None, pseudo_infinity=None, meta=None, matrix_names=frozenset())
¶
The simple way to state a claim: one string, relation included.
claim("f(-x) = -f(x)")
claim("f(x)^2 ≥ 0")
claim("min(x) <= f(x, alpha)", name="lower_bound")
claim("f(x) == g(x) / log(2)", funcs={"g": nats_version})
claim("raises(f(x), ValueError)")
claim("let m = m1, for m1 in [0.1,1000], x1 in [-100,100], x2 in [-100,100],"
" f(m,x1,m,x2) == (x1+x2)/2")
claim("let g = pkg.mod.func1, for x in (0,100], g(x) >= 0")
claim("let c be [-1e6,1e6], for dh in [-1e6,1e6], t in [0,1000], ds in [-1e6,1e6],"
" d(f(dh+c,t,ds), t) == d(f(dh,t,ds), t)")
Spellings are normalized by grammar.py (^ is power, = reads as ==,
Unicode ≤ ≥ − · × π accepted), so equivalent spellings are the same
statement. Accepted relations: ==, <=, >=, plus the raises(...)
predicate and the family-derive-only is_pole_safe(param)/is_builtin_safe(param)
predicates (no f(...) wrapper; these are facts about param's own
declared domain, not fn's return value). route defaults to "best",
which cascades: the fast proof attempt, then the extensive strategy
ladder, then probing, and the output record names whichever route
actually settled it. "derive" makes the fast proof attempt only; a
claim it cannot decide still falls through to probing, with the
derive attempt's status kept in meta["mathema.derive_status"].
"probe" samples only (seeded, verdict holds/falsified) ("auto" is retired, not a
legacy spelling of "best"). A derive proof is the mathematics in
exact arithmetic; mathema.check pairs it with a <name>[float]
companion claim about the implementation, and "derive:math_only"
asks for the proof alone, with no companion. the safety predicates always adjudicate on the examine route
regardless of the route passed in. A
leading let name = expr, ... (see grammar.extract_let_bindings)
substitutes each name for (expr) everywhere later in the law,
e.g. binding one fresh name to two real parameters, forcing them
equal, or, when expr is a bare dotted path, binds a callable
letter (like g above) the same way an explicit funcs= entry
would, without needing one; let name be bounds instead declares a
genuinely free variable (no real parameter to alias, e.g. a gauge-
invariance claim's arbitrary shift constant), be, not in, so
it never reads like a for binding, exempted from the usual
real-parameter-only domain-key check. If name also turns out to be
a real parameter of whichever function is checked, with no separate
for name in ... of its own in this same claim, that's harmless
(there's still only one declared bound) and adjudicates like an
ordinary for, noted in the result rather than treated as an error;
declaring the same name via both let ... be ... and
for ... in ... in one claim, though, raises ConflictingDomainBinding
immediately; two different bounds for one name has no principled
default to pick silently.
Source code in mathema/conjecture.py
735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 | |
check_conjectures(fn, conjectures, domain=None, trials=None, trials_scale=1.0, facts=None, extensive=False, known_premises=None, float_companions=False)
¶
Adjudicate proposed claims against the live function.
trials omitted (None) uses the same structural-risk-based
adaptive budget probe()'s own laws always have
(probing._starting_budget), a claim about a structurally
riskier function gets more trials, the same signal either way,
since a claim is adjudicated by the exact same shared sampling
setup (probing._prepare_sampling) probe()'s remaining
structural checks use, computed lazily, at most once per call,
the first time some claim actually reaches a probe-needing
attempt. A batch of route="derive" claims whose proofs all decide
never triggers it at all (an UNDECIDED derive claim now falls back
to probing, empirical evidence supersedes an unknown, and pays
for the setup like any probe claim). An explicit trials= always
wins.
trials_scale is the same dev-loop knob probe() takes, clamped to
(0, 1] (shrinks only) and floored the same way (never below
_RiskPolicy.min_trials_floor_when_scaled or len(_SPECIALS), see
probe()'s own docstring for why), so a single --trials-scale flag
turns down every probe-route check in one call's worth of claims at
once, built-in laws and author-stated conjectures alike.
extensive reaches a route="derive" claim's own case-split
fallback (symbolic._prove.try_prove), widening its wall-clock cap
when an ordinary proof attempt comes back undecided, and also
widens the critical-point analysis behind a probe-route claim's own
sampling the same way it does for probe() (see
probing._prepare_sampling). Default False, same as probe()'s
own extensive.
float_companions=True makes every claim the derive route proves
(route "derive" or "best") spawn its implementation claim,
<name>[float], emitted directly after it: a derive proven is
the mathematics in exact arithmetic, and the companion is the same
relation executed against the real code in float (see
gates._float_companion). A claim authored with route
derive:math_only adjudicates as a derive claim and spawns none,
which its proof's meta states. check(), and so every CLI and
store path, asks for companions; the default here keeps the
adjudicator at one probe per claim.
Returns one Probe per conjecture (plus any float companions): holds
(n=…) / falsified (with the counterexample) / skipped (invalid law,
nothing evaluable, or a different grammar entirely, see the
grammar check below, tagged in meta["mathema.foreign_grammar"]
so a caller can tell the two kinds of skip apart), each noting who
proposed it.
Source code in mathema/conjecture.py
2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 | |
mathema.symbolic¶
mathema.symbolic
¶
Symbolic proof: the derive evidence route. Public re-export surface
for the package, import from here (from mathema.symbolic import lift,
try_prove), never from a private mathema.symbolic._xxx submodule
directly; those are implementation detail and may be reorganized without
notice.
The underscore-prefixed names here are deliberately exported: they are
the package-internal analysis surface inventory/analysis/tiers/
_tier_text consume (lift context walking, expression conversion,
loop-header classification, branch explanation), internal to mathema,
stable enough for its own sibling modules, not for third parties. The
per-shape lift record classes (FoldLift and friends) are not exported:
nothing outside this package constructs or annotates one.
Lifted
dataclass
¶
A function body lifted to a sympy expression: the symbolic form the
derive route reasons over. expr is an ordinary sympy.Expr for a
function with a single return value; for a function whose return
statement is a tuple (return x, y), it's a plain Python tuple of
sympy.Expr, one per element; there is no vector/matrix type here,
just a fixed-length group of otherwise-independent scalar results.
params maps every liftable quantity to its symbol, ordinarily
one entry per signature parameter, but a parameter that bundles many
scalars (a flat dataclass, or a dict accessed by literal string keys,
see _bind_params) expands into several composite-keyed entries
("cfg.a", "cfg.b") instead of one. sig_params/aggregate are what
let f(...) call substitution still work in terms of the real
signature: sig_params is the actual positional parameter order,
and aggregate[p] is the list of composite keys p expanded into
(or None for an ordinary, single-symbol parameter). opaque, when
this lift encountered any non-numeric value (a string, None, an
Enum member, see finite_sets.py), is the registry those values
were registered through, carried here so a claim's own law text
can register its own literals into the same registry (f(x) ==
"positive" needs "positive" to resolve to the identical symbol
the body itself produced, not an unrelated fresh one).
Source code in mathema/symbolic/_base.py
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | |
ProofResult
dataclass
¶
The outcome of a single derive-route decision: try_prove()/
try_prove_raises()'s return type. sketch is a short, human-
readable explanation of how the status was reached (which
expressions simplified to what, or why a sign/equality couldn't be
settled), always present except when status is "unliftable"
for a reason obvious from context. quantifier is only ever set
alongside status == "proven": a closed-form '∀ x ∈ ℝ, ...' clause
naming exactly the variables the proof actually holds over (see
_quantifier_clause); a proof has no trial count the way a probe
does, so this is what stands in its place.
Source code in mathema/symbolic/_proof_support.py
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 | |
lift(fn, facts, max_callee_depth=3, domain=None, _ctx=None, _opaque=None)
¶
Translate a function body to a sympy expression. Only a loop-free,
branch-free, non-recursive, all-scalar body is attempted; everything
else returns None rather than guessing at a closed form; this gate
is unaffected by domain, which never lets this function's own
branches through (that's lift_conditioned()'s job); it only ever
widens what a callee can resolve, below.
A call this body makes to a name that isn't a known math function
(_SYMPY_FUNCS) but does resolve to a real, plain Python function;
_display(np.minimum(r, R_CLIP))'s _display, say, isn't refused
outright: _try_inline_callee recursively lifts the callee (this
same function, one step further down) and substitutes its own
parameters with this call site's argument expressions, up to
max_callee_depth levels deep (mirrors docstring.docstring_sync()'s
own max_callee_depth, the same concept applied to code/docstring
sync rather than proof). If the callee itself has a branch, domain,
fn's own declared domain, over fn's own parameters; lets
_try_inline_callee derive a domain for the callee's parameters too
(only for a bare-parameter-passthrough call-site argument, see
_derive_passthrough_domain) and try lift_conditioned() on it as a
fallback. This doesn't weaken lift()'s own contract for fn: the
proof's claimed validity was already scoped to domain by
try_prove()'s own quantifier-clause wrapping regardless of whether
lift() itself consulted it, so a callee resolved this way is no less
sound than one resolved directly in fn's own body would be.
_ctx is private, only _try_inline_callee passes it, to carry
the shrinking depth budget and the set of already-inlined callees
(cycle protection) down through a recursive lift() call; an external
caller only ever sets max_callee_depth/domain. _opaque is
likewise private, _try_inline_callee forwards the caller's own
registry so an opaque value means the same thing throughout a
callee-inlining chain; an external caller never sets it (a fresh
registry is built automatically).
Source code in mathema/symbolic/_base.py
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 | |
lift_conditioned(fn, facts, domain, max_callee_depth=3, _ctx=None, _opaque=None)
¶
Like lift(), but for a function whose branches resolve under the
given domain (see _branch_condition_truth/_prune_body), still
refuses loops/recursion/non-scalar parameters outright, unchanged
from lift(); this only ever widens what counts as branch-free for
this specific domain. None if there's nothing to prune (lift()
already covers that), the domain doesn't settle enough branches, or
the resolution budget (_MAX_PRUNED_BRANCHES) runs out. max_callee_depth
is the same callee-inlining budget lift() itself takes, see there,
applied here to the pruned body's own final walk, which also
carries domain itself through so a callee this pruned body calls
can attempt its own domain-derived conditioning in turn (see
_try_inline_callee/_derive_passthrough_domain). _ctx/_opaque are
private, same role as lift()'s own, only a recursive call from
_try_inline_callee (inlining a callee that itself needs
conditioning) passes them.
Source code in mathema/symbolic/_conditioned.py
977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 | |
try_prove(fn, facts, lhs_src, rhs_src, relation, domain=None, tolerance=None, max_callee_depth=3, extensive=False, _split_depth=0, funcs=None, assumption=None, assume_defined=False)
¶
See _try_prove. A proof is kept only when every raise region
of the functions it reads was read too: a statement the raise-region
pass stops at could hide a raise in the domain, and a value claim is
false wherever the code raises.
Source code in mathema/symbolic/_prove.py
2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 | |
try_prove_raises(fn, facts, call_src, exc_name, domain=None, max_callee_depth=3)
¶
A raises(...) claim on the derive route, only ever reachable via
domain-conditioned branch pruning: a raises claim with no declared
domain specific enough to determine which branch runs stays
unliftable, the same as any other derive claim would with an
unresolvable branch. call_src must be a bare f(...) call (no
multi-function claims here, same restriction check_conjectures()
already applies before this is ever called). max_callee_depth is
forwarded to lift_conditioned(), see lift()'s own docstring.
Source code in mathema/symbolic/_prove.py
3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 | |
mathema.inventory¶
mathema.inventory
¶
Facts about one function: is it pure enough that a claim is
realistically expected, does its docstring pass a checklist, is it
liftable for a derive-route proof, does it depend on state outside its
own parameters, is it exercised by the target's own tests (best-effort,
only if a coverage report already exists). Everything here answers a
question about a single function, standalone, e.g. mathema.
docstring_report() calls straight into docstring_quality() for just
one function, no sweep involved.
Distinct from audit.py, which sweeps a whole population by calling
these primitives across it and shapes the results into audit rows/
rollups; that's "found N functions, called is_pure_enough() on each,
built a table"; this module is what it's calling. Distinct too from
cli.py's per-function "claims X/Y adjudicated": that's the completeness
of ONE already-declared claim set, whereas this module (via audit.py)
surfaces functions with zero claims, which the declared/verified
store alone can never show.
is_pure_enough(fn)
¶
A purity proxy, not a judgment about claim-worthiness on its
own: reuses symbolic.py's own derive-route liftability check
(loop-free, branch-free, non-recursive, scalar parameters, and;
for a method; read-only over self: field reads and sibling
calls derive; writes or escapes of self do not). None means mathema couldn't even retrieve the
source, not a verdict either way, e.g. a function from a C
extension or built dynamically.
Source code in mathema/inventory.py
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | |
purity_reason(fn)
¶
Why is_pure_enough() said no; None means it's liftable (or
source wasn't available, same as is_pure_enough()'s own None).
Mirrors lift()'s own gate conditions exactly (same facts fields,
same order), so the reason reported here is always consistent with
what lift() actually did, not a separate guess at why.
Purity here is specifically "liftable for a derive-route proof"; it says nothing about whether a probe-route claim is viable. Probe doesn't lift anything; it calls the real function on sampled inputs, so branches, loops, and non-numeric parameters are no obstacle to it at all. A function reported not-pure here can still be claimed about perfectly well on the probe route; this only ever narrows which functions can additionally get proof-strength (derive-route) evidence, not which functions can be claimed about at all.
Source code in mathema/inventory.py
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 | |
claim_floor(fn, facts=None)
¶
Intent
The least this function's shape gives you to state: one claim
per relevant claim family, per target. {"floor": int,
"aspects": [[aspect, target], ...]}, or None when the source
cannot be retrieved.
Notes
A floor, never a ceiling, a function carrying more claims than this is not over its budget, and nothing here divides by it. How many claims a function of this shape typically carries is a different question, answered by a corpus, not by structure.
Claims answering the same question about the same target count
once (convex[x]/concave[x]/affine[x] are one aspect,
see families.CLAIM_ASPECTS), while every member of a keyword
group counts separately, since stateless expanding to three
claims is three separate things to say.
Deliberately independent of whether the function lifts. Most families offer a probe route, so an unliftable function still has a floor, a smaller one, because the derive-only families drop out on their own structural gates.
Source code in mathema/inventory.py
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 | |
derivability_report(fn)
¶
Deep-dive diagnosis of why a function isn't derivable
(mathema audit --deriv-report), unlike purity_reason()'s single
summary string, this locates the exact blocking construct (source
line and a stable, versioned "category" code) for an
unsupported-syntax failure, and for a branch, classifies each
branch's condition individually as either resolvable (naming which
parameters a claim would need to declare a domain for) or
structurally blocked (naming why), see _explain_branch().
purity_reason()'s single generic "uses unsupported expression syntax" bucket only reports that lifting failed, not where: it swallows the actual NotSymbolic message and source location. A function can fail to lift for a reason several statements away from where the failure is easiest to guess at (a tuple return further down the body, say, when the real blocker is an earlier ternary); this function surfaces the precise statement and category instead of requiring that tracing to be done by hand.
Deliberately raw: every field here is a mechanical fact about the
function's own source (which construct, which line, which stable
category name from symbolic.NotSymbolic's fixed vocabulary),
never an editorial verdict about whether it's worth fixing. A
caller wanting that judgment call layers it on top of this report,
not the other way around.
None if the source isn't available at all (same convention as
purity_reason()/is_pure_enough()). Otherwise a dict with at least
"liftable" (True means this function shouldn't have been asked
about at all; callers should only reach for this on rows where
pure is False) and, when not liftable, a stable "blocker" name
plus blocker-specific raw detail; "branches" (a list of
{"line", "condition", "kind", ...}, one per ast.If anywhere in
the body) for the branch case specifically, since a function can
have several independent branches with different fates.
Source code in mathema/inventory.py
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 | |
structural_complexity(fn)
¶
Loop/branch/recursive-call counts, plus a complexity number in the
spirit of cyclomatic complexity (decision points + 1: one per
branch, one per loop) but with a heavier, depth-scaled charge for
nested loops, the standard McCabe formula charges a flat point
per loop regardless of nesting, which understates the real cost: a
loop two levels deep is harder to reason about than one four levels
wide and flat, and a loop three or four levels deep is a different
order of difficulty again, not just "one more of the same thing."
Each loop pays _NESTED_LOOP_PENALTY points per level of nesting it
sits at (LoopFact.depth), so depth 1 costs 2 extra, depth 2 costs
4 extra, depth 3 costs 6 extra, and so on; deeper nesting is
charged more, not the same, each additional level down. loops is
the total loop count including nested ones; nested_loops is the
subset of loops that sit inside another loop's body (depth > 0).
recursive_calls counts self-call sites in the function's own
body, not recursion depth or mutual recursion across multiple
functions; true call-graph cycle detection across a codebase is a
different, bigger feature, not attempted here. None if source
wasn't available.
Source code in mathema/inventory.py
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 | |
scope_dependencies(fn)
¶
(global_vars, global_funcs, unresolved), names a function
reads that aren't its own parameters/locals, split by what kind of
dependency they actually are: global_vars are a genuine global
variable dependency (real, if hidden, state, a claim about this
function is only as reliable as that global's current value);
global_funcs are an ordinary reference to a sibling function/
class/module (normal code structure, not a state dependency at all,
a different error surface from global_vars, not the same one);
unresolved have no binding mathema can find at all (likely a real
bug, or a name only defined at call time). None if source wasn't
retrievable. This is exactly what analyze_source() already warns
about on every call, surfaced here as data instead of text, since
a population sweep wants one row per function, not one interleaved
warning per function.
Source code in mathema/inventory.py
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 | |
mutated_globals(fn)
¶
Intent
Module-level names this function writes through, CACHE[k] = v,
CONFIG.field = x, rather than merely reads. None if the
source wasn't retrievable.
Notes
Deliberately separate from scope_dependencies' global_vars,
which reports names a function depends on. Writing is the
stronger relationship: it makes this function the reason some
other caller's answer changed. A name appears here and not in
global_vars when the function only ever writes it.
Source code in mathema/inventory.py
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 | |
typing_info(fn)
¶
How much of the signature actually carries type hints, params
annotated vs total, and whether the return type is. A cheap signal
on its own (an untyped parameter is exactly where mathema has least
to go on inferring anything), and the prerequisite for a richer one:
a bare str parameter carries none of a proper Literal[...]/
Enum hint's finite-value information. finite_domains is
populated whenever a parameter's own type hint already states its
entire meaningful value set, exactly the shape lift_conditioned()'s
branch pruning needs, read directly from the signature rather than
requiring a claim to redeclare it.
Source code in mathema/inventory.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
wrapped_target(fn)
¶
Is this function's body exactly one statement; return
<call>(...), a thin pass-through with no real computation of its
own (a docstring beforehand doesn't disqualify it, same convention
lift() already uses)? If so, a best-effort name for what it calls:
module.qualname when the callable can be resolved (a bare-name
call resolved against fn's own globals, or an attribute call
resolved against whatever object its root name is bound to,
np.sqrt resolves through the real np module object, not by
guessing), else just the name as written (self.helper, foo)
when it can't be. None if the body isn't exactly this shape.
Source code in mathema/inventory.py
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | |
docstring_quality(fn)
¶
A best-practice checklist, not a style grade, has a docstring at
all, has a real summary (non-empty text before the first recognized
section header, either convention), documents its parameters (each
real parameter's name found literally somewhere in the docstring,
a presence heuristic, not true cross-referencing), documents what it
returns (only applicable if it actually returns something,
analyze_source()'s own returns_kind, so a function returning None is
never penalized for not documenting a return value it doesn't have),
documents raising (only applicable if the body actually raises at
least one exception with a resolvable type name; see
_raised_exception_names(); passing requires both a Raises/
Exceptions section AND every one of those exception names mentioned
in it by name, not just the section existing). score/applicable
let a caller compute "N/M" without redefining what counts,
applicable shrinks when a criterion genuinely doesn't apply (no
params, no raise, no return value), rather than counting it as a
failure.
Source code in mathema/inventory.py
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | |
docs_checklist(q)
¶
docstring_quality()'s result as checkbox-style lines (✓/✗, ·
for informational-only); one shared renderer so
mathema.DocstringReport.__repr__ and mathema audit --docs-only
can't drift apart on what a checkmark means. No leading indentation;
callers indent as their own context needs.
Source code in mathema/inventory.py
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | |
is_test_covered(fn, coverage_data)
¶
None means unknown (no coverage report found, or this
function's file wasn't in it), never conflate that with False.
Source code in mathema/inventory.py
854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 | |
read_test_coverage(root='.')
¶
Best-effort executed-line-numbers-per-file, from an existing
coverage.py report; this never runs the target's tests itself.
Tries coverage.json (coverage.py's own coverage json export)
first, since that needs no import of the coverage package at all;
falls back to a native .coverage data file if coverage happens
to be importable. None (not {}) if neither is available or
readable; callers must treat that as "unknown", not "nothing was
covered".
Source code in mathema/inventory.py
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 | |
suggest_coverage_command(root='.')
¶
Best-effort: does this project look like it uses pytest? If so,
the command that would produce the coverage.json report
read_test_coverage() already knows how to read, never runs it,
only detects whether suggesting it makes sense. None if nothing
pytest-shaped is found, no guess is better than a wrong one for a
project that uses something else entirely. The export runs whatever
the test run's exit status: a failing test still leaves the lines
every other test executed.
Uses python -m coverage/python -m pytest, not the bare coverage/
pytest console scripts; those aren't guaranteed to be on PATH even
when the packages themselves are installed. Checks whether coverage
is actually importable in this process first (mathema is expected
to run in the same venv as the target project, per the walkthrough)
and prepends an install step if not, a suggested command that
fails with "command not found" is worse than no suggestion.
Source code in mathema/inventory.py
812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 | |
mathema.types¶
mathema.types
¶
Semantic type markers: attach a typing.Annotated marker to a
parameter or return type, and mathema infers a standard claim
automatically, a fourth claim-authoring surface, alongside the file/
decorator/docstring ones in authoring.py, except inferred rather than
explicitly stated.
from typing import Annotated
from mathema.types import Probability, Shape
def bayes_update(prior: Annotated[float, Probability],
likelihood: Annotated[float, Probability]) -> Annotated[float, Probability]:
...
def matmul(a: Annotated[list, Shape("m", "n")],
b: Annotated[list, Shape("n", "p")]) -> Annotated[list, Shape("m", "p")]:
...
Markers are deliberately general mathematics only, matching the
domain-specific-goes-outside boundary drawn in
05-pushing-derive-further.md: Probability/Positive/Shape are math, not a
business vertical (a StockPrice marker would not belong here).
Domain markers (Probability/Positive/Nonnegative) fold into an ordinary
domain-scoped claim, reusing check()'s existing domain= mechanism.
Shape is structural, not algebraic; it can't be expressed in the
scalar claim-law grammar (there is no index quantifier over a variable-
length structure), so it's checked the way probing.py already checks
monotone/is_numerically_stable: a bespoke Python probe, not a parsed
law string. Precedence-wise this surface is the lowest of the four: a
human writing a decorator or docstring claim is a more deliberate
statement than an incidentally-implied one from a type hint, so a
same-named explicit claim from any other surface overrides an inferred
one (see authoring.declared_from_function, which does not consult this
module; type inference is folded in one level further out, in
type_probes(), so it never competes with an ordinary claim name).
Probability
dataclass
¶
Value in [0, 1].
Source code in mathema/types.py
49 50 51 | |
Positive
dataclass
¶
Value > 0.
Source code in mathema/types.py
54 55 56 | |
Nonnegative
dataclass
¶
Value >= 0.
Source code in mathema/types.py
59 60 61 | |
Shape
dataclass
¶
Expected outer dimensions of a nested-list "matrix"/"vector"
value: concrete ints or symbolic dimension-name strings shared across
the signature (Shape("m", "n") on one parameter and Shape("n",
"p") on another means both must agree on the size bound to "n"
within a trial). 1-D: Shape("n"); 2-D: Shape("m", "n").
Source code in mathema/types.py
100 101 102 103 104 105 106 107 108 109 110 | |
mathema.identity¶
mathema.identity
¶
Identity: the two Mathema hashes.
form ; how it's written: rename/format-invariant structural hash (locals and parameters replaced by binding-order names). sig ; the coarsest facet of meaning: the typed shape of the transformation.
Line-anchored artifacts bind to form; everything else binds to meaning (signature + lifted normal form, when available).
form_hash(fdef)
¶
The form identity: a 12-hex-char hash of the function's
alpha-normalized AST (normalized(), locals/parameters replaced
by binding-order names, so a pure rename doesn't change it, but any
real structural change does), rendered by form_text so the hash
is the same on every supported Python.
Source code in mathema/identity.py
142 143 144 145 146 147 148 | |
mathema.compiled¶
mathema.compiled
¶
Executable closed forms: a sympy expression compiled back into a runnable callable, with the provenance and domain-validity facts that make evidence from running it honest.
A compiled form is mathema's RECONSTRUCTION of some mathematics, a
lifted function body, a rewrite-gallery form, the resolved intermediate
of a stalled proof, never the user's own code. Numeric evidence from
evaluating one is evidence about the symbolic form, and only
transitively (through the lift) about any code it came from, so every
consumer states that provenance: the numeric-fallback route is
probe:lifted_numeric, its ceiling is holds, and the record names
the form that was actually sampled.
A compiled form evaluates; it does not print. Turning one into compilable source in another language is not part of this module, and nothing here emits foreign code.
CompiledForm
dataclass
¶
One executable closed form: the expression, the callable
compiled from it, the ordered free-variable names the callable
takes, where the form came from (provenance, e.g. the rewrites
applied), the conditions under which it is a valid rendering of its
origin (validity), and the numeric backend that runs it.
Source code in mathema/compiled.py
34 35 36 37 38 39 40 41 42 43 44 45 46 | |
compile_form(expr, names=None, provenance=(), validity='', backend='math', pseudo_infinity=None)
¶
Intent
Lambdify expr over names (defaulting to its sorted free
symbols) into a CompiledForm, or None when the expression
contains something the backend cannot run (an unevaluated
Integral under the plain math backend, an unmapped special
function).
Notes
pseudo_infinity is the claim's resolved (lo, hi) operational
infinity (records.pseudo_infinity_range). An unevaluated
Integral with an infinite bound is read at that range, the
same empirical reading of infinity every other consumer of a
let |inf| be v binding uses, and the truncation is stated
in the form's validity. Without a declared operational
infinity there is no honest finite reading, so the form
declines to compile.
Source code in mathema/compiled.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
numeric_check(lhs, rhs, relation, domain, tolerance=1e-09, admits=None, trials=_NUMERIC_TRIALS)
¶
Intent
Sample the two compiled sides over the declared domain and
adjudicate the relation numerically: the fallback evidence for
a claim whose symbolic comparison stalled and whose original
form the probe route cannot evaluate (a d()/integrate()/Sum()
law). Returns (verdict, checked, counterexample_text) with
verdict one of "holds" / "falsified" / None (not enough
evaluable samples). The ceiling is holds; this is sampling,
never proof, and it samples the RECONSTRUCTION, which the
caller must say.
Notes
Strict relations get no tolerance credit and equality gets the
claim's own tolerance, matching the probe loop's comparison
rules exactly. admits, when given, filters candidate points
(an assuming surface).
Source code in mathema/compiled.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | |
mathema.forms¶
mathema.forms
¶
Other closed forms of arbitrary code: the public face of the rewrite gallery and the substitution library.
closed_forms(fn) lifts a function's body to its symbolic closed form
and returns every structurally distinct equivalent the gallery can
produce (factored, expanded, trig-simplified, ...), each named by the
rewrite that made it. substituted_forms(fn) lists the known changes
of variable that apply to the body's shape, t = log(x) for a
logarithmic body, the Weierstrass half-angle for a trigonometric one,
with the transformed expression and the domain condition each one
needs. register_substitution extends that library: a registered
substitution is immediately available here and to the extensive proof
ladder, so a domain-specific change of variable contributed once can
start closing proofs.
These are the same registries extensive=True adjudication draws on,
exposed directly because an alternative form of one's own code is
useful beyond proving: reading it, simplifying it, or spotting that
two implementations share a closed form.
register_substitution = _register_substitution
module-attribute
¶
Form
dataclass
¶
One closed form of a function's body: name is the rewrite that
produced it ("as written" for the lift itself), expr the sympy
expression, text its canonical rendering.
Source code in mathema/forms.py
42 43 44 45 46 47 48 49 | |
SubstitutedForm
dataclass
¶
A function's body carried through one known change of variable:
name is the substitution ("t = log(x)"), param the parameter it
replaced, expr/text the transformed body in the new variable
var, and requires the domain condition under which the
substitution is a monotone bijection (and the transformed form
therefore takes exactly the original's values).
Source code in mathema/forms.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
Substitution
dataclass
¶
One classic change of variable, t = g(x).
name is the display form ("t = log(x)"); requires states, in
plain text, the domain condition under which the substitution is a
monotone bijection ("x > 0"); forward maps a value of x to the
corresponding t (used on domain endpoints); inverse maps the new
variable back (x = g⁻¹(t), substituted into the expression);
detect says whether an expression contains the shape this
substitution is known to help with; applicable checks the domain
condition on exact interval endpoints; monotone is "increasing"
or "decreasing" and controls whether the mapped endpoints swap.
A substitution transforms a sign or equality question without
changing its answer: g is a bijection from the declared interval
onto its image, so expr(x) and expr(g⁻¹(t)) take exactly the
same set of values. That is what lets a proof found in t-space
stand as a proof of the original claim.
Source code in mathema/symbolic/_forms.py
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
closed_forms(fn, facts=None)
¶
Every structurally distinct closed form of fn's body the
rewrite gallery produces, the as-written lift first.
>>> def parallel(a: float, b: float) -> float:
... return 1.0 / (1.0 / a + 1.0 / b)
>>> [f.name for f in closed_forms(parallel)] # doctest: +SKIP
['as written', 'cancel', 'together', ...]
Each Form carries the producing rewrite's name, the sympy
expression, and its canonical text. Forms are deduplicated
structurally, so two rewrites that land on the same expression
yield one entry (the first name wins). Raises ValueError, with
the blocking construct named, for a function that has no scalar
closed form at all.
Source code in mathema/forms.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
substituted_forms(fn, facts=None)
¶
The known changes of variable that apply to fn's body, one
SubstitutedForm per (substitution, parameter) pair whose shape
the substitution recognizes.
>>> import math
>>> def loglaw(x: float) -> float:
... return math.log(x) ** 2 - 2.0 * math.log(x) + 1.5
>>> [s.name for s in substituted_forms(loglaw)] # doctest: +SKIP
['t = log(x)']
The transformed expression is simplified in the new variable, and
requires states the domain condition ("x > 0") under which the
substitution is a monotone bijection, on such a domain the
transformed form takes exactly the values the original does, which
is what makes it usable in a proof and not just a display. Raises
ValueError for a function with no scalar closed form.
Source code in mathema/forms.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
executable_forms(fn, facts=None, backend='math')
¶
Intent
Every closed form of fn's body as a runnable CompiledForm:
the as-written lift and each gallery rewrite, compiled through
mathema.compiled.compile_form, carrying the provenance chain
(which rewrite produced it) and, for a substituted form, the
domain condition its equivalence needs. A compiled form is
mathema's reconstruction, never the original code, evidence
from running one is evidence about the symbolic form, which is
exactly what makes it useful for cross-checking the original
against its own mathematics.
import mathema.forms def parallel(a: float, b: float) -> float: ... return 1.0 / (1.0 / a + 1.0 / b) cfs = mathema.forms.executable_forms(parallel) # doctest: +SKIP cfs[0].fn(2.0, 2.0) # doctest: +SKIP 1.0
Notes
A form the backend cannot compile (an unevaluated calculus
atom, an unmapped special function) is skipped, not an error;
an unliftable function raises the same ValueError
closed_forms does. Substituted forms carry their requires
condition in validity; equivalence holds only where the
change of variable is a monotone bijection.
Source code in mathema/forms.py
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | |
mathema.targets¶
mathema.targets
¶
The one target resolver: any spelling of "which function(s)" that a
mathema command accepts resolves here, to live callables keyed by their
canonical dotted name (module.qualname, the same shape the
declared/verified stores use).
Accepted spellings:
pkg/pkg.mod, an importable package or module; a package is walked into every submodule.pkg.mod.fn/pkg.mod.Class.method; one function or method, found by importing the longest importable prefix and walking attributes for the rest.pkg.mod:fn/pkg.mod:Class.method, the explicit one-function form;pkg:namealso matchesnameanywhere underpkgwhen that suffix is unique.path/to/file.py/path/to/file.py:fn, a file, imported under its real dotted name with full package context (the package root is found by walking up through__init__.pydirectories), so relative imports inside the target work and the loaded functions key identically to normally-imported ones.
Every command and programmatic surface resolves through resolve /
resolve_function; a target that cannot be resolved raises
TargetError with a printable message (CLI exit 2).
Target
dataclass
¶
One resolved target: kind is "package", "module", or
"function"; functions maps each canonical dotted key to its live
callable; module_name is the dotted module actually imported (or
None for a package walk); skipped lists (submodule, error)
pairs for package submodules that failed to import.
Source code in mathema/targets.py
44 45 46 47 48 49 50 51 52 53 54 55 | |
TargetError
¶
Bases: Exception
A target string that cannot be resolved to any function. The message is written to be printed as-is to a CLI user.
Source code in mathema/targets.py
39 40 41 | |
resolve(target, root='.', *, skipped=None)
¶
Resolve a target spelling to its functions, keyed by canonical
dotted name. See the module docstring for the accepted spellings.
skipped, when given, collects (submodule, error) pairs for
package submodules that failed to import (also carried on the
returned Target).
Raises:
| Type | Description |
|---|---|
TargetError
|
nothing resolvable at this spelling; the message is printable as-is. |
Source code in mathema/targets.py
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | |
resolve_function(target, root='.')
¶
Resolve a target that must name exactly one function, returning
(key, fn).
Raises:
| Type | Description |
|---|---|
TargetError
|
the target resolves to nothing, or to more than one function (the message lists candidates). |
Source code in mathema/targets.py
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | |