How can I check if a function can be written as $a * \sin (b * x + c) + d$?
Here is a differential equations/operator approach, leading to mathematics-based solutions instead of expression-matching ones. As the OP observes, matching algebraic expressions with expression patterns can be tricky, despite Mathematica's Optional
patterns and Default
values. For trigonometric functions it is usually trickier since, as @alx observes, trig functions have automatic simplification rules that change, for instance, Sin[t + Pi/2]
to Cos[t]
. Sometimes a mathematical approach is simpler or more robust.
Mathematical background
The expression $y = a * \sin (b * x + c)$ is the general solution to $$y'' + b^2 y = 0 \,,$$ and thus $y = a * \sin (b * x + c) + d$ is the general solution to $$y''' + b^2 y' = 0\,.\tag{1}$$ Even further, it is the general solution to $${d \over dx} {y''' \over y'} = {d \over dx} \left(-b^2\right) \,,$$ or equivalently $$y'y^{(4)}-y''y''' = 0 \,.\tag{2}$$ Equation (2) is equivalent to the vanishing of the second-order Wronskian of $y'$ and $y'''$, $$W \equiv \left|\matrix{ y' & y''' \\ y'' & y^{(4)} \\}\right| = 0 \,. \tag{3}$$ The condition $W=0$ is equivalent to $y'$ and $y'''$ being linearly dependent. A fourth-order ODE is as far as we can go, since the form $y = a * \sin (b * x + c) + d$ has four parameters.
Solution methods
A solution based on (1), using the internal, undocumented function Periodic`PeriodicFunctionPeriod
is the following:
ClearAll[sinQ];
sinQ[f_, x_] :=
With[{b = 2 Pi/Periodic`PeriodicFunctionPeriod[f, x]},
Simplify[D[f, {x, 3}] + b^2 D[f, x]] === 0 /; FreeQ[b, $Failed]];
sinQ[__] = False;
A solution based on (2) is the following:
ClearAll[sinQ];
sinQ[f_, x_] :=
Simplify[D[f, {x, 1}] D[f, {x, 4}] - D[f, {x, 2}] D[f, {x, 3}]] === 0;
And a solution based on (3), using the alternative PossibleZeroQ
to simplifying the Wronskian, is the following:
ClearAll[sinQ];
sinQ[f_, x_] :=
PossibleZeroQ[Det@Partition[Rest@NestList[D[#, x] &, f, 4], 2]];
Examples
sinQ[a*Sin[b*x + c] + d, x]
(* True *)
sinQ[5 Sin[4 (t - 1)] + Cos[2 t]^2 - 1, t]
(* True *)
sinQ[5 Sin[4 (t - 1)] + Cos[4 t]^2, t]
(* False *)
Note: Only the method that uses PossibleZeroQ
works on the following example, which has approximate real parameter values:
sinQ[5 Sin[4. (t - 1)] + Cos[2. t + 0.1]^2, t]
(* True *)
One can use PossibleZeroQ
in the other methods. It will work with the second example but not with the first, because Periodic`PeriodicFunctionPeriod
fails to recognize the function with approximate coefficients as periodic. One could Rationalize
the argument f
in that case, if one wanted to extend that method to such as examples as this last one.
Addendum
It wasn't asked for but here is a way to compute the parameters. One can compute the offset d
and amplitude b
from integrals, which for a sine curve can be computed exactly and quickly with the trapezoid rule with a sampling of four points per period (see trapsample
below). This can be used to verify sinQ
, about whose accuracy a question arose.
ClearAll[sinParameters];
sinParameters[e_, t_] := Module[
{df, aa, bb, cc, dd, trapsample},
df = NestList[D[#, t] &, e, 4]; (* y''' + b^2 y' == 0 *)
( bb = Simplify@ Sqrt[-df[[4]]/df[[2]]];
trapsample = Map[e /. t -> # &, Pi {1/4, 3/4, 0, 1/2, 1, 3/2}/bb];
dd = Simplify@ Mean@ trapsample[[3 ;;]];
aa = Simplify@ Sqrt[2 Mean[(trapsample[[;; 4]] - dd)^2]];
cc = Simplify@ ArcSin[(e - dd)/aa /. t -> 0];
If[df[[2]] != aa bb /. t -> -cc/bb, (* adjust quadrant of ArcSin *)
aa = -aa; (* alternatively, leave aa as is and set cc=Pi-cc *)
cc = -cc
];
Thread[{a, b, c, d} -> {aa, bb, cc, dd}]
) /; PossibleZeroQ[Det@Partition[Rest@df, 2]] (* same test as sinQ *)
];
On the second example above, @alx noted that @alx's method returns False
. One can see that it can be written in the desired form from the following:
fn = 5 Sin[4 (t - 1)] + Cos[2 t]^2 - 1;
a * Sin[b*t + c] + d /. sinParameters[fn, t]
(*
-(1/2) - 1/2 Sqrt[101 - 20 Sin[4]] *
Sin[ 4 t - ArcSin[(1 - 10 Sin[4])/Sqrt[101 - 20 Sin[4]]] ]
*)
fn - % // TrigToExp // FullSimplify
(* 0 *)
I wrote a symbolic analysis solver for the parameters, but the code is about twice as long and runs more than a 100 times more slowly. I didn't think it worth sharing.
sinQ[fn, t] // RepeatedTiming
sinParameters[fn, t] // RepeatedTiming
(*
{0.000087, True}
{0.00054, {
a -> -(1/2) Sqrt[101 - 20 Sin[4]],
b -> 4,
c -> -ArcSin[(1 - 10 Sin[4])/Sqrt[101 - 20 Sin[4]]],
d -> -(1/2)}}
*)
You can use TrigReduce
to come to form with multiple angles instead of powers and then use MatchQ
to compare the result with Sin[...]
pattern. I also use here Inactive[Sin]
to keep results of conversion Cos
terms to corresponding Sin
terms:
MatchQ[(TrigReduce[expr]//Expand)/.{Sin[x_] -> Inactive[Sin][x],
Cos[x_] -> Inactive[Sin][x + \[Pi]/2]},
a_. Inactive[Sin][b_. x + c_.] + d_. /; FreeQ[d, x]
So this will match only to a Sin[b x+c]+d
, where all a, b, c, d
are constants.