Skip to content

Commit aea71c6

Browse files
Where the rewrite graph actually stands is held by a test (#1190)
#746 tier 2 is the rewrite graph, and what it can do today was described in documents and measured nowhere. Two facts, pinned: The safe ceiling is nearly inert on ordinary input. Of five expressions -- (a + b) / (a * b), x / x, (x + 1) / (x + 1), the Pythagorean identity and (x + 1) * (x - 1) -- saturation under SafeRules moves exactly one and hands four back unchanged. That is not a defect in the expressions; it is the trade tier 2 has not resolved. SafeRules is RulesUpTo(Rearranges), 142 of 324, and the 152 at Unknown are excluded because their growth was never judged: the ceiling that is safe finds little, and the ceiling that finds things admits rewrites nobody measured. And no written pattern can put a condition into the graph at all. Only 35 of the 324 rules have a right-hand side written as a pattern, and none mentions `provided` -- so a Providedf reaches an e-class only along the code-built path, where a rule extracts a witness term, runs code on it and puts the result back. That last one narrows a question EqualitySaturationReviewFindings.md was still being read as leaving open. The document had already retracted the claim that the e-graph cannot represent a conditional equivalence -- it was the buildable type table, and a line in Construct was the fix -- and this says why the case is hard to reach at all: the union that would need a meaning is not one e-matching can currently form. Held at zero, so the first two-sided conditional rule fails there rather than quietly making it live again. Both were written first as `>= 0` assertions, which pass by asking nothing. Replaced with the numbers, which is the only version worth having. Full suite 9567 passed, 0 failed. Part of #746. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 68c3add commit aea71c6

2 files changed

Lines changed: 133 additions & 0 deletions

File tree

‎Sources/AngouriMath/Docs/Contributing/EqualitySaturationReviewFindings.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,14 @@ whitelist rather than the node model: `Providedf` is an ordinary two-child node,
174174
`Construct` a line for it was the whole fix. What remains is genuinely about the node model —
175175
per-node data that is not a child, of which `Codomain` is the only instance the library has today.
176176

177+
*And narrower again, measured rather than argued.* **No written pattern can put a condition into the
178+
graph at all.** Only 35 of the 324 rules have a right-hand side written as a pattern, and none of
179+
them mentions `provided` — so a `Providedf` reaches an e-class only along the code-built path, where
180+
a rule extracts a witness term, runs code on it and puts the result back. That is worth knowing
181+
before anyone reopens this: the union that would need a meaning is not one e-matching can currently
182+
form. `ProvidedInAnEClassTest.NoWrittenPatternIntroducesACondition` holds it at zero, so the first
183+
two-sided conditional rule fails there rather than quietly making the question live again.
184+
177185
## What is not decided here
178186

179187
The one question above, restated as the decision it is: **how much should ride along on an e-node
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using System.Collections.Generic;
10+
using System.Linq;
11+
using AngouriMath.Core;
12+
using AngouriMath.Core.Budgets;
13+
using AngouriMath.Core.Transformations;
14+
using AngouriMath.Core.Transformations.Matching;
15+
using AngouriMath.Extensions;
16+
using Xunit;
17+
18+
namespace AngouriMath.Tests.Core.Transformations
19+
{
20+
/// <summary>
21+
/// What an e-class does with a <see cref="Entity.Providedf"/>, which is the one question
22+
/// <c>Docs/Contributing/EqualitySaturationReviewFindings.md</c> leaves open and the thing
23+
/// standing between the rewrite graph and a production caller.
24+
/// </summary>
25+
[Trait("Area", "Core")]
26+
public sealed class ProvidedInAnEClassTest
27+
{
28+
private static Entity? Saturate(string source, RewriteRuleGrowth ceiling)
29+
{
30+
var graph = new EGraph();
31+
var root = graph.AddEntity(source.ToEntity());
32+
var ledger = BudgetLedger.For(nameof(ProvidedInAnEClassTest),
33+
new WorkBudget { Steps = 200_000, Time = TimeSpan.FromSeconds(20) });
34+
Saturation.Run(graph, Saturation.RulesUpTo(ceiling), ledger,
35+
node => node.Complexity);
36+
return graph.Extract(root, node => node.Complexity);
37+
}
38+
39+
/// <summary>
40+
/// <b>A condition is not a decoration on a value, and a union says it is.</b> Two nodes
41+
/// join an e-class when the graph is told they are equal. <c>p</c> and
42+
/// <c>p provided c</c> are <i>not</i> equal — they differ exactly where <c>c</c> is false,
43+
/// which is the only place the condition was written for — so a rule producing the second
44+
/// from the first is asking the graph to record something untrue, and extraction may then
45+
/// hand back either.
46+
/// </summary>
47+
[Fact]
48+
public void AConditionedValueIsNotTheValue()
49+
{
50+
var bare = "1".ToEntity();
51+
var conditioned = "1 provided not x = 0".ToEntity();
52+
53+
Assert.NotEqual(bare, conditioned);
54+
Assert.Equal("1", bare.Substitute("x", 0).Evaled.Stringize());
55+
Assert.Equal("NaN", conditioned.Substitute("x", 0).Evaled.Stringize());
56+
}
57+
58+
/// <summary>
59+
/// <b>No condition can enter the graph through a written pattern.</b> Which narrows the
60+
/// open question considerably: a <c>Providedf</c> reaches an e-class only along the
61+
/// code-built path, where a rule extracts a witness term, runs code on it and puts the
62+
/// result back.
63+
/// </summary>
64+
/// <remarks>
65+
/// Only 35 of the 324 rules have a right-hand side written as a pattern at all, and none
66+
/// of those mentions a condition — so the case
67+
/// <c>EqualitySaturationReviewFindings.md</c> leaves open cannot arise from e-matching as
68+
/// it stands. It arises from the other 289. Asserted at zero so that writing the first
69+
/// two-sided conditional rule fails here rather than quietly making the question live.
70+
/// </remarks>
71+
[Fact]
72+
public void NoWrittenPatternIntroducesACondition()
73+
{
74+
var withCondition = Saturation.RulesUpTo(RewriteRuleGrowth.Unknown)
75+
.Where(rule => rule.Right is not null
76+
&& rule.Right.ToString()!.Contains("provided"))
77+
.ToList();
78+
79+
Assert.True(withCondition.Count == 0,
80+
$"{withCondition.Count} two-sided rules now mention a condition — "
81+
+ "the e-class question is live for them, and what a union of `p` with "
82+
+ "`p provided c` is supposed to mean has to be decided before they fire: "
83+
+ string.Join(", ", withCondition.Select(r => r.Name)));
84+
}
85+
86+
/// <summary>
87+
/// <b>The safe ceiling is nearly inert on ordinary input.</b> Four of these five come back
88+
/// exactly as they went in; only the Pythagorean identity moves. That is not a defect in
89+
/// the expressions — it is what the graph can do today with the rules it is allowed to
90+
/// use, and it is the measurement
91+
/// <a href="https://github.com/asc-community/AngouriMath/issues/746">#746</a> tier 2 needs
92+
/// before the graph is wired into anything.
93+
/// </summary>
94+
/// <remarks>
95+
/// <c>SafeRules</c> is <c>RulesUpTo(Rearranges)</c>, 142 of 324 rules. The 152 sitting at
96+
/// <c>Unknown</c> are excluded by design, since their growth was never judged — so the
97+
/// ceiling that is safe to run finds little, and the ceiling that finds things admits
98+
/// rewrites nobody measured. That trade is the open part of tier 2, and this pins where it
99+
/// currently sits rather than describing it.
100+
/// </remarks>
101+
[Fact]
102+
public void TheSafeCeilingIsNearlyInertOnOrdinaryInput()
103+
{
104+
var moved = new List<string>();
105+
var still = new List<string>();
106+
foreach (var source in new[]
107+
{
108+
"(a + b) / (a * b)", "x / x", "(x + 1) / (x + 1)",
109+
"sin(x) ^ 2 + cos(x) ^ 2", "(x + 1) * (x - 1)",
110+
})
111+
{
112+
var extracted = Saturate(source, RewriteRuleGrowth.Rearranges);
113+
if (extracted is null || extracted == source.ToEntity())
114+
still.Add(source);
115+
else
116+
moved.Add($"{source} -> {extracted.Stringize()}");
117+
}
118+
119+
Assert.True(still.Count == 4 && moved.Count == 1,
120+
$"the safe ceiling moved {moved.Count} of 5 and left {still.Count}; it was 1 and 4.\n"
121+
+ "moved:\n" + string.Join("\n", moved)
122+
+ "\nunchanged:\n" + string.Join("\n", still));
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)