1

I use ParametricNDSolve to find a solution to a very cumbersome ODE:

pf2 = ParametricNDSolveValue[{ode2$[z, k, \[Beta], 
     Rm, {q, Km}, \[CapitalLambda]] == 0, \[Phi][0] == 
    1, \[Phi]'[0] == 0}, {\[Phi]'[zR$], \[Phi][zR$]}, {z, 
   0, (1 + 10^-7) zR$}
  , {{\[Beta], 0, 1}, {\[CapitalLambda], 1, 1000}}
  , Method -> {"EquationSimplification" -> "Residual"}
  ]

Equation ode2$[z, k, \[Beta], Rm, {q, Km}, \[CapitalLambda]] is too big to show here. The only significant thing is that the parameters Rm, q, Km are given by numbers in some Do loop, and the parameters \[CapitalLambda], \[Beta] are free. The parametric function pf2[\[Beta], \[CapitalLambda]] returned by ParametricNDSolveValue is then used to construct another equation:

eqn2[\[Beta]_?NumericQ, \[CapitalLambda]_?NumericQ] = 
 pf2[\[Beta], \[CapitalLambda]] . {1, -BC1$[k, \[Beta], 
     Rm, {q, Km}, \[CapitalLambda]]}

I find the root of the equation eqn2[\[Beta], \[CapitalLambda]] ==0 given the value \[CapitalLambda]. All of this works great, but in the particular case of k==4 the calculations take about 300 times longer than with other values of k.

The reason is probably that for k==4 the ode2 equation contains 73 calls to the Appell hypergeometric function AppellF1. They occur both in the numerator and denominator of the coefficients of the equation. However, out of these 73 functions, only 7 have a different set of arguments. So I'm wondering how to make it so that I can first calculate these 7 functions and then re-use the calculated values in the coefficients of the equation. Here's how I came up with the equation modification:

"original equation ODE of 2nd order, too long to show "
ode[z, 4, p, R, \[CapitalLambda]];
"compose list of all AppellF1 functions"
n = 1;
list = {};
ode[z, 4, p, 
   R, \[CapitalLambda]] //. {AppellF1[a_, b1_, b2_, c_, x_, 
     y_] :> {AppendTo[list, AppellF1[a, b1, b2, c, x, y]];}};
"number of such functions"
list // Length
"list of unique functions and number of its occurences"
Tally[list]
list = Sort@DeleteDuplicates[list];
% // Length
"compose Rule to make substitutions in ODE"
n = 1;
rule4 = (# -> F[4, n++]) & /@ list
"compose algebraic equations for substituted AppellF1 functions"
n = 1;
setOde[z_, 4, p_, R_] = 
 Table[F[4, n], {n, Length[list]}] == list // Thread
"modifield equation ODE of 2nd order, too long to show "
ode[z, 4, p, R, \[CapitalLambda]] /. rule4;

This code gives this result:

"original equation ODE of 2nd order, too long to show "

"compose list of all AppellF1 functions"

"number of such functions"

73

"list of unique functions and number of its occurences"

{{AppellF1[1/4, -(1/2), 1/2, 5/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], 25}, {AppellF1[1/4, 1/2, 1/2, 5/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], 17}, {AppellF1[5/4, 1/2, 1/2, 9/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], 17}, {AppellF1[5/4, -(1/2), 3/2, 9/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], 9}, {AppellF1[5/4, 1/2, 3/2, 9/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], 2}, {AppellF1[9/4, 1/2, 3/2, 13/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], 2}, {AppellF1[9/4, -(1/2), 5/2, 13/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], 1}}

7

"compose Rule to make substitutions in ODE"

{AppellF1[1/4, -(1/2), 1/2, 5/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))] -> F[4, 1], AppellF1[1/4, 1/2, 1/2, 5/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))] -> F[4, 2], AppellF1[5/4, -(1/2), 3/2, 9/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))] -> F[4, 3], AppellF1[5/4, 1/2, 1/2, 9/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))] -> F[4, 4], AppellF1[5/4, 1/2, 3/2, 9/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))] -> F[4, 5], AppellF1[9/4, -(1/2), 5/2, 13/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))] -> F[4, 6], AppellF1[9/4, 1/2, 3/2, 13/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))] -> F[4, 7]}

"compose algebraic equations for substituted AppellF1 functions"

{F[4, 1] == AppellF1[1/4, -(1/2), 1/2, 5/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], F[4, 2] == AppellF1[1/4, 1/2, 1/2, 5/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], F[4, 3] == AppellF1[5/4, -(1/2), 3/2, 9/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], F[4, 4] == AppellF1[5/4, 1/2, 1/2, 9/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], F[4, 5] == AppellF1[5/4, 1/2, 3/2, 9/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], F[4, 6] == AppellF1[9/4, -(1/2), 5/2, 13/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))], F[4, 7] == AppellF1[9/4, 1/2, 3/2, 13/ 4, -((2 p)/(-2 p + R^2)), -((2 p)/(-2 p + bv[z]^2))]}

"modifield equation ODE of 2nd order, too long to show "

Finally, we solve DAE. I'm planning on doing something like

