diff --git a/Sources/AngouriMath/Core/Transformations/Matching/MatchedRules.cs b/Sources/AngouriMath/Core/Transformations/Matching/MatchedRules.cs index 378044c79..7a66a8338 100644 --- a/Sources/AngouriMath/Core/Transformations/Matching/MatchedRules.cs +++ b/Sources/AngouriMath/Core/Transformations/Matching/MatchedRules.cs @@ -2541,5 +2541,344 @@ is var (divided, remainder) Soundness.Sound, when: bound => Functions.Patterns.ReduceRadical( (Integer)bound["radicand"], (Rational)bound["power"]) is not null)); + + /// + /// , as data. + /// + /// + /// + /// The last set, and the one the exchange was always going to be judged on: a + /// hundred arms, more than any other, and the great majority of them one shape written + /// out in every orientation its operands can take. A commutative pattern says all of + /// them at once, so the hundred arms are 62 rules -- and none of that is a guess about + /// what the `switch` covers, because every orientation collapsed here is one the + /// `switch` writes out. Where it writes only one, the pattern is a node and stays one. + /// + /// + /// Order is load-bearing throughout, more than in any set so far: a * a becoming + /// a ^ 2 sits in the middle of the file and would swallow half of what is above it + /// if it were moved up. The rules are in the arms' order and the agreement test is what + /// says that is enough. + /// + /// + internal static MatchedRuleSet Common { get; } = new( + nameof(Common), + + new MatchedRule( + "a-numeric-factor-floats-out-of-a-product-of-functions", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("f")), MatchPattern.Any("g")), + bound => bound["f"] * bound["g"] * bound["c"], + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-product-of-two-quotients-is-one-quotient", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("b")), MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("d"))), + bound => bound["a"] * bound["c"] / (bound["b"] * bound["d"]), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "dividing-by-a-quotient-multiplies-by-its-reciprocal", + MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Node(MatchPattern.Any("b"), MatchPattern.Any("c"))), + bound => bound["a"] * bound["c"] / bound["b"], + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-quotient-times-a-thing-keeps-the-divisor-outermost", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("b")), MatchPattern.Any("c")), + bound => bound["a"] * bound["c"] / bound["b"], + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-numeric-quotient-of-a-numeric-multiple-collects-its-numbers", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("a")), MatchPattern.Any("d")), + bound => (Number)bound["c"] / (Number)bound["d"] * bound["a"], + Soundness.SoundUnderAssumptions), + new MatchedRule( + "dividing-twice-divides-by-the-product", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("b")), MatchPattern.Any("c")), + bound => bound["a"] / (bound["b"] * bound["c"]), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-thing-times-a-quotient-keeps-the-divisor-outermost", + MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Node(MatchPattern.Any("b"), MatchPattern.Any("c"))), + bound => bound["a"] * bound["b"] / bound["c"], + Soundness.SoundUnderAssumptions), + // Both orientations of the outer product are written out in the `switch`. + new MatchedRule( + "two-numeric-factors-around-a-function-collect", + MatchPattern.Commutative(MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("f")), MatchPattern.Any("d")), + bound => (Number)bound["c"] * (Number)bound["d"] * bound["f"], + Soundness.SoundUnderAssumptions), + new MatchedRule( + "two-numeric-multiples-of-functions-collect-their-numbers", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("f")), MatchPattern.Node(MatchPattern.Any("d"), MatchPattern.Any("g"))), + bound => bound["f"] * bound["g"] * ((Number)bound["c"] * (Number)bound["d"]), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "two-functions-in-a-sum-come-together", + MatchPattern.Commutative(MatchPattern.Node(MatchPattern.Any("f"), MatchPattern.Any("a")), MatchPattern.Any("g")), + bound => bound["f"] + bound["g"] + bound["a"], + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-variable-times-a-number-puts-the-number-first", + MatchPattern.Node(MatchPattern.Any("v"), MatchPattern.Any("c")), + bound => bound["c"] * bound["v"], + Soundness.Sound), + new MatchedRule( + "a-number-plus-a-variable-puts-the-variable-first", + MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("v")), + bound => bound["v"] + bound["c"], + Soundness.Sound), + new MatchedRule( + "a-function-times-a-number-puts-the-number-first", + MatchPattern.Node(MatchPattern.Any("f"), MatchPattern.Any("c")), + bound => bound["c"] * bound["f"], + Soundness.Sound), + new MatchedRule( + "a-number-plus-a-function-puts-the-function-first", + MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("f")), + bound => bound["f"] + bound["c"], + Soundness.Sound), + new MatchedRule( + "two-numeric-multiples-of-one-variable-add", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("v")), MatchPattern.Node(MatchPattern.Any("d"), MatchPattern.Any("v"))), + bound => ((Number)bound["c"] + (Number)bound["d"]) * bound["v"], + Soundness.Sound), + new MatchedRule( + "two-numeric-multiples-of-one-variable-subtract", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("v")), MatchPattern.Node(MatchPattern.Any("d"), MatchPattern.Any("v"))), + bound => ((Number)bound["c"] - (Number)bound["d"]) * bound["v"], + Soundness.Sound), + // All four orientations are written out in the `switch`, which is what makes one commutative pattern on each side of the sum exact rather than wider. + new MatchedRule( + "a-common-factor-of-two-added-products-comes-out", + MatchPattern.Node(MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("b")), MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("c"))), + bound => bound["a"] * (bound["b"] + bound["c"]), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-term-shared-with-a-product-added-to-it-comes-out", + MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("b"))), + bound => bound["a"] * (1 + bound["b"]), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-term-added-to-itself-doubles", + MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("a")), + bound => 2 * bound["a"], + Soundness.Sound), + new MatchedRule( + "a-common-factor-of-two-subtracted-products-comes-out", + MatchPattern.Node(MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("b")), MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("c"))), + bound => bound["a"] * (bound["b"] - bound["c"]), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-term-with-a-product-of-itself-taken-from-it-comes-out", + MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("b"))), + bound => bound["a"] * (1 - bound["b"]), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-term-taken-from-a-product-of-itself-comes-out", + MatchPattern.Node(MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("b")), MatchPattern.Any("a")), + bound => bound["a"] * (bound["b"] - 1), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-term-subtracted-from-itself-vanishes", + MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("a")), + bound => Integer.Zero, + Soundness.Sound), + new MatchedRule( + "a-factor-shared-by-a-quotient-and-a-product-added-comes-out", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("b")), MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("c"))), + bound => bound["a"] * (1 / bound["b"] + bound["c"]), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-factor-shared-by-a-product-and-a-quotient-added-comes-out", + MatchPattern.Node(MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("c")), MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("b"))), + bound => bound["a"] * (bound["c"] + 1 / bound["b"]), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-term-added-to-a-quotient-of-itself-comes-out", + MatchPattern.Commutative(MatchPattern.Any("a", one => one is not Integer(1)), MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("b"))), + bound => bound["a"] * (1 + 1 / bound["b"]), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-thing-times-itself-is-its-square", + MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("a")), + bound => new Powf(bound["a"], 2), + Soundness.Sound), + new MatchedRule( + "two-numeric-factors-around-a-variable-collect", + MatchPattern.Commutative(MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("v")), MatchPattern.Any("d")), + bound => (Number)bound["c"] * (Number)bound["d"] * bound["v"], + Soundness.Sound), + new MatchedRule( + "two-numeric-terms-around-a-variable-collect", + MatchPattern.Commutative(MatchPattern.Node(MatchPattern.Any("v"), MatchPattern.Any("c")), MatchPattern.Any("d")), + bound => bound["v"] + ((Number)bound["c"] + (Number)bound["d"]), + Soundness.Sound), + new MatchedRule( + "a-factor-repeated-across-a-product-squares", + MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("b"))), + bound => new Powf(bound["a"], 2) * bound["b"], + Soundness.Sound), + new MatchedRule( + "a-negated-term-in-a-sum-is-a-subtraction", + MatchPattern.Commutative(MatchPattern.Node(MatchPattern.Exact(Integer.Create(-1)), MatchPattern.Any("neg")), MatchPattern.Any("rest")), + bound => bound["rest"] - bound["neg"], + Soundness.Sound), + new MatchedRule( + "a-difference-times-a-sum-of-one-pair-is-a-difference-of-squares", + MatchPattern.Commutative(MatchPattern.Node(MatchPattern.Any("v"), MatchPattern.Any("a")), MatchPattern.Node(MatchPattern.Any("v"), MatchPattern.Any("a"))), + bound => new Powf(bound["v"], 2) - new Powf(bound["a"], 2), + Soundness.Sound), + new MatchedRule( + "a-quotient-of-a-thing-by-itself-is-one-unless-it-is-zero", + MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("a")), + bound => Integer.One.Provided(!bound["a"].EqualTo(Integer.Zero)), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-shared-factor-cancels-out-of-a-quotient", + MatchPattern.Node(MatchPattern.Commutative(MatchPattern.Any("keep"), MatchPattern.Any("c")), MatchPattern.Any("c")), + bound => bound["keep"].Provided(!bound["c"].EqualTo(Integer.Zero)), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-shared-factor-cancels-between-two-products", + MatchPattern.Node(MatchPattern.Commutative(MatchPattern.Any("num"), MatchPattern.Any("c")), MatchPattern.Commutative(MatchPattern.Any("c"), MatchPattern.Any("den"))), + bound => (bound["num"] / bound["den"]).Provided(!bound["c"].EqualTo(Integer.Zero)), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-difference-over-its-own-reverse-is-minus-one", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("b")), MatchPattern.Node(MatchPattern.Any("b"), MatchPattern.Any("a"))), + (node, bound) => new Providedf(-1, !node.DirectChildren[1].EqualTo(0)), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-sum-over-its-own-reverse-is-one", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("b")), MatchPattern.Node(MatchPattern.Any("b"), MatchPattern.Any("a"))), + (node, bound) => new Providedf(1, !node.DirectChildren[1].EqualTo(0)), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-number-over-a-numeric-multiple-splits", + MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Commutative(MatchPattern.Any("d"), MatchPattern.Any("a"))), + bound => (Number)bound["c"] / (Number)bound["d"] / bound["a"], + Soundness.SoundUnderAssumptions), + new MatchedRule( + "two-numbers-around-a-factor-collect", + MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Node(MatchPattern.Any("d"), MatchPattern.Any("a"))), + bound => (Number)bound["c"] * (Number)bound["d"] * bound["a"], + Soundness.Sound), + new MatchedRule( + "a-term-repeated-across-a-sum-doubles", + MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("b"))), + bound => 2 * bound["a"] + bound["b"], + Soundness.Sound), + new MatchedRule( + "a-term-taken-back-out-of-a-sum-it-is-in", + MatchPattern.Node(MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("b")), MatchPattern.Any("a")), + bound => bound["b"], + Soundness.Sound), + new MatchedRule( + "a-sum-containing-a-term-taken-from-that-term-leaves-the-rest-negated", + MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Any("b"))), + bound => -bound["b"], + Soundness.Sound), + new MatchedRule( + "a-term-added-to-a-difference-that-takes-it-away", + MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Node(MatchPattern.Any("b"), MatchPattern.Any("a"))), + bound => bound["b"], + Soundness.Sound), + new MatchedRule( + "a-term-added-to-a-difference-that-starts-from-it", + MatchPattern.Commutative(MatchPattern.Any("a"), MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("b"))), + bound => 2 * bound["a"] - bound["b"], + Soundness.Sound), + new MatchedRule( + "a-difference-that-takes-a-term-away-subtracted-from-it", + MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Node(MatchPattern.Any("b"), MatchPattern.Any("a"))), + bound => 2 * bound["a"] - bound["b"], + Soundness.Sound), + new MatchedRule( + "a-difference-that-starts-from-a-term-subtracted-from-it", + MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("b"))), + bound => bound["b"], + Soundness.Sound), + new MatchedRule( + "a-term-taken-from-a-difference-that-already-took-it", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("b"), MatchPattern.Any("a")), MatchPattern.Any("a")), + bound => bound["b"] - 2 * bound["a"], + Soundness.Sound), + new MatchedRule( + "a-term-taken-from-a-difference-that-starts-from-it", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("b")), MatchPattern.Any("a")), + bound => -bound["b"], + Soundness.Sound), + new MatchedRule( + "a-product-of-two-absolute-values-is-the-absolute-value-of-the-product", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("a")), MatchPattern.Node(MatchPattern.Any("b"))), + bound => new Absf(bound["a"] * bound["b"]), + Soundness.Sound), + new MatchedRule( + "a-quotient-of-two-absolute-values-is-the-absolute-value-of-the-quotient", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("a")), MatchPattern.Node(MatchPattern.Any("b"))), + bound => new Absf(bound["a"] / bound["b"]), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-sign-times-a-thing-over-its-own-absolute-value-cancels", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("a")), MatchPattern.Node(MatchPattern.Any("b"), MatchPattern.Any("a"))), MatchPattern.Node(MatchPattern.Any("a"))), + bound => bound["b"].Provided(!bound["a"].EqualTo(Integer.Zero)), + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-sign-times-an-absolute-value-of-one-thing-is-that-thing", + MatchPattern.Commutative(MatchPattern.Node(MatchPattern.Any("a")), MatchPattern.Node(MatchPattern.Any("a"))), + bound => bound["a"], + Soundness.SoundUnderAssumptions), + new MatchedRule( + "a-reciprocal-rational-factor-is-a-division", + MatchPattern.Commutative(MatchPattern.Any("r", one => Functions.Patterns.IsWholeReciprocal(one, 1)), MatchPattern.Any("a")), + bound => bound["a"] / Functions.Patterns.DenominatorOf(bound["r"]), + Soundness.Sound), + new MatchedRule( + "a-negated-reciprocal-rational-factor-is-a-negated-division", + MatchPattern.Commutative(MatchPattern.Any("r", one => Functions.Patterns.IsWholeReciprocal(one, -1)), MatchPattern.Any("a")), + bound => -(bound["a"] / Functions.Patterns.DenominatorOf(bound["r"])), + Soundness.Sound), + // Parity, over the whole complex plane. The poles of the odd ones sit symmetrically + // about zero -- tan(-z) is undefined exactly where tan(z) is -- so the domain neither + // widens nor narrows and no condition is owed. + // https://github.com/asc-community/AngouriMath/issues/929 + new MatchedRule( + "an-even-function-of-a-negative-multiple-drops-the-sign-cos", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("neg", real => real.IsNegative), MatchPattern.Any("rest"))), + bound => new Cosf((-(Real)bound["neg"]) * bound["rest"]), + Soundness.Sound), + new MatchedRule( + "an-even-function-of-a-negative-multiple-drops-the-sign-secant", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("neg", real => real.IsNegative), MatchPattern.Any("rest"))), + bound => new Secantf((-(Real)bound["neg"]) * bound["rest"]), + Soundness.Sound), + new MatchedRule( + "an-even-function-of-a-negative-multiple-drops-the-sign-abs", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("neg", real => real.IsNegative), MatchPattern.Any("rest"))), + bound => new Absf((-(Real)bound["neg"]) * bound["rest"]), + Soundness.Sound), + new MatchedRule( + "an-odd-function-of-a-negative-multiple-negates-sin", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("neg", real => real.IsNegative), MatchPattern.Any("rest"))), + bound => -new Sinf((-(Real)bound["neg"]) * bound["rest"]), + Soundness.Sound), + new MatchedRule( + "an-odd-function-of-a-negative-multiple-negates-tan", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("neg", real => real.IsNegative), MatchPattern.Any("rest"))), + bound => -new Tanf((-(Real)bound["neg"]) * bound["rest"]), + Soundness.Sound), + new MatchedRule( + "an-odd-function-of-a-negative-multiple-negates-cotan", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("neg", real => real.IsNegative), MatchPattern.Any("rest"))), + bound => -new Cotanf((-(Real)bound["neg"]) * bound["rest"]), + Soundness.Sound), + new MatchedRule( + "an-odd-function-of-a-negative-multiple-negates-cosecant", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("neg", real => real.IsNegative), MatchPattern.Any("rest"))), + bound => -new Cosecantf((-(Real)bound["neg"]) * bound["rest"]), + Soundness.Sound), + new MatchedRule( + "an-odd-function-of-a-negative-multiple-negates-signum", + MatchPattern.Node(MatchPattern.Node(MatchPattern.Any("neg", real => real.IsNegative), MatchPattern.Any("rest"))), + bound => -new Signumf((-(Real)bound["neg"]) * bound["rest"]), + Soundness.Sound)); } } diff --git a/Sources/AngouriMath/Core/Transformations/RewriteRules.cs b/Sources/AngouriMath/Core/Transformations/RewriteRules.cs index 5fc36b844..4d7b64afe 100644 --- a/Sources/AngouriMath/Core/Transformations/RewriteRules.cs +++ b/Sources/AngouriMath/Core/Transformations/RewriteRules.cs @@ -123,7 +123,7 @@ public static class RewriteRules "Collects like terms and normalises the arrangement of products and quotients.", TransformationRelation.Equivalence, Soundness.SoundUnderAssumptions, - Patterns.CommonRules, + Matching.MatchedRules.Common.ApplyHere, Patterns.CommonRulesArms); /// diff --git a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Common.cs b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Common.cs index 720fb55f3..6ed8396f5 100644 --- a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Common.cs +++ b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Common.cs @@ -63,6 +63,22 @@ internal static partial class Patterns _ => x }; + /// + /// Whether this is 1/n or -1/n for a whole n other than 1 — the + /// coefficient the two "a rational factor is a division" rules key on. + /// + /// + /// Written once and asked by both the switch and MatchedRules.Common, so the + /// two forms cannot come to read Rational(var one, var den) differently. An + /// deconstructs with a denominator of 1 and is excluded by it. + /// + internal static bool IsWholeReciprocal(Entity entity, int numerator) + => entity is Rational(var num, var den) && num == numerator && den != 1; + + /// The denominator of a rational, for the rules admits. + internal static Integer DenominatorOf(Entity entity) + => entity is Rational(_, var den) ? den : Integer.One; + [AddressableRules] internal static Entity CommonRules(Entity x) => x switch { @@ -231,11 +247,11 @@ internal static partial class Patterns Mulf(Signumf(var any1), Absf(var any1a)) when any1 == any1a => any1, Mulf(Absf(var any1a), Signumf(var any1)) when any1 == any1a => any1, - Mulf(Rational(var one, var den), var any1) when one == 1 && den != 1 => any1 / den, - Mulf(var any1, Rational(var one, var den)) when one == 1 && den != 1 => any1 / den, + Mulf(var ratio, var any1) when IsWholeReciprocal(ratio, 1) => any1 / DenominatorOf(ratio), + Mulf(var any1, var ratio) when IsWholeReciprocal(ratio, 1) => any1 / DenominatorOf(ratio), - Mulf(Rational(var mOne, var den), var any1) when mOne == -1 && den != 1 => -(any1 / den), - Mulf(var any1, Rational(var mOne, var den)) when mOne == -1 && den != 1 => -(any1 / den), + Mulf(var ratio, var any1) when IsWholeReciprocal(ratio, -1) => -(any1 / DenominatorOf(ratio)), + Mulf(var any1, var ratio) when IsWholeReciprocal(ratio, -1) => -(any1 / DenominatorOf(ratio)), // Parity. Each of these holds on the whole complex plane, and the poles of the odd // ones sit symmetrically about zero -- tan(-z) is undefined exactly where tan(z) diff --git a/Sources/Tests/UnitTests/Core/Transformations/MatchedRulesAgreeWithTheSwitchTest.cs b/Sources/Tests/UnitTests/Core/Transformations/MatchedRulesAgreeWithTheSwitchTest.cs index 194866384..e28d3814e 100644 --- a/Sources/Tests/UnitTests/Core/Transformations/MatchedRulesAgreeWithTheSwitchTest.cs +++ b/Sources/Tests/UnitTests/Core/Transformations/MatchedRulesAgreeWithTheSwitchTest.cs @@ -530,6 +530,41 @@ public void PowerAsDataMatchesTheSwitch() "8 ^ (1/2)", "54 ^ (1/3)", "12 ^ (1/2)", "7 ^ (1/2)", "8 ^ (3/2)", }); + /// + /// The last set, and the largest: a hundred arms. Most of them are one shape + /// written out in every orientation its operands can take, which is what a `switch` has + /// to do and a commutative pattern does not — so the hundred are sixty-two rules. Every + /// orientation collapsed here is one the `switch` writes out, and this is what says so: + /// `firesWhereTheSwitchDoesNot` is empty, so a pattern that reached one shape more than + /// its arms did would fail rather than pass quietly. + /// + [Fact] + public void CommonAsDataMatchesTheSwitch() + => AssertAgrees("Common", Patterns.CommonRules, + MatchedRules.Common, leastFirings: 400, extra: new[] + { + "2 * sin(x) * cos(x)", "(2 * sin(x)) * 3", "3 * (2 * sin(x))", + "(2 * sin(x)) * (3 * cos(x))", "(sin(x) + y) + cos(x)", "cos(x) + (sin(x) + y)", + "sin(x) * 2", "2 + sin(x)", "x * 2", "2 + x", + "2 * x + 3 * x", "2 * x - 3 * x", "(2 * x) * 3", "3 * (2 * x)", + "(x + 2) + 3", "3 + (x + 2)", + "abs(x) * abs(y)", "abs(x) / abs(y)", + "sgn(x) * (y * x) / abs(x)", "sgn(x) * abs(x)", "abs(x) * sgn(x)", + "x / 2", "(1/2) * x", "x * (1/2)", "(-1/2) * x", "x * (-1/2)", + "cos(-x)", "cos(-2 * x)", "sec(-x)", "abs(-x)", "sin(-x)", "sin(-2 * x)", + "tan(-x)", "cotan(-x)", "cosec(-x)", "sgn(-x)", + "(x - y) * (x + y)", "(x + y) * (x - y)", + "x * y / y", "y * x / y", "x * y / (y * z)", "x * y / (z * y)", + "(x - y) / (y - x)", "(x + y) / (y + x)", + "2 / (3 * x)", "2 / (x * 3)", "2 * (3 * x)", + "x + (y + x)", "x + (x + y)", "(y + x) + x", "(x + y) + x", + "(x + y) - x", "(y + x) - x", "x - (y + x)", "x - (x + y)", + "x + (y - x)", "x + (x - y)", "(y - x) + x", "(x - y) + x", + "x - (y - x)", "x - (x - y)", "(y - x) - x", "(x - y) - x", + "x / y + x * z", "x / y + z * x", "z * x + x / y", "x * z + x / y", + "x + x / y", "x / y + x", "2 + 2 / y", + }); + /// /// A predicate on a hole that is a mathematical property rather than a sign or a type. /// The corpus reaches it rarely, so the shapes are given here as well — a set that fires diff --git a/Sources/Tests/UnitTests/Core/Transformations/ReversibleRuleTest.cs b/Sources/Tests/UnitTests/Core/Transformations/ReversibleRuleTest.cs index 7160153eb..f9e9e3bd0 100644 --- a/Sources/Tests/UnitTests/Core/Transformations/ReversibleRuleTest.cs +++ b/Sources/Tests/UnitTests/Core/Transformations/ReversibleRuleTest.cs @@ -144,6 +144,8 @@ public void EveryDataRuleIsClassifiedAsWritten() "a-chain-of-greaters-implies-its-own-ends: ReplacementIsCode", "a-chain-of-lesss-implies-its-own-ends: ReplacementIsCode", "a-common-factor-is-collected-out-of-a-whole-sum: ReplacementIsCode", + "a-common-factor-of-two-added-products-comes-out: ReplacementIsCode", + "a-common-factor-of-two-subtracted-products-comes-out: ReplacementIsCode", "a-conditional-set-whose-condition-is-its-own-membership-is-that-set: ReplacementIsCode", "a-conjunction-absorbs-a-disjunction-it-shares-an-operand-with: ReplacementIsCode", "a-conjunction-chain-is-sorted-and-grouped: ReplacementIsCode", @@ -158,6 +160,10 @@ public void EveryDataRuleIsClassifiedAsWritten() "a-difference-chain-is-sorted-and-grouped: ReplacementIsCode", "a-difference-of-even-powers-splits: ReplacementIsCode", "a-difference-of-two-negatives-turns-round: ReplacementIsCode", + "a-difference-over-its-own-reverse-is-minus-one: ReplacementIsCode", + "a-difference-that-starts-from-a-term-subtracted-from-it: ReplacementIsCode", + "a-difference-that-takes-a-term-away-subtracted-from-it: ReplacementIsCode", + "a-difference-times-a-sum-of-one-pair-is-a-difference-of-squares: ReplacementIsCode", "a-disjunction-absorbs-a-conjunction-it-shares-an-operand-with: ReplacementIsCode", "a-disjunction-chain-is-sorted-and-grouped: ReplacementIsCode", "a-disjunction-drops-a-negated-copy-of-its-other-operand: ReplacementIsCode", @@ -169,10 +175,14 @@ public void EveryDataRuleIsClassifiedAsWritten() "a-doubled-sine-times-a-cosecant-is-twice-the-cosine: ReplacementIsCode", "a-equals-with-a-number-on-the-left-turns-round: ReplacementIsCode", "a-equals-with-zero-on-the-left-turns-round: ReplacementIsCode", + "a-factor-repeated-across-a-product-squares: ReplacementIsCode", + "a-factor-shared-by-a-product-and-a-quotient-added-comes-out: ReplacementIsCode", + "a-factor-shared-by-a-quotient-and-a-product-added-comes-out: ReplacementIsCode", "a-factor-shared-by-two-added-products-comes-out: ReplacementIsCode", "a-factor-shared-by-two-subtracted-products-comes-out: ReplacementIsCode", "a-factor-subtracted-from-a-product-it-is-in: ReplacementIsCode", "a-factorial-is-never-zero: ReplacementIsCode", + "a-function-times-a-number-puts-the-number-first: ReplacementIsCode", "a-greater-of-a-thing-with-itself-is-decided: ReplacementIsCode", "a-greater-than-or-equal-as-written-is-at-least: ReplacementIsCode", "a-greater-than-or-equal-the-other-way-round-is-at-most: ReplacementIsCode", @@ -195,6 +205,8 @@ public void EveryDataRuleIsClassifiedAsWritten() "a-logarithm-of-its-own-base-is-one-where-it-is-defined: ReplacementIsCode", "a-negated-conjunction-becomes-a-disjunction-of-negations: ReplacementIsCode", "a-negated-disjunction-becomes-a-conjunction-of-negations: ReplacementIsCode", + "a-negated-reciprocal-rational-factor-is-a-negated-division: ReplacementIsCode", + "a-negated-term-in-a-sum-is-a-subtraction: ReplacementIsCode", "a-negation-or-something-is-an-implication: ReplacementIsCode", "a-negative-added-is-subtracted: ReplacementIsCode", "a-negative-divisor-drops-out-of-a-equals-with-zero: ReplacementIsCode", @@ -220,9 +232,14 @@ public void EveryDataRuleIsClassifiedAsWritten() "a-negative-integer-power-becomes-a-reciprocal: ReplacementIsCode", "a-negative-minuend-comes-out-in-front: ReplacementIsCode", "a-negative-subtracted-is-added: ReplacementIsCode", + "a-number-over-a-numeric-multiple-splits: ReplacementIsCode", + "a-number-plus-a-function-puts-the-function-first: ReplacementIsCode", + "a-number-plus-a-variable-puts-the-variable-first: ReplacementIsCode", "a-number-raised-to-a-logarithm-of-itself-is-the-antilogarithm: ReplacementIsCode", "a-numeric-coefficient-is-gathered-over-a-surd: ReplacementIsCode", "a-numeric-factor-comes-out-of-a-power-of-a-product: ReplacementIsCode", + "a-numeric-factor-floats-out-of-a-product-of-functions: ReplacementIsCode", + "a-numeric-quotient-of-a-numeric-multiple-collects-its-numbers: ReplacementIsCode", "a-plain-factorial-times-the-next-term: ReplacementIsCode", "a-positive-divisor-drops-out-of-a-equals-with-zero: ReplacementIsCode", "a-positive-divisor-drops-out-of-a-greater-with-zero: ReplacementIsCode", @@ -248,7 +265,9 @@ public void EveryDataRuleIsClassifiedAsWritten() "a-power-whose-exponent-divides-by-a-logarithm-of-its-own-base-changes-base: ReplacementIsCode", "a-power-with-a-real-positive-exponent-is-zero-when-its-base-is: ReplacementIsCode", "a-product-chain-is-sorted-and-grouped: ReplacementIsCode", + "a-product-of-two-absolute-values-is-the-absolute-value-of-the-product: ReplacementIsCode", "a-product-of-two-negatives-is-positive: ReplacementIsCode", + "a-product-of-two-quotients-is-one-quotient: ReplacementIsCode", "a-product-subtracted-from-its-own-factor: ReplacementIsCode", "a-quotient-by-a-cosecant-is-a-sine: ReplacementIsCode", "a-quotient-by-a-secant-is-a-cosine: ReplacementIsCode", @@ -262,13 +281,20 @@ public void EveryDataRuleIsClassifiedAsWritten() "a-quotient-of-powers-whose-exponents-differ-by-a-whole-factor-takes-it-into-the-divisor: ReplacementIsCode", "a-quotient-of-shifted-factorials: ReplacementIsCode", "a-quotient-of-symbolic-parts-is-grouped-pairwise: ReplacementIsCode", + "a-quotient-of-two-absolute-values-is-the-absolute-value-of-the-quotient: ReplacementIsCode", "a-quotient-of-two-negatives-is-positive: ReplacementIsCode", + "a-quotient-times-a-thing-keeps-the-divisor-outermost: ReplacementIsCode", "a-reciprocal-is-never-zero: ReplacementIsCode", "a-reciprocal-power-is-a-quotient: ReplacementIsCode", + "a-reciprocal-rational-factor-is-a-division: ReplacementIsCode", "a-secant-times-a-cosine-of-one-angle-is-one: ReplacementIsCode", "a-set-less-itself-is-empty: ReplacementIsCode", + "a-shared-factor-cancels-between-two-products: ReplacementIsCode", + "a-shared-factor-cancels-out-of-a-quotient: ReplacementIsCode", "a-shifted-factorial-times-a-bare-term: ReplacementIsCode", "a-shifted-factorial-times-the-next-term: ReplacementIsCode", + "a-sign-times-a-thing-over-its-own-absolute-value-cancels: ReplacementIsCode", + "a-sign-times-an-absolute-value-of-one-thing-is-that-thing: ReplacementIsCode", "a-sine-of-an-arcsine: PatternCannotBeBuilt", "a-sine-times-a-cosine-of-one-angle-is-half-the-doubled-sine: ReplacementIsCode", "a-square-less-a-number-splits: ReplacementIsCode", @@ -281,17 +307,31 @@ public void EveryDataRuleIsClassifiedAsWritten() "a-statement-implies-itself: ReplacementIsCode", "a-statement-or-its-negation-is-true-where-it-has-a-truth-value: ReplacementIsCode", "a-sum-chain-is-sorted-and-grouped: ReplacementIsCode", + "a-sum-containing-a-term-taken-from-that-term-leaves-the-rest-negated: ReplacementIsCode", "a-sum-of-two-negatives-is-a-negated-sum: ReplacementIsCode", "a-sum-or-difference-that-is-a-perfect-square: ReplacementIsCode", + "a-sum-over-its-own-reverse-is-one: ReplacementIsCode", "a-tangent-of-an-arctangent: PatternCannotBeBuilt", "a-tangent-times-a-cotangent-of-one-angle-is-one: ReplacementIsCode", + "a-term-added-to-a-difference-that-starts-from-it: ReplacementIsCode", + "a-term-added-to-a-difference-that-takes-it-away: ReplacementIsCode", + "a-term-added-to-a-quotient-of-itself-comes-out: ReplacementIsCode", "a-term-added-to-itself-doubles: ReplacementIsCode", + "a-term-repeated-across-a-sum-doubles: ReplacementIsCode", "a-term-shared-with-a-product-added-to-it-comes-out: ReplacementIsCode", "a-term-subtracted-from-itself-vanishes: ReplacementIsCode", + "a-term-taken-back-out-of-a-sum-it-is-in: ReplacementIsCode", + "a-term-taken-from-a-difference-that-already-took-it: ReplacementIsCode", + "a-term-taken-from-a-difference-that-starts-from-it: ReplacementIsCode", + "a-term-taken-from-a-product-of-itself-comes-out: ReplacementIsCode", + "a-term-with-a-product-of-itself-taken-from-it-comes-out: ReplacementIsCode", "a-thing-over-a-power-of-itself-is-one-power: ReplacementIsCode", + "a-thing-times-a-quotient-keeps-the-divisor-outermost: ReplacementIsCode", + "a-thing-times-itself-is-its-square: ReplacementIsCode", "a-two-term-denominator-is-multiplied-by-its-conjugate: ReplacementIsCode", "a-union-chain-is-sorted-and-grouped: ReplacementIsCode", "a-union-with-itself-is-itself: PatternCannotBeBuilt", + "a-variable-times-a-number-puts-the-number-first: ReplacementIsCode", "a-variable-times-a-power-puts-the-power-first: ReplacementIsCode", "a-whole-power-comes-out-from-under-a-radical: ReplacementIsCode", "an-arccosecant-of-a-numeric-reciprocal-is-an-arcsine: ReplacementIsCode", @@ -302,6 +342,9 @@ public void EveryDataRuleIsClassifiedAsWritten() "an-equality-or-a-greater-than-the-other-way-round-is-at-most: ReplacementIsCode", "an-equality-or-a-less-than-as-written-is-at-most: ReplacementIsCode", "an-equality-or-a-less-than-the-other-way-round-is-at-least: ReplacementIsCode", + "an-even-function-of-a-negative-multiple-drops-the-sign-abs: ReplacementIsCode", + "an-even-function-of-a-negative-multiple-drops-the-sign-cos: ReplacementIsCode", + "an-even-function-of-a-negative-multiple-drops-the-sign-secant: ReplacementIsCode", "an-exclusive-disjunction-chain-is-sorted-and-grouped: ReplacementIsCode", "an-exponent-comes-out-of-a-logarithm: ReplacementIsCode", "an-implication-between-negations-turns-round: ReplacementIsCode", @@ -309,6 +352,11 @@ public void EveryDataRuleIsClassifiedAsWritten() "an-intersection-distributes-over-a-union-on-its-left: ReplacementIsCode", "an-intersection-distributes-over-a-union-on-its-right: ReplacementIsCode", "an-intersection-with-itself-is-itself: PatternCannotBeBuilt", + "an-odd-function-of-a-negative-multiple-negates-cosecant: ReplacementIsCode", + "an-odd-function-of-a-negative-multiple-negates-cotan: ReplacementIsCode", + "an-odd-function-of-a-negative-multiple-negates-signum: ReplacementIsCode", + "an-odd-function-of-a-negative-multiple-negates-sin: ReplacementIsCode", + "an-odd-function-of-a-negative-multiple-negates-tan: ReplacementIsCode", "an-unbounded-interval-is-a-whole-domain: ReplacementIsCode", "anything-follows-from-a-falsehood: ReplacementIsCode", "arccosine-of-a-cosine-inside-its-own-interval: ReplacementIsCode", @@ -319,9 +367,11 @@ public void EveryDataRuleIsClassifiedAsWritten() "arctangent-plus-arccotangent-is-a-right-angle-with-the-sign-of-the-argument: ReplacementIsCode", "cosine-of-a-whole-multiple-of-an-angle: ReplacementIsCode", "dividing-by-a-power-and-then-by-its-base-raises-the-exponent: ReplacementIsCode", + "dividing-by-a-quotient-multiplies-by-its-reciprocal: ReplacementIsCode", "dividing-by-a-thing-and-then-by-a-power-of-it-raises-the-exponent: ReplacementIsCode", "dividing-by-two-powers-of-one-base-adds-the-exponents: ReplacementIsCode", "dividing-twice-by-one-thing-squares-it: ReplacementIsCode", + "dividing-twice-divides-by-the-product: ReplacementIsCode", "eulers-totient-of-a-prime-power: ReplacementIsCode", "membership-of-a-singleton-is-an-equality: ReplacementIsCode", "membership-of-an-interval-is-written-out: ReplacementIsCode", @@ -343,8 +393,16 @@ public void EveryDataRuleIsClassifiedAsWritten() "two-arctangents-of-numbers-add-by-the-tangent-formula: ReplacementIsCode", "two-comparisons-of-one-pair-that-exclude-each-other-are-false: ReplacementIsCode", "two-comparisons-of-one-pair-that-leave-no-case-are-true: ReplacementIsCode", + "two-functions-in-a-sum-come-together: ReplacementIsCode", "two-logarithms-of-one-base-add-by-multiplying-their-antilogarithms: ReplacementIsCode", "two-logarithms-of-one-base-subtract-by-dividing-their-antilogarithms: ReplacementIsCode", + "two-numbers-around-a-factor-collect: ReplacementIsCode", + "two-numeric-factors-around-a-function-collect: ReplacementIsCode", + "two-numeric-factors-around-a-variable-collect: ReplacementIsCode", + "two-numeric-multiples-of-functions-collect-their-numbers: ReplacementIsCode", + "two-numeric-multiples-of-one-variable-add: ReplacementIsCode", + "two-numeric-multiples-of-one-variable-subtract: ReplacementIsCode", + "two-numeric-terms-around-a-variable-collect: ReplacementIsCode", "two-powers-of-one-base-divide-by-subtracting-exponents: ReplacementIsCode", "two-powers-of-one-base-multiply-by-adding-exponents: ReplacementIsCode", "two-powers-of-one-exponent-share-a-base: ReplacementIsCode",