comparison scripts/ode/ode45.m @ 31263:449ed6f427cb

ode45/23/23s: Implement Events, OutputFcn, & Refine options (bug #49408 and #63063) * scripts/ode/ode23.m: Remove disabling of Refine option with struct output. Modify solution struct to output two sets of solution variables: output_t, output_x and ode_t and ode_x, and transpose struct output variables for improved Matlab compatibility. Update BISTs and perform minor code formatting. * scripts/ode/ode23s.m: Make same changes as ode23.m. * scripts/ode/ode45.m: Make same changes as ode23.m. Remove comment indicating that Refine is not implemented. * scripts/ode/private/integrate_adaptive.m: Update internal handling of variables t and x, separating them into ode_t & ode_x for internal integration and output_t & output_x for function output or calls to OutputFcn. Replace prior attempt at Refine option with new implementation. Specify time output or Refine != 0 are both interpolated from internal variables (ode_t, ode_x) for output of non-struct variables and/or for use with OutputFcn. Improve event handling when multiple Events (including at least one terminal Event) are detected in a single simulation step so that all Events up to and including the first terminal one are reported, and final data point is set to that of terminal Event. Send multiple data points in a single call to OutputFcn if they are all interpolated from a single integration step. Remove warning for termination when term signal is received from Events or OutputFcn. Return both internal variables (ode_t, ode_x) and interpolated variables (output_t, output_x) to allow calling function to correctly return either struct or separate variables. * scripts/ode/private/ode_event_handler.m: Sort multiple Events in ascending time order when they are encountered in one integration step. Remove any events after the time of a terminal Event. * scripts/ode/odeset.m: Update docstring to remove indication that Refine is not implemented * scripts/ode/odeplot.m: Update docstring to indicate that input t can be a scalar or vector. Add file test. * etc/NEWS.8.md: Add descriptions of changes under General improvements and Matlab compatibility.
author Ken Marek <marek_ka@mercer.edu>
date Wed, 05 Oct 2022 16:53:01 -0400
parents e1788b1a315f
children c332a2f2959f
comparison
equal deleted inserted replaced
31262:b8f4ec18e728 31263:449ed6f427cb
154 ## Start preprocessing, have a look which options are set in odeopts, 154 ## Start preprocessing, have a look which options are set in odeopts,
155 ## check if an invalid or unused option is set 155 ## check if an invalid or unused option is set
156 [defaults, classes, attributes] = odedefaults (numel (init), 156 [defaults, classes, attributes] = odedefaults (numel (init),
157 trange(1), trange(end)); 157 trange(1), trange(end));
158 158
159 ## FIXME: Refine is not correctly implemented yet
160 defaults = odeset (defaults, "Refine", 4); 159 defaults = odeset (defaults, "Refine", 4);
161 160
162 persistent ode45_ignore_options = ... 161 persistent ode45_ignore_options = ...
163 {"BDF", "InitialSlope", "Jacobian", "JPattern", 162 {"BDF", "InitialSlope", "Jacobian", "JPattern",
164 "MassSingular", "MaxOrder", "MvPattern", "Vectorized"}; 163 "MassSingular", "MaxOrder", "MvPattern", "Vectorized"};
194 193
195 if (isempty (odeopts.InitialStep)) 194 if (isempty (odeopts.InitialStep))
196 odeopts.InitialStep = odeopts.direction * ... 195 odeopts.InitialStep = odeopts.direction * ...
197 starting_stepsize (order, fcn, trange(1), init, 196 starting_stepsize (order, fcn, trange(1), init,
198 odeopts.AbsTol, odeopts.RelTol, 197 odeopts.AbsTol, odeopts.RelTol,
199 strcmpi (odeopts.NormControl, "on"), 198 strcmpi (odeopts.NormControl,
200 odeopts.funarguments); 199 "on"), odeopts.funarguments);
201 endif 200 endif
202 201
203 if (! isempty (odeopts.Mass)) 202 if (! isempty (odeopts.Mass))
204 if (isnumeric (odeopts.Mass)) 203 if (isnumeric (odeopts.Mass))
205 havemasshandle = false; 204 havemasshandle = false;
227 fcn = @(t,x) mass (t, odeopts.funarguments{:}) ... 226 fcn = @(t,x) mass (t, odeopts.funarguments{:}) ...
228 \ fcn (t, x, odeopts.funarguments{:}); 227 \ fcn (t, x, odeopts.funarguments{:});
229 endif 228 endif
230 endif 229 endif
231 230
232 if (nargout == 1) 231 if (numel (trange) > 2)
233 ## Single output requires auto-selected intermediate times,
234 ## which is obtained by NOT specifying specific solution times.
235 trange = [trange(1); trange(end)];
236 odeopts.Refine = []; # disable Refine when single output requested
237 elseif (numel (trange) > 2)
238 odeopts.Refine = []; # disable Refine when specific times requested 232 odeopts.Refine = []; # disable Refine when specific times requested
239 endif 233 endif
240 234
241 solution = integrate_adaptive (@runge_kutta_45_dorpri, 235 solution = integrate_adaptive (@runge_kutta_45_dorpri,
242 order, fcn, trange, init, odeopts); 236 order, fcn, trange, init, odeopts);
244 ## Postprocessing, do whatever when terminating integration algorithm 238 ## Postprocessing, do whatever when terminating integration algorithm
245 if (odeopts.haveoutputfunction) # Cleanup plotter 239 if (odeopts.haveoutputfunction) # Cleanup plotter
246 feval (odeopts.OutputFcn, [], [], "done", odeopts.funarguments{:}); 240 feval (odeopts.OutputFcn, [], [], "done", odeopts.funarguments{:});
247 endif 241 endif
248 if (! isempty (odeopts.Events)) # Cleanup event function handling 242 if (! isempty (odeopts.Events)) # Cleanup event function handling
249 ode_event_handler (odeopts.Events, solution.t(end), 243 ode_event_handler (odeopts.Events, solution.ode_t(end), ...
250 solution.x(end,:).', "done", odeopts.funarguments{:}); 244 solution.ode_x(end,:).', "done", ...
245 odeopts.funarguments{:});
251 endif 246 endif
252 247
253 ## Print additional information if option Stats is set 248 ## Print additional information if option Stats is set
254 if (strcmpi (odeopts.Stats, "on")) 249 if (strcmpi (odeopts.Stats, "on"))
255 nsteps = solution.cntloop; # cntloop from 2..end 250 nsteps = solution.cntloop; # cntloop from 2..end
263 printf ("Number of failed attempts: %d\n", nfailed); 258 printf ("Number of failed attempts: %d\n", nfailed);
264 printf ("Number of function calls: %d\n", nfevals); 259 printf ("Number of function calls: %d\n", nfevals);
265 endif 260 endif
266 261
267 if (nargout == 2) 262 if (nargout == 2)
268 varargout{1} = solution.t; # Time stamps are first output argument 263 varargout{1} = solution.output_t; # Time stamps are first output argument
269 varargout{2} = solution.x; # Results are second output argument 264 varargout{2} = solution.output_x; # Results are second output argument
270 elseif (nargout == 1) 265 elseif (nargout == 1)
271 varargout{1}.x = solution.t.'; # Time stamps saved in field x (row vector) 266 varargout{1}.x = solution.ode_t.'; #Time stamps saved in field x (row vect.)
272 varargout{1}.y = solution.x.'; # Results are saved in field y (row vector) 267 varargout{1}.y = solution.ode_x.'; #Results are saved in field y (row vect.)
273 varargout{1}.solver = solver; # Solver name is saved in field solver 268 varargout{1}.solver = solver; # Solver name is saved in field solver
274 if (! isempty (odeopts.Events)) 269 if (! isempty (odeopts.Events))
275 varargout{1}.xe = solution.event{3}; # Time info when an event occurred 270 varargout{1}.xe = solution.event{3}.'; # Time info when an event occurred
276 varargout{1}.ye = solution.event{4}; # Results when an event occurred 271 varargout{1}.ye = solution.event{4}.'; # Results when an event occurred
277 varargout{1}.ie = solution.event{2}; # Index info which event occurred 272 varargout{1}.ie = solution.event{2}.'; # Index info which event occurred
278 endif 273 endif
279 if (strcmpi (odeopts.Stats, "on")) 274 if (strcmpi (odeopts.Stats, "on"))
280 varargout{1}.stats = struct (); 275 varargout{1}.stats = struct ();
281 varargout{1}.stats.nsteps = nsteps; 276 varargout{1}.stats.nsteps = nsteps;
282 varargout{1}.stats.nfailed = nfailed; 277 varargout{1}.stats.nfailed = nfailed;
285 varargout{1}.stats.ndecomps = ndecomps; 280 varargout{1}.stats.ndecomps = ndecomps;
286 varargout{1}.stats.nlinsols = nlinsols; 281 varargout{1}.stats.nlinsols = nlinsols;
287 endif 282 endif
288 elseif (nargout > 2) 283 elseif (nargout > 2)
289 varargout = cell (1,5); 284 varargout = cell (1,5);
290 varargout{1} = solution.t; 285 varargout{1} = solution.output_t;
291 varargout{2} = solution.x; 286 varargout{2} = solution.output_x;
292 if (! isempty (odeopts.Events)) 287 if (! isempty (odeopts.Events))
293 varargout{3} = solution.event{3}; # Time info when an event occurred 288 varargout{3} = solution.event{3}; # Time info when an event occurred
294 varargout{4} = solution.event{4}; # Results when an event occurred 289 varargout{4} = solution.event{4}; # Results when an event occurred
295 varargout{5} = solution.event{2}; # Index info which event occurred 290 varargout{5} = solution.event{2}; # Index info which event occurred
296 endif 291 endif
353 %! if (strcmp (flag, "init")) 348 %! if (strcmp (flag, "init"))
354 %! if (! isequal (size (t), [2, 1])) 349 %! if (! isequal (size (t), [2, 1]))
355 %! error ('fout: step "init"'); 350 %! error ('fout: step "init"');
356 %! endif 351 %! endif
357 %! elseif (isempty (flag)) 352 %! elseif (isempty (flag))
358 %! if (! isequal (size (t), [1, 1])) 353 %! # Multiple steps can be sent in one function call
354 %! if (! isequal ( size (t), size (y)))
359 %! error ('fout: step "calc"'); 355 %! error ('fout: step "calc"');
360 %! endif 356 %! endif
361 %! elseif (strcmp (flag, "done")) 357 %! elseif (strcmp (flag, "done"))
362 %! if (! isempty (t)) 358 %! if (! isempty (t))
363 %! warning ('fout: step "done"'); 359 %! warning ('fout: step "done"');
369 %! 365 %!
370 %!test # two output arguments 366 %!test # two output arguments
371 %! [t, y] = ode45 (@fpol, [0 2], [2 0]); 367 %! [t, y] = ode45 (@fpol, [0 2], [2 0]);
372 %! assert ([t(end), y(end,:)], [2, fref], 1e-2); 368 %! assert ([t(end), y(end,:)], [2, fref], 1e-2);
373 %!test # not too many steps 369 %!test # not too many steps
374 %! [t, y] = ode45 (@fpol, [0 2], [2 0]); 370 %! [t, y] = ode45 (@fpol, [0 2], [2 0], odeset("Refine",1));
375 %! assert (size (t) < 20); 371 %! assert (size (t) < 20);
372 %!test # correct number of steps with Refine
373 %! [t1, y1] = ode45 (@fpol, [0 2], [2 0], odeset ("Refine", 1));
374 %! [t2, y2] = ode45 (@fpol, [0 2], [2 0], odeset ("Refine", 4));
375 %! [t3, y3] = ode45 (@fpol, [0 2], [2 0]); #default Refine=4
376 %! s = ode45 (@fpol, [0 2], [2 0], odeset ("Refine", 4));
377 %! assert (length (t2) == length (t3));
378 %! assert (length (t2) == 4*length (t1) - 3);
379 %! assert (length (s.x) == length (t1));
376 %!test # anonymous function instead of real function 380 %!test # anonymous function instead of real function
377 %! fvdp = @(t,y) [y(2); (1 - y(1)^2) * y(2) - y(1)]; 381 %! fvdp = @(t,y) [y(2); (1 - y(1)^2) * y(2) - y(1)];
378 %! [t, y] = ode45 (fvdp, [0 2], [2 0]); 382 %! [t, y] = ode45 (fvdp, [0 2], [2 0]);
379 %! assert ([t(end), y(end,:)], [2, fref], 1e-2); 383 %! assert ([t(end), y(end,:)], [2, fref], 1e-2);
380 %!test # string instead of function 384 %!test # string instead of function
390 %!test # Solve another anonymous function below zero 394 %!test # Solve another anonymous function below zero
391 %! vref = [0, 14.77810590694212]; 395 %! vref = [0, 14.77810590694212];
392 %! [t, y] = ode45 (@(t,y) y, [-2 0], 2); 396 %! [t, y] = ode45 (@(t,y) y, [-2 0], 2);
393 %! assert ([t(end), y(end,:)], vref, 1e-1); 397 %! assert ([t(end), y(end,:)], vref, 1e-1);
394 %!test # InitialStep option 398 %!test # InitialStep option
395 %! opt = odeset ("InitialStep", 1e-8); 399 %! opt = odeset ("InitialStep", 1e-8, "Refine", 1);
396 %! [t, y] = ode45 (@fpol, [0 0.2], [2 0], opt); 400 %! [t, y] = ode45 (@fpol, [0 0.2], [2 0], opt);
397 %! assert ([t(2)-t(1)], [1e-8], 1e-9); 401 %! assert ([t(2)-t(1)], [1e-8], 1e-9);
398 %!test # MaxStep option 402 %!test # MaxStep option
399 %! opt = odeset ("MaxStep", 1e-3); 403 %! opt = odeset ("MaxStep", 1e-3);
400 %! sol = ode45 (@fpol, [0 0.2], [2 0], opt); 404 %! sol = ode45 (@fpol, [0 0.2], [2 0], opt);
445 %! assert ([sol.x(end); sol.y(:,end)], [2; fref'], 1e-5); 449 %! assert ([sol.x(end); sol.y(:,end)], [2; fref'], 1e-5);
446 %!test # Keeps initial values while integrating 450 %!test # Keeps initial values while integrating
447 %! opt = odeset ("NonNegative", 2); 451 %! opt = odeset ("NonNegative", 2);
448 %! sol = ode45 (@fpol, [0 2], [2 0], opt); 452 %! sol = ode45 (@fpol, [0 2], [2 0], opt);
449 %! assert ([sol.x(end); sol.y(:,end)], [2; 2; 0], 0.5); 453 %! assert ([sol.x(end); sol.y(:,end)], [2; 2; 0], 0.5);
450 %!test # Details of OutputSel and Refine can't be tested 454 %!test # Details of OutputSel can't be tested
451 %! opt = odeset ("OutputFcn", @fout, "OutputSel", 1, "Refine", 5); 455 %! opt = odeset ("OutputFcn", @fout, "OutputSel", 1);
452 %! sol = ode45 (@fpol, [0 2], [2 0], opt); 456 %! sol = ode45 (@fpol, [0 2], [2 0], opt);
453 %!test # Stats must add further elements in sol 457 %!test # Stats must add further elements in sol
454 %! opt = odeset ("Stats", "on"); 458 %! opt = odeset ("Stats", "on");
455 %! stat_str = evalc ("sol = ode45 (@fpol, [0 2], [2 0], opt);"); 459 %! stat_str = evalc ("sol = ode45 (@fpol, [0 2], [2 0], opt);");
456 %! assert (strncmp (stat_str, "Number of successful steps:", 27)); 460 %! assert (strncmp (stat_str, "Number of successful steps:", 27));
462 %! assert (isfield (sol, "ie")); 466 %! assert (isfield (sol, "ie"));
463 %! assert (sol.ie(1), 2); 467 %! assert (sol.ie(1), 2);
464 %! assert (isfield (sol, "xe")); 468 %! assert (isfield (sol, "xe"));
465 %! assert (isfield (sol, "ye")); 469 %! assert (isfield (sol, "ye"));
466 %!test # Events option, now stop integration 470 %!test # Events option, now stop integration
467 %! warning ("off", "integrate_adaptive:unexpected_termination", "local");
468 %! opt = odeset ("Events", @fevn, "NormControl", "on"); 471 %! opt = odeset ("Events", @fevn, "NormControl", "on");
469 %! sol = ode45 (@fpol, [0 10], [2 0], opt); 472 %! sol = ode45 (@fpol, [0 10], [2 0], opt);
470 %! assert ([sol.ie, sol.xe, sol.ye], 473 %! assert ([sol.ie, sol.xe, sol.ye.'],
471 %! [2.0, 2.496110, -0.830550, -2.677589], 6e-1); 474 %! [2.0, 2.496110, -0.830550, -2.677589], 6e-1);
472 %!test # Events option, five output arguments 475 %!test # Events option, five output arguments
473 %! warning ("off", "integrate_adaptive:unexpected_termination", "local");
474 %! opt = odeset ("Events", @fevn, "NormControl", "on"); 476 %! opt = odeset ("Events", @fevn, "NormControl", "on");
475 %! [t, y, vxe, ye, vie] = ode45 (@fpol, [0 10], [2 0], opt); 477 %! [t, y, vxe, ye, vie] = ode45 (@fpol, [0 10], [2 0], opt);
476 %! assert ([vie, vxe, ye], 478 %! assert ([vie, vxe, ye],
477 %! [2.0, 2.496110, -0.830550, -2.677589], 6e-1); 479 %! [2.0, 2.496110, -0.830550, -2.677589], 6e-1);
478 %!test # Mass option as function 480 %!test # Mass option as function
509 ## "Vectorized" 511 ## "Vectorized"
510 512
511 %!test # Check that imaginary part of solution does not get inverted 513 %!test # Check that imaginary part of solution does not get inverted
512 %! sol = ode45 (@(x,y) 1, [0 1], 1i); 514 %! sol = ode45 (@(x,y) 1, [0 1], 1i);
513 %! assert (imag (sol.y), ones (size (sol.y))); 515 %! assert (imag (sol.y), ones (size (sol.y)));
514 %! [x, y] = ode45 (@(x,y) 1, [0 1], 1i); 516 %! [x, y] = ode45 (@(x,y) 1, [0 1], 1i, odeset ("Refine", 1));
515 %! assert (imag (y), ones (size (y))); 517 %! assert (imag (y), ones (size (y)));
518
519 ## FIXME: convert to demo or a visible=off test with failable assert/error
520 ## statemments
521 ##%!test # Make sure odeplot works (default OutputFcn when no return value)
522 ##%! ode45 (@fpol, [0 2], [2 0]);
523 ##%! close all
516 524
517 %!error <Invalid call> ode45 () 525 %!error <Invalid call> ode45 ()
518 %!error <Invalid call> ode45 (1) 526 %!error <Invalid call> ode45 (1)
519 %!error <Invalid call> ode45 (1,2) 527 %!error <Invalid call> ode45 (1,2)
520 %!error <TRANGE must be a numeric> ode45 (@fpol, {[0 25]}, [3 15 1]) 528 %!error <TRANGE must be a numeric> ode45 (@fpol, {[0 25]}, [3 15 1])