Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,38 @@ public static Transformation Rewriting(RewriteRuleSet ruleSet)
/// How hard to look; the same argument <see cref="Entity.Simplify(int)"/> takes.
/// </param>
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));

/// <summary>
/// The full simplification pipeline at a chosen level, rating candidates by
/// <paramref name="costModel"/> instead of <see cref="MathS.Settings.ComplexityCriteria"/>.
/// </summary>
/// <param name="level">
/// How hard to look; the same argument <see cref="Entity.Simplify(int)"/> takes.
/// </param>
/// <param name="costModel">
/// Which candidate counts as cheapest. <see cref="Core.CostModel.Default"/> here behaves
/// exactly like <see cref="SimplificationAtLevel(int)"/> — passing it is how a caller
/// states that on purpose rather than by leaving a parameter out.
/// </param>
/// <remarks>
/// <a href="https://github.com/asc-community/AngouriMath/issues/746">#746</a> tier 2 named
/// this remaining on its own row: "a cost model that reaches an API rather than an ambient
/// setting". <see cref="MathS.Settings.ComplexityCriteria"/> already <i>is</i> an API in
/// the sense that a caller can scope it explicitly and safely — it is backed by the same
/// <see cref="Convenience.Setting{T}"/> <see cref="MathS.Settings.Budget"/> 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 <em>this</em> API, the addressable one <see cref="Transformation"/>
/// is: composing <c>SimplificationAtLevel(2, costModel).Then(...)</c> 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
/// <c>Simplificator</c> -- that would be a second pipeline to keep in step with the one
/// <see cref="Entity.Simplify(int)"/> actually runs. It scopes the existing, already-tested
/// setting for the duration of this one call instead of introducing a second pipeline.
/// </remarks>
public static Transformation SimplificationAtLevel(int level, CostModel costModel)
=> new SimplificationTransformation(
level, costModel ?? throw new ArgumentNullException(nameof(costModel)));

/// <summary>
/// Multiplies products over sums out, as <see cref="Entity.Expand(int)"/> does.
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Sources/Tests/UnitTests/Common/PublicApi.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions Sources/Tests/UnitTests/Core/Transformations/TransformationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Loading