-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathbdControl.m
1278 lines (1103 loc) · 52.8 KB
/
bdControl.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
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
classdef bdControl < handle
%bdControl Control panel for the Brain Dynamics Toolbox GUI.
% The bdControl class implements the graphical control panel used
% by bdGUI. It is not intended to be called directly by users.
%
%AUTHORS
% Stewart Heitmann (2016a,2017a-c,2018a-b,2019a)
% Copyright (C) 2016-2019 QIMR Berghofer Medical Research Institute
% All rights reserved.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions
% are met:
%
% 1. Redistributions of source code must retain the above copyright
% notice, this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright
% notice, this list of conditions and the following disclaimer in
% the documentation and/or other materials provided with the
% distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
% COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
% POSSIBILITY OF SUCH DAMAGE.
properties
sys % working copy of the user-supplied system definition
sol = [] % solution struct returned by the matlab solver
par = [] % copy of the the parameters used to compute sol
lag = [] % copy of the the lag parameters used to compute sol
tindx % indices of the non-transient time steps in sol.x
solver % the active solver function
solvertype % solver type string ('odesolver' or 'ddesolver' or 'sdesolver')
end
properties (Access=private)
fig % handle to the parent figue
cpanel % handle to control panel
spanel % handle to solver panel
% Widgets in the control panel
ui_hold % handle to the noise HOLD button (SDE only)
ui_evolve % handle to the EVOLVE button
ui_perturb % handle to the PERTURB button
ui_rand % handle to the RAND button
ui_run % handle to the RUN button
ui_reverse % handle to the REVERSE button
% Widgets in the solver panel
ui_solver % handle to the SOLVER popup menu
ui_halt % handle to the HALT button
ui_nsteps % handle to the NSTEPS counter
ui_nfailed % handle to the NFAILED counter
ui_nfevals % handle to the NFEVALS counter
ui_cputime % handle to the CPU counter
ui_progress % handle to the PROGRESS counter
ui_warning % handle to the WARNING text
% internal states
recomputeflag % flag for recompute events
cpustart % cpu start time
timer % handle to timer object
end
properties (Constant)
cpanely = 0; % vertical position of the control panel
cpanelm = 0; % vertical margin at top of control panel
cpanelw = 244; % width of the control panel
end
events
recompute % notifies the control panel that sol must be recomputed
redraw % notifes all display panels that sol must be replotted
refresh % notifies the control panel widgets to refresh their values
vardef % notifies the control panel widgets that sys.vardef has changed
pardef % notifies the control panel widgets that sys.pardef has changed
lagdef % notifies the control panel widgets that sys.lagdef has changed
end
methods
% Constructor.
function this = bdControl(fig,sys)
% Check the contents of sys and fill any missing fields with
% default values. Rethrow any problems back to the caller.
try
this.sys = bd.syscheck(sys);
catch ME
throwAsCaller(MException('bdtoolkit:bdControl',ME.message));
end
% init the recompute flag
this.recomputeflag = false;
% remember the parent figure
this.fig = fig;
% init the sol struct
this.sol.x=[];
this.sol.y=[];
this.sol.yp=[];
this.sol.stats.nsteps=0;
this.sol.stats.nfailed=0;
this.sol.stats.nfevals=0;
% init the indicies of the non-transient time steps in sol.x
this.tindx = (this.sol.x >= this.sys.tval);
% currently active solver (FIX ME TO ALLOW SOLVER SELECTION BEYOND THE FIRST SOLVER ONLY)
if isfield(this.sys,'odesolver')
this.solver = this.sys.odesolver{1};
this.solvertype = 'odesolver';
end
if isfield(this.sys,'ddesolver')
this.solver = this.sys.ddesolver{1};
this.solvertype = 'ddesolver';
end
if isfield(this.sys,'sdesolver')
this.solver = this.sys.sdesolver{1};
this.solvertype = 'sdesolver';
end
% initialise the control panel
this.ControlPanelInit();
% initialise the solver panel
this.SolverPanelInit();
% force a refresh of all widgets at startup
this.RefreshListener();
% listen for future widget refresh events
addlistener(this,'refresh',@(~,~) this.RefreshListener());
% listen for redraw events
%addlistener(this,'redraw',@(~,~) this.RedrawListener());
% listen for recompute events
addlistener(this,'recompute',@(~,~) this.RecomputeListener());
% init the timer object and start it.
this.timer = timer('BusyMode','drop', ...
'ExecutionMode','fixedSpacing', ...
'Period',0.05, ...
'TimerFcn', @(~,~) this.TimerFcn());
start(this.timer);
end
% Load a user-supplied sol structure.
function LoadSol(this,sol)
% Load the solution structure
this.sol = sol;
% Replicate some of the post-compute operations normally done by this.Recompute()
% 1. Remember the parameters of the (to be computed) solution in the control.par struct.
for indx = 1:numel(this.sys.pardef)
name = this.sys.pardef(indx).name;
value = this.sys.pardef(indx).value;
this.par.(name) = value;
end
% 2. Remember the lag parameters too (if applicable)
if isfield(this.sys,'lagdef')
for indx = 1:numel(this.sys.lagdef)
name = this.sys.lagdef(indx).name;
value = this.sys.lagdef(indx).value;
this.lag.(name) = value;
end
end
% 3. Update the indices of the non-transient steps in sol.x
this.tindx = (this.sol.x >= this.sys.tval) & min(isfinite(this.sol.y));
% notify all listeners that a redraw is required
notify(this,'redraw');
end
function pos = CanvasPosition(this)
% get parent figure geometry
figw = this.fig.Position(3);
figh = this.fig.Position(4);
% get cpanel width
cpanelw = this.cpanel.Position(3);
% position of canvas
pos = [0 50 figw-cpanelw figh-50];
end
% Resize the control panel to fit the figure window.
function SizeChanged(this,fig)
% get parent figure geometry
figw = fig.Position(3);
figh = fig.Position(4);
% new geometry of the control panel
x = figw - this.cpanelw;
y = this.cpanely;
w = this.cpanelw;
h = figh - this.cpanely - this.cpanelm;
this.cpanel.Position = [x y w h];
% new geometry of the solver panel
x = 5;
y = 5;
w = figw - bdControl.cpanelw - 5;
w = max(w,0);
h = 50;
this.spanel.Position = [x y w h];
end
% Force a recompute and wait until complete
function RecomputeWait(this)
this.recomputeflag = false;
this.Recompute();
end
% Destructor
function delete(this)
stop(this.timer);
delete(this.timer);
end
end
methods (Access=private)
function ControlPanelInit(this)
% get parent figure geometry
figw = this.fig.Position(3);
figh = this.fig.Position(4);
% construct the container uipanel
x = figw - this.cpanelw;
y = this.cpanely;
w = this.cpanelw;
h = figh - this.cpanely - this.cpanelm;
this.cpanel = uipanel(this.fig,'Units','pixels','Position',[x y w h],'BorderType','none');
% construct a scrolling panel within the container panel
scroll = bdScroll(this.cpanel,220,600);
% eliminate the border on the scroll viewport
scroll.vpanel.BorderType = 'none';
% Widget geometry constants
rowh = 22;
boxw = 50;
boxh = 20;
col1 = 4;
col2 = col1 + boxw + 5;
col3 = col2 + boxw + 5;
col4 = col3 + boxw + 5;
col5 = col4 + boxw + 5;
% Populate the scroll panel with widgets from bottom to top.
% It makes it easier to resizie the scroll panel to its final height.
ypos = 0.5*rowh;
% % REVERSE button
% this.ui_reverse = uicontrol('Style','radio', ...
% 'String','Reverse', ...
% 'Value',this.reverse, ...
% 'HorizontalAlignment','left', ...
% 'FontUnits','pixels', ...
% 'FontSize',12, ...
% 'Parent', scroll.panel, ...
% 'ToolTipString', 'Run the simulation backwards in time', ...
% 'Position',[col3 ypos col5-col3 boxh]);
%
% % next row
% ypos = ypos + 1.25*boxh;
% Time Domain" checkbox (drawn in the wrong place but we need it now)
timecheckbox = uicontrol('Style','checkbox',...
'String','Time Domain', ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','bold', ...
'Value',1, ...
'Callback', @(~,~) notify(this,'refresh'), ...
'Parent', scroll.panel, ...
'Position',[col1 ypos col5-col1 boxh]);
% Add the time domain control widget
bdControlTime(this,scroll.panel,ypos,timecheckbox);
% next row
ypos = ypos + 1.25*boxh;
% Move the "Initial Conditions" checkbox to its proper position
timecheckbox.Position = [col1 ypos col5-col1 boxh];
% next row
ypos = ypos + 2*boxh;
% EVOLVE button
this.ui_evolve = uicontrol('Style','radio', ...
'String','Evolve', ...
'Value',this.sys.evolve, ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'Parent', scroll.panel, ...
'Callback', @(src,~) this.EvolveCallback(src), ...
'ToolTipString', 'Replace the Initial Conditions with the final state before each run', ...
'Position',[col1+3 ypos col3-col1 boxh]);
% PERTURB button
this.ui_perturb = uicontrol('Style','radio', ...
'String','Perturb', ...
'Value',this.sys.perturb, ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'Parent', scroll.panel, ...
'Callback', @(src,~) this.PerturbCallback(src), ...
'ToolTipString', 'Perturb the Initial Conditions (5%) before each run', ...
'Position',[85 ypos 70 boxh]);
% RAND button
% this.ui_rand = uicontrol('Style','pushbutton', ...
% 'String','RAND', ...
% 'Value',0, ...
% 'HorizontalAlignment','center', ...
% 'FontUnits','pixels', ...
% 'FontSize',12, ...
% 'Parent', scroll.panel, ...
% 'Callback', @(src,~) this.RandCallback(src), ...
% 'ToolTipString', 'Assign Uniform Random values to all Initial Conditions', ...
% 'Position',[col4-1 ypos col5-col4-5 boxh]);
% Custom java RAND button.
% We use a java button here because it can detect when it is held
% down (armed) prior to its release.
this.ui_rand = bdControlJButton(this,scroll.panel,[col4-1 ypos col5-col4-5 boxh],'RAND','on');
% RUN button
% this.ui_run = uicontrol('Style','pushbutton', ...
% 'String','RUN', ...
% 'Value',0, ...
% 'Visible','off', ...
% 'HorizontalAlignment','center', ...
% 'FontUnits','pixels', ...
% 'FontSize',12, ...
% 'Parent', scroll.panel, ...
% ... 'Callback', @(src,~) notify(this,'recompute'), ...
% 'Callback', @(src,~) this.RunCallback(src), ...
% 'ToolTipString', 'Run (evolve) the simulation once more', ...
% 'Position',[col4-1 ypos col5-col4-5 boxh]);
% Custom java RUN button.
% We use a java button here because it can detect when it is held
% down (armed) prior to its release.
this.ui_run = bdControlJButton(this,scroll.panel,[col4-1 ypos col5-col4-5 boxh],'RUN','off');
% next row
ypos = ypos + 1.25*boxh;
% "Initial Conditions" checkbox (drawn in the wrong place but we need it now)
varcheckbox = uicontrol('Style','checkbox',...
'String','Initial Conditions', ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','bold', ...
'Value',1, ...
'Callback', @(~,~) notify(this,'vardef'), ...
'Parent', scroll.panel, ...
'Position',[col1 ypos col5-col1 boxh]);
% ODE var widgets (initial conditions)
for varindx=numel(this.sys.vardef):-1:1
% switch depending on the value being a scalar, vector or matrix
switch ScalarVectorMatrix(this.sys.vardef(varindx).value)
case 1
% construct a scalar edit box widget
bdControlScalar(this,'vardef',varindx,scroll.panel,ypos,varcheckbox);
ypos = ypos + bdControlScalar.rowh;
case 2
% construct a vector widget
bdControlVector(this,'vardef',varindx,scroll.panel,ypos,varcheckbox);
ypos = ypos + bdControlVector.rowh;
case 3
% construct a matrix widget
bdControlMatrix(this,'vardef',varindx,scroll.panel,ypos,varcheckbox);
ypos = ypos + bdControlMatrix.rowh;
end
end
% Move the "Initial Conditions" checkbox to its proper position
varcheckbox.Position = [col1 ypos col5-col1 boxh];
% DDE lag widgets (if applicable)
if isfield(this.sys,'lagdef')
% next row
ypos = ypos + 2*boxh;
% "Lag Parameters" checkbox (drawn in the wrong place but we need it now)
lagcheckbox = uicontrol('Style','checkbox', ...
'String','Time Lags', ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','bold', ...
'Value',1, ...
'Callback', @(~,~) notify(this,'lagdef'), ...
'Parent', scroll.panel, ...
'Position',[col1 ypos col5-col1 boxh]);
% for each lagdef entry
for lagindx=numel(this.sys.lagdef):-1:1
% switch depending on the value being a scalar, vector or matrix
switch ScalarVectorMatrix(this.sys.lagdef(lagindx).value)
case 1
% construct a scalar edit box widget
bdControlScalar(this,'lagdef',lagindx,scroll.panel,ypos,lagcheckbox);
ypos = ypos + bdControlScalar.rowh;
case 2
% construct a vector widget
bdControlVector(this,'lagdef',lagindx,scroll.panel,ypos,lagcheckbox);
ypos = ypos + bdControlVector.rowh;
case 3
% construct a matrix widget
bdControlMatrix(this,'lagdef',lagindx,scroll.panel,ypos,lagcheckbox);
ypos = ypos + bdControlMatrix.rowh;
end
end
% Move the "Lag Parameters" checkbox to its proper position
lagcheckbox.Position = [col1 ypos col5-col1 boxh];
end
% SDE random-hold widgets (if applicable)
if isfield(this.sys,'sdeF')
% next row
ypos = ypos + 2*boxh;
% HOLD button
this.ui_hold = uicontrol('Style','radio', ...
'String','Hold', ...
'Value', (isfield(this.sys.sdeoption,'randn') && ~isempty(this.sys.sdeoption.randn) ), ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','normal', ...
'ForegroundColor', 'k', ...
'Parent', scroll.panel, ...
'ToolTipString', 'Hold the random samples fixed', ...
'Callback', @(src,~) this.HoldCallback(src), ...
'Position',[col1+3 ypos col5-col1 boxh]);
% next row
ypos = ypos + boxh;
% Noise title
uicontrol('Style','text', ...
'String','Noise Samples', ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','bold', ...
'Parent', scroll.panel, ...
'Position',[col1 ypos col5-col1 boxh]);
end
% next row
ypos = ypos + 2*boxh;
% "Parameters" checkbox (drawn in the wrong place but we need it now)
parcheckbox = uicontrol('Style','checkbox', ...
'String','Parameters', ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','bold', ...
'Value',1, ...
'Callback', @(~,~) notify(this,'pardef'), ...
'Parent', scroll.panel, ...
'Position',[col1 ypos col5-col1 boxh]);
% ODE parameter widgets
for parindx=numel(this.sys.pardef):-1:1
% switch depending on the value being a scalar, vector or matrix
switch ScalarVectorMatrix(this.sys.pardef(parindx).value)
case 1
% construct a scalar edit box widget
bdControlScalar(this,'pardef',parindx,scroll.panel,ypos,parcheckbox);
ypos = ypos + bdControlScalar.rowh;
case 2
% construct a vector widget
bdControlVector(this,'pardef',parindx,scroll.panel,ypos,parcheckbox);
ypos = ypos + bdControlVector.rowh;
case 3
% construct a matrix widget
bdControlMatrix(this,'pardef',parindx,scroll.panel,ypos,parcheckbox);
ypos = ypos + bdControlMatrix.rowh;
end
end
% Move the "Parameters" checkbox to its proper position
parcheckbox.Position = [col1 ypos col5-col1 boxh];
% next row
ypos = ypos + 1.5*boxh;
% adjust the height of the scroll panel so that it fits the widgets snugly
scroll.panel.Position(4) = ypos;
end
function SolverPanelInit(this)
% get figure geometry
figw = this.fig.Position(3);
figh = this.fig.Position(4);
% Widget geometry constants
row1 = 2;
row2 = row1 + 20;
row3 = row2 + 27;
col1 = 4;
col2 = col1 + 100;
col3 = col2 + 55;
col4 = col3 + 55;
col5 = col4 + 60;
col6 = col5 + 55;
col7 = col6 + 60;
col8 = col7 + 70;
% construct the container uipanel
x = 5;
y = 5;
w = figw - bdControl.cpanelw - 5;
this.spanel = uipanel(this.fig,'Units','pixels','Position',[x y w row3],'BorderType','none');
% SOLVER pop-up menu
this.ui_solver = uicontrol('Style','popupmenu',...
'String', this.SolverStrings(), ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',14, ...
'FontWeight','normal', ...
'Parent', this.spanel, ...
'Position',[col1 row2+2 col2-col1 25], ...
'Callback', @(menuitem,~) this.SolverMenuCallback(menuitem), ...
'ToolTipString','Solver');
% HALT button
this.ui_halt = uicontrol('Style','radio', ...
'String','HALT', ...
'Value',this.sys.halt, ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',14, ...
'FontWeight','bold', ...
'ForegroundColor', 'r', ...
'Parent', this.spanel, ...
'ToolTipString', 'Halt the solver', ...
'Callback', @(src,~) this.HaltCallback(src), ...
'Position',[col1+4 row1 col2-col1 row2-row1]);
% nsteps Heading
uicontrol('Style','text', ...
'String','nsteps', ...
'HorizontalAlignment','center', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','normal', ...
'Parent', this.spanel, ...
'Position',[col2 row2 col3-col2 20]);
% nsteps counter
this.ui_nsteps = uicontrol('Style','text',...
'String','0', ...
'HorizontalAlignment','center', ...
'FontUnits','pixels', ...
'FontSize',14, ...
'FontWeight','normal', ...
'Parent', this.spanel, ...
'ToolTipString','Number of steps taken by the solver', ...
'Position',[col2 row1 col3-col2 row2-row1]);
% nfailed Heading
uicontrol('Style','text', ...
'String','nfailed', ...
'HorizontalAlignment','center', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','normal', ...
'Parent', this.spanel, ...
'Position',[col3 row2 col4-col3 20]);
% nfailed counter
this.ui_nfailed = uicontrol('Style','text',...
'String','0', ...
'HorizontalAlignment','center', ...
'FontUnits','pixels', ...
'FontSize',14, ...
'FontWeight','normal', ...
'Parent', this.spanel, ...
'ToolTipString','Number of steps that failed', ...
'Position',[col3 row1 col4-col3 row2-row1]);
% nfevals Heading
uicontrol('Style','text', ...
'String','nfevals', ...
'HorizontalAlignment','center', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','normal', ...
'Parent', this.spanel, ...
'Position',[col4 row2 col5-col4 20]);
% nfevals counter
this.ui_nfevals = uicontrol('Style','text',...
'String','0', ...
'HorizontalAlignment','center', ...
'FontUnits','pixels', ...
'FontSize',14, ...
'FontWeight','normal', ...
'Parent', this.spanel, ...
'ToolTipString','Number of function evaluations', ...
'Position',[col4 row1 col5-col4 row2-row1]);
% Progress Heading
uicontrol('Style','text', ...
'String','Progress', ...
'HorizontalAlignment','center', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','normal', ...
'Parent', this.spanel, ...
'Position',[col5 row2 col6-col5 20]);
% Progress counter
this.ui_progress = uicontrol('Style','text',...
'String','0%', ...
'HorizontalAlignment','center', ...
'FontUnits','pixels', ...
'FontSize',14, ...
'FontWeight','normal', ...
... 'ForegroundColor', [0.5 0.5 0.5], ...
'Parent', this.spanel, ...
'ToolTipString','Progress of the solver algorithm', ...
'Position',[col5 row1 col6-col5 row2-row1]);
% CPU Heading
uicontrol('Style','text', ...
'String','CPU', ...
'HorizontalAlignment','center', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','normal', ...
'Parent', this.spanel, ...
'Position',[col6 row2 col7-col6 20]);
% CPU time
this.ui_cputime = uicontrol('Style','text',...
'String','0.00s', ...
'HorizontalAlignment','center', ...
'FontUnits','pixels', ...
'FontSize',14, ...
'FontWeight','normal', ...
'Parent', this.spanel, ...
'ToolTipString','CPU time (secs)', ...
'Position',[col6 row1 col7-col6 row2-row1]);
% Warning Heading
uicontrol('Style','text', ...
'String','Warning', ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',12, ...
'FontWeight','normal', ...
'Parent', this.spanel, ...
'Position',[col7 row2 col8-col7 20]);
% WARNING text
this.ui_warning = uicontrol('Style','text',...
'String','none', ...
'HorizontalAlignment','left', ...
'FontUnits','pixels', ...
'FontSize',14, ...
'FontWeight','normal', ...
'ForegroundColor','k', ...
'Parent', this.spanel, ...
'ToolTipString','Solver Warning Message', ...
'Position',[col7 row1 150 row2-row1]);
end
% Return a cell array of strings for the solver functions
function str = SolverStrings(this)
str = {};
if isfield(this.sys,'odesolver')
for indx=1:numel(this.sys.odesolver)
str{indx} = func2str(this.sys.odesolver{indx});
end
end
if isfield(this.sys,'ddesolver')
for indx=1:numel(this.sys.ddesolver)
str{indx} = func2str(this.sys.ddesolver{indx});
end
end
if isfield(this.sys,'sdesolver')
for indx=1:numel(this.sys.sdesolver)
str{indx} = func2str(this.sys.sdesolver{indx});
end
end
end
% Solver Popup Menu Callback
function SolverMenuCallback(this,menuitem)
% Set the index of the active solver
%this.solveridx = menuitem.UserData;
strindx = menuitem.Value;
this.solver = str2func(menuitem.String{strindx});
% Recompute using the new solver
notify(this,'recompute');
end
% Listener for widget REFRESH events
function RefreshListener(this)
%disp('bdControl.RefreshListener');
% refresh the NSTEPS counter
this.ui_nsteps.String = num2str(this.sol.stats.nsteps,'%d');
% refresh the NFAILED counter
this.ui_nfailed.String = num2str(this.sol.stats.nfailed,'%d');
% refresh the NFEVALS counter
this.ui_nfevals.String = num2str(this.sol.stats.nfevals,'%d');
% refresh the HALT button
this.ui_halt.Value = this.sys.halt;
if this.sys.halt
% change the solver stats to red
this.ui_nsteps.ForegroundColor = 'r';
this.ui_nfailed.ForegroundColor = 'r';
this.ui_nfevals.ForegroundColor = 'r';
this.ui_cputime.ForegroundColor = 'r';
this.ui_progress.ForegroundColor = 'r';
%this.ui_cputime.String = '0.00s';
%this.ui_progress.String = '-';
% disable the RUN button
%this.ui_run.Value = 0;
this.ui_run.Enable = 'off';
%this.ui_run.ForegroundColor = 'r';
else
% change the solver stats to black
this.ui_nsteps.ForegroundColor = 'k';
this.ui_nfailed.ForegroundColor = 'k';
this.ui_nfevals.ForegroundColor = 'k';
this.ui_cputime.ForegroundColor = 'k';
this.ui_progress.ForegroundColor = 'k';
% enable the RUN button
this.ui_run.Enable = 'on';
%this.ui_run.ForegroundColor = 'k';
end
% refresh the EVOLVE button
this.ui_evolve.Value = this.sys.evolve;
if this.sys.evolve
% Hide the RAND button
this.ui_rand.Visible = 'off';
% Show the RUN button
this.ui_run.Visible = 'on';
else
% Show the RAND button
this.ui_rand.Visible = 'on';
% Hide the RUN button
this.ui_run.Visible = 'off';
end
% refresh the PERTURB button
this.ui_perturb.Value = this.sys.perturb;
end
% Listener for RECOMPUTE events
function RecomputeListener(this)
% Recompute events often come faster than we can recompute the
% solution. So we just note the arrival of the event here and
% let the timer loop do the actual computation when it is ready.
this.recomputeflag = true;
end
% Recompute the solution (called by the timer)
function Recompute(this)
% Remember the parameters of the (to be computed) solution in the control.par struct.
% We do this because the controls can change the values in sys.pardef
% faster than the solver can keep up.
for indx = 1:numel(this.sys.pardef)
name = this.sys.pardef(indx).name;
value = this.sys.pardef(indx).value;
this.par.(name) = value;
end
% Remember the lag parameters too (if applicable)
if isfield(this.sys,'lagdef')
for indx = 1:numel(this.sys.lagdef)
name = this.sys.lagdef(indx).name;
value = this.sys.lagdef(indx).value;
this.lag.(name) = value;
end
end
% Do no more if the HALT button is active
if this.sys.halt
return
end
% clear the last warning message
lastwarn('');
oldwarn = warning('off','backtrace');
% if the EVOLVE button is ON then ....
if this.sys.evolve
% Update the initial conditions and compute the new solution
this.Evolve();
else
% Compute the solution without altering the initial conditions
this.Solve();
end
% restore the old warning state
warning(oldwarn.state,'backtrace');
% Display any warnings from the solver
[msg,msgid] = lastwarn();
ix = find(msgid==':',1,'last');
if ~isempty(ix)
this.ui_warning.String = msgid((ix+1):end);
this.ui_warning.TooltipString = msg;
this.ui_warning.ForegroundColor = 'r';
else
this.ui_warning.String = 'none';
this.ui_warning.TooltipString = 'Solver Warning Message';
this.ui_warning.ForegroundColor = 'k';
end
% Hold the SDEnoise if the HOLD button is 'on'
switch this.solvertype
case 'sdesolver'
if this.ui_hold.Value==1 && isempty(this.sys.sdeoption.randn)
dt = this.sol.x(2) - this.sol.x(1);
this.sys.sdeoption.randn = this.sol.dW ./ sqrt(dt);
end
end
% notify all listeners that a redraw is required
notify(this,'redraw');
% refresh the solver NSTEPS counter
this.ui_nsteps.String = num2str(this.sol.stats.nsteps,'%d');
% refresh the solver NFAILED counter
this.ui_nfailed.String = num2str(this.sol.stats.nfailed,'%d');
% refresh the solver NFEVALS counter
this.ui_nfevals.String = num2str(this.sol.stats.nfevals,'%d');
% update the CPU time to include the time to redraw/refresh the GUI
cpu = cputime - this.cpustart;
this.ui_cputime.String = num2str(cpu,'%5.2fs');
end
% Returns a uniform pertubation that is suitable for the initial
% conditions. The amp parameter dictates the amplitude of the
% perturbation relative to [lo hi] limits of each variable.
function P0 = Perturbation(this,amp)
% Initialise P0 to the same size as Y0. The contents do not matter.
P0 = bdGetValues(this.sys.vardef);
% for each entry in sys.vardef
for indx = 1:numel(this.sys.vardef)
% determine the limits of the variable
lo = this.sys.vardef(indx).lim(1);
hi = this.sys.vardef(indx).lim(2);
% get the indices of the variable in Y0
solindx = this.sys.vardef(indx).solindx;
% determine the size of the variable in Y0
solsize = [numel(solindx) 1];
% create the perturbation as a uniform random value scaled by 'amp'
P0(solindx) = amp*(hi-lo)*(rand(solsize)-0.5);
end
end
% Call the solver
function Solve(this)
% Get the initial conditions
Y0 = bdGetValues(this.sys.vardef);
% If the perturb button state is ON then ...
if this.sys.perturb
% Add 5 percent perturbation to the initial conditions
Y0 = Y0 + this.Perturbation(0.05);
end
% Get the system parameters as a cell array
parcell = {this.sys.pardef.value};
% The type of the solver function determines how we apply it
switch this.solvertype
case 'odesolver'
% case of an ODE solver (eg ode45)
odeoption = odeset(this.sys.odeoption, 'OutputFcn',@this.odeOutputFcn, 'OutputSel',[]);
this.sol = this.solver(this.sys.odefun, ...
this.sys.tspan, ...
Y0, ...
odeoption, ...
parcell{:});
case 'ddesolver'
% case of a DDE solver (eg dde23)
ddeoption = ddeset(this.sys.ddeoption, 'OutputFcn',@this.odeOutputFcn, 'OutputSel',[]);
lags = bdGetValues(this.sys.lagdef);
this.sol = this.solver(this.sys.ddefun, ...
lags, ...
Y0, ...
this.sys.tspan, ...
ddeoption, ...
parcell{:});
case 'sdesolver'
% case of an SDE solver
sdeoption = this.sys.sdeoption;
sdeoption.OutputFcn = @this.odeOutputFcn;
sdeoption.OutputSel = [];
this.sol = this.solver(this.sys.sdeF, ...
this.sys.sdeG, ...
this.sys.tspan, ...
Y0, ...
sdeoption, ...
parcell{:});
end
% Update the indices of the non-transient steps of sol.x
% Note: tindx can be all zeros in cases where the solver
% terminated early because of blow-out or tolerance failures.
this.tindx = (this.sol.x >= this.sys.tval) & min(isfinite(this.sol.y));
end
% Call the solver using the final state of the previous run
% as the initial conditions for the current run
function Evolve(this)
% copy the final states in sol.y to the initial conditions in sys.vardef
if ~isempty(this.sol.y)
if all(isfinite(this.sol.y(:,end)))
for indx=1:numel(this.sys.vardef)
valsize = size(this.sys.vardef(indx).value); % size of the variable value
solindx = this.sys.vardef(indx).solindx; % corresponding indices of the variable in sol
val = reshape( this.sol.y(solindx,end), valsize); % the final value in the solution ...
this.sys.vardef(indx).value = val; % ... replaces the initial value
end
% notify the vardef widgets to refresh themselves
notify(this,'vardef');
else
% warn about Infs and NaNs
oldwarn = warning('off','backtrace');
warning('bdGUI:Overflow','The computed solution exceeds machine limits.');
warning(oldwarn.state,'backtrace');
% turn the EVOLVE button OFF
this.sys.evolve = false;
% turn the HALT button ON
this.sys.halt = true;
% notify the widgets to refresh themselves
notify(this,'refresh');
end
end
% Get the system parameters as a cell array
parcell = {this.sys.pardef.value};
% The type of the solver function determines how we apply it
switch this.solvertype
case 'odesolver'
% Case of an ODE solver (eg ode45)
odeoption = odeset(this.sys.odeoption, 'OutputFcn',@this.odeOutputFcn, 'OutputSel',[]);
% Get the initial conditions