Skip to content

The solver layer, two seed solvers, and the CLI - #7

Open
roed-math wants to merge 1 commit into
split/05-classifyfrom
split/06-solvers
Open

The solver layer, two seed solvers, and the CLI#7
roed-math wants to merge 1 commit into
split/05-classifyfrom
split/06-solvers

Conversation

@roed-math

@roed-math roed-math commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

The solver layer.

SolutionSet is the result type: a finite complete list, an empty set with a
reason, or an infinite family that streams (iter(S), S.first(n)). When no
solver is wired up, SolverUnavailable carries the registry's software
pointers and filled code templates, so an equation page can always offer
runnable code.

API notes:

  • A solver is Callable[[Classification, Match], SolutionSet] and works in its
    family's standard coordinates. solve() dispatches over the matches that
    were actually emitted, most specific first, handing each solver its own
    match; it no longer walks [slug] + lineage, which could invoke an ancestor
    solver with data belonging to another match.
  • SOLVER_DOMAINS declares the domains each solver answers over. The integer
    answer does not describe the rational solution set, so a QQ question is
    declined rather than answered with it; linear gains a real QQ solver (a
    complete affine parametrization, enumerated by rational height) and an NN
    solver that is complete where it answers and declines otherwise.
  • _finalize_solution_set wraps every solver: it pulls each tuple back through
    the match's transform, orders it by the original equation's unknowns, and
    checks the domain, the side conditions and the original equation exactly —
    lazily, for infinite families. solve("1/(x - 1) = 1/(y - 1)") cannot emit
    (1, 1).
  • SolutionSet validates its own kind on construction (an infinite set must
    carry a stream, so iteration cannot stop at the stored witness) and owns its
    serialization, as_dict(). Coordinates keep their exact values: the CLI used
    to push every one through int(), turning the root 1/2 into 0.

The two seed solvers (linear, univariate) are here; every other solver arrives
with its family, which is what makes those PRs independent. The module-level
enumeration bounds and the Sage imports are the union used by those solvers.

cli.py (dioclassify 'x^2 - 61*y^2 = 1' --solve) also lands here, since it
needs both halves: classification for --json, solving for --solve.

Part of the series that splits #1 into reviewable pieces:

  1. 01-bibliography — packaging, docs, annotated bibliography
  2. 02-parsing — equation strings to a term model
  3. 03-registry — the YAML family registry (3 seed families)
  4. 04-matchers — shape recognizers
  5. 05-classify — the classification pipeline
  6. 06-solvers — solver framework, two seed solvers, and the CLI
  7. 07..09-backbone — the 23 parent families of the DAG, by depth
  8. one PR per remaining family (38 of them, mutually independent)
  9. 99-polish — restore the full doctests and tighten the invariants

The solver layer.

`SolutionSet` is the result type: a finite complete list, an empty set with a
reason, or an infinite family that streams (`iter(S)`, `S.first(n)`).  When no
solver is wired up, `SolverUnavailable` carries the registry's software
pointers and filled code templates, so an equation page can always offer
runnable code.

**API notes:**

- A solver is `Callable[[Classification, Match], SolutionSet]` and works in its
  family's standard coordinates.  `solve()` dispatches over the matches that
  were actually emitted, most specific first, handing each solver its own
  match; it no longer walks `[slug] + lineage`, which could invoke an ancestor
  solver with data belonging to another match.
- `SOLVER_DOMAINS` declares the domains each solver answers over.  The integer
  answer does not describe the rational solution set, so a `QQ` question is
  declined rather than answered with it; `linear` gains a real `QQ` solver (a
  complete affine parametrization, enumerated by rational height) and an `NN`
  solver that is complete where it answers and declines otherwise.
- `_finalize_solution_set` wraps every solver: it pulls each tuple back through
  the match's transform, orders it by the original equation's unknowns, and
  checks the domain, the side conditions and the original equation exactly —
  lazily, for infinite families.  `solve("1/(x - 1) = 1/(y - 1)")` cannot emit
  `(1, 1)`.
- `SolutionSet` validates its own kind on construction (an `infinite` set must
  carry a stream, so iteration cannot stop at the stored witness) and owns its
  serialization, `as_dict()`.  Coordinates keep their exact values: the CLI used
  to push every one through `int()`, turning the root `1/2` into `0`.

The two seed solvers (linear, univariate) are here; every other solver arrives
with its family, which is what makes those PRs independent.  The module-level
enumeration bounds and the Sage imports are the union used by those solvers.

`cli.py` (`dioclassify 'x^2 - 61*y^2 = 1' --solve`) also lands here, since it
needs both halves: classification for `--json`, solving for `--solve`.

Part of the series that splits #1 into reviewable pieces:

1. `01-bibliography` — packaging, docs, annotated bibliography
2. `02-parsing` — equation strings to a term model
3. `03-registry` — the YAML family registry (3 seed families)
4. `04-matchers` — shape recognizers
5. `05-classify` — the classification pipeline
6. `06-solvers` — solver framework, two seed solvers, and the CLI
7. `07..09-backbone` — the 23 parent families of the DAG, by depth
8. one PR per remaining family (38 of them, mutually independent)
9. `99-polish` — restore the full doctests and tighten the invariants
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants