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
12 changes: 12 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,18 @@ is `null`. A row with symbols in it is tried at one value of them, and where it
| `MathS.Equations("ln(x) + ln(y)", "x - y").Solve("x", "y")` | `[[-1, -1], [1, 1]]` | `[[1, 1]]`: `ln(-1) + ln(-1)` is `2 pi i` |
| `MathS.Equations("sqrt(y) - x", "y - a").Solve("x", "y")` | `[[sqrt(a), (sqrt(a)) ^ 2], [-sqrt(a), (-sqrt(a)) ^ 2]]` | `null`: the second row is a solution only at `a = 0` |

### The power reduction of a squared cosine is proved, as the squared sine's is

**Silent: an identity reaches its value.** `cos(x)^2 = (1 + cos(2x))/2` was not proved while
`sin(x)^2 = (1 - cos(2x))/2` was: the difference reached `-1/2 + cos(x)^2 / 2 + sin(x)^2 / 2`, where the squares
carry their coefficient as a divisor, and no rule found a squared sine and cosine over one divisor among other terms.
`sin(x)^2 / d + cos(x)^2 / d = 1 / d` is now found wherever the two sit in a sum
([#2142](https://github.com/asc-community/AngouriMath/issues/2142)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"cos(x)^2 - (1 + cos(2*x))/2".Simplify()` | `cos(x) ^ 2 - (1 + cos(2 * x)) / 2` | `0` |

### `conj`, `arg` and `det` are refused by name

**Not silent: these now throw `UnrecognizedFunctionParseException` where they parsed.** A name the parser does not
Expand Down
26 changes: 26 additions & 0 deletions Sources/AngouriMath/Core/Transformations/Matching/MatchedRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,32 @@ internal static class MatchedRules
Soundness.Sound,
description: "k sin(x)^2 + k cos(x)^2 = k"),

// ... + sin(x)^2 / d + ... + cos(x)^2 / d + ... -> 1 / d + (everything else)
// A rational coefficient is written as a divisor, cos(x)^2 / 2 and not 1/2 cos(x)^2,
// so the rule above does not see it. The power reduction cos(x)^2 = (1 + cos(2x))/2
// reached -1/2 + cos(x)^2 / 2 + sin(x)^2 / 2 and stopped, while its sine twin was 0.
// Sound: both sides are defined exactly where d is not 0.
// https://github.com/asc-community/AngouriMath/issues/2142
new MatchedRule(
"squared-sine-and-cosine-over-one-divisor-sum-to-its-reciprocal",
MatchPattern.Gathered<Sumf>(
"rest",
MatchPattern.Node<Divf>(
MatchPattern.Node<Powf>(
MatchPattern.Node<Sinf>(MatchPattern.Any("x")),
MatchPattern.Exact(Integer.Create(2))),
MatchPattern.Any("d")),
MatchPattern.Node<Divf>(
MatchPattern.Node<Powf>(
MatchPattern.Node<Cosf>(MatchPattern.Any("x")),
MatchPattern.Exact(Integer.Create(2))),
MatchPattern.Any("d"))),
MatchPattern.Node<Sumf>(
MatchPattern.Node<Divf>(MatchPattern.Exact(Integer.Create(1)), MatchPattern.Any("d")),
MatchPattern.Any("rest")),
Soundness.Sound,
description: "sin(x)^2 / d + cos(x)^2 / d = 1 / d"),

// ... + 1 + ... + tan(x)^2 + ... -> sec(x)^2 + (everything else), and the same with
// the cotangent. The identity divided through by cos^2 and by sin^2 is also in
// TrigonometricRules, but for two siblings only, so the sort decided again: secant
Expand Down
4 changes: 2 additions & 2 deletions Sources/AngouriMath/Core/Transformations/Saturation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ internal static class Saturation
/// one, and that is a measurement rather than a preference.</b> It reads like the value a
/// ceiling should refuse — it means the rule's growth was not judged, because its
/// replacement is code rather than a written pattern, so admitting it accepts a rewrite
/// nobody measured. But it is where the rules are: of the 340 rules in
/// <see cref="MatchedRules"/>, <b>129 collect, 49 rearrange, 36 expand and 126 are
/// nobody measured. But it is where the rules are: of the 341 rules in
/// <see cref="MatchedRules"/>, <b>130 collect, 49 rearrange, 36 expand and 126 are
/// unjudged</b>. A ceiling that refuses the fourth value still refuses 37% of the library,
/// and what is left could not do much when this was written — over six expression pairs
/// equal only through a larger intermediate form, the
Expand Down
16 changes: 8 additions & 8 deletions Sources/AngouriMath/Docs/Contributing/WritingARule.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
**340** rules live there today.
**341** 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.
Expand Down Expand Up @@ -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 311 distinct rule names are,
So the name has to be a clause that survives being read that way. **All 312 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.

Expand All @@ -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. **337** rules carry one today; a new rule should.
rule happens to be applied in. **338** rules carry one today; a new rule should.

## The pattern language

Expand Down Expand Up @@ -123,14 +123,14 @@ matches and silently builds nothing. `BuildableNodeTypesTest` is the list.

## Replacement: a pattern, or code

**41** of the 340 rules have a pattern on both sides. The rest build their answer in code, and both
**42** of the 341 rules have a pattern on both sides. The rest build their answer in code, and both
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 **36** of the 41
two-sided rules have one. The other five forget something: `sin²+cos² = 1` and
`k sin² + k cos² = k` forget the angle,
- **a direction.** `MatchedRule.Reversed` is the rule read the other way, and **36** of the 42
two-sided rules have one. The other six forget something: `sin²+cos² = 1`,
`k sin² + k cos² = k` and `sin²/d + cos²/d = 1/d` forget 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.
Expand Down Expand Up @@ -227,7 +227,7 @@ limit, against nothing for handing the node over.
| `SoundUnderAssumptions` | holds given something the rule does not check |
| `Heuristic` | usually right |

**189** of the 340 rules are `Sound` and **151** are conditional. Every one of the thirty registered
**190** of the 341 rules are `Sound` and **151** 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.
Expand Down
43 changes: 43 additions & 0 deletions Sources/Tests/UnitTests/Common/PowerReductionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// 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;
using static AngouriMath.Entity.Number;

namespace AngouriMath.Tests.Common
{
/// <summary>
/// <c>cos(x)^2 = (1 + cos(2x))/2</c> was not proved while its sine twin was: the difference
/// reached <c>-1/2 + cos(x)^2 / 2 + sin(x)^2 / 2</c>, and a squared sine and cosine over one
/// divisor was found by no rule.
/// <a href="https://github.com/asc-community/AngouriMath/issues/2142">#2142</a>
/// </summary>
[Trait("Area", "Common")]
public sealed class PowerReductionTest
{
[Theory]
[InlineData("cos(x)^2 - (1 + cos(2*x))/2", "0")]
[InlineData("sin(x)^2 - (1 - cos(2*x))/2", "0")]
[InlineData("sin(x)^2/3 + cos(x)^2/3", "1/3")]
[InlineData("a + sin(t)^2/b + cos(t)^2/b", "a + 1/b")]
public void TheIdentityHolds(string input, string expected)
{
// a + 1/b less itself is 0 provided b is not 0, and the condition is not what is compared.
var difference = (input.Simplify() - expected.ToEntity()).Simplify();
while (difference is AngouriMath.Entity.Providedf(var inner, _))
difference = inner;
Assert.Equal(Integer.Zero, difference);
}

/// <summary>Two different divisors are not the identity.</summary>
[Fact]
public void DifferentDivisorsAreLeft()
=> Assert.Contains("sin", "sin(x)^2/2 + cos(x)^2/3".Simplify().Stringize());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ public void EveryDataRuleIsClassifiedAsWritten()
"one-less-a-squared-sine-is-a-squared-cosine: ReplacementIsCode",
"sine-of-a-whole-multiple-of-an-angle: ReplacementIsCode",
"squared-sine-and-cosine-of-one-argument-sum-to-one: ReplacementDropsHoles",
"squared-sine-and-cosine-over-one-divisor-sum-to-its-reciprocal: ReplacementDropsHoles",
"squared-sine-and-cosine-with-one-coefficient-sum-to-it: ReplacementDropsHoles",
"the-arctangent-of-one-over-root-three: ReplacementIsCode",
"the-arctangent-of-root-three: ReplacementIsCode",
Expand Down Expand Up @@ -437,13 +438,13 @@ public void AReversedRuleSaysWhatAnExpressionCameFrom()
[Fact]
public void ARuleThatForgetsAHoleHasNoBackwardsReading()
{
// Both Pythagorean rules of the set, with a coefficient and without, drop the
// argument. The set's other two, 1 + tan^2 = sec^2 and its cotangent twin, keep it,
// The three Pythagorean rules of the set, with a coefficient, a divisor or neither,
// drop the argument. The set's other two, 1 + tan^2 = sec^2 and its cotangent twin, keep it,
// and they are the only ones its reversal holds.
var pythagoras = MatchedRules.PythagoreanIdentity.Rules
.Where(rule => rule.Name.StartsWith("squared-sine-and-cosine", StringComparison.Ordinal))
.ToList();
Assert.Equal(2, pythagoras.Count);
Assert.Equal(3, pythagoras.Count);
foreach (var rule in pythagoras)
{
Assert.Equal(RuleReversal.ReplacementDropsHoles, rule.Reversal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(340, MatchedRules.All.Sum(set => set.Rules.Count), "the number of rules written as data");
Stated(341, 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
Expand All @@ -64,7 +64,7 @@ public void TheNameIsASentence()
{
var names = MatchedRules.All.SelectMany(set => set.Rules)
.Select(rule => rule.Name).Distinct(StringComparer.Ordinal).ToList();
Stated(311, names.Count, "the number of distinct rule names");
Stated(312, 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");
Expand All @@ -73,7 +73,7 @@ public void TheNameIsASentence()

[Fact]
public void TheIdentityIsNotTheName()
=> Stated(337,
=> Stated(338,
MatchedRules.All.SelectMany(set => set.Rules).Count(rule => rule.Description is not null),
"how many rules carry an identity");

Expand All @@ -85,7 +85,7 @@ public void ThePatternLanguage()
public void ReplacementAPatternOrCode()
{
var rules = MatchedRules.All.SelectMany(set => set.Rules).ToList();
Stated(41, rules.Count(rule => rule.Right is not null),
Stated(42, rules.Count(rule => rule.Right is not null),
"how many rules have a pattern on both sides");
Stated(36, rules.Count(rule => rule.Reversed is not null),
"how many two-sided rules have a direction");
Expand All @@ -97,7 +97,7 @@ 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(129, rules.Count(rule => rule.Growth is RewriteRuleGrowth.Collects),
Stated(130, 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");
Expand All @@ -121,6 +121,7 @@ public void ReplacementAPatternOrCode()
"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",
"squared-sine-and-cosine-over-one-divisor-sum-to-its-reciprocal",
"squared-sine-and-cosine-with-one-coefficient-sum-to-it",
},
oneWay);
Expand All @@ -133,7 +134,7 @@ public void ReplacementAPatternOrCode()
public void SoundnessIsPerRule()
{
var rules = MatchedRules.All.SelectMany(set => set.Rules).ToList();
Stated(189, rules.Count(rule => rule.Soundness is Soundness.Sound),
Stated(190, rules.Count(rule => rule.Soundness is Soundness.Sound),
"how many rules are Sound");
Stated(151, rules.Count(rule => rule.Soundness is Soundness.SoundUnderAssumptions),
"how many rules are SoundUnderAssumptions");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ public void AddressableGrowthIsTheRulesOwnAnswer()
public void SoundnessIsFinerPerRuleThanPerSet()
{
var rules = MatchedRules.All.SelectMany(set => set.Rules).ToList();
Assert.Equal(340, rules.Count);
Assert.Equal(189,rules.Count(rule => rule.Soundness is Soundness.Sound));
Assert.Equal(341, rules.Count);
Assert.Equal(190,rules.Count(rule => rule.Soundness is Soundness.Sound));
Assert.Equal(151, rules.Count(rule => rule.Soundness is Soundness.SoundUnderAssumptions));

// And every set still reports the weakest of them, which is what makes the set grain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(311, names.Count);
Assert.Equal(312, 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"));

Expand Down
Loading