diff --git a/Sources/AngouriMath/Core/Transformations/Transformation.Catalogue.cs b/Sources/AngouriMath/Core/Transformations/Transformation.Catalogue.cs
index 070282199..314323308 100644
--- a/Sources/AngouriMath/Core/Transformations/Transformation.Catalogue.cs
+++ b/Sources/AngouriMath/Core/Transformations/Transformation.Catalogue.cs
@@ -40,7 +40,38 @@ public static Transformation Rewriting(RewriteRuleSet ruleSet)
/// How hard to look; the same argument takes.
///
public static Transformation SimplificationAtLevel(int level)
- => LevelledCache.Simplification.For(level, static l => new SimplificationTransformation(l));
+ => LevelledCache.Simplification.For(level, static l => new SimplificationTransformation(l, null));
+
+ ///
+ /// The full simplification pipeline at a chosen level, rating candidates by
+ /// instead of .
+ ///
+ ///
+ /// How hard to look; the same argument takes.
+ ///
+ ///
+ /// Which candidate counts as cheapest. here behaves
+ /// exactly like — passing it is how a caller
+ /// states that on purpose rather than by leaving a parameter out.
+ ///
+ ///
+ /// #746 tier 2 named
+ /// this remaining on its own row: "a cost model that reaches an API rather than an ambient
+ /// setting". already is an API in
+ /// the sense that a caller can scope it explicitly and safely — it is backed by the same
+ /// uses, async-local rather
+ /// than thread-static, so one caller's override cannot leak into another's concurrent call.
+ /// What it lacked was a place in this API, the addressable one
+ /// is: composing SimplificationAtLevel(2, costModel).Then(...) names the choice
+ /// where setting an ambient value around a call does not. This overload does not
+ /// reimplement candidate search against an explicit parameter threaded through
+ /// Simplificator -- that would be a second pipeline to keep in step with the one
+ /// actually runs. It scopes the existing, already-tested
+ /// setting for the duration of this one call instead of introducing a second pipeline.
+ ///
+ public static Transformation SimplificationAtLevel(int level, CostModel costModel)
+ => new SimplificationTransformation(
+ level, costModel ?? throw new ArgumentNullException(nameof(costModel)));
///
/// Multiplies products over sums out, as does.
@@ -427,16 +458,25 @@ private sealed class InnerSimplificationTransformation : Transformation
private sealed class SimplificationTransformation : Transformation
{
private readonly int level;
+ private readonly CostModel? costModel;
- internal SimplificationTransformation(int level) => this.level = level;
+ internal SimplificationTransformation(int level, CostModel? costModel)
+ => (this.level, this.costModel) = (level, costModel);
- public override string Name => $"simplify[{level}]";
+ public override string Name
+ => costModel is null ? $"simplify[{level}]" : $"simplify[{level}, {costModel.Name}]";
public override TransformationRelation Relation => TransformationRelation.Equivalence;
public override Soundness Soundness => Soundness.SoundUnderAssumptions;
- protected override Entity? ApplyCore(Entity input) => Simplificator.Simplify(input, level);
+ protected override Entity? ApplyCore(Entity input)
+ {
+ if (costModel is null)
+ return Simplificator.Simplify(input, level);
+ using var _ = MathS.Settings.ComplexityCriteria.Set(costModel.Cost);
+ return Simplificator.Simplify(input, level);
+ }
}
private sealed class ExpansionTransformation : Transformation
diff --git a/Sources/Tests/UnitTests/Common/PublicApi.txt b/Sources/Tests/UnitTests/Common/PublicApi.txt
index 8a34c5f42..9e9598d73 100644
--- a/Sources/Tests/UnitTests/Common/PublicApi.txt
+++ b/Sources/Tests/UnitTests/Common/PublicApi.txt
@@ -264,6 +264,7 @@ AngouriMath.Core.Transformations.Transformation.Rewriting(AngouriMath.Core.Trans
AngouriMath.Core.Transformations.Transformation.RuleBasedFactorizationAtLevel(System.Int32) : AngouriMath.Core.Transformations.Transformation
AngouriMath.Core.Transformations.Transformation.Simplification { } : AngouriMath.Core.Transformations.Transformation
AngouriMath.Core.Transformations.Transformation.SimplificationAtLevel(System.Int32) : AngouriMath.Core.Transformations.Transformation
+AngouriMath.Core.Transformations.Transformation.SimplificationAtLevel(System.Int32, AngouriMath.Core.CostModel) : AngouriMath.Core.Transformations.Transformation
AngouriMath.Core.Transformations.Transformation.Soundness { } : AngouriMath.Core.Transformations.Soundness
AngouriMath.Core.Transformations.Transformation.Substitution(AngouriMath.Entity, AngouriMath.Entity) : AngouriMath.Core.Transformations.Transformation
AngouriMath.Core.Transformations.Transformation.Then(AngouriMath.Core.Transformations.Transformation) : AngouriMath.Core.Transformations.Transformation
diff --git a/Sources/Tests/UnitTests/Core/Transformations/TransformationTest.cs b/Sources/Tests/UnitTests/Core/Transformations/TransformationTest.cs
index 192bc8147..758ed295b 100644
--- a/Sources/Tests/UnitTests/Core/Transformations/TransformationTest.cs
+++ b/Sources/Tests/UnitTests/Core/Transformations/TransformationTest.cs
@@ -546,6 +546,41 @@ public void RationalizationClearsASurdOutOfADenominator()
node => node is Entity.Divf(_, var denominator) && denominator.Nodes.Any(n => n is Entity.Powf));
}
+ [Fact]
+ public void SimplificationAtLevelWithACostModelConsultsThatModelRatherThanTheAmbientDefault()
+ {
+ var calls = 0;
+ var spy = new CostModel("spy", "counts how many candidates it was asked to rate", e =>
+ {
+ calls++;
+ return CostModel.Default.Cost(e);
+ });
+
+ Transformation.SimplificationAtLevel(2, spy).Apply(Parse("(x + 1) ^ 2"));
+
+ Assert.True(calls > 0);
+ }
+
+ [Fact]
+ public void SimplificationAtLevelWithACostModelDoesNotLeakTheOverrideAfterwards()
+ {
+ Assert.False(MathS.Settings.ComplexityCriteria.IsOverriden);
+
+ Transformation.SimplificationAtLevel(2, CostModel.FewestDivisions).Apply(Parse("(x + 1) ^ 2"));
+
+ Assert.False(MathS.Settings.ComplexityCriteria.IsOverriden);
+ }
+
+ [Fact]
+ public void SimplificationAtLevelWithTheDefaultCostModelMatchesTheOverloadWithout()
+ {
+ var withoutModel = Transformation.SimplificationAtLevel(2).Apply(Parse("sin(x) / tan(x) + a / (b / c)"));
+ var withDefaultModel = Transformation.SimplificationAtLevel(2, CostModel.Default)
+ .Apply(Parse("sin(x) / tan(x) + a / (b / c)"));
+
+ Assert.Equal(withoutModel.Output, withDefaultModel.Output);
+ }
+
#endregion
}
}