-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathpreprima.m
2025 lines (1881 loc) · 91.3 KB
/
preprima.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
function [fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, options, probinfo] = preprima(fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, options)
%PREPRIMA preprocesses the input to prima.
%
% ***********************************************************************
% Authors: Tom M. RAGONNEAU (tom.ragonneau@connect.polyu.hk)
% and Zaikun ZHANG (zaikun.zhang@polyu.edu.hk)
% Department of Applied Mathematics,
% The Hong Kong Polytechnic University
%
% Dedicated to the late Professor M. J. D. Powell FRS (1936--2015).
% ***********************************************************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Attribute: private (not supposed to be called by users)
%
% Remarks
% 1. Input/output names: MATLAB allows to use the same name for inputs and outputs.
% 2. invoker: invoker is the function that calls preprima.
%
% TODO: None
% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% preprima starts
warnings = {}; % A cell that records all the warnings, will be recorded in probinfo
% Who is calling this function? Is it a correct invoker?
invoker_list = [all_solvers(), 'prima'];
% Sometimes a .m is appended to the invoker name. Observed 20230926 on macOS with MATLAB R2022b.
invoker_list = [invoker_list, strcat(invoker_list, '.m')];
callstack = dbstack;
funname = callstack(1).name; % Name of the current function
if (length(callstack) == 1 || ~ismember(callstack(2).name, invoker_list))
% Private/unexpected error
error(sprintf('%s:InvalidInvoker', funname), ...
'%s: UNEXPECTED ERROR: %s should only be called by %s.', funname, funname, strjoin(invoker_list, ', '));
else
invoker = callstack(2).name; % Name of the function who calls this function
end
if (nargin ~= 1) && (nargin ~= 10)
% Private/unexpected error
error(sprintf('%s:InvalidInput', funname), '%s: UNEXPECTED ERROR: 1 or 10 inputs.', funname);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% If invoker is a solver called by prima, then preprima should have been called in prima.
if (length(callstack) >= 3) && strcmp(callstack(3).name, 'prima')
if nargin ~= 10 % There should be 10 input arguments
% Private/unexpected error
error(sprintf('%s:InvalidInput', funname), ...
'%s: UNEXPECTED ERROR: %d inputs received; this should not happen as preprima has been called once in prima.', funname, nargin);
end
% In this case, we set probinfo to empty.
probinfo = [];
return % Return because preprima has already been called in prima.
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Decode the problem if it is defined by a structure.
if (nargin == 1)
[fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, options, warnings] = decode_problem(invoker, fun, warnings);
end
% Save problem information in probinfo.
% At return, probinfo has the following fields:
% 1. raw_data: problem data before preprocessing/validating, including
% fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, options.
% raw_data is set to struct() unless in debug mode.
% 2. refined_data: problem data after preprocessing/validating, including
% fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, options.
% refined_data is set to struct() unless in debug mode or the problem is scaled.
% 3. fixedx: a true/false vector indicating which variables are fixed by
% bound constraints
% 4. fixedx_value: the values of the variables fixed by bound constraints
% 5. nofreex: whether all variables are fixed by bound constraints
% 6. infeasible_bound: a true/false vector indicating which bound constraints
% are infeasible
% 7. infeasible_lineq: a true/false vector indicating which linear inequality
% constraints are infeasible (up to naive tests)
% 8. infeasible_leq: a true/false vector indicating which linear equality
% constraints are infeasible (up to naive tests)
% 9. trivial_lineq
% 10. trivial_leq: a true/false vector indicating which linear equality
% constraints are trivial (up to naive tests)
% 11. infeasible: whether the problem is infeasible (up to naive tests)
% 12. scaled: whether the problem is scaled
% 13. scaling_factor: vector of scaling factors
% 14. shift: vector of shifts
% 15. reduced: whether the problem is reduced (due to fixed variables)
% 16. raw_type: problem type before reduction
% 17. raw_dim: problem dimension before reduction
% 18. refined_type: problem type after reduction
% 19. refined_dim: problem dimension after reduction
% 20. feasibility_problem: whether the problem is a feasibility problem
% 21. user_options_fields: the fields in the user-specified options
% 22. options: (refined) options for calling the solvers
% 23. warnings: warnings during the preprocessing/validation
% 24. boundmax: the large allowed absolute value of bound constraints
% 25. funcmax: the largest allowed value of the objective function
% 26. constrmax: the largest allowed absolute value of the constraints
% 27. x0_is_row: whether x0 is a row
probinfo = struct();
% Save the raw data (date before validation/preprocessing) in probinfo.
% The raw data can be useful when debugging. At the end of preprima, if
% we are not in debug mode, raw_data will be removed from probinfo.
% NOTE: Surely, here we are making copies of the data, which may take some
% time and space, matrices Aineq and Aeq being the major concern.
% However, fortunately, this package is not intended for large problems.
% It is designed for problems with at most ~1000 variables and several
% thousands of constraints, tens/hundreds of variables and tens/hundreds
% of constraints being typical. Therefore, making several copies (<10) of
% the data does not do much harm, especially when we solve problems with
% expensive (temporally or monetarily) function evaluations.
probinfo.raw_data = struct('objective', fun, 'x0', x0, 'Aineq', Aineq, 'bineq', bineq, ...
'Aeq', Aeq, 'beq', beq, 'lb', lb, 'ub', ub, 'nonlcon', nonlcon, 'options', options);
% Decide the precision ('single', 'double', or 'quadruple') of the real calculation within the
% Fortran solvers. This is needed ONLY by `boundmax`, `funcmax`, and `constrmax` defined below by
% calling `getmax`. These three numbers will be used in `pre_x0`, `pre_fun`, and `pre_nonlcon`
% respectively in the sequel. Note the following.
% 1. `precision` takes effect only if Fortran solvers are called (i.e., when options.fortran = true).
% 2. `precision` is passed only to `getmax`, which defines huge values (e.g., `boundmax`, `funcmax`).
precision = 'double';
% Since `options` is not validated yet, validations are needed before inquiring options.precision.
if isa(options, 'struct') && isfield(options, 'precision') && ischarstr(options.precision) && ...
ismember(lower(options.precision), all_precisions())
precision = lower(options.precision);
end
probinfo.boundmax = getmax('bound', precision);
probinfo.funcmax = getmax('function', precision);
probinfo.constrmax = getmax('constraint', precision);
% Validate and preprocess x0
[x0, warnings, x0_is_row] = pre_x0(invoker, x0, precision, warnings);
lenx0 = length(x0); % Within this file, for clarity, we denote length(x0) by lenx0 instead of n
probinfo.x0_is_row = x0_is_row;
% Validate and preprocess the bound constraints
% In addition, get the indices of infeasible bounds and 'fixed variables'
% such that ub-lb < 2*eps (if any) and save the information in probinfo.
% If there is any infeasible bound, the problem is infeasible, and we define
% that there is no fixed variable.
[lb, ub, infeasible_bound, fixedx, fixedx_value, warnings] = pre_bcon(invoker, lb, ub, lenx0, warnings);
probinfo.infeasible_bound = infeasible_bound; % A vector of true/false
probinfo.fixedx = fixedx; % A vector of true/false
fixedx_value_save = fixedx_value; % Values of fixed variables
% Since fixedx_value may be revised in pre_lcon, we will record it in
% probinfo only after that. We save its current value in
% fixedx_value_save, which will be used when calculating the constraint
% violation at x0.
% Problem type before reduction
% This has to be done after preprocessing the bound constraints (because
% min(ub) and max(lb) are evaluated) and before preprocessing the
% linear/nonlinear constraints (because these constraints will be
% reduced during the preprocessing). Note that Aineq, Aeq, and nonlcon will
% not be "evaluated" in problem_type, so there is no worry about the
% validity of them.
probinfo.raw_type = problem_type(Aineq, Aeq, lb, ub, nonlcon);
% Raise a warning if x0 is a row and the problem has linear constraints (before the reduction),
% as the formulation of linear constraints (Aineq*x <= bineq, Aeq*x = beq) assumes that the decision
% variable x is a column. If x0 is a row, pre_x0 transposes it, and pre_fun, pre_nonlcon redefines
% fun and nonlcon, so that the solvers only need to deal with x as a column.
if x0_is_row && ~(isempty(Aineq) && isempty(Aeq))
wid = sprintf('%s:X0IsRow', invoker);
wmsg = sprintf('%s: x0 is a row, but a column is expected; it is transposed.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
% Validate and preprocess the linear constraints
% 1. The constraints will be reduced if some but not all variables are
% fixed by the bound constraints. See pre_lcon for why we do not
% reduce the problem when all variables are fixed.
% 2. The 'trivial constraints' will be excluded (if any).
% 3. In addition, get the indices of infeasible and trivial constraints (if any)
% and save the information in probinfo.
% 4. fixedx_value is revised to the corresponding values of x0 if infeasible linear constraints are detected
[Aineq, bineq, Aeq, beq, infeasible_lineq, trivial_lineq, infeasible_leq, trivial_leq, fixedx_value, warnings] = pre_lcon(invoker, x0, Aineq, bineq, Aeq, beq, lenx0, fixedx, fixedx_value, warnings);
probinfo.fixedx_value = fixedx_value; % Possibly revised value of the fixed x entries
probinfo.infeasible_lineq = infeasible_lineq; % A vector of true/false
probinfo.trivial_lineq = trivial_lineq; % A vector of true/false
probinfo.infeasible_leq = infeasible_leq; % A vector of true/false
probinfo.trivial_leq = trivial_leq; % A vector of true/false
% Validate and preprocess fun
% 1. The objective function will be reduced if some but not all variables are
% fixed by the bound constraints. See pre_lcon for why we do not reduce the
% problem when all variables are fixed.
% 2. This should be done after preprocessing the bound and linear constraints, which defines
% and may revise fixedx_value.
[fun, probinfo.feasibility_problem, warnings] = pre_fun(invoker, fun, fixedx, fixedx_value, probinfo.funcmax, x0_is_row, warnings);
% Validate and preprocess the nonlinear constraints
% 1. The constraints will be reduced if some but not all variables are fixed by the bound
% constraints. See pre_lcon for why we do not reduce the problem when all variables
% are fixed.
% 2. This should be done after preprocessing the bound and linear constraints, which defines
% and may revise fixedx_value.
% 3. This should be done before evaluating probinfo.constrv_x0 or probinfo.constrv_fixedx.
nonlcon = pre_nonlcon(invoker, nonlcon, fixedx, fixedx_value, probinfo.constrmax, x0_is_row);
% Reduce x0, lb, and ub if some but not all variables are fixed by
% the bound constraints. See pre_lcon for why we do not reduce the
% problem when all variables are fixed.
probinfo.raw_dim = lenx0; % Problem dimension before reduction
if any(fixedx) && any(~fixedx)
freex = ~fixedx; % A vector of true/false indicating whether the variable is free or not
x0 = x0(freex); % x0 after reduction
lenx0 = length(x0);
lb = lb(freex); % lb after reduction
ub = ub(freex); % ub after reduction
end
probinfo.refined_type = problem_type(Aineq, Aeq, lb, ub, nonlcon); % Problem type after reduction
probinfo.refined_dim = length(x0); % Problem dimension after reduction
probinfo.reduced = any(fixedx) && any(~fixedx); % Whether the problem has been reduced
% After the preprocessing, the problem may turn out infeasible
probinfo.infeasible = any([probinfo.infeasible_lineq; probinfo.infeasible_leq; probinfo.infeasible_bound]);
if probinfo.infeasible % The problem turns out infeasible
[probinfo.constrv_x0, probinfo.nlcineq_x0, probinfo.nlceq_x0] = get_constrv(x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon);
% The constraint violation calculated by constrv does not include
% the violation of x0 for the bounds corresponding to fixedx; the
% corresponding values of x0 are in fixedx_value, while the values
% of the bounds (lb and ub are the same up to eps) are in
% fixedx_value_save. Thus the violation is abs(fixedx_value-fixedx_value_save).
rbounds = abs(fixedx_value - fixedx_value_save);
rbounds = rbounds(fixedx_value ~= fixedx_value_save); % Prevent NaN in case both are +/-Inf
probinfo.constrv_x0 = max([probinfo.constrv_x0; rbounds], [], 'includenan');
end
% After the preprocessing, x may turn out fixed by the bounds
probinfo.nofreex = all(fixedx);
if probinfo.nofreex % x turns out fixed by the bound constraints
[probinfo.constrv_fixedx, probinfo.nlcineq_fixedx, probinfo.nlceq_fixedx] = get_constrv(probinfo.fixedx_value, Aineq, bineq, Aeq, beq, lb, ub, nonlcon);
end
% Can the invoker handle the given problem?
% This should be done after the problem type has bee 'refined'.
if ~prob_solv_match(probinfo.refined_type, invoker)
if strcmp(invoker, 'prima') || (nargin ~= 1)
% Private/unexpected error
error(sprintf('%s:InvalidProb', funname), ...
'%s: UNEXPECTED ERROR: problem and solver do not match; this should not happen when %s is called by %s or the problem is not a structure.', funname, funname, invoker);
else
% Public/normal error
error(sprintf('%s:InvalidProb', invoker), ...
'%s: %s problem received; %s cannot solve it.', invoker, strrep(probinfo.refined_type, '-', ' '), invoker);
end
end
% Validate and preprocess options, adopting default options if needed.
% This should be done after reducing the problem, because BOBYQA
% requires rhobeg <= min(ub-lb)/2.
% user_options_fields is a cell array that contains the names of all the
% user-defined options (even if the options turns out invalid). It will be
% needed if the user does not specify a solver or specifies a wrong solver.
% In such a scenario, we will select the solver later, and the options may
% have to be revised accordingly. We will raise a warning when revising
% an option that is in user_options_fields. No warning is needed if we
% are dealing with an option that is not in user_options_fields.
[options, probinfo.user_options_fields, warnings] = pre_options(invoker, options, lenx0, lb, ub, warnings);
% Revise x0 for bound and linearly constrained problems.
% This is necessary for LINCOA, which accepts only feasible x0.
% Should we do this even if there are nonlinear constraints?
% For now, we do not, because doing so may dramatically increase the
% infeasibility of x0 with respect to the nonlinear constraints.
if ismember(probinfo.refined_type, {'bound-constrained', 'linearly-constrained'}) && ~probinfo.nofreex && ~probinfo.infeasible
% Another possibility for bound-constrained problems:
% xind = (x0 < lb) | (x0 > ub);
% x0(xind) = (lb(xind) + ub(xind))/2;
x0_new = project(Aineq, bineq, Aeq, beq, lb, ub, x0);
if get_cstrv(x0_new, Aineq, bineq, Aeq, beq, lb, ub) < get_cstrv(x0, Aineq, bineq, Aeq, beq, lb, ub)
if norm(x0_new-x0) > eps*max(1, norm(x0)) && ~probinfo.feasibility_problem
% No warning about revising x0 if the problem is a linear feasibility problem.
% Note that the linearity is guaranteed by THE OUTER IF.
wid = sprintf('%s:ReviseX0', invoker);
wmsg = sprintf('%s: x0 is revised to satisfy the constraints better.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
x0 = x0_new;
end
if get_cstrv(x0, Aineq, bineq, Aeq, beq, lb, ub) > 1.0e-10*max(abs([1; bineq; beq; x0]))
wid = sprintf('%s:InfeasibleX0', invoker);
wmsg = sprintf('%s: preprocessing code did not find a feasible x0; problem is likely infeasible.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
end
% Scale the problem if necessary and if intended.
% x_before_scaling = scaling_factor.*x_after_scaling + shift
% This should be done after revising x0, which can affect the shift.
probinfo.scaled = false;
probinfo.scaling_factor = ones(size(x0));
probinfo.shift = zeros(size(x0));
if options.scale && ~probinfo.nofreex && ~probinfo.infeasible
[fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, scaling_factor, shift, warnings] = scale_problem(invoker, fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, warnings);
% Scale and shift the problem so that all the bounds become [-1, 1]
% It is done only if all variables have both lower and upper bounds
probinfo.scaled = true;
probinfo.scaling_factor = scaling_factor;
probinfo.shift = shift;
end
% Record the refined data (excluding options) after preprocessing
% This has to be done before select_solver, because probinfo.refined_data.lb
% and probinfo.refined_data.ub will be used for defining rhobeg if bobyqa is selected.
probinfo.refined_data = struct('objective', fun, 'x0', x0, 'Aineq', Aineq, 'bineq', bineq, ...
'Aeq', Aeq, 'beq', beq, 'lb', lb, 'ub', ub, 'nonlcon', nonlcon);
% Select a solver if invoker = 'prima'; record the solver in options.solver.
% Some options will be revised accordingly, including npt, rhobeg, rhoend.
% Of course, if the user-defined options.solver is valid, we accept it.
if strcmp(invoker, 'prima')
[options, warnings] = select_solver(invoker, options, probinfo, warnings);
end
% If options.fortran is true, check whether the Fortran MEX function is available. If no, set
% options.fortran to false, and the MATLAB version of the solver will be called (we are assuming
% that the MATLAB version is available).
if options.fortran
[options, warnings] = is_fortran_available(invoker, options, warnings);
end
if strcmpi(options.solver, 'bobyqa') && ~probinfo.nofreex && ~probinfo.infeasible && ~probinfo.feasibility_problem
% BOBYQA will revise x0 so that the distance between x0 and the inactive bounds
% is at least rhobeg. We do it here in order to raise a warning when such a
% revision occurs. After this, BOBYQA will not revise x0 again. If options.honour_x0
% is true, then we keep x0 unchanged and revise rhobeg if necessary.
% N.B.: If x0 violates the bounds, then it is always revised by `project` to respect the bounds.
[x0, options, warnings] = pre_rhobeg_x0(invoker, x0, lb, ub, probinfo.user_options_fields, options, warnings);
probinfo.refined_data.x0 = x0; % x0 may have been revised.
end
% Record the options in probinfo
% This has to be done after select_solver, because select_solver updates
% options.solver, and possibly options.npt and options.rhobeg.
% Also, pre_rhobeg_x0 may change options.rhobeg and options.rhoend.
probinfo.options = options;
% We do NOT record options in probinfo.refined_data, because we do not
% carry refined_data with us unless in debug mode or the problem is scaled.
if probinfo.feasibility_problem && ~strcmp(probinfo.refined_type, 'nonlinearly-constrained')
% When the problem is a linear feasibility problem, PRIMA will return the
% current x0, which has been revised by project. The constraint violation
% at x0 is needed to set the output. Note that there is no nonlinear
% constraint in this case.
probinfo.constrv_x0 = get_constrv(x0, Aineq, bineq, Aeq, beq, lb, ub, []);
end
probinfo.warnings = warnings; % Record the warnings in probinfo
if ~options.debug % Do not carry the raw data with us unless in debug mode.
probinfo.raw_data = struct();
% Set this field to empty instead of remove it, because postprima
% requires this field to exist.
end
if ~options.debug && ~probinfo.scaled
% The refined data is used only when the problem is scaled. It can
% also be useful when debugging.
probinfo.refined_data = struct();
% Set this field to empty instead of remove it, because postprima
% requires this field to exist.
end
% preprima ends
return
%%%%%%%%%%%%%%%%%%%%%%%% Function for problem decoding %%%%%%%%%%%%%%%%%
function [fun, x0, Aineq, bineq, Aeq, beq, lb, ub, nonlcon, options, warnings] = decode_problem(invoker, problem, warnings)
% Read the fields of the 'problem' structure but do not validate them.
% The decoded problem will be sent to the preprima function for validation.
% NOTE: We treat field names case-sensitively.
% Possible invokers
invoker_list = [all_solvers(), 'prima'];
% Sometimes a .m is appended to the invoker name. Observed 20230926 on macOS with MATLAB R2022b.
invoker_list = [invoker_list, strcat(invoker_list, '.m')];
callstack = dbstack;
funname = callstack(1).name; % Name of the current function
if ~ismember(invoker, invoker_list)
% invoker affects the behavior of this function, so we check invoker
% again, even though it should have been checked in function preprima
% Private/unexpected error
error(sprintf('%s:InvalidInvoker', funname), ...
'%s: UNEXPECTED ERROR: %s serves only %s.', funname, funname, strjoin(invoker_list, ', '));
end
if ~isa(problem, 'struct')
% Public/normal error
error(sprintf('%s:InvalidProb', invoker), '%s: the unique input is not a problem-defining structure.', invoker);
end
% Which fields are specified?
problem = rmempty(problem); % Remove empty fields
problem_fields = fieldnames(problem);
% Are the obligatory field(s) present?
obligatory_fields = {'x0'}; % There is only 1 obligatory field
missing_fields = setdiff(obligatory_fields, problem_fields);
if ~isempty(missing_fields)
% Public/normal error
error(sprintf('%s:InvalidProb', invoker), ...
'%s: PROBLEM misses the %s field(s).', invoker, strjoin(missing_fields, ', '));
end
x0 = problem.x0;
if isfield(problem, 'objective')
fun = problem.objective;
else % There is no objective; this is a feasibility problem
fun = []; % We use [] to signify that fun is not specified. pre_fun will replace [] by by @(x)0
end
% Are there unknown fields?
known_fields = {'objective', 'x0', 'Aineq', 'bineq', 'Aeq', 'beq', 'lb', 'ub', 'nonlcon', 'options', 'solver'};
% 1. When invoker is in {uobyqa, ..., cobyla}, we will not complain that
% a solver is specified unless invoker ~= solver. See function pre_options.
% 2. When invoker is in {uobyqa, ..., cobyla}, if the problem turns out
% unsolvable for the invoker, then we will raise an error in preprima.
% We do not do it here because the problem has not been validated/preprocessed
% yet. Maybe some constraints are trivial and hence can be removed
% (e.g., bineq = inf, lb = -inf), which can change the problem type.
unknown_fields = setdiff(problem_fields, known_fields);
problem = rmfield(problem, unknown_fields); % Remove the unknown fields
if ~isempty(unknown_fields)
wid = sprintf('%s:UnknownProbField', invoker);
if length(unknown_fields) == 1
wmsg = sprintf('%s: problem with an unknown field %s; it is ignored.', invoker, strjoin(unknown_fields, ', '));
else
wmsg = sprintf('%s: problem with unknown fields %s; they are ignored.', invoker, strjoin(unknown_fields, ', '));
end
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
% Read the fields of problem. They will be validated in function preprima
Aineq = [];
bineq = [];
Aeq = [];
beq = [];
lb = [];
ub = [];
nonlcon = [];
options = struct();
if isfield(problem,'Aineq')
Aineq = problem.Aineq;
end
if isfield(problem,'bineq')
bineq = problem.bineq;
end
if isfield(problem,'Aeq')
Aeq = problem.Aeq;
end
if isfield(problem,'beq')
beq = problem.beq;
end
if isfield(problem,'lb')
lb = problem.lb;
end
if isfield(problem,'ub')
ub = problem.ub;
end
if isfield(problem,'nonlcon')
nonlcon = problem.nonlcon;
end
if isfield(problem,'options')
options = problem.options;
end
if isfield(problem,'solver')
options.solver = problem.solver;
% After last step, options.solver = problem.options.solver;
% after this step, if problem.solver is defined and nonempty,
% then options.solver = problem.solver.
end
return
%%%%%%%%%%%%%%%%%%%%%%%% Function for x0 preprocessing %%%%%%%%%%%%%%%%%
function [x0, warnings, x0_is_row] = pre_x0(invoker, x0, precision, warnings)
[isrv, lenx0] = isrealvector(x0);
if ~(isrv && (lenx0 > 0))
% Public/normal error
error(sprintf('%s:InvalidX0', invoker), '%s: X0 should be a real vector/scalar.', invoker);
end
if (lenx0 > maxint())
% Public/normal error
error(sprintf('%s:ProblemTooLarge', invoker), '%s: The problem is too large; at most %d variables are allowed.', invoker, maxint());
end
x0_is_row = (lenx0 >= 2) && isrealrow(x0);
x0 = double(x0(:)); % Transpose x0 if it is a row; fun and nonlcon will be redefined accordingly.
abnormal_x0 = isnan(x0) | (abs(x0) >= inf);
if any(abnormal_x0)
x0(isnan(x0)) = 0;
maxfloat = getmax('real', precision);
x0(isinf(x0) & x0 > 0) = maxfloat;
x0(isinf(x0) & x0 < 0) = -maxfloat;
wid = sprintf('%s:AbnormalX0', invoker);
wmsg = sprintf('%s: X0 contains NaN or infinite values; NaN is replaced by 0 and Inf by %g.', invoker, maxfloat);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
return
%%%%%%%%%%%%%%%%% Function for bound constraint preprocessing %%%%%%%%%%
function [lb, ub, infeasible_bound, fixedx, fixedx_value, warnings] = pre_bcon(invoker, lb, ub, lenx0, warnings)
% Lower bounds (lb)
[isrvlb, lenlb] = isrealvector(lb);
if ~(isrvlb && (lenlb == lenx0 || lenlb == 0))
% Public/normal error
error(sprintf('%s:InvalidBound', invoker), ...
'%s: lb should be a real vector and length(lb) = length(x0) unless lb = [].', invoker);
end
if (lenlb == 0)
lb = -inf(lenx0,1); % After pre_bcon, length(lb) = length(x0)
end
lb = double(lb(:));
if any(isnan(lb))
wid = sprintf('%s:NaNInLB', invoker);
wmsg = sprintf('%s: LB contains NaN; the problem is hence infeasible.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
% Upper bounds (ub)
[isrvub, lenub] = isrealvector(ub);
if ~(isrvub && (lenub == lenx0 || lenub == 0))
% Public/normal error
error(sprintf('%s:InvalidBound', invoker), ...
'%s: ub should be a real vector and length(ub) = length(x0) unless ub = [].', invoker);
end
if (lenub == 0)
ub = inf(lenx0,1); % After pre_bcon, length(ub) = length(x0)
end
ub = double(ub(:));
if any(isnan(ub))
wid = sprintf('%s:NaNInUB', invoker);
wmsg = sprintf('%s: UB contains NaN; the problem is hence infeasible.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
infeasible_bound = ~(lb <= ub); % A vector of true/false; true if lb or ub is NaN or lb > ub
if any(infeasible_bound)
fixedx = false(lenx0, 1);
fixedx_value = [];
else
fixedx = (ub <= lb + 2*eps); % Avoid ub - lb in case ub = lb = +/-Inf. Use <= instead of <.
fixedx_value = (lb(fixedx)+ub(fixedx))/2;
end
return
%%%%%%%%%%%%%%%%% Function for linear constraint preprocessing %%%%%%%%%%
function [Aineq, bineq, Aeq, beq, infeasible_lineq, trivial_lineq, infeasible_leq, trivial_leq, fixedx_value, warnings] = pre_lcon(invoker, x0, Aineq, bineq, Aeq, beq, lenx0, fixedx, fixedx_value, warnings)
freex = ~fixedx; % A vector of true/false indicating whether the variable is free or not
% Preprocess linear inequalities: Aineq*x <= bineq.
% Check whether Aineq and bineq are real matrices and vectors of proper size, respectively.
[isrm, mA, nA] = isrealmatrix(Aineq);
[isrv, lenb] = isrealvector(bineq); % The same as fmincon, we allow bineq to be a row
% No matter whether x0 or bineq is a row or column, we always require that size(Aineq) is
% [length(bineq), length(x0)] unless Aineq = bineq = []. This is consistent with fmincon.
if ~(isrm && isrv && (mA == lenb) && (nA == lenx0 || nA == 0))
% Public/normal error
error(sprintf('%s:InvalidLinIneq', invoker), ...
'%s: Aineq should be a real matrix, bineq should be a real column, and size(Aineq) = [length(bineq), length(X0)] unless Aineq = bineq = [].', invoker);
end
if (lenb >= 2) && isrealrow(bineq)
wid = sprintf('%s:BineqIsRow', invoker);
wmsg = sprintf('%s: bineq is a row, but a column is expected; it is transposed.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
bineq = double(bineq(:));
Aineq = double(Aineq);
% Warn about inequality constraints containing NaN.
nan_ineq = any(isnan(Aineq), 2) | isnan(bineq);
if any(nan_ineq)
wid = sprintf('%s:NaNInequality', invoker);
wmsg = sprintf('%s: Aineq or bineq contains NaN; the problem is hence infeasible.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
% Warn about inequality constraints containing Inf.
inf_ineq = any(abs(Aineq) >= inf, 2) | (bineq <= -inf);
if any(inf_ineq)
wid = sprintf('%s:InfInequality', invoker);
wmsg = sprintf('%s: Aineq contains infinite values or bineq contains -Inf; the problem is considered infeasible.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
% Reduce the inequality constraints if some but not all variables are fixed.
% 1. This has to be done before detecting the "zero constraints" (i.e., constraints with zero
% gradients), because nonzero constraints may become zero after reduction.
% 2. We should NOT reduce the problem if all variables are fixed. Otherwise, Aineq would be [], and
% then bineq will be set to [] in the end. In this way, we lose completely the information in
% these constraints. Consequently, we cannot evaluate the constraint violation correctly when needed.
lineq_reduced = false; % Whether linear inequality constraints are reduced
if ~isempty(Aineq) && any(fixedx) && any(~fixedx)
Aineq_fixed = Aineq(:, fixedx); % Aineq_fixed and bineq_save will be used when revising fixedx_value
bineq_save = bineq;
bineq = bineq - Aineq_fixed * fixedx_value;
Aineq = Aineq(:, freex);
lineq_reduced = true;
end
% Define infeasible_lineq.
if isempty(Aineq)
infeasible_lineq = [];
else
Aineq_rownorm1 = sum(abs(Aineq), 2);
zero_ineq = (Aineq_rownorm1 == 0);
Aineq_rownorm1(zero_ineq) = 1;
infeasible_zero_ineq = (bineq < 0 & zero_ineq);
% bineq has been revised during the reduction; we regard the constraint as infeasible if Inf or
% NaN arises after the reduction.
nan_ineq = nan_ineq | isnan(bineq);
inf_ineq = inf_ineq | (abs(bineq) >= inf);
infeasible_lineq = (bineq ./ Aineq_rownorm1 <= -inf) | infeasible_zero_ineq | nan_ineq | inf_ineq; % A vector of true/false
end
% Preprocess linear equalities: Aeq*x == beq
% Check whether Aeq and beq are real matrices and vectors of proper size, respectively.
[isrm, mA, nA] = isrealmatrix(Aeq);
[isrv, lenb] = isrealvector(beq); % The same as fmincon, we allow beq to be a row
% No matter whether x0 or beq is a row or column, we always require that size(Aeq) is
% [length(beq), length(x0)] unless Aeq = beq = []. This is consistent with fmincon.
if ~(isrm && isrv && (mA == lenb) && (nA == lenx0 || nA == 0))
% Public/normal error
error(sprintf('%s:InvalidLinEq', invoker), ...
'%s: Aeq should be a real matrix, beq should be a real column, and size(Aeq) = [length(beq), length(X0)] unless Aeq = beq = [].', invoker);
end
if (lenb >= 2) && isrealrow(beq)
wid = sprintf('%s:BeqIsRow', invoker);
wmsg = sprintf('%s: beq is a row, but a column is expected; it is transposed.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
beq = double(beq(:));
Aeq = double(Aeq);
% Warn about equality constraints containing NaN.
nan_eq = any(isnan(Aeq), 2) | isnan(beq);
if any(nan_eq)
wid = sprintf('%s:NaNEquality', invoker);
wmsg = sprintf('%s: Aeq or beq contains NaN; The problem is hence infeasible.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
% Warn about equality constraints containing Inf.
inf_eq = any(abs(Aeq) >= inf, 2) | (abs(beq) >= inf);
if any(inf_eq)
wid = sprintf('%s:InfEquality', invoker);
wmsg = sprintf('%s: Aeq or beq contains infinite values; the problem is considered infeasible.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
% Reduce the equality constraints if some but not all variables are fixed.
% 1. This has to be done before detecting the "zero constraints" (i.e., constraints with zero
% gradients), because nonzero constraints may become zero after reduction.
% 2. We should NOT reduce the problem if all variables are fixed. Otherwise, Aeq would be [], and
% then beq will be set to [] in the end. In this way, we lose completely the information in
% these constraints. Consequently, we cannot evaluate the constraint violation correctly when needed.
leq_reduced = false; % Whether linear equality constraints are reduced
if ~isempty(Aeq) && any(fixedx) && any(~fixedx)
Aeq_fixed = Aeq(:, fixedx); % Aeq_fixed and beq_save may be used when revising fixedx_value
beq_save = beq;
beq = beq - Aeq_fixed * fixedx_value;
Aeq = Aeq(:, freex);
leq_reduced = true;
end
% Define infeasible_leq.
if isempty(Aeq)
infeasible_leq = [];
else
Aeq_rownorm1 = sum(abs(Aeq), 2);
zero_eq = (Aeq_rownorm1 == 0);
Aeq_rownorm1(zero_eq) = 1;
infeasible_zero_eq = (beq ~= 0 & zero_eq);
% beq has been revised during the reduction; we regard the constraint as infeasible if Inf or
% NaN arises after the reduction.
nan_eq = nan_eq | isnan(beq);
inf_eq = inf_eq | (abs(beq) >= inf);
infeasible_leq = (abs(beq ./ Aeq_rownorm1) >= inf) | infeasible_zero_eq | nan_eq | inf_eq; % A vector of true/false
end
% Define trivial_lineq and trivial_leq; remove the trivial constraints.
infeasible = (any(infeasible_lineq) || any(infeasible_leq));
if infeasible
trivial_lineq = false(size(bineq));
trivial_leq = false(size(beq));
else
if isempty(Aineq)
trivial_lineq = [];
else
trivial_lineq = ((bineq ./ Aineq_rownorm1 == inf) | (bineq >= inf) | (bineq >= 0 & zero_ineq));
Aineq = Aineq(~trivial_lineq, :); % Remove the trivial linear inequalities
bineq = bineq(~trivial_lineq);
end
if isempty(Aeq)
trivial_leq = [];
else
trivial_leq = (beq == 0 & zero_eq);
Aeq = Aeq(~trivial_leq, :); % Remove trivial linear equalities
beq = beq(~trivial_leq);
end
end
% If infeasibility is detected, then we will return x0 without further calculations. Thus we need to
% revise fixedx_value to x0(fixedx), and redefine bineq/beq so that they are reduced with
% x(fixedx) = x0(fixedx) (otherwise, the constraint violation cannot be correctly calculated later).
reduced = (lineq_reduced || leq_reduced);
if infeasible && reduced
fixedx_value = x0(fixedx);
if lineq_reduced
bineq = bineq_save - Aineq_fixed * fixedx_value;
end
if leq_reduced
beq = beq_save - Aeq_fixed * fixedx_value;
end
end
% We uniformly use [] to represent empty numerical matrices/vectors;
% its size is 0x0. Changing this may cause matrix dimension inconsistency.
if isempty(Aeq)
Aeq = [];
beq = [];
end
if isempty(Aineq)
Aineq = [];
bineq = [];
end
if (max([length(beq), length(bineq)]) > maxint())
% Public/normal error
error(sprintf('%s:ProblemTooLarge', invoker), '%s: The problem is too large; at most %d constraints are allowed.', invoker, maxint());
end
return
%%%%%%%%%%%%%%%%%%%%%%%% Function for fun preprocessing %%%%%%%%%%%%%%%%%
function [fun, feasibility_problem, warnings] = pre_fun(invoker, fun, fixedx, fixedx_value, funcmax, x0_is_row, warnings)
if ~(isempty(fun) || ischarstr(fun) || isa(fun, 'function_handle'))
% Public/normal error
error(sprintf('%s:InvalidFun', invoker), ...
'%s: FUN should be a function handle or a function name.', invoker);
end
feasibility_problem = false; % Is this a feasibility problem?
if isempty(fun)
fun = @(x) 0; % No objective function
feasibility_problem = true; % This is a feasibility problem
wid = sprintf('%s:NoObjective', invoker);
wmsg = sprintf('%s: there is no objective function. A feasibility problem will be solved.', invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
elseif ischarstr(fun)
fun = str2func(fun);
% Work with function handles instead of function names to avoid using 'feval'
end
if ~exist('OCTAVE_VERSION', 'builtin')
% Check whether fun has at least 1 output.
% nargout(fun) = #outputs in the definition of fun.
% If fun includes varargout in definition, nargout(fun) = -#outputs.
% Octave does not support nargout for built-in function (as of 2019-08-16)!
try
% If fun is not a properly defined function, then nargout
% can encounter an error. Wrap the error as a public error.
nout = nargout(fun);
catch exception
% Public/normal error
% Note that the identifier of a public error should start with 'invoker:'
error(sprintf('%s:InvalidFun', invoker), '%s: %s', invoker, exception.message);
end
if (nout == 0)
% Public/normal error
error(sprintf('%s:InvalidFun', invoker), ...
'%s: FUN has no output; it should return the objective function value.', invoker);
end
end
% During the calculation, x is always a column vector. We assume that fun expects a row vector if
% x0 is a row.
if x0_is_row
fun = @(x) evalobj(invoker, fun, x', funcmax); % See evalobj.m for evalobj
else
fun = @(x) evalobj(invoker, fun, x, funcmax); % See evalobj.m for evalobj
end
% Reduce fun if some but not all variables are fixed by the bounds.
% Note that we do not reduce the problem when all variables are fixed. See pre_lcon for the reason.
if any(fixedx) && any(~fixedx)
fun = @(freex_value) fun(fullx(freex_value, fixedx_value, fixedx));
end
return
%%%%%%%%%%%%%%%%% Function for nonlinear constraint preprocessing %%%%%%%%%%
function nonlcon = pre_nonlcon(invoker, nonlcon, fixedx, fixedx_value, constrmax, x0_is_row)
if ~(isempty(nonlcon) || isa(nonlcon, 'function_handle') || ischarstr(nonlcon))
% Public/normal error
error(sprintf('%s:InvalidCon', invoker), ...
'%s: nonlcon should be a function handle or a function name.', invoker);
end
if isempty(nonlcon)
nonlcon = []; % We use [] to signify that nonlcon is not specified; its size is 0x0
else
if ischarstr(nonlcon)
nonlcon = str2func(nonlcon);
% work with function handles instead of function names to avoid using 'feval'
end
if ~exist('OCTAVE_VERSION', 'builtin')
% Check whether nonlcon has at least 2 outputs.
% nargout(fun) = #outputs in the definition of fun.
% If fun includes varargout in definition, nargout(fun) = -#outputs.
% Octave does not support nargout for built-in functions (as of 2019-08-16)!
try
% If nonlcon is not a properly defined function, then nargout
% can encounter an error. Wrap the error as a public error.
nout = nargout(nonlcon);
catch exception
% Public/normal error
% Note that the identifier of a public error should start with 'invoker:'
error(sprintf('%s:InvalidCon', invoker), '%s: %s', invoker, exception.message);
end
if (nout == 0) || (nout == 1)
% Public/normal error
error(sprintf('%s:InvalidCon', invoker), ...
'%s: nonlcon has too few outputs; it should return [cineq, ceq], the constraints being cineq(x) <= 0, ceq(x) = 0.', invoker);
end
end
% During the calculation, x is always a column vector. We assume that nonlcon expects a row
% vector if x0 is a row.
if x0_is_row
nonlcon = @(x) evalcon(invoker, nonlcon, x', constrmax); % See evalcon.m for evalcon
else
nonlcon = @(x) evalcon(invoker, nonlcon, x, constrmax); % See evalcon.m for evalcon
end
% Reduce the nonlcon if some but not all variables are fixed by the bounds
% Note that we do not reduce the problem when all variables are fixed. See pre_lcon for the reason.
if any(fixedx) && any(~fixedx)
nonlcon = @(freex_value) nonlcon(fullx(freex_value, fixedx_value, fixedx));
end
end
return
%%%%%%%%%%%%%%%%% Function fullx used when reducing the problem %%%%%%%%
function x = fullx(freex_value, fixedx_value, fixedx)
x = NaN(length(freex_value)+length(fixedx_value), 1);
x(~fixedx) = freex_value;
x(fixedx) = fixedx_value;
return
%%%%%%%%%%%%%%%%% Function for option preprocessing %%%%%%%%%%
function [options, user_options_fields, warnings] = pre_options(invoker, options, lenx0, lb, ub, warnings)
% NOTE: We treat field names case-sensitively.
% Possible solvers
solver_list = all_solvers();
% We may add other solvers in the future!
% If a new solver is included, we should do the following.
% 0. Include it into the invoker_list (in this and other functions).
% 1. What options does it expect? Set known_fields accordingly.
% 2. Set default options accordingly.
% 3. Check other functions (especially decode_problem, whose behavior
% depends on the invoker/solver. See known_fields there).
% Possible invokers
invoker_list = [solver_list, 'prima'];
% Sometimes a .m is appended to the invoker name. Observed 20230926 on macOS with MATLAB R2022b.
invoker_list = [invoker_list, strcat(invoker_list, '.m')];
callstack = dbstack;
funname = callstack(1).name;
% invoker affects the behavior of this function, so we check invoker
% again, even though it should have been checked in function preprima
if ~ismember(invoker, invoker_list)
% Private/unexpected error
error(sprintf('%s:InvalidInvoker', funname), ...
'%s: UNEXPECTED ERROR: %s serves only %s.', funname, funname, strjoin(invoker_list, ', '));
end
% Default values of the options.
% npt = ! LATER ! % The default npt depends on solver and will be set later in this function
maxfun = 500*lenx0;
rhobeg = 1; % The default rhobeg and rhoend will be revised if solver = 'bobyqa'
rhoend = 1e-6;
ftarget = -inf;
ctol = sqrt(eps); % Tolerance for constraint violation; a point with a constraint violation at most ctol is considered feasible
cweight = 1e8; % The weight of constraint violation in the selection of the returned x
classical = false; % Call the classical Powell code? Classical mode recommended only for research purpose
precision = 'double'; % The precision of the real calculation within the solver
fortran = true; % Call the Fortran code?
scale = false; % Scale the problem according to bounds? Scale only if the bounds reflect well the scale of the problem
scale = (scale && max(ub-lb)<inf); % ! NEVER remove this ! Scale only if all variables are with finite lower and upper bounds
honour_x0 = false; % Respect the user-defined x0? Needed by BOBYQA
iprint = 0;
quiet = true;
debug_flag = false; % Do not use 'debug' as the name, which is a MATLAB function
chkfunval = false;
output_xhist = false; % Output the history of x?
output_nlchist = false; % Output the history of the nonlinear constraints?
min_maxfilt = 200; % The smallest value of maxfilt; if maxfilt is too small, the returned x may not be the best one visited
maxfilt = 10*min_maxfilt; % Length of the filter used for selecting the returned x in constrained problems
eta1 = 0.1; % Reduction ratio threshold for shrinking the trust region
eta2 = 0.7; % Reduction ratio threshold for expanding the trust region
gamma1 = 0.5; % Factor for shrinking the trust region
gamma2 = 2; % Factor for expanding the trust region
if ~(isa(options, 'struct') || isempty(options))
% Public/normal error
error(sprintf('%s:InvalidOptions', invoker), '%s: OPTIONS should be a structure.', invoker);
end
% Which fields are specified?
options = rmempty(options); % Remove empty fields
options_fields = fieldnames(options);
% The list of fields in options will be returned and used elsewhere. We
% save it right now in case we "intelligently" change options_fields
% after this line in future versions.
user_options_fields = options_fields;
% Validate options.solver
% We need to know what is the solver in order to decide which fields
% are 'known' (e.g., expected), and also to set npt, rhobeg, rhoend.
% We do the following:
% 1. If invoker = 'prima':
% 1.1 If no solver is specified or solver = 'prima', we do not complain
% and set options.solver = solver = '', i.e., an empty char array;
% 1.2 Else if solver is not in solver_list, we warn about 'unknown solver'
% and set options.solver = solver = '', i.e., an empty char array;
% 1.3 Else, we set solver = options.solver.
% 2. If invoker is in solver_list:
% 2.1 If options.solver exists but options.solver ~= invoker, we warn
% about 'invalid solver' and set options.solver = solver = invoker;
% 2.2 Else, we do not complain and set options.solver = solver = invoker.
% In this way, options.solver and solver either end up with a member of
% solver_list or ''. The second case is possible only if invoker = 'prima',
% and solver will be selected later.
if isfield(options, 'solver') && ~ischarstr(options.solver)
options.solver = 'UNKNOWN_SOLVER';
% We have to change options.solver to a char/string so that we can use strcmpi
% We do not need to worry about the case where solver is empty, because
% all the empty fields have been removed from options.
end
if strcmp(invoker, 'prima')
% We set the default value of solver to '', an empty char array.
% 1. DO NOT change this default value! It will affect known_fields
% and select_solver.
% 2. DO NOT use [], which is an empty double array and may cause some
% functions (e.g., ismember) to complain about incompatible types.
solver = '';
if isfield(options, 'solver')
if any(strcmpi(options.solver, solver_list))
solver = lower(options.solver);
elseif ~strcmpi(options.solver, 'prima')
% We should not complain about 'unknown solver' if invoker = options.solver = 'prima'
wid = sprintf('%s:UnknownSolver', invoker);
wmsg = sprintf('%s: unknown solver specified; %s will select one automatically.', invoker, invoker);
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
end
else % invoker is in {'uobyqa', ..., 'cobyla'}
if isfield(options, 'solver') && ~strcmpi(options.solver, invoker)
wid = sprintf('%s:InvalidSolver', invoker);
wmsg = sprintf('%s: a solver different from %s is specified; it is ignored.', invoker, invoker);
% Do not display the value of solver in last message, because it
% can be 'unknow_solver'.
warning(wid, '%s', wmsg);
warnings = [warnings, wmsg];
end
solver = invoker;
end
options.solver = char(solver); % Record solver in options.solver; will be used in postprima
% When the invoker is prima, options.solver = solver = '' unless the user defines
% an options.solver in solver_list. Here, '' is an empty char array to signify
% that the solver is yet to decide.
% Check unknown fields according to solver
% solver is '' if it has not been decided yet; in that case, we suppose (for
% simplicity) that all possible fields are known.
known_fields = {'iprint', 'maxfun', 'rhobeg', 'rhoend', 'ftarget', 'classical', 'quiet', 'debug', 'chkfunval', 'solver', 'maxhist', 'output_xhist', 'fortran', 'precision'};
if ~isfield(options, 'classical') || (islogicalscalar(options.classical) && ~options.classical)
known_fields = [known_fields, 'eta1', 'eta2', 'gamma1', 'gamma2'];
end