7
$\begingroup$

With version "11.1.1 for Microsoft Windows (64-bit) (April 18, 2017)",

DSolve[{SI'[s] == Sin[PH[s]] , PH'[s] == Sin[SI[s]]/e}, {SI[s], PH[s]}, s]

produces the common warning message,

Solve::ifun: Inverse functions are being used by Solve, so some solutions may not be found; use Reduce for complete solution information.

and then a lengthy answer, the Last half of which is given in the answer to 147688, all in about 5 sec. A typical method of employing Reduce, as the warning suggests, is

SetOptions[Solve, Method -> Reduce];

However, DSolve appears to ignore this setting, returning the same warning message and answer in the same amount of time.

With version "10.4.1 for Microsoft Windows (64-bit) (April 11, 2016)", and the default Method for Solve, DSolve again produces the same warning message and answer in the same amount of time. In contrast, with Method -> Reduce DSolve runs about 250 sec before returning unevaluated.

Question - How is this difference to be interpreted? Possibilities that come to mind include:

  1. Version 11.1.1 of DSolve immediately recognizes that Reduce cannot produce an answer and ignores the instruction to use it.
  2. Version 11.1.1 contains a bug, albeit fortuitous in this case.
  3. There is a new way to invoke Reduce with DSolve, and I am the last to know it.

By the way, this is not the first instance in which I have seen DSolve ver 11.1.1 ignore Method -> Reduce.

$\endgroup$
  • 2
    $\begingroup$ See mathematica.stackexchange.com/a/143422/4999 for some info. See if it answers your Q. Basically, V11, DSolve calls Solve with a specialized Method; so you cannot override it with SetOptions anymore. $\endgroup$ – Michael E2 Jun 12 '17 at 23:12
  • 2
    $\begingroup$ Actually, in this case, it seems to be calling Solve with Method -> "Algebraic". (I do not know what these undocumented methods do, nor how many there are.) $\endgroup$ – Michael E2 Jun 12 '17 at 23:24
  • $\begingroup$ @MichaelE2 Very helpful (+1). How did you find this information? Trace with TraceInternal -> True? Unfortunately, DSolve with Method -> Reduce in your code returns unevaluated, as it did in Ver 10.4.1. I see that I really need to learn about Internal``InheritedBlock. What is the best source of information? Thanks. $\endgroup$ – bbgodfrey Jun 12 '17 at 23:42
  • 2
    $\begingroup$ I put a Print[{opts}] statement, among other such debugging statements, in the redefined Solve in my linked answer. The Internal`InheritedBlock approach allows much more targeted responses to different conditions. $\endgroup$ – Michael E2 Jun 12 '17 at 23:51
  • $\begingroup$ @MichaelE2 I found your comments very useful, and I imagine that others will too. May I suggest, therefore, that you convert them into an answer to my question. Thanks. $\endgroup$ – bbgodfrey Jun 13 '17 at 21:49
7
$\begingroup$

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?

$\endgroup$
  • $\begingroup$ Superb! Thank you. $\endgroup$ – bbgodfrey Jun 16 '17 at 1:21
  • $\begingroup$ @bbgodfrey You're welcome. $\endgroup$ – Michael E2 Jun 16 '17 at 4:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.