Skip to content

Yield every goal of a partial-term query, and each of them once - #99

Merged
mrhaandi merged 1 commit into
developfrom
bugfix/full-residual
Aug 21, 2026
Merged

Yield every goal of a partial-term query, and each of them once#99
mrhaandi merged 1 commit into
developfrom
bugfix/full-residual

Conversation

@FelixLaarmann

@FelixLaarmann FelixLaarmann commented Aug 21, 2026

Copy link
Copy Markdown
Member

Problem:

SolutionSpace.goal_from_tree(start, tree, pos) builds the goals of the query that prescribes
tree everywhere except at pos, where it leaves a variable. It returned after the first goal
it found:

if self._is_goal_for_position(goal, pos, is_pos_leaf):
    yield goal
    return  # one goal is enough ??!!!

Four lines above, its own docstring promises "Yields all valid goals". Three defects follow:

  1. A non-terminal is reached by more than one clause. For an intersection of arrows the
    inhabitation emits one clause per admissible subset of paths, and those clauses ask different
    sorts of their arguments. With f : (A -> C) & (B -> C) over c : A & B, a : A and
    b : B, the variable in f(_) is completed by all three constants; the query offered
    whichever two the search reached first.
  2. The root position was matched against a term about to be discarded. For pos == () the
    whole term is replaced, yet the initial goals kept only clauses whose terminal matches the
    root of tree. In E -> lit | neg(E) | add(E, E), handed neg(lit) and asked to replace it
    entirely, the query returned neg terms and nothing else — one clause out of three.
  3. Removing the early return alone makes it worse. The traversal expanded every open
    subgoal in one step, so a goal with several open positions was reached once per order in which
    they happened to be expanded — six times for (0, 0) of add(add(x, x), neg(x)), eight for
    (1, 0). Invisible while the search stops at the first hit, a factorial over-count once it
    does not.

What changes:

Each goal is yielded once. The success test is applied when a goal leaves the frontier, and such
a goal is not expanded further. For pos == () the clauses of start become goals directly,
without the root match. The traversal expands one subgoal per step: the deepest open one other
than pos, leftmost among those — the selection depth_first_resolution and
breadth_first_resolution already document and use, so the reachable goals are the same and only
the derivation tree walked to reach them changes.

`goal_from_tree` describes the subterms that complete a term with a variable at
one position into an inhabitant. It returned after the first goal it found --
"one goal is enough ??!!!" -- although its own docstring promises "Yields all
valid goals" four lines above.

A non-terminal is in general reached by more than one clause: for an
intersection of arrows the inhabitation emits one clause per admissible subset
of paths, and those clauses ask different sorts of their arguments. With
`f : (A -> C) & (B -> C)` over `c : A & B`, `a : A` and `b : B`, all three
constants complete `f(_)` and the query offered two of them.

The root position was wrong in a way the early return hid. For `pos == ()` the
whole term is replaced, so nothing of it constrains the query, but the initial
goals were still matched against the root of the term about to be discarded --
which keeps only the clauses sharing its terminal. In the expression space of
the new tests that is one clause out of three.

Dropping the early return alone makes it worse. The traversal expanded every
open subgoal in one step and pushed all the results, reaching the same goal
once per order in which those positions happened to be expanded: six times for
the variable at (0, 0) of add(add(x, x), neg(x)), eight for (1, 0). It expands
one subgoal per step now, the deepest open one other than `pos`, which is the
selection the uninformed resolution strategies already use.

Measured on the expression space: a query at an inner position is unchanged, a
root query streams the terms it was missing (169 -> 183 within depth 3) at +7%
time, and a prescribed term with many open positions gets cheaper because the
factorial expansion is gone -- a chain of seven add nodes queried at (1, 0)
drops from 0.527 ms to 0.402 ms.

`resolution` is the only caller, reached through `depth_first_resolution`,
`breadth_first_resolution` and `sample_tree` when `tree` and `pos` are given,
and through `sample_tree` by `ResolutionMutation`. The resolutions return more
results than before. `sample_tree` returns one, and for `pos == ()` it now
returns what `sample_tree` without `tree` and `pos` returns -- the first ground
clause -- because the root filter that used to hide it is gone.
`ResolutionMutation` is unaffected: it mutates neither the root nor a leaf, and
an inner position leaves every goal open. Calls without `tree` and `pos` are
unaffected.

@tudo-seal-workflows tudo-seal-workflows Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark CoSy

Details
Benchmark suite Current: e41a3ae Previous: be71b5d Ratio
benchmarks/test_benchmark_maximal_elements.py::test_benchmark_maximal_elements 16.54398307334902 iter/sec (stddev: 0.0007198152092830312) 9.608281351562356 iter/sec (stddev: 0.010017085420616013) 0.58
benchmarks/test_benchmark_maze.py::test_benchmark_maze 6.405888544593071 iter/sec (stddev: 0.01093601297431421) 3.9235166334572096 iter/sec (stddev: 0.018797551923491352) 0.61
benchmarks/test_benchmark_maze_contains.py::test_benchmark_maze_contains 5.890040602372219 iter/sec (stddev: 0.01157984005005016) 3.5391214334611916 iter/sec (stddev: 0.025877608823890046) 0.60
benchmarks/test_benchmark_maze_loopfree.py::test_benchmark_maze_loopfree 6.3832975991926135 iter/sec (stddev: 0.014913532822996654) 3.8477221717914887 iter/sec (stddev: 0.02072136799063595) 0.60

This comment was automatically generated by workflow using github-action-benchmark.

@codecov-commenter

codecov-commenter commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.94631% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.28%. Comparing base (6358f35) to head (e41a3ae).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
tests/search_fixtures.py 75.00% 7 Missing ⚠️
tests/test_partial_term_queries.py 95.32% 5 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop      #99      +/-   ##
===========================================
+ Coverage    78.69%   79.28%   +0.59%     
===========================================
  Files           45       47       +2     
  Lines         3703     3833     +130     
  Branches       532      532              
===========================================
+ Hits          2914     3039     +125     
- Misses         688      693       +5     
  Partials       101      101              
Flag Coverage Δ
macos-latest-3.10 79.20% <91.94%> (+0.59%) ⬆️
macos-latest-3.11 79.25% <91.94%> (+0.59%) ⬆️
macos-latest-3.12 79.25% <91.94%> (+0.59%) ⬆️
macos-latest-3.13 79.25% <91.94%> (+0.59%) ⬆️
ubuntu-latest-3.10 79.20% <91.94%> (+0.59%) ⬆️
ubuntu-latest-3.11 79.25% <91.94%> (+0.59%) ⬆️
ubuntu-latest-3.12 79.25% <91.94%> (+0.59%) ⬆️
ubuntu-latest-3.13 79.25% <91.94%> (+0.59%) ⬆️
windows-latest-3.10 79.20% <91.94%> (+0.59%) ⬆️
windows-latest-3.11 79.25% <91.94%> (+0.59%) ⬆️
windows-latest-3.12 79.25% <91.94%> (+0.59%) ⬆️
windows-latest-3.13 79.25% <91.94%> (+0.59%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@FelixLaarmann
FelixLaarmann requested a review from mrhaandi August 21, 2026 07:26
@mrhaandi
mrhaandi merged commit d762d5b into develop Aug 21, 2026
15 checks passed
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.

3 participants