From 7c40302bc1ee2956c8beca7264519b34a957fef1 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 20 Sep 2026 22:46:11 +0000 Subject: [PATCH] A constant raised to a multiple of a logarithm of itself is a power of the antilogarithm c ^ (k * log(c, b)) is b ^ k: c ^ z is exp(z ln c) and log(c, b) is ln(b) / ln(c) on the principal branch, so the two ln(c) cancel and exp(k ln b) is left, which is what b ^ k means for every complex b other than 0. The k = 1 case folded since #1428 and this shape did not, and it is the shape the integrator's exponential substitution writes its antiderivatives in: e ^ (-3/2 * ln(u)) stood in answers that were otherwise algebraic. Three arms by what can be said of the base, as for the k = 1 rules, in both spellings: a number other than 0 and 1, a constant, and a symbol under the logarithm's own domain condition. The multiplier may sit on either side of the logarithm. The constant arm is written with a pattern on the right so it is data in both directions. The guide's counts and the pinned lists move: 332 rules, 303 names, 329 identities, 146 conditional, 126 Collects, 34 Expands, 30 subsumptions (constant before symbol), 963 subsumption claims, 36 two-sided rules of which 32 have a direction, and the longest name is nineteen words. BREAKING-CHANGES.md rows measured on a v2.5.0 build. https://github.com/asc-community/AngouriMath/issues/994 https://github.com/asc-community/AngouriMath/issues/718 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 2 + Sources/.editorconfig | 3 + .../Transformations/Matching/MatchedRules.cs | 53 +++++++++++++ .../Docs/Contributing/WritingARule.md | 21 +++--- .../Simplification/Patterns/Patterns.Power.cs | 13 ++++ .../Common/PowerOfALogarithmMultipleTest.cs | 74 +++++++++++++++++++ .../Transformations/AddressableRulesTest.cs | 2 +- .../Transformations/ReversibleRuleTest.cs | 3 + .../Transformations/RuleAuthoringGuideTest.cs | 19 ++--- .../Core/Transformations/RuleMetadataTest.cs | 6 +- .../Core/Transformations/RulePriorityTest.cs | 5 +- .../Transformations/StepAsASentenceTest.cs | 2 +- 12 files changed, 177 insertions(+), 26 deletions(-) create mode 100644 Sources/Tests/UnitTests/Common/PowerOfALogarithmMultipleTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dbe52f0c1..f13d7d318 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -679,6 +679,8 @@ they had. | | `"e ^ ln(x)".ToEntity().Simplify()`, and every exponential of a natural logarithm | `e ^ ln(x)` — left as written | `x` | | | `"pi ^ log(pi, x)".ToEntity().Simplify()`, and every constant raised to a logarithm of itself | `pi ^ log(pi, x)` — left as written | `x` | | | `"a ^ log(a, x)".ToEntity().Simplify()`, and every symbolic base raised to a logarithm of itself | `a ^ log(a, x)` — left as written | `x provided not a = 0 and not a = 1` — the logarithm's own domain condition, which the rewrite would otherwise lose ([#994](https://github.com/asc-community/AngouriMath/issues/994)) | +| | `"e ^ (2 * ln(x))".ToEntity().Simplify()`, and every number or constant raised to a multiple of a logarithm of itself | `e ^ (2 * ln(x))` — left as written | `x ^ 2`; `e ^ (-3/2 * ln(u))` is `u ^ (-3/2)`, `2 ^ (k * log(2, x))` is `x ^ k`, `pi ^ (k * log(pi, x))` is `x ^ k` | +| | `"a ^ (k * log(a, x))".ToEntity().Simplify()`, and every symbolic base raised to a multiple of a logarithm of itself | `a ^ (log(a, x) * k)` — left as written | `x ^ k provided not a = 0 and not a = 1` ([#994](https://github.com/asc-community/AngouriMath/issues/994)) | | **Silent** | `MathS.Ln("x").Substitute("e", 3)`, and `"exp(x)".ToEntity().Substitute("e", 3)` | `log(3, x)` and `3 ^ x` — the base of `ln` and `exp` was reached as a mention of `e` | `ln(x)` and `exp(x)` — it is Euler's number in the operator's definition, not a mention; `log(e, x)` and `e ^ x`, where the writer named `e`, still become `log(3, x)` and `3 ^ x` | | | `"a => a + 3".ToEntity()`, and every lambda written with an arrow | `UnhandledParseException` | `lambda(a, a + 3)` | | | `"sqrt(5 + 2 * sqrt(6))".ToEntity().Simplify()`, and every nested radical whose discriminant is a rational square | `sqrt(5 + 2 * sqrt(6))` — left as written | `sqrt(3) + sqrt(2)` | diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 622c70e03..7e1ba7822 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -270,6 +270,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Core/Transformations/LogarithmSpellingsTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Common/PowerOfALogarithmMultipleTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [AngouriMath/Functions/Compilation/IntoLinq/TensorCompilation.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Core/Transformations/Matching/MatchedRules.cs b/Sources/AngouriMath/Core/Transformations/Matching/MatchedRules.cs index d7c101134..efac99430 100644 --- a/Sources/AngouriMath/Core/Transformations/Matching/MatchedRules.cs +++ b/Sources/AngouriMath/Core/Transformations/Matching/MatchedRules.cs @@ -3029,6 +3029,59 @@ is var (divided, remainder) // for it: more than the power and the logarithm that go. growth: RewriteRuleGrowth.Expands), + // The same identity with a multiplier on the logarithm: c ^ (k log_c(b)) is b ^ k, + // since c ^ z is exp(z ln c) and log_c(b) is ln(b) / ln(c) on the principal branch, + // so the two ln(c) cancel and what is left is exp(k ln b), which is the definition + // of b ^ k. Exact for every complex b other than 0, any k, and a base that is + // decidably neither 0 nor 1 -- a number or a constant; a symbolic base is left to + // the rule below, which attaches the condition. This is what e ^ (-3/2 ln(u)) was + // waiting for: the exponential substitution in the integrator writes its + // antiderivatives in that shape and nothing folded them. + // https://github.com/asc-community/AngouriMath/issues/994 + // https://github.com/asc-community/AngouriMath/issues/718 + new MatchedRule( + "a-number-raised-to-a-multiple-of-a-logarithm-of-itself-is-a-power-of-the-antilogarithm", + MatchPattern.Node( + MatchPattern.Any("c", static c => c != Integer.Zero && c != Integer.One), + MatchPattern.Commutative( + MatchPattern.Any("k"), + MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("a")))), + bound => new Powf(bound["a"], bound["k"]), + Soundness.SoundUnderAssumptions, + description: "a ^ (k * log(a, b)) = b ^ k", + // The base, matched twice, and the product and the logarithm go; the power is + // rebuilt over the two holes: -(2|c| + 2). + growth: RewriteRuleGrowth.Collects), + + new MatchedRule( + "a-constant-raised-to-a-multiple-of-a-logarithm-of-itself-is-a-power-of-the-antilogarithm", + MatchPattern.Node( + MatchPattern.Any("c"), + MatchPattern.Commutative( + MatchPattern.Any("k"), + MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("a")))), + // A pattern on the right, as for its k = 1 sibling above, so the rule is data in + // both directions; like that sibling it is one-way all the same, because the + // answer forgets which constant the base was. + MatchPattern.Node(MatchPattern.Any("a"), MatchPattern.Any("k")), + Soundness.SoundUnderAssumptions, + description: "e ^ (k * ln(b)) = b ^ k"), + + new MatchedRule( + "a-symbol-raised-to-a-multiple-of-a-logarithm-of-itself-is-a-power-of-the-antilogarithm-where-defined", + MatchPattern.Node( + MatchPattern.Any("c"), + MatchPattern.Commutative( + MatchPattern.Any("k"), + MatchPattern.Node(MatchPattern.Any("c"), MatchPattern.Any("a")))), + bound => new Powf(bound["a"], bound["k"]).Provided(!bound["c"].EqualTo(0) & !bound["c"].EqualTo(1)), + Soundness.SoundUnderAssumptions, + when: bound => bound["c"] is not Number and not Constant, + description: "a ^ (k * log(a, b)) = b ^ k provided not a = 0 and not a = 1", + // The condition writes the base twice more, beside six new nodes: more than the + // product and the logarithm that go. + growth: RewriteRuleGrowth.Expands), + // Four `switch` arms: the power on either side of a product, and the shared base in // either position inside it. A commutative pattern says both halves at once. new MatchedRule( diff --git a/Sources/AngouriMath/Docs/Contributing/WritingARule.md b/Sources/AngouriMath/Docs/Contributing/WritingARule.md index 67cdd97a6..2e3f61703 100644 --- a/Sources/AngouriMath/Docs/Contributing/WritingARule.md +++ b/Sources/AngouriMath/Docs/Contributing/WritingARule.md @@ -18,7 +18,7 @@ effect". This is how to supply those seven things. ## Where a rule goes `Core/Transformations/Matching/MatchedRules.cs`, as a value in a `MatchedRuleSet`. **33** sets and -**329** rules live there today. +**332** rules live there today. The `switch` statements in `Functions/Simplification/Patterns` are the older form. **All thirty registered sets now run as data and describe what they run**; none executes its `switch` any more. @@ -64,7 +64,7 @@ in a name and putting the identity in brackets after it: 2. Tangent is sine over cosine (tan(a) = sin(a) / cos(a)), so tan(x) becomes sin(x) / cos(x). ``` -So the name has to be a clause that survives being read that way. **All 300 distinct rule names are, +So the name has to be a clause that survives being read that way. **All 303 distinct rule names are, and `StepAsASentenceTest` holds them to it** — a name with a capital, a bracket or an underscore fails that test rather than degrading the prose quietly. @@ -77,7 +77,7 @@ Write what the rule *says*, not what it operates on: | ✘ | `divf-simplification-2` — not a clause, and the 2 will be wrong when somebody inserts one | | ✘ | `DivideByItself` — reads as an identifier in the middle of a sentence | -The names run from four to sixteen words. Long is fine; a name is read once and a wrong rewrite is +The names run from four to nineteen words. Long is fine; a name is read once and a wrong rewrite is debugged for an afternoon. ## The identity is not the name @@ -91,7 +91,7 @@ debugged for an afternoon. | `Left.ToString()` | `Divf(var a, Divf(var b, var c))` — how the matcher spells it | Write the identity with `=`, not `->`: it is an equality, and the arrow belongs to the direction the -rule happens to be applied in. **326** rules carry one today; a new rule should. +rule happens to be applied in. **329** rules carry one today; a new rule should. ## The pattern language @@ -128,10 +128,11 @@ are legitimate — but the choice costs two things, so make it deliberately. A pattern replacement gets: -- **a direction.** `MatchedRule.Reversed` is the rule read the other way, and **32** of the 35 - two-sided rules have one. The other three forget something: `sin²+cos² = 1` forgets the angle, - `{ x : x in S } = S` forgets the name the set builder bound, and `c ^ log(c, a) = a` for a - constant `c` forgets which constant, so none has anything to read back. +- **a direction.** `MatchedRule.Reversed` is the rule read the other way, and **32** of the 36 + two-sided rules have one. The other four forget something: `sin²+cos² = 1` forgets the angle, + `{ x : x in S } = S` forgets the name the set builder bound, and `c ^ log(c, a) = a` and + `c ^ (k * log(c, a)) = a ^ k` for a constant `c` forget which constant, so none has anything to + read back. See [ReversibleRules.md](ReversibleRules.md). - **an exact growth**, counted from the two patterns rather than declared. @@ -225,7 +226,7 @@ limit, against nothing for handing the node over. | `SoundUnderAssumptions` | holds given something the rule does not check | | `Heuristic` | usually right | -**186** of the 329 rules are `Sound` and **143** are conditional. Every one of the thirty registered +**186** of the 332 rules are `Sound` and **146** are conditional. Every one of the thirty registered *sets* declares `SoundUnderAssumptions`, because a set's tier is the **minimum** over its rules — so the set grain says nothing and the rule grain says everything. A derivation reports the rule's tier (`RewriteStep.Soundness`), which is why getting it right matters beyond the label. @@ -242,7 +243,7 @@ first decides the answer**. Two cases, and only one of them is a decision: everything `Mulf(Divf(a, b), Divf(c, d))` matches and more, so the general rule would swallow the special one and the special one would never fire. `MatchPattern.Subsumes` computes this and `MatchedRuleSet.RulesByPriority` applies it: the specific rule is tried first because of what the two -patterns *are*, not because of where you typed them. **29** rule pairs are ordered that way. +patterns *are*, not because of where you typed them. **30** rule pairs are ordered that way. **Where neither subsumes the other, the order is a bare choice and nothing but your placement makes it.** There are **39** such conflicts, and `RulePriorityTest` records every one by name. If your rule diff --git a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Power.cs b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Power.cs index 402cd3697..5244a6e37 100644 --- a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Power.cs +++ b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.Power.cs @@ -159,6 +159,19 @@ expr is Powf(var @base, Integer { IsNegative: true } pow) when base1 == base1a && base1 is not Number and not Constant => any1.Provided(!base1.EqualTo(0) & !base1.EqualTo(1)), + // c ^ (k log(c, a)) = a ^ k, the same three ways, with the multiplier on either side + // of the logarithm. https://github.com/asc-community/AngouriMath/issues/994 + Powf(Number const1, Mulf(var k, Logf(Number const1a, var any1))) when const1 == const1a && const1 != Integer.Zero && const1 != Integer.One => new Powf(any1, k), + Powf(Number const1, Mulf(Logf(Number const1a, var any1), var k)) when const1 == const1a && const1 != Integer.Zero && const1 != Integer.One => new Powf(any1, k), + Powf(Constant base1, Mulf(var k, Logf(Constant base1a, var any1))) when base1 == base1a => new Powf(any1, k), + Powf(Constant base1, Mulf(Logf(Constant base1a, var any1), var k)) when base1 == base1a => new Powf(any1, k), + Powf(var base1, Mulf(var k, Logf(var base1a, var any1))) + when base1 == base1a && base1 is not Number and not Constant + => new Powf(any1, k).Provided(!base1.EqualTo(0) & !base1.EqualTo(1)), + Powf(var base1, Mulf(Logf(var base1a, var any1), var k)) + when base1 == base1a && base1 is not Number and not Constant + => new Powf(any1, k).Provided(!base1.EqualTo(0) & !base1.EqualTo(1)), + Mulf(Powf(var any1, var any3), Mulf(var any1a, var any2)) when any1 == any1a => new Powf(any1, any3 + 1) * any2, Mulf(Powf(var any1, var any3), Mulf(var any2, var any1a)) when any1 == any1a => new Powf(any1, any3 + 1) * any2, Mulf(Mulf(var any1, var any2), Powf(var any1a, var any3)) when any1 == any1a => new Powf(any1, any3 + 1) * any2, diff --git a/Sources/Tests/UnitTests/Common/PowerOfALogarithmMultipleTest.cs b/Sources/Tests/UnitTests/Common/PowerOfALogarithmMultipleTest.cs new file mode 100644 index 000000000..f3636add7 --- /dev/null +++ b/Sources/Tests/UnitTests/Common/PowerOfALogarithmMultipleTest.cs @@ -0,0 +1,74 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using AngouriMath; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Common +{ + /// + /// c ^ (k * log(c, a)) = a ^ k: the antilogarithm of + /// with a multiplier on the logarithm. c ^ z is exp(z * ln(c)) and log(c, a) + /// is ln(a) / ln(c), so the two ln(c) cancel and exp(k * ln(a)) is left, + /// which is what a ^ k means -- on the principal branch, for every complex a other + /// than 0. The integrator's exponential substitution writes its antiderivatives in this + /// shape, e ^ (-3/2 * ln(u)), and nothing folded them. + /// , + /// . + /// + [Trait("Area", "Common")] + public sealed class PowerOfALogarithmMultipleTest + { + [Theory] + [InlineData("e ^ (2 * ln(x))", "x ^ 2")] + [InlineData("e ^ (ln(x) * 2)", "x ^ 2")] + [InlineData("e ^ (-3/2 * ln(u))", "u ^ (-3/2)")] + [InlineData("e ^ (k * ln(x + 1))", "(1 + x) ^ k")] + [InlineData("2 ^ (k * log(2, x))", "x ^ k")] + [InlineData("10 ^ (3 * log(10, x))", "x ^ 3")] + [InlineData("pi ^ (k * log(pi, x))", "x ^ k")] + public void TheMultiplierBecomesTheExponent(string input, string expected) + => Assert.Equal(expected.ToEntity(), input.ToEntity().Simplify()); + + /// + /// A symbolic base needs ln(b) non-zero, which a symbol cannot promise, so the + /// logarithm's own condition travels with the answer. + /// + [Fact] + public void ASymbolicBaseFoldsWhereTheLogarithmIsDefined() + => Assert.Equal("x ^ k provided not a = 0 and not a = 1".ToEntity(), "a ^ (k * log(a, x))".ToEntity().Simplify()); + + /// And the base it would be wrong for is still answered the way it was. + [Fact] + public void TheBaseOneCaseIsUnchanged() + => Assert.Equal("NaN".ToEntity().Evaled, "1 ^ (2 * log(1, x))".ToEntity().Simplify().Evaled); + + /// + /// The rewrite must not move a value anywhere the original had one, on the negative reals + /// included, where the principal branch is what makes it true: ln(-3) is + /// ln(3) + i*pi, and e ^ (1/2 * (ln(3) + i*pi)) is i * sqrt(3), which is + /// the principal (-3) ^ (1/2). + /// + [Theory] + [InlineData("e ^ (2 * ln(x))", 3.0)] + [InlineData("e ^ (2 * ln(x))", -3.0)] + [InlineData("e ^ (1/2 * ln(x))", -3.0)] + [InlineData("e ^ (-3/2 * ln(x))", 0.25)] + [InlineData("e ^ (-3/2 * ln(x))", -1.5)] + [InlineData("2 ^ (3 * log(2, x))", -4.0)] + public void TheValueIsPreserved(string input, double at) + { + var original = input.ToEntity(); + var before = original.Substitute("x", at).Evaled; + var after = original.Simplify().Substitute("x", at).Evaled; + Assert.True(before is Entity.Number.Complex b && after is Entity.Number.Complex a + && (b - a).Abs().EvalNumerical().RealPart.EDecimal.CompareTo(PeterO.Numbers.EDecimal.FromDouble(1e-9)) < 0, + $"{input} at {at}: {before} became {after}"); + } + } +} diff --git a/Sources/Tests/UnitTests/Core/Transformations/AddressableRulesTest.cs b/Sources/Tests/UnitTests/Core/Transformations/AddressableRulesTest.cs index b571c4969..34e73502c 100644 --- a/Sources/Tests/UnitTests/Core/Transformations/AddressableRulesTest.cs +++ b/Sources/Tests/UnitTests/Core/Transformations/AddressableRulesTest.cs @@ -299,7 +299,7 @@ public void TheRegistryIsAddressableAsFarAsItSaysItIs() // are thirty-three; NumericNeat's sixteen are eleven, six of them being three rules // written once per side a negative factor can sit on; and the two factorial sets are // eight arms each written as three. Every other repointed set is one arm for one rule. - Assert.Equal(326, withRules.Sum(set => set.Rules.Count)); + Assert.Equal(329, withRules.Sum(set => set.Rules.Count)); } /// diff --git a/Sources/Tests/UnitTests/Core/Transformations/ReversibleRuleTest.cs b/Sources/Tests/UnitTests/Core/Transformations/ReversibleRuleTest.cs index 102dcee5f..6a4e837e5 100644 --- a/Sources/Tests/UnitTests/Core/Transformations/ReversibleRuleTest.cs +++ b/Sources/Tests/UnitTests/Core/Transformations/ReversibleRuleTest.cs @@ -137,6 +137,7 @@ public void EveryDataRuleIsClassifiedAsWritten() "a-conjunction-with-a-falsehood-is-false: ReplacementIsCode", "a-conjunction-with-itself-is-itself: ReplacementIsCode", "a-constant-raised-to-a-logarithm-of-itself-is-the-antilogarithm: ReplacementDropsHoles", + "a-constant-raised-to-a-multiple-of-a-logarithm-of-itself-is-a-power-of-the-antilogarithm: ReplacementDropsHoles", "a-cosecant-times-a-sine-of-one-angle-is-one: ReplacementIsCode", "a-difference-chain-is-sorted-and-grouped: ReplacementIsCode", "a-difference-of-even-powers-splits: ReplacementIsCode", @@ -222,6 +223,7 @@ public void EveryDataRuleIsClassifiedAsWritten() "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-number-raised-to-a-multiple-of-a-logarithm-of-itself-is-a-power-of-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", @@ -297,6 +299,7 @@ public void EveryDataRuleIsClassifiedAsWritten() "a-sum-or-difference-that-is-a-perfect-square: ReplacementIsCode", "a-sum-over-its-own-reverse-is-one: ReplacementIsCode", "a-symbol-raised-to-a-logarithm-of-itself-is-the-antilogarithm-where-defined: ReplacementIsCode", + "a-symbol-raised-to-a-multiple-of-a-logarithm-of-itself-is-a-power-of-the-antilogarithm-where-defined: ReplacementIsCode", "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", diff --git a/Sources/Tests/UnitTests/Core/Transformations/RuleAuthoringGuideTest.cs b/Sources/Tests/UnitTests/Core/Transformations/RuleAuthoringGuideTest.cs index e19460a8a..c5c866dba 100644 --- a/Sources/Tests/UnitTests/Core/Transformations/RuleAuthoringGuideTest.cs +++ b/Sources/Tests/UnitTests/Core/Transformations/RuleAuthoringGuideTest.cs @@ -43,7 +43,7 @@ private static void Stated(int stated, int measured, string claim) public void WhereARuleGoes() { Stated(33, MatchedRules.All.Count, "the number of rule sets written as data"); - Stated(329, MatchedRules.All.Sum(set => set.Rules.Count), "the number of rules written as data"); + Stated(332, MatchedRules.All.Sum(set => set.Rules.Count), "the number of rules written as data"); Stated(30, RewriteRules.All.Count, "the number of registered sets"); // Two families register under one name and run a data set under another -- // CommonDenominator over MatchedRules.CommonDenominator(level), CanonicalOrder over @@ -64,16 +64,16 @@ public void TheNameIsASentence() { var names = MatchedRules.All.SelectMany(set => set.Rules) .Select(rule => rule.Name).Distinct(StringComparer.Ordinal).ToList(); - Stated(300, names.Count, "the number of distinct rule names"); + Stated(303, names.Count, "the number of distinct rule names"); var words = names.Select(name => name.Split('-').Length).ToList(); Stated(4, words.Min(), "the shortest rule name in words"); - Stated(16, words.Max(), "the longest rule name in words"); + Stated(19, words.Max(), "the longest rule name in words"); } [Fact] public void TheIdentityIsNotTheName() - => Stated(326, + => Stated(329, MatchedRules.All.SelectMany(set => set.Rules).Count(rule => rule.Description is not null), "how many rules carry an identity"); @@ -85,7 +85,7 @@ public void ThePatternLanguage() public void ReplacementAPatternOrCode() { var rules = MatchedRules.All.SelectMany(set => set.Rules).ToList(); - Stated(35, rules.Count(rule => rule.Right is not null), + Stated(36, rules.Count(rule => rule.Right is not null), "how many rules have a pattern on both sides"); Stated(32, rules.Count(rule => rule.Reversed is not null), "how many two-sided rules have a direction"); @@ -97,11 +97,11 @@ public void ReplacementAPatternOrCode() // rearrange, 9 expand and 270 unjudged", measured before the growth declarations // went in. The argument survived; the arithmetic did not. A number a remark reasons // from belongs somewhere that fails when it moves. - Stated(124, rules.Count(rule => rule.Growth is RewriteRuleGrowth.Collects), + Stated(126, rules.Count(rule => rule.Growth is RewriteRuleGrowth.Collects), "how many rules are declared Collects"); Stated(49, rules.Count(rule => rule.Growth is RewriteRuleGrowth.Rearranges), "how many rules are declared Rearranges"); - Stated(33, rules.Count(rule => rule.Growth is RewriteRuleGrowth.Expands), + Stated(34, rules.Count(rule => rule.Growth is RewriteRuleGrowth.Expands), "how many rules are declared Expands"); // And the three the document names. By their own names rather than by what the prose @@ -119,6 +119,7 @@ public void ReplacementAPatternOrCode() { "a-conditional-set-whose-condition-is-its-own-membership-is-that-set", "a-constant-raised-to-a-logarithm-of-itself-is-the-antilogarithm", + "a-constant-raised-to-a-multiple-of-a-logarithm-of-itself-is-a-power-of-the-antilogarithm", "squared-sine-and-cosine-of-one-argument-sum-to-one", }, oneWay); @@ -133,7 +134,7 @@ public void SoundnessIsPerRule() var rules = MatchedRules.All.SelectMany(set => set.Rules).ToList(); Stated(186, rules.Count(rule => rule.Soundness is Soundness.Sound), "how many rules are Sound"); - Stated(143, rules.Count(rule => rule.Soundness is Soundness.SoundUnderAssumptions), + Stated(146, rules.Count(rule => rule.Soundness is Soundness.SoundUnderAssumptions), "how many rules are SoundUnderAssumptions"); Assert.All(RewriteRules.All, set => Assert.Equal(Soundness.SoundUnderAssumptions, set.Soundness)); @@ -160,7 +161,7 @@ public void OrderAndWhenItIsYoursToChoose() subsuming++; } } - Stated(29, subsuming, "how many rule pairs are ordered by subsumption"); + Stated(30, subsuming, "how many rule pairs are ordered by subsumption"); } /// diff --git a/Sources/Tests/UnitTests/Core/Transformations/RuleMetadataTest.cs b/Sources/Tests/UnitTests/Core/Transformations/RuleMetadataTest.cs index 0d2589c7c..81800cba9 100644 --- a/Sources/Tests/UnitTests/Core/Transformations/RuleMetadataTest.cs +++ b/Sources/Tests/UnitTests/Core/Transformations/RuleMetadataTest.cs @@ -88,9 +88,9 @@ public void AddressableGrowthIsTheRulesOwnAnswer() public void SoundnessIsFinerPerRuleThanPerSet() { var rules = MatchedRules.All.SelectMany(set => set.Rules).ToList(); - Assert.Equal(329, rules.Count); + Assert.Equal(332, rules.Count); Assert.Equal(186,rules.Count(rule => rule.Soundness is Soundness.Sound)); - Assert.Equal(143, rules.Count(rule => rule.Soundness is Soundness.SoundUnderAssumptions)); + Assert.Equal(146, rules.Count(rule => rule.Soundness is Soundness.SoundUnderAssumptions)); // And every set still reports the weakest of them, which is what makes the set grain // uninformative rather than wrong. @@ -174,7 +174,7 @@ public void EveryRuleOfARepointedSetCarriesItsIdentity() var repointed = RewriteRules.All .Where(set => set.Rules.Count > 0 && set.Rules.All(rule => rule.Soundness is not null)) .ToList(); - Assert.Equal(326, repointed.Sum(set => set.Rules.Count)); + Assert.Equal(329, repointed.Sum(set => set.Rules.Count)); Assert.All(repointed, set => Assert.All(set.Rules, rule => Assert.NotNull(rule.Description))); } diff --git a/Sources/Tests/UnitTests/Core/Transformations/RulePriorityTest.cs b/Sources/Tests/UnitTests/Core/Transformations/RulePriorityTest.cs index 3a32159fa..3c7ec72b4 100644 --- a/Sources/Tests/UnitTests/Core/Transformations/RulePriorityTest.cs +++ b/Sources/Tests/UnitTests/Core/Transformations/RulePriorityTest.cs @@ -144,7 +144,7 @@ private static SortedSet Subsumptions() /// /// /// Over every rule rather than within a set, because the relation is about patterns and - /// nothing about it stops at a set boundary: 962 ordered pairs claim subsumption, + /// nothing about it stops at a set boundary: 963 ordered pairs claim subsumption, /// 502 of them (501 before the constant antilogarithm rule of #994 subsumed the symbolic one) are put to the test by the corpus containing something the narrower /// pattern matches, and none is contradicted across 85,153 nodes. All three counts /// are asserted — a corpus that stopped reaching these shapes would otherwise turn this @@ -205,7 +205,7 @@ public void SubsumptionIsNeverContradictedByMatching() if (put) witnessed++; } - Assert.Equal(962, claims.Count); + Assert.Equal(963, claims.Count); Assert.Equal(502, witnessed); // Asserted so the two figures in the remark above cannot go stale in silence: the // whole point of `witnessed` is that it is a coverage number, and it means nothing @@ -283,6 +283,7 @@ public void TheRecordedSubsumptionsAreTheOnesThereAre() // by a predicate, which subsumption does not read, so it is ordered by declaration. // https://github.com/asc-community/AngouriMath/issues/994 "Power: a-constant-raised-to-a-logarithm-of-itself-is-the-antilogarithm before a-symbol-raised-to-a-logarithm-of-itself-is-the-antilogarithm-where-defined", + "Power: a-constant-raised-to-a-multiple-of-a-logarithm-of-itself-is-a-power-of-the-antilogarithm before a-symbol-raised-to-a-multiple-of-a-logarithm-of-itself-is-a-power-of-the-antilogarithm-where-defined", }; Assert.Equal(recorded.OrderBy(name => name, StringComparer.Ordinal), Subsumptions()); } diff --git a/Sources/Tests/UnitTests/Core/Transformations/StepAsASentenceTest.cs b/Sources/Tests/UnitTests/Core/Transformations/StepAsASentenceTest.cs index 8f4ed6dff..c191ad0e9 100644 --- a/Sources/Tests/UnitTests/Core/Transformations/StepAsASentenceTest.cs +++ b/Sources/Tests/UnitTests/Core/Transformations/StepAsASentenceTest.cs @@ -59,7 +59,7 @@ public void EveryRuleWrittenAsDataIsNamedInProse() { var names = MatchedRules.All.SelectMany(set => set.Rules).Select(rule => rule.Name) .Distinct(StringComparer.Ordinal).ToList(); - Assert.Equal(300, names.Count); + Assert.Equal(303, names.Count); Assert.All(names, name => Assert.True(Explanation.IsProse(name), $"'{name}' is a rule name that does not read as a phrase in English"));