mathnet/mathnet-symbolics

algebraically exact values of sine function

diluculo opened this issue · 5 comments

This Symbolics says that "Expressions always appear in a simplified form according to a set of rules."

If so, how about adding some known rules to the trigonometric functions? For some m/n*pi (with integer m and n), trigonometric functions can be simply expressed as algebraic numbers. For example, sin(pi/3) returns sqrt(3)/2. However, we can't handle all of cases, so, if we limit to just a few cases, we can easily apply this rule with lookup tables.

Rules can be added:
(1) Argument that are rational multiple of pi can be expressed in algebraic numbers: sin(11/12*pi) = (sqrt(3) - 1)/(2*sqrt(2)), ...
(2) Hence, exponential function can be rewritten as cos and sin terms: exp(m/n*pi*j) = cos(m/n*pi) + j*sin(m/n*pi), where j is imaginary unit
(3) Argument that are multiple of j can be expressed in terms of hyperbolic functions: sin(z*j) = j*sinh(z), ...

There are many rules we can apply to elementary functions such as exp(x), sin(x) and sinh(x). Here, I would like to ask which of the following is appropriate for the purpose of this library. I'd like to send a PR for selected rules.

(1) auto-simplification of argument: sin(x + y - (x + y)) = sin(0) = 0
(2) undefined and infinities : sin(infinity) = undefined
(3) zero, one, pi, and j: sin(pi) = 0, sin(j) = j*sinh(1)
(4) known rational multiples of pi: sin(1/4*pi) = 1/sqrt(2), ...
(5) multiples of j: sin(j*x) = j*sinh(x)
(6) remove pi term: sin(2*pi + x) = sin(x), sin(pi + x) = -sin(x), sin(1/2*pi + x) = cos(x), sin(3/2*pi + x) = -cos(x)
(7) negative argument: sin(-x) = -sin(x)
(8) negative first term: sin(-x + y) = -sin(x - y)
(9) forward-inverse identities: sin(asin(x)) = x
and more....

(2), (3) and (7) are already partially implemented.

Regarding (1): in general, auto-simplification should not expand expressions. However, in this case it might be a bug that the inner expression is not auto-simplified to zero already (need to rethink this). So, once fixed, this should work out of the box.

@cdrnet, I think so. Now it is clear -(a + b) is not auto-simplified to -a - b. In that respect, returns in the below table are very acceptable, except (d). For consistency, how about to allow only minus or -1 to be distributed inside the parenthesis. i.e. -(a + b) = -a - b. I think the last column may be the correct behavior.

No Input Return Expected
(a) a + b - (a + b) a + b - (a + b) 0
(b) a + b + (-a - b) 0 0
(c) a + b - 2*(a + b) a + b - 2*(a + b) a + b - 2*(a + b)
(d) -(a + b) + 2*(a + b) a + b -a - b + 2*(a + b)
(e) -a - b + 2*(a + b) -a - b + 2*(a + b) -a - b + 2*(a + b)
(f) 2*(a + b) - 2*(a + b) 0 0

Yes, we'd only distribute -1, otherwise the product is not just a technical artifact of our internal representation (-x is (-1)*x internally). d) would no longer auto-simplify to a+b, but that may be ok.

I agree, I'd also expect the auto-simplified form of the last column.

I can get the last column by simply adding a rule(| Sum ax as x' ...) to the valueMul function as shown below.

and multiply x y =
      ...
      let rec valueMul (v:Value) x =
            if Value.isZero v then zero else
            match x with
            ...
            | Product ax -> if Value.isOne v then x else Product (Values.unpack v::ax)
            | Sum ax as x'-> if Value.isOne v then x' // intentionally distribute -1, e.g. -(a + b) = -a - b
                             else if Value.isMinusOne v then ax |> List.map (function xi -> valueMul v xi) |> Sum
                             else Product [Values.unpack v; x']
            | x -> if Value.isOne v then x else Product [Values.unpack v; x]
  • Just now, I sent new PR. Please let me know if there is a better way...