p0Rule2 = {p0 -> (R^2 \[Beta])/(2 (-1 + R^2 + \[Beta]))};
bvRule2 = {bv[z] -> bv[z, {q, K}], 
\!\(\*SuperscriptBox[\(bv\), 
TagBox[
RowBox[{"(", "n_", ")"}],
Derivative],
MultilineFunction->None]\)[z] -> 
\!\(\*SuperscriptBox[\(bv\), 
TagBox[
RowBox[{"(", 
RowBox[{"1", ",", 
RowBox[{"{", 
RowBox[{"0", ",", "0"}], "}"}]}], ")"}],
Derivative],
MultilineFunction->None]\)[z, {q, K}]};
bv$[z_, {q_, K_}] = 1 + (-1 + K) Sin[(\[Pi] z)/2]^q;

ode2$[z_, k, [Beta], R, {q_, K_}, [CapitalLambda]] = ode[z, k, p0, R, [CapitalLambda]] /. p0Rule2 /. bvRule2 /. bv -> bv$; setOde$[z, k, [Beta], R, {q_, K_}] = setOde[z, k, p0, R] /. p0Rule2 /. bvRule2 /. bv -> bv$;

pf2 = ParametricNDSolveValue[{ode2$[z, k, [Beta], Rm, {q, Km}, [CapitalLambda]] == 0, setOde$[z, k, [Beta], Rm, {q, Km}], [Phi][0] == 1, [Phi]'[0] == 0}, {[Phi]'[zR$], [Phi][zR$]}, {z, 0, (1 + 10^-7) zR$}, {{[Beta], 0, 1}, {[CapitalLambda], 1, 1000}}, Method -> {"EquationSimplification" -> "Residual"}]

However, on this site I found many answers, the authors of which, on the contrary, advise converting the system of differential-algebraic equations DAE into the system of differential equations ODE. So I ask: What are the ways to speed up the calculations in my example?

EDIT #1: It seems that ParametricNDSolve in contrast to NDSolve cannot handle DAEs. My code suggested above does not work, and careful reading of documentation confirms that it should not work.

Igor Kotelnikov
  • 749
  • 3
  • 12
  • 2
    To remember previously calculated function values you can use "memoization:": E.g.: f[x_]:= f[x] = "calculation of function value". – Daniel Huber Jul 20 '22 at 10:06
  • 4
    As usual with questions where part of the code is "too big to show", you can only get generic answers because we cannot run your code to troubleshoot it or improve it! You could always try to provide a smaller example that reproduces your problem though. – MarcoB Jul 20 '22 at 14:11
  • 1
    Experimental`OptimizeExpression should reduce the number of calls to AppellF to seven, if there are only seven different sets of arguments. – Michael E2 Jul 20 '22 at 22:05
  • 1
    See also: https://mathematica.stackexchange.com/questions/8787/find-subexpression-to-minimize-leafcount-after-replacment-with-temporary-variabl/8806#8806 – Michael E2 Jul 20 '22 at 22:11
  • "It seems that ParametricNDSolve in contrast to NDSolve cannot handle DAEs." No: psol = ParametricNDSolve[{x'[t] == y[t]^2 + x[t] y[t], 2 x[t]^2 + y[t]^2 == 1, x[0] == a}, {x, y}, {t, 0, 10}, a]; psol[0] – xzczd Jul 21 '22 at 02:47
  • @xzczd. Your example is not completed. Perhaps, it should be: psol = ParametricNDSolve[{x'[t] == y[t]^2 + x[t] y[t], 2 x[t]^2 + y[t]^2 == 1, x[0] == a}, {x, y}, {t, 0, 10}, a]; f = {x, y} /. psol; f[0][1]. But this gives nothing numerical. – Igor Kotelnikov Jul 21 '22 at 05:25
  • Oops, I forgot to modify it to ParametricNDSolveValue. But still, ParametricNDSolve can be used: psol = ParametricNDSolve[{x'[t] == y[t]^2 + x[t] y[t], 2 x[t]^2 + y[t]^2 == 1, x[0] == a}, {x, y}, {t, 0, 10}, a]; {xfunc, yfunc} = {x[0], y[0]} /. psol – xzczd Jul 21 '22 at 05:31
  • Your f can be used, too. For example Map[func |-> func[0][1]][f] or #[0][1] & /@ f – xzczd Jul 21 '22 at 05:40
  • @xzczd: psol = ParametricNDSolveValue[{x'[t] == y[t]^2 + x[t] y[t], 2 x[t]^2 + y[t]^2 == 1, x[0] == a}, {x, y}, {t, 0, 10}, a]; psol[0][1] yields {InterpolatingFunction[{{0., ... "]}, {Automatic}]}[1], not a number, – Igor Kotelnikov Jul 21 '22 at 06:03
  • psol[0][1] // Through – xzczd Jul 21 '22 at 06:14
  • @xzczd: Thank you! – Igor Kotelnikov Jul 21 '22 at 06:34
  • @MichaelE2: Please take a look at my next post https://mathematica.stackexchange.com/questions/271146/how-to-use-experimentaloptimizeexpression-to-set-equation-for-parametricnds – Igor Kotelnikov Jul 22 '22 at 09:14
  • @DanielHuber: I'm afraid memorization is not practical for my case. The test run took 43 minutes without memorization and 28 minutes with memorization , and less than 1 minute on the second run with memorization. However, the amount of memory used has been increased from 9 GB to 17 GB on a computer with 32 GB memory installed. – Igor Kotelnikov Jul 22 '22 at 10:54

0 Answers0