-
Notifications
You must be signed in to change notification settings - Fork 3
/
pnes_master.m
735 lines (689 loc) · 30.8 KB
/
pnes_master.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
function varargout = pnes_master(fcn, x0, opt)
% pnes_master - Parameterized Nonlinear Equation Solver wrapper function.
% ::
%
% [X, F, EXITFLAG, OUTPUT, JAC] = PNES_MASTER(FCN, X0, OPT)
% [X, F, EXITFLAG, OUTPUT, JAC] = PNES_MASTER(PROBLEM)
% A common wrapper function for numerical continuation methods for
% solving parameterized nonlinear equations. Traces the solutions of
% a parameterized nonlinear equation f(x) = 0, beginning from a starting
% point x0, where f(x) has dimension n and x has dimension n+1.
%
% In the current implementation, the last element of x is taken to be
% the parameter lambda, where lambda = 0 corresponds to the base solution.
%
% Inputs:
% FCN : handle to function that evaluates the function f(x) to
% be solved and its Jacobian, J(x). Calling syntax for this
% function is:
% f = FCN(x)
% [f, J] = FCN(x)
% For a parameterized function, f is n x 1, x is (n+1) x 1,
% and J is the n x (n+1) matrix of partial derivatives of
% f (rows) w.r.t. x (cols).
% X0 : starting value, x0, of vector x ((n+1) x 1)
% OPT : optional options structure with the following fields,
% all of which are also optional (default values shown in
% parentheses)
% alg ('DEFAULT') : determines which solver to use
% 'DEFAULT' : automatic, currently there is only one
% solver implementation, a predictor/corrector method
% verbose (0) - controls level of progress output displayed
% 0 = no progress output
% 1-5 = increasing levels of progress output
% nleqs_opt - options struct for NLEQS_MASTER used for
% corrector stage (see NLEQS_MASTER for details), default
% sets nleqs_opt.verbose to 0, otherwise to 2 if OPT.verbose > 4
% solve_base (1) : 0/1 flag that determines whether or not to
% run a corrector stage for initial solution point, x0
% parameterization (3) - choice of parameterization
% 1 - natural
% 2 - arc len
% 3 - pseudo arc len
% stop_at ('NOSE') - determines stopping criterion
% 'NOSE' - stop when limit or nose point is reached
% 'FULL' - trace full continuation curve
% <lam_stop> - stop upon reaching specified target lambda value
% max_it (2000) - maximum number of continuation steps
% step (0.05) - continuation step size
% adapt_step (0) - toggle adaptive step size feature
% 0 - adaptive step size disabled
% 1 - adaptive step size enabled
% adapt_step_damping (0.7) - damping factor for adaptive step sizing
% adapt_step_tol (1e-3) - tolerance for adaptive step sizing
% adapt_step_ws (1) - scale factor for default initial step size
% when warm-starting with adaptive step size enabled
% step_min (1e-4) - minimum allowed step size
% step_max (0.2) - maximum allowed step size
% default_event_tol (1e-3) - default tolerance for event functions
% target_lam_tol (0) - tolerance for target lambda detection, 0 means
% use the value of default_event_tol
% nose_tol (0) - tolerance for nose point detection, 0 means use
% the value of default_event_tol
% events (<empty>) - cell array of specs for user-defined event
% functions, passed as MY_EVENTS arg to PNE_REGISTER_EVENTS
% (see PNE_REGISTER_EVENTS for details).
% callbacks (<empty>) - cell array of specs for user-defined callback
% functions, to be passed as MY_CBACKS arg to
% PNE_REGISTER_CALLBACKS (see PNE_REGISTER_CALLBACKS for details).
% output_fcn (<empty>) - handle to custom output function, called by
% PNE_CALLBACK_DEFAULT
% plot - options for plotting of continuation curve by
% PNE_CALLBACK_DEFAULT
% .level (0) - control plotting of continuation curve
% 0 - do not plot continuation curve
% 1 - plot when completed
% 2 - plot incrementally at each continuation step
% 3 - same as 2, with 'pause' at each continuation step
% .idx (<empty>) - index of quantity to plot, passed to yfcn()
% .idx_default (<empty>) - function to provide default value
% for idx, if none provided
% .xname ('lam') - name of output field holding values that
% determine horizontal coordinates of plot
% .yname ('x') - name of output field holding values that
% determine vertical coordinates of plot
% .xfcn (<empty>) - handle to function that maps a value from
% the field of the OUTPUT indicated by value of plot.xname
% to a horizontal coordinate for plotting
% .yfcn (<empty>) - handle to function that maps a value from
% the field of the OUTPUT indicated by value of plot.yname
% and an index to be applied to that value into a vertical
% coordinate for plotting
% .xlabel ('\lambda') - label for horizontal axis
% .ylabel ('Variable Value') - label for vertical axis
% .title ('Value of Variable %d') - plot title used for plot of
% single variable, can use %d as placeholder for var index
% .title2 ('Value of Multiple Variables') - plot title used for
% plot of multiple variables
% .legend ('Variable %d') - legend label, %d can be used as
% placeholder for variable index
% warmstart (<empty>) - struct containing warm-start state, see
% warmstart field in OUTPUT below for details of expected
% fields
% PROBLEM : The inputs can alternatively be supplied in a single
% PROBLEM struct with fields corresponding to the input arguments
% described above: fcn, x0, opt
%
% Outputs (all optional, except X):
% X : solution vector x
% F : final function value, f(x)
% EXITFLAG : exit flag
% 1 = succeeded
% 0 = failed
% OUTPUT : output struct with the following fields:
% corrector - output return value from NLEQS_MASTER from final
% corrector run (see NLEQS_MASTER for details)
% iterations - N, total number of continuation steps performed
% events - struct array of size NE of events detected with fields:
% k - continuation step at which event was located
% name - name of detected event
% idx - index(es) of critical elements in corresponding event
% function
% msg - descriptive text detailing the event
% done_msg - message describing cause of continuation termination
% steps - (N+1) row vector of stepsizes taken at each continuation
% step
% lam_hat - (N+1) row vector of lambda values from prediction steps
% lam - (N+1) row vector of lambda values from correction steps
% max_lam - maximum value of parameter lambda (from OUTPUT.lam)
% warmstart - optional output with information needed for
% warm-starting an updated continuation problem, with fields:
% cont_steps - current value of continuation step counter
% direction - +1 or -1, for tracing of curve in same or
% opposite direction, respectively
% dir_from_jac_eigs - 0/1 flag to indicate whether to use
% the sign of the smallest eigenvalue of the Jacobian to
% determine the initial direction
% x - current solution vector
% z - current tangent vector
% xp - previous step solution vector
% zp - previous step tangent vector
% parm - function handle for current parameterization function
% default_parm - function handle for default parameterization fcn
% default_step - default step size
% events - current event log, same as OUTPUT.events
% cbs - struct containing user state information for callbacks
% see PNES_CALLBACK_DEFAULT for more details
% (others) - depends on OPT.output_fcn, by default (i.e. with no
% explicitly provided output function) includes fields:
% x_hat - NX x (N+1) matrix of solution values from
% prediction steps
% x - NX x (N+1) matrix of solution values from correction
% steps
% JAC : final Jacobian matrix, J(x)
%
% Calling syntax options:
% [x, f, exitflag, output, jac] = pnes_master(fcn, x0);
% [x, f, exitflag, output, jac] = pnes_master(fcn, x0, opt);
% x = pnes_master(problem);
% where problem is a struct with fields: fcn, x0, opt
% where opt is optional
% x = pnes_master(...);
% [x, f] = pnes_master(...);
% [x, f, exitflag] = pnes_master(...);
% [x, f, exitflag, output] = pnes_master(...);
% [x, f, exitflag, output, jac] = pnes_master(...);
%
% Example: (based on https://www.chilimath.com/lessons/advanced-algebra/systems-non-linear-equations/)
% function [f, J] = f1p(x)
% f = [ x(1) + x(2) + 6*x(3) - 1;
% -x(1)^2 + x(2) + 5 ];
% if nargout > 1
% J = [1 1 6; -2*x(1) 1 0];
% end
% end
% problem = struct( ...
% 'fcn', @(x)f1p(x), ...
% 'x0', [-1; 0; 0], ...
% 'opt', struct('verbose', 2, 'adapt_step', 1, 'step_max', 10) ...
% );
% [x, f, exitflag, output, jac] = pnes_master(problem);
%
% See also pne_callback_default, pne_register_callbacks, pne_register_events.
% MP-Opt-Model
% Copyright (c) 2013-2024, Power Systems Engineering Research Center (PSERC)
% by Ray Zimmerman, PSERC Cornell,
% Shrirang Abhyankar, Argonne National Laboratory,
% and Alexander Flueck, IIT
%
% This file is part of MP-Opt-Model.
% Covered by the 3-clause BSD License (see LICENSE file for details).
% See https://github.com/MATPOWER/mp-opt-model for more info.
%%----- input argument handling -----
%% gather inputs
if nargin == 1 && isstruct(fcn) %% problem struct
p = fcn;
fcn = p.fcn;
x0 = p.x0;
if isfield(p, 'opt'), opt = p.opt; else, opt = struct(); end
else %% individual args
if nargin < 3
opt = struct();
end
end
%% default options
if isfield(opt, 'verbose') && opt.verbose > 4
nleqs_opt_verbose = 2;
else
nleqs_opt_verbose = 0;
end
dopts = struct( ...
'alg', 'DEFAULT', ... %% algorithm
'verbose', 0, ...
'nleqs_opt', struct('verbose', nleqs_opt_verbose), ...
'solve_base', 1, ... %% run corrector for initial point
'parameterization', 3, ... %% 1 - natural, 2 - arc len, 3 - pseudo arc len
'stop_at', 'NOSE', ... %% 'NOSE', 'FULL', <lam_stop>
'max_it', 2000, ... %% maximum number of continuation steps
'step', 0.05, ... %% continuation step size
'step_min', 1e-4, ... %% minimum allowed step size
'step_max', 0.2, ... %% maximum allowed step size
'adapt_step', 0, ... %% 0/1 toggle, adaptive step size
'adapt_step_ws', 1, ... %% warm start inital step size scale factor
'adapt_step_damping', 0.7, ... %% adaptive step sizing damping factor
'adapt_step_tol', 1e-3, ... %% adaptive step sizing tolerance
'default_event_tol',1e-3, ... %% default event function tolerance
'target_lam_tol', 0, ... %% event function tolerance for TARGET_LAM event
'nose_tol', 0, ... %% event function tolerance for NOSE event
'events', {{}}, ... %% user-defined event detection functions
'callbacks', {{}}, ... %% user-defined callback functions
'output_fcn', [], ... %% custom output fcn, default callback
'warmstart', [], ... %% struct containing warm-start state
'plot', struct( ... %% used by pne_callback_default() for plotting
'level', 0, ... %% 0 - no plot, 1 - final, 2 - steps, 3 - steps w/pause
'idx', [], ... %% index of quantity to plot, passed to yfcn()
'idx_default', [], ... %% fcn to provide default value for idx, if none provided
'xfcn', [], ... %% fcn to compute x-coord from data
'yfcn', [], ... %% fcn to compute x-coord from data, idx
'title', 'Value of Variable %d', ... %% plot title for single var plot
'title2', 'Value of Multiple Variables', ... %% plot title for multiple var plot
'xname', 'lam', ... %% name of output field holding x vals
'yname', 'x', ... %% name of output field holding y vals
'xlabel', '\lambda', ... %% horizontal axis label
'ylabel', 'Variable Value', ... %% vertical axis label
'legend', 'Variable %d' ... %% legend label
) ...
);
opt = nested_struct_copy(dopts, opt);
%% use opt.default_event_tol for NOSE and TARGET_LAM events, unless specified
if opt.target_lam_tol == 0
opt.target_lam_tol = opt.default_event_tol;
end
if opt.nose_tol == 0
opt.nose_tol = opt.default_event_tol;
end
if opt.max_it == 0 %% zero means use the default
opt.max_it == dopts.max_it;
end
%% initialize
warmstarted = ~isempty(opt.warmstart);
s = struct( ... %% container struct for various variables, flags
'done', 0, ... %% flag indicating continuation has terminated
'done_msg', '', ... %% termination message
'warmstart',[], ... %% warm start state to return when done (to pass
...%% to subsequent warm-started call to PNES_MASTER)
'rollback', 0, ... %% flag to indicate a step must be rolled back
'events', [], ... %% struct array for detected events
'results', [] ); %% results struct
%% register event and callback functions
switch opt.stop_at
case 'NOSE'
my_events = {{'NOSE', @pne_event_nose, opt.nose_tol}, opt.events{:}};
my_cbacks = {{@pne_callback_nose, 51}, opt.callbacks{:}};
case 'FULL'
my_events = {{'TARGET_LAM', @pne_event_target_lam, opt.target_lam_tol}, opt.events{:}};
my_cbacks = {{@pne_callback_target_lam, 50}, opt.callbacks{:}};
otherwise %% numeric, stop at target lam or nose point, whichever is 1st
my_events = {{'TARGET_LAM', @pne_event_target_lam, opt.target_lam_tol}, ...
{'NOSE', @pne_event_nose, opt.nose_tol}, ...
opt.events{:}};
my_cbacks = {{@pne_callback_target_lam, 50}, ...
{@pne_callback_nose, 51}, ...
opt.callbacks{:}};
end
my_cbacks{end+1} = {@pne_callback_default, 0};
reg_ev = pne_register_events(my_events, opt); %% registered event functions
reg_cb = pne_register_callbacks(my_cbacks); %% registered callback functions
nef = length(reg_ev); %% number of registered event functions
ncb = length(reg_cb); %% number of registered callback functions
%% initialize continuation step counter
if warmstarted
cont_steps = opt.warmstart.cont_steps + 1;
if opt.verbose
fprintf('... CONTINUATION RESUMED\n');
end
else
cont_steps = 0;
if opt.verbose
v = mpomver('all');
fprintf('\nMP-Opt-Model Version %s, %s', v.Version, v.Date);
fprintf(' -- Predictor/Corrector Continuation Method\n');
end
end
%% solve corrector step for base point
if opt.solve_base && ~warmstarted
cfcn = @(xx)pne_corrector_fcn(xx, fcn, @pne_pfcn_natural, x0, 0, []);
[x, f, exitflag, out] = nleqs_master(cfcn, x0, opt.nleqs_opt);
if exitflag
if opt.verbose > 1
fprintf('step %3d : lambda = %6.3f, %2d corrector steps\n', cont_steps, x0(end), out.iterations);
end
else
s.done = 1;
s.done_msg = sprintf('base solution did not converge in %d iterations', out.iterations);
if opt.verbose
fprintf('%s\n', s.done_msg);
end
end
else
x = x0; %% ignored for warmstart, overwritten by warmstart.cx
end
%% initialize numerical continuation
if ~s.done
locating = 0; %% flag to indicate that an event interval was detected,
%% but the event has not yet been located
rb_cnt_ef = 0; %% counter for rollback steps triggered by event function intervals
rb_cnt_cb = 0; %% counter for rollback steps triggered directly by callbacks
if warmstarted
manual_direction_switch = 0; %% set to 1 to manually prompt for
%% direction change upon warmstart
ws = opt.warmstart;
x = ws.x; %% starting value solution vector
z = ws.z; %% starting value of tangent vector
step = 0; %% re-solve current point
parm = ws.parm;
direction = ws.direction;
default_parm = ws.default_parm;
default_step = ws.default_step;
cbs = ws.cbs;
event_log = ws.events;
if manual_direction_switch
%% decide whether to switch directions
reply = input('Switch directions? Y/N [N]:','s');
if strcmp(upper(reply), 'Y')
direction = -direction;
end
elseif isfield(ws, 'dir_from_jac_eigs') && ws.dir_from_jac_eigs
%% attempt to determine direction from smalles Jacobian eigenvalue
[~, J] = fcn(x);
eigs_opt.tol = 1e-3;
eigs_opt.maxit = 2*length(x);
direction = sign(z(end) * ...
min(real(eigs(J(:,1:end-1), 1, 'sr', eigs_opt))));
end
if opt.adapt_step %% hey, maybe slow down, things might have changed
default_step = default_step * opt.adapt_step_ws;
end
else
%% initialize parameterization function
switch opt.parameterization
case 1
parm = @pne_pfcn_natural; %% NAT
case 2
parm = @pne_pfcn_arc_len; %% ARC
case 3
parm = @pne_pfcn_pseudo_arc_len; %% PAL
otherwise
error('pnes_master: OPT.parameterization (= %d) must be 1, 2, or 3', opt.parameterization);
end
%% finish initializing tangent vector
direction = 1;
z0 = zeros(length(x), 1); z0(end) = direction; %% +ve lambda direction
z = pne_tangent(x, x, z0, fcn, parm, direction);
step = opt.step;
default_step = step;
default_parm = parm;
cbs = [];
event_log = [];
end
%% initialize state struct for current continuation step
cx = struct( ... %% current state
'x_hat', x, ... %% predicted solution value
'x', x, ... %% corrected solution value
'z', z, ... %% normalized tangent vector
'default_step', default_step, ... %% default step size
'default_parm', default_parm, ... %% default parameterization
'this_step', [], ... %% step size for this step only
'this_parm', [], ... %% parameterization for this step only
'step', step, ... %% current step size
'parm', parm, ... %% current parameterization
'events', event_log, ... %% event log
'cbs', cbs, ... %% user-defined callback state
'efv', [] ... %% event function values
);
%% initialize event function values
cx.efv = cell(nef, 1);
for k = 1:nef
cx.efv{k} = reg_ev(k).fcn(cx, opt);
end
if warmstarted %% no need to initialize callbacks
%% initialize state for previous continuation step
px = cx;
px.x = ws.xp; %% use warm start value for solution value
px.z = ws.zp; %% use warm start value for tangent
else
%% invoke callbacks - "initialize" context
for k = 1:ncb
[nx, cx, s] = reg_cb(k).fcn(cont_steps, cx, cx, cx, s, opt);
end
cont_steps = cont_steps + 1;
%% check for case with base and target the same
%% evaluate function at lambda = 0 (base)
if opt.solve_base
fb = f(1:end-1);
else
fb = fcn(x);
exitflag = 1;
end
%% evaluate function at lambda = 1 (target)
xt = x;
xt(end) = 1;
ft = fcn(xt);
if norm(fb - ft, Inf) < 1e-12
s.done = 1;
s.done_msg = 'base and target functions are identical';
end
%% initialize state for previous continuation step
px = cx;
end
end
%%----- run numerical continuation -----
while ~s.done
%% initialize next candidate with current state
nx = cx;
%% predictor step
nx.x_hat = cx.x + cx.step * cx.z;
%% corrector step
cfcn = @(xx)pne_corrector_fcn(xx, fcn, cx.parm, cx.x, cx.step, cx.z);
[nx.x, f, exitflag, out] = nleqs_master(cfcn, nx.x_hat, opt.nleqs_opt);
if ~exitflag %% corrector failed
s.done = 1;
s.done_msg = sprintf('Corrector did not converge in %d iterations.', out.iterations);
if opt.verbose
fprintf('step %3d : %s stepsize = %-9.3g lambda = %6.3f corrector did not converge in %d iterations\n', cont_steps, pne_ptag(cx.parm), cx.step, nx.x(end), out.iterations);
end
cont_steps = max(cont_steps - 1, 1); %% go back to last step, but not to 0
break;
end
%% compute new tangent direction, based on current or prev state: tx
if nx.step == 0 %% if this is a re-do step, cx and nx are the same
tx = px; %% so use px as the previous state
else %% otherwise
tx = cx; %% use cx as the previous state
end
nx.z = pne_tangent(nx.x, tx.x, tx.z, fcn, nx.parm, direction);
direction = 1; %% continue in same direction
%% detect events
for k = 1:nef
nx.efv{k} = reg_ev(k).fcn(nx, opt); %% update event function values
end
[s.rollback, s.events, nx.efv] = ...
pne_detect_events(reg_ev, nx.efv, cx.efv, nx.step);
%% adjust step-size to locate event function zero, if necessary
if s.rollback %% current step overshot
%% roll back & initialize next step size based on rollback and previous
rx = nx; %% save state we're rolling back from
rx_evnts = s.events; %% and critical event info
cx.this_step = s.events.step_scale * rx.step;
cx.this_parm = rx.parm; %% keep same parameterization as last step
locating = 1; %% enter "locating" mode (or stay in it)
rb_cnt_ef = rb_cnt_ef + 1; %% increment rollback counter for ef intervals
if rb_cnt_ef > 26
s.done = 1;
s.done_msg = sprintf('Could not locate %s event!', s.events.name);
end
if opt.verbose > 3
loc_msg = sprintf('OVERSHOOT : f = [%g, <<%g>>], step <-- %.4g', ...
cx.efv{s.events.eidx}(s.events.idx(1)), ...
rx.efv{s.events.eidx}(s.events.idx(1)), cx.this_step);
end
elseif locating
if s.events(1).zero %% found the zero!
%% step size will be reset to previously used default step size
locating = 0; %% exit "locating" mode
rb_cnt_ef = 0; %% reset rollback counter for ef intervals
if opt.verbose > 3
loc_msg = sprintf('ZERO! : f = %g, step <-- %.4g', ...
nx.efv{rx_evnts.eidx}(rx_evnts.idx(1)), nx.default_step);
end
else %% prev rollback undershot
%% initialize next step size based on critical event function
%% values from prev rollback step and current step
rx_efv = rx.efv{rx_evnts.eidx}(rx_evnts.idx(1));
cx_efv = nx.efv{rx_evnts.eidx}(rx_evnts.idx(1));
step_scale = cx_efv / (cx_efv - rx_efv);
nx.this_step = step_scale * (rx.step - nx.step);
rb_cnt_ef = 0; %% reset rollback counter for ef intervals
if opt.verbose > 3
loc_msg = sprintf('UNDERSHOOT : f [<<%g>>, %g], step <-- %.4g', ...
cx_efv, rx_efv, nx.this_step);
end
end
else %% normal step, not locating anything
loc_msg = '';
end
%% invoke callbacks - "iterations" context
rb = s.rollback;
for k = 1:ncb
[nx, cx, s] = reg_cb(k).fcn(cont_steps, nx, cx, px, s, opt);
end
if ~rb && s.rollback %% rollback triggered by callback (vs event function interval)
rb_cnt_cb = rb_cnt_cb + 1; %% increment rollback counter for callbacks
if rb_cnt_cb > 26
s.done = 1;
s.done_msg = 'Too many rollback steps triggered by callbacks!';
end
else
rb_cnt_cb = 0; %% reset rollback counter for callbacks
end
%% print iteration information
if opt.verbose > 1
%% set label for rollback step counter
if rb_cnt_ef
sub_step = char('a' + rb_cnt_ef - 1);
elseif rb_cnt_cb
sub_step = char('A' + rb_cnt_cb - 1);
else
sub_step = ' ';
end
fprintf('step %3d%s : %s stepsize = %-9.3g lambda = %6.3f', cont_steps, sub_step, pne_ptag(cx.parm), cx.step, nx.x(end));
if opt.verbose < 5
fprintf(' %2d corrector steps', out.iterations);
end
if s.rollback
fprintf(' ^ ROLLBACK\n');
else
fprintf('\n');
end
if opt.verbose > 3 && ~isempty(loc_msg)
fprintf(' LOCATING -- %s\n', loc_msg);
end
end
%% log events
for k = 1:length(s.events)
if s.events(k).log
e = struct( 'k', cont_steps, ...
'name', s.events(k).name, ...
'idx', s.events(k).idx, ...
'msg', s.events(k).msg );
if isempty(nx.events)
nx.events = e;
else
nx.events(end+1) = e;
end
end
if (opt.verbose > 2 && s.events(k).log) || ...
(opt.verbose > 3 && s.events(k).eidx)
fprintf(' %s\n', s.events(k).msg);
end
end
%% adapt stepsize if requested and not terminating, locating a zero
%% or warm starting
if opt.adapt_step && ~s.done && ~locating && ~s.events(1).zero && nx.step ~= 0
pred_error = norm(nx.x - nx.x_hat, Inf);
%% new nominal step size is current size * tol/err, but we reduce
%% the change from the current size by a damping factor and limit
%% increases to a factor of 2
step_scale = min(2, 1 + opt.adapt_step_damping * ...
(opt.adapt_step_tol/pred_error - 1));
nx.default_step = nx.step * step_scale;
%% limit step-size
if nx.default_step > opt.step_max
nx.default_step = opt.step_max;
end
if nx.default_step < opt.step_min
nx.default_step = opt.step_min;
end
end
%% if this is a normal step
if ~s.rollback
px = cx; %% save current state before update
cx = nx; %% update current state to next candidate
if ~s.done
if cont_steps >= opt.max_it
s.done = 1;
s.done_msg = sprintf('Reached maximun number of continuation steps (opt.max_it = %d)', opt.max_it);
else
cont_steps = cont_steps + 1;
end
end
end
%% set step size and parameterization, from one-time or defaults
if isempty(cx.this_step)
cx.step = cx.default_step;
else
cx.step = cx.this_step;
cx.this_step = []; %% disable for next time
end
if isempty(cx.this_parm)
cx.parm = cx.default_parm;
else
cx.parm = cx.this_parm;
cx.this_parm = []; %% disable for next time
end
end %% while ~s.done
%% invoke callbacks - "final" context
s.results = struct(); %% initialize results struct
for k = 1:ncb
[nx, cx, s] = reg_cb(k).fcn(-cont_steps, nx, cx, px, s, opt);
end
output = s.results;
output.done_msg = s.done_msg;
output.events = cx.events; %% copy eventlog to results
output.corrector = out; %% output from last corrector run
%% prepare to exit
if isempty(s.warmstart)
if opt.verbose
fprintf('CONTINUATION TERMINATION: %s\n', s.done_msg);
end
else
%% save warmstart values
ws = s.warmstart;
ws.cont_steps = cont_steps;
ws.direction = direction;
%% from state at current step
ws.x = cx.x; %% state from current step
ws.z = cx.z; %% tangent vector from current step
ws.parm = cx.parm;
ws.default_parm = cx.default_parm;
ws.default_step = cx.default_step;
ws.events = cx.events;
ws.cbs = cx.cbs;
%% from state at previous step
ws.xp = px.x; %% state from previous step
ws.zp = px.z; %% tangent vector from previous step
output.warmstart = ws;
if opt.verbose
fprintf('%s : CONTINUATION SUSPENDED ...\n', s.done_msg);
end
end
%% output arguments
if nargout > 4
[f, J] = fcn(cx.x);
elseif nargout > 1
f = fcn(cx.x);
end
varargout{1} = cx.x;
if nargout > 1
varargout{2} = f;
if nargout > 2
varargout{3} = exitflag;
if nargout > 3
varargout{4} = output;
if nargout > 4
varargout{5} = J;
end
end
end
end
%%----- pne_corrector_fcn -----
%% fcn(x) combined with parameterization constraint
function [fp, dfp] = pne_corrector_fcn(x, fcn, parm, cx_x, step, z)
if nargout < 2
fp = [ fcn(x); parm(x, cx_x, step, z) ];
else
[f, df] = fcn(x);
[p, dp] = parm(x, cx_x, step, z);
fp = [f; p];
dfp = [df; dp];
end
%%----- pne_tangent -----
%% find normalized tangent vector
function z = pne_tangent(x, xp, zp, fcn, parm, direction)
[f, df] = fcn(x);
[p, dp] = parm(x, xp, 0, zp);
rhs = [ zeros(length(f), 1); direction ];
z = [df; dp] \ rhs;
z = z / norm(z); %% normalize it
%%----- pne_ptag -----
%% return 3-letter string to indicate parameterization scheme
%% NAT - natural, ARC - arc length, PAL - pseudo arc length
function ptag = pne_ptag(parm)
switch func2str(parm)
case 'pne_pfcn_natural'
ptag = 'NAT';
case 'pne_pfcn_arc_len'
ptag = 'ARC';
case 'pne_pfcn_pseudo_arc_len'
ptag = 'PAL';
end