Introduction
I will discuss the issues that lead to the inverse function message and a few debugging tools I used, which it might be well to have collected in one place for reference. Finally I will show how to use some of these tools on the OP's example.
In getting around the inverse function warning we are able to recover a branch winding number as a discrete parameter that is missed by the straight DSolve
call.
With respect to the main point, the question itself about how to control the method used by Solve
is addressed. In fact how to target the specific call to Solve
that emits the Solve::ifun
message is shown.
Another thing to keep in mind is that DSolve
analyzes the differential equation for particular special types. It is conceivable that a message might be generated in checking a submethod that is irrelevant (i.e. that would not solve it). Fixing the error message would not help in such a case.
Discussion of the problem
One can see (using techniques discussed in the following sections) that the offending equation seems quite simple:
Solve[-Cos[SI[PH]]==C[1]-e Cos[PH],SI[PH]]
If you try to solve it outright, there is no problem. You get the following with no error/warning messages:
{{SI[PH] ->
ConditionalExpression[-ArcCos[-C[1] + e Cos[PH]] + 2 π C[2], C[2] ∈ Integers]},
{SI[PH] ->
ConditionalExpression[ArcCos[-C[1] + e Cos[PH]] + 2 π C[2], C[2] ∈ Integers]}}
Note the term 2 π C[2]
, where the integer parameter C[2]
could be called a "winding number." It turns out that DSolve[]
does two things that affect the ability of Solve[]
to handle the equation. It makes the following calls:
SetSystemOptions[ReduceOptions->UseTranscendentalSolve->False]
SetOptions[Solve, Method -> "Restricted"]
Note that the second one, SetOptions[]
, is executed after DSolve
starts and overrides the SetOptions[Solve, Method -> Reduced]
in this answer. On the other hand, it is "ReduceOptions"
setting that leads to the Solve::ifun
warning in the present case, which you can check with the following, with or without the Method
setting:
With[{redopts = SystemOptions["ReduceOptions"]},
Internal`WithLocalSettings[
SetSystemOptions["ReduceOptions" -> "UseTranscendentalSolve" -> False],
Solve[-Cos[SI[PH]] == C[1] - e Cos[PH], SI[PH](*, Method -> "Restricted"*)],
SetSystemOptions[redopts]
]]
One thing to keep in mind is that automatic settings depend on heuristics, which are chosen to be helpful in most (but not always all) cases.
I suspect that solutions containing ConditionalExpression
are more trouble than they are worth, especially ones depending on a new parameter like C[2]
. Hence, the "ReduceOptions"
setting used by DSolve
leads to the solution in the example. Likewise, the undocumented Method -> "Restricted"
, whatever it does, must remove or tweak cases that arise in the Automatic
setting and are troublesome for DSolve
.
Some debugging tools
It may be of general interest and useful to collect some of the ways of investigating DSolve
. A few of these are undocumented but often found on this site. Some of these techniques can be used with other solvers, too. It seems almost obvious that a general solver DSolve
might call other solvers like Integrate
and Solve
, and possibly others like Reduce
(no) and Simplify
(yes). It is worth remarking that while it appears from the docs that DSolve
does not use assumptions, some of these other functions do, Integrate
and Simplify
being noteworthy examples. However, it does reset $Assumptions
before returning (as I discovered below).
Tracing.
There are Trace
and
On
.
On[Solve]
and On[SetOptions]
yields information on calls to each function. On[SetOptions]
generates quite a few calls. A more targeted approach is to use Trace
with patterns:
Trace[
DSolve[{SI'[s] == Sin[PH[s]], PH'[s] == Sin[SI[s]]/e}, {SI[s], PH[s]}, s],
_Solve | SetOptions[Solve, _],
TraceInternal -> True] // Flatten
Trapping system function calls.
Internal`InheritedBlock
allows one to intercept system function calls, add definitions to them and print out or Sow
information about the call. The great thing here is that when it is done, the system function reverts to its old self, and you don't have to worry about forgetting to reset something.
Villegas-Gayley.
The Villegas-Gayley trick is the way to insert your own code between a system function call and the execution of the function.
f[args...] /; !TrueQ[someVariable] := Block[{someVariable = True},
<your code here>;
f[args]]
You have to get the new definition ahead of all others, and sometimes one has to do it explicitly by setting its DownValues
.
Temporarily setting system options (or whatever).
A typical Internal`WithLocalSettings
usage has the form
With[{currentsettings = ...},
Internal`WithLocalSettings[
<settings>,
<code>,
<reset currentsettings/cleanup>
]]
The <settings>
is executed first, then <code>
, then <reset..>
.
The advantage here is that the reset happens even if the <code>
is aborted (e.g., by the menu command Evaluation > Abort Evaluation).
Intercepting Solve[]
and solving the differential equation
This section serves two functions, to compute an enhanced solution and to show how to alter the options of Solve
when called by DSolve
. We will use many of the things discussed above to reset SystemOptions
and change the options passed to Solve
.
In particular, Check
is used to make an alternative call to Solve
when the original call emits the Solve:ifun
message.
In fact the alternate call to Solve
returns conditional expressions, which DSolve
did not handle successfully nor quickly. So I thought to break it down and add the condition to $Assumptions
and post-proces the result of DSolve
to be a ConditionalExpression
based on the conditions accumulated. More importantly in this case, the conditions depend on a integer parameter that with the default settings is C[2]
, the same as a continuous parameter used by DSolve
in the general solution. This conflict has to be resolved, which I did by manually setting the GeneratedParameters
option in Solve
. Another potential bug is that independent calls to Solve
might generate independent parameters with the same name. While it in fact happens in this case, ignoring it turns out to be safe. (It could be dealt with, but this example does not seem worth the trouble.)
A utility to add conditions to a DSolve
solution:
(* convert a DSolve solution to a ConditionExpression *)
addConditions[conditions_] := {
HoldPattern[var_ -> Function[x_, expr_]] :>
var -> With[{cond = Simplify[And @@ Flatten@conditions]},
Function @@ Hold[x, ConditionalExpression[expr, cond]]],
HoldPattern[var_ -> expr_] :>
var -> ConditionalExpression[expr, Simplify[And @@ Flatten@conditions]]};
The example intercept of Solve
within DSolve
:
On[$Assumptions]; (* shows when $Assumptions is changed *)
Module[{assum = True},
Internal`InheritedBlock[{Solve, $Assumptions},
Unprotect[Solve];
call : Solve[eq_, v_, opts___] /; ! TrueQ[$in] :=
Block[{$in = True, $res1, $res2},
Check[ (* Use Check to try default settings & respond to messages *)
$res1 = call (* try original call *)
, (* Check[] *)
Print["Trying \"ReduceOptions\" -> \"UseTranscendentalSolve\" -> True on ",
HoldForm[call]];
With[{redopts = SystemOptions["ReduceOptions"]},
Internal`WithLocalSettings[
SetSystemOptions[
"ReduceOptions" -> "UseTranscendentalSolve" -> True], (* I`WLS: init *)
$res2 = Solve[eq, v, GeneratedParameters -> $S, opts];
$res2 = $res2 /. ConditionalExpression[e_, c_] :> (* map condition to assumption *)
($Assumptions = $Assumptions && c; (* for solvers and Simplify[] *)
assum = assum && c; (* for addConditions[] at end *)
e);
Print["$Assumptions now ", $Assumptions]; (* context for On[$Assumptions] *)
$res2 = Simplify[$res2];
Print["Result= ", $res2],
SetSystemOptions[redopts] (* I`WLS: reset *)
];
];
If[FreeQ[$res2, Solve], $res2, $res1]
, (* Check[] *)
{Solve::ifun}]
];
Protect[Solve];
sol = DSolve[{SI'[s] == Sin[PH[s]], PH'[s] == Sin[SI[s]]/e}, {SI,
PH}, s];
Print["$Assumptions finally ", $Assumptions]; (* context for On[$Assumptions] *)
sol = sol /. addConditions[assum]
]]
Off[$Assumptions];
Message
and Print
output (Print
statements shown in italics):
$Assumptions::trace: $Assumptions --> True
. ... (x3)
Solve::ifun
: Inverse functions are being used by Solve
, so some
solutions may not be found; use Reduce for complete solution
information.
Trying "ReduceOptions" -> "UseTranscendentalSolve"->True
on Solve[-Cos[SI[PH]]==C[1]-e Cos[PH],SI[PH]]
$Assumptions::trace: $Assumptions --> True
.
$Assumptions::trace: $Assumptions --> $S[1]∈Integers
.
$Assumptions::trace: $Assumptions --> $S[1]∈Integers &&
$S[1]∈Integers
.
$Assumptions
now $S[1]∈Integers && $S[1]∈Integers
$Assumptions::trace: $Assumptions --> $S[1]∈Integers &&
$S[1]∈Integers
.
Result= {{SI[PH]->-ArcCos[-C[1]+e Cos[PH]]+2 π $S[1]},{SI[PH]->ArcCos[-C[1]+e Cos[PH]]+2 π $S[1]}}
$Assumptions::trace: $Assumptions --> $S[1]∈Integers &&
$S[1]∈Integers
.
$Assumptions::trace: $Assumptions --> True
.
$Assumptions
finally True
$Assumptions::trace: $Assumptions --> True
. ... (x4)
Output (abridged). The $S[1]
is the "winding number" parameter lost in the default DSolve
by "Inverse functions...being used"; it appears only in SI
.
{{SI -> Function[{s},
ConditionalExpression[-ArcCos[ ..] + 2 π $S[1], $S[1] ∈ Integers]],
PH -> Function[{s},
ConditionalExpression[InverseFunction[.. &][-(s/e) + C[2]], $S[1] ∈ Integers]]},
{SI -> Function[{s},
ConditionalExpression[ArcCos[ ..] + 2 π $S[1], $S[1] ∈ Integers]],
PH -> Function[{s},
ConditionalExpression[InverseFunction[.. &][s/e + C[2]], $S[1] ∈ Integers]]}}
Example print statements and Solve
commands that might be used to investigate :
(* Print statements *)
Print[HoldForm[call]];
Print["SystemOptions[\"ReduceOptions\"]= ", SystemOptions["ReduceOptions"]];
Print["opts= ", {opts}];
Print["Trying Method -> Reduce on ", HoldForm[call],
", Options[Solve]= ", Options[Solve],
", SystemOptions[\"ReduceOptions\"]= ",
SystemOptions["ReduceOptions"]];
Print["Trying \"ReduceOptions\" -> \"UseTranscendentalSolve\" -> True on ",
HoldForm[call]];
Print["Result= ", $res2];
Print@Stack[];
(* Alternative Solve[] ideas *)
Solve[eq, v, GeneratedParameters -> $S, Method -> Reduce, opts] (* Method -> Reduce *)
TimeConstrained[Solve[eq, v, GeneratedParameters -> $S, opts], 10] (* abort long Solve *)
References for undocumented features
Villegas-Gayley:
What are some advanced uses for Block?,
SO/4198961.
Internal`InheritedBlock
:
How safe is the use of Block and Internal`InheritedBlock
Internal`WithLocalSettings
:
SO/7586735,
How to flush machine underflows to zero and prevent conversion to arbitrary precision?
DSolve
callsSolve
with a specializedMethod
; so you cannot override it withSetOptions
anymore. $\endgroup$Solve
withMethod -> "Algebraic"
. (I do not know what these undocumented methods do, nor how many there are.) $\endgroup$Trace
withTraceInternal -> True
? Unfortunately,DSolve
withMethod -> Reduce
in your code returns unevaluated, as it did in Ver 10.4.1. I see that I really need to learn aboutInternal``InheritedBlock
. What is the best source of information? Thanks. $\endgroup$Print[{opts}]
statement, among other such debugging statements, in the redefinedSolve
in my linked answer. TheInternal`InheritedBlock
approach allows much more targeted responses to different conditions. $\endgroup$