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 } }