Skip to content

Parity identities are missing: sin(-x) + sin(x) does not reduce to 0 #929

Description

@Rafael-SOWNet

What happens

The parity identities — cos(-u) = cos(u), sin(-u) = -sin(u), tan(-u) = -tan(u), abs(-u) = abs(u) — are essentially absent, so expressions that cancel exactly do not.

Measured on a clean build of master at b4385a86, .NET 10, default settings:

"sin(-x) + sin(x)".Simplify()   ->  sin(-x) + sin(x)        should be  0
"cos(-x) - cos(x)".Simplify()   ->  cos(-x) - cos(x)        should be  0

"cos(-x)".Simplify()            ->  cos(-x)
"sin(-x)".Simplify()            ->  sin(-x)
"tan(-x)".Simplify()            ->  tan(-x)
"abs(-x)".Simplify()            ->  abs(-x)

"sin(-2 * x)".Simplify()        ->  sin((-2) * x)
"tan(-2 * x)".Simplify()        ->  tan((-2) * x)
"abs(-2 * x)".Simplify()        ->  abs((-2) * x)
"cos(-2 * x)".Simplify()        ->  cos(2 * x)              the one case that does fold

The last line is the whole shape of it: cos folds a negative numeric coefficient and nothing else folds anything. sin, tan and abs do not fold even that, and no function folds a bare negation.

sin(-x) + sin(x) not reaching 0 is the part worth treating as more than a coverage gap. It is not a wrong answer — nothing false is asserted — but an expression that is identically zero is left standing, and anything downstream that tests a residual against zero (the equation solver's verification step does exactly this) sees a non-zero residual where there is none.

Why

Two different shapes are involved and only one is looked for.

"-x".ToEntity()       prints  -x            
"-2 * x".ToEntity()   prints  (-2) * x      

A bare negation and a negative numeric coefficient are not the same tree, and the rule that handles cos(-2 * x) keys on the second. Nothing keys on the first.

What would fix it

A parity rule per function, stated on the argument's sign rather than on a coefficient shape:

  • even — cos, sec, cosh, abs: drop a leading negation from the argument;
  • odd — sin, tan, cotan, cosec, sinh, tanh, sgn: lift it out to the front of the node.

The predicate has to be "the argument is a negation of something" in the tree sense, which covers both -x and (-2) * x and also -x - 1, none of which is reached today. Deciding it on the value would be wrong: cos(a - b) where a < b is not a case for this rule, because the rule is about the written form and must hold symbolically.

Worth checking before starting

  • Which functions are genuinely even or odd over the complex plane, not just the reals. This is the class of thing that has shipped wrong here before — see Contributing/SimplificationContract.md on branch cuts, and note that arccotan's range in this library is (-pi/2, pi/2] rather than the textbook one, so parity for the inverse functions wants measuring at +1, -1 and 0 before anything is written down.
  • That lifting a negation out of an odd function does not fight the complexity criteria and lose the comparison it exists to win — -sin(x) and sin(-x) rate closely, and a rewrite that produces a correct-but-not-shorter candidate is invisible. alternate:: in the analysis workspace's probe shows the candidate pool.
  • Termination: an odd-function rule produces a negation above the node, and a rule that pushes negations down would cycle with it.

How it was found

canoncheck, a new harness for #746 tier 1's canonical-form work, which checks whether two writings of one expression reach the same form. abs(-x) vs abs(x), sin(-x) vs -sin(x) and cos(-x) vs cos(x) are three of its listed pairs and all three disagree under both InnerSimplified and Simplify. See Contributing/CanonicalForm.md.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions