1/*	The MIT License (MIT)
    2 *
    3 *	Copyright (c) 2022-2024 Rick Workman
    4 *
    5 *	Permission is hereby granted, free of charge, to any person obtaining a copy
    6 *	of this software and associated documentation files (the "Software"), to deal
    7 *	in the Software without restriction, including without limitation the rights
    8 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    9 *	copies of the Software, and to permit persons to whom the Software is
   10 *	furnished to do so, subject to the following conditions:
   11 *
   12 *	The above copyright notice and this permission notice shall be included in all
   13 *	copies or substantial portions of the Software.
   14 *
   15 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   16 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   17 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   18 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   19 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   20 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   21 *	SOFTWARE.
   22 */
   23:- module(clpBNR_toolkit,       % SWI module declaration
   24	[
   25	iterate_until/3,            % general purpose iterator
   26	mid_split_one/1,            % contractor to split largest interval at midpoint
   27	mid_split/1,                % contractor to split an interval at midpoint
   28	taylor_contractor/2,        % build cf_contractor based on Taylor expansion
   29	taylor_merged_contractor/2, % build merged Taylor cf_contractor from list of equations
   30	cf_contractor/2,            % execute cf_contractor
   31	cf_solve/1, cf_solve/2,     % a solve predicate for centre form contractors
   32
   33	integrate/3, integrate/4,   % simple numerical integration
   34	boundary_values/2, boundary_values/3, boundary_values/4,  % solve boundary value problems
   35
   36	lin_minimum/3,              % find minimum of linear problem using library(simplex)
   37	lin_maximum/3,              % find maximum of linear problem using library(simplex)
   38	lin_minimize/3,             % lin_minimum/3 plus bind vars to solution minimizers
   39	lin_maximize/3,             % lin_maximum/3 plus bind vars to solution maximizers
   40
   41	local_minima/1,             % apply KT constraints for objective function expression (OFE)
   42	local_maxima/1,             % semantically equivalent to local_minima/1
   43	local_minima/2,             % apply KT constraints for minima with constraints
   44	local_maxima/2              % apply KT constraints for maxima with constraints
   45	]).

clpBNR_toolkit: Toolkit of various utilities used for solving problems with clpBNR

CLP(BNR) (library(clpBNR)) is a CLP over the domain of real numbers extended with ±∞. This module contains a number of useful utilities for specific problem domains like the optimization of linear systems, enforcing local optima conditions, and constructing centre form contractors to improve performance (e.g., Taylor extensions of constraints). For more detailed discussion, see A Guide to CLP(BNR) (HTML version included with this pack in directory docs/).

Documentation for exported predicates follows. The "custom" types include:

   56:- use_module(library(apply),[maplist/3]).   57:- use_module(library(apply_macros)).  % compiler support for `maplist`, helps a bit
   58:- use_module(library(clpBNR)).   59:- use_module(library(simplex)).   60
   61% sandboxing for SWISH
   62:- multifile(sandbox:safe_primitive/1).   63
   64% messages for noisy failure
   65fail_msg_(FString,Args) :-
   66	debug(clpBNR,FString,Args), fail.
   67	
   68:- set_prolog_flag(optimise,true).  % for arithmetic, this module only
 iterate_until(+Count:integer, Test, Goal) is nondet
Succeeds when Goal succeeds; otherwise fails. Goal will be called recursively up to Count times as long as Test succeeds Example using this predicate for simple labelling using Test small/2 and Goal midsplit/1 :
?- X::real(-1,1),iterate_until(10,small(X,0),mid_split(X)),format("X = ~w\n",X),fail;true.
X = _6288{real(-1,-1r2)}
X = _6288{real(-1r2,0)}
X = _6288{real(0,1r2)}
X = _6288{real(1r2,1)}
true.

The specific intended use case is to provide an iterator for meta-contractors such as the centre-form contractor such as midsplit/1 (example above) or as constructed by taylor_contractor/2 as in:

?- X::real,taylor_contractor({X**4-4*X**3+4*X**2-4*X+3==0},T),
iterate_until(50,small(X),(T,mid_split_one([X]))),format("X = ~w\n",X),fail;true.
X = _150{real(0.999999999926943,1.00000000007306)}
X = _150{real(2.999999999484828,3.0000000005152105)}
true.

(Aside: For some problems, solving with Taylor contractors can be a faster and more precise alternative to clpBNR:solve/1.) */

   92%
   93% General purpose iterator: execute Goal a maximum of N times or until Test succeeds
   94%
   95iterate_until(N,Test,Goal) :- N>0, !,
   96	Goal,
   97	N1 is N-1,
   98	(Test
   99	 -> true
  100	  ; iterate_until(N1,Test,Goal)
  101	).
  102iterate_until(_N,_,_).  % non-positive N --> exit
  103
  104sandbox:safe_meta(clpBNR_toolkit:iterate_until(_N,Test,Goal), [Test, Goal]).
 mid_split_one(+Xs:numeric_list) is nondet
Succeeds splitting the widest interval in Xs, a list of intervals; fails if Xs is not a list of intervals. See mid_split for details of interval splitting for this predicate.
See also
- mid_split/1 */
  113mid_split_one(Xs) :-
  114	select_split(Xs,X),  % select largest interval with largest width
  115	mid_split(X).        % split it
 mid_split(X:numeric) is nondet
Succeeds if X is an interval that can be split at its midpoint narrowing X to it's lower "half"; on backtracking X is constrained to the upper half; fails if X is not a numeric. If X is an interval, defined as:
mid_split(X) :-
        M is midpoint(X),
        ({X=<M} ; {M=<X}).

Note that mid_split succeeds if X is a number, but doesn't do anything.

Use clpBNR:small as a pre-test to avoid splitting intervals which are already small enough.

See also
- clpBNR:small/1 */
  131mid_split(X) :-
  132	(number(X)                    % optimise number case
  133	 -> true
  134	 ;  (small(X)
  135	     -> true
  136	     ;  midpoint(X,M),       % fails if not an interval
  137	        ({X=<M} ; {M=<X})    % possible choicepoint
  138	    )
  139	).
  140%
  141% select interval with largest width
  142%
  143select_split([X],X) :- !.       % select last remaining element
  144select_split([X1,X2|Xs],X) :-   % compare widths and discard one interval
  145	delta(X1,D1),
  146	delta(X2,D2),
  147	(D1 >= D2
  148	 -> select_split([X1|Xs],X)
  149	 ;  select_split([X2|Xs],X)
  150	).
 cf_contractor(Xs:interval_list, As:interval_list) is semidet
Succeeds if each interval in As can be unified with the midpoints of the respective interval in Xs; otherwise fails. This predicate executes one narrowing step of the centre form contractor such as that generated by taylor_contractor. In normal usage, a direct call to cf_contractor does appear; instead use cf_contractor or in a Goal for iterate_until/3.
See also
- taylor_contractor/2, cf_solve/1, iterate_until/3 */
  159%
  160% centred form contractor
  161%
  162% Bind the values of As to the midpoints of Xs. To support repetitive application 
  163% of the contractor (required by the iterator), the contractor should not permanently 
  164% bind anything so findall/3 will be used to achieve this "forward checking" 
  165% (as suggested in [CLIP]). After the call to findall, the bounds of the resulting list
  166% of narrowed domains (XDs) are then applied to Xs.
  167%
  168% This contractor can be used with any "centred form", e.g., Newton or Krawczyk, since it
  169% only depends on intervals and their midpoints, hence its name `cf_contractor`. The 
  170% details which distinguish the variety of centred form are built into the variables' 
  171% constraints.
  172%
  173cf_contractor(Xs,As) :-
  174	findall(Ds,(maplist(bind_to_midpoint,Xs,As),maplist(cf_domain,Xs,Ds)),[XDs]),
  175	maplist(set_domain,Xs,XDs).
  176	
  177bind_to_midpoint(X,A) :- A is float(midpoint(X)).
  178
  179cf_domain(X,D) :- 
  180	number(X) -> D = X ; domain(X,D).  % in case X narrowed to a point
  181	
  182set_domain(X,D) :- 
  183	number(D) -> X = D ; X::D.
 cf_solve(+Contractor) is nondet
Equivalent to cf_solve/2 using default precision. */
 cf_solve(+Contractor, +Precision:integer) is nondet
Succeeds if a solution can be found for all variables in the centre form contractor, Contractor, where the resultant domain of any variable is narrower than the limit specified by Precision (for cf_solve/1, default Precision is number of digits as defined by the environment flag clpBNR_default_precision); otherwise fails.

This is done by using iterate_until/3 limited to a count determined by the flag clpBNR_iteration_limit. Examples:

?- X::real, taylor_contractor({X**4-4*X**3+4*X**2-4*X+3==0},T), cf_solve(T).
T = cf_contractor([X], [_A]),
X:: 1.000000000...,
_A::real(-1.0Inf, 1.0Inf) ;
T = cf_contractor([X], [_A]),
X:: 3.00000000...,
_A::real(-1.0Inf, 1.0Inf) ;
false.

?- taylor_contractor({2*X1+5*X1**3+1==X2*(1+X2), 2*X2+5*X2**3+1==X1*(1+X1)},T), cf_solve(T).
T = cf_contractor([X2, X1], [_A, _B]),
X1:: -0.42730462...,
X2:: -0.42730462...,
_B::real(-1.0Inf, 1.0Inf),
_A::real(-1.0Inf, 1.0Inf) ;
false.
See also
- taylor_contractor/2, cf_contractor/2 */
  217cf_solve(T) :-
  218    current_prolog_flag(clpBNR_default_precision,P),
  219    cf_solve(T,P).
  220cf_solve(cf_contractor(Xs,As),P) :-
  221    current_prolog_flag(clpBNR_iteration_limit,L),
  222    Count is L div 10,          % heuristic - primitive iteration limit/10    
  223    cf_iterate_(Count,Xs,As,P).
  224
  225cf_iterate_(Count,Xs,As,P) :-
  226	Count > 0,
  227	\+ small(Xs,P),             % at least one var not narrow enough
  228	!, 
  229	cf_contractor(Xs,As),       % execute contractor
  230	select_split(Xs,X),         % select widest
  231	(small(X,P)                 % still wide enough to split?
  232	 -> true                    % no, we're done 
  233	  ; mid_split(X),           % yes, split it
  234	    Count1 is Count-1,
  235	    cf_iterate_(Count1,Xs,As,P)  % and iterate
  236	).
  237cf_iterate_(_,_,_,_).           % done (Count=<0 or all small Xs)
 taylor_contractor(+Constraints, -Contractor) is semidet
Succeeds if a centre form contractor Contractor can be generated from one or more multivariate equality (== or =:=) constraints Constraints; otherwise fails. Example:
?- taylor_contractor({X**4-4*X**3+4*X**2-4*X+3==0},T).
T = cf_contractor([X], [_A]),
X::real(-1.509169756145379, 4.18727500493995),
_A::real(-1.0Inf, 1.0Inf).

Use the contractor with cf_solve to search for solutions, as in:

?- X::real,taylor_contractor({X**4-4*X**3+4*X**2-4*X+3==0},T), cf_solve(T).
T = cf_contractor([X], [_A]),
X:: 1.000000000...,
_A::real(-1.0Inf, 1.0Inf) ;
T = cf_contractor([X], [_A]),
X:: 3.00000000...,
_A::real(-1.0Inf, 1.0Inf) ;
false.

Multiple equality constraints are supported, as in this example of the Broyden banded problem (N=2):

?- taylor_contractor({2*X1+5*X1**3+1==X2*(1+X2), 2*X2+5*X2**3+1==X1*(1+X1)},T), cf_solve(T).
T = cf_contractor([X2, X1], [_A, _B]),
X1:: -0.42730462...,
X2:: -0.42730462...,
_B::real(-1.0Inf, 1.0Inf),
_A::real(-1.0Inf, 1.0Inf) ;
false.

Centre form contractors can converge faster than the general purpose builtin fixed point iteration provided by solve/1.

See also
- cf_solve/1 */
  274%
  275% build a cf_contractor for a multivariate expression based on Taylor expansion
  276%
  277taylor_contractor({E1=:=E2},CF) :-
  278	taylor_contractor({E1==E2},CF).
  279taylor_contractor({E1==E2},cf_contractor(Xs,As)) :-
  280	Exp=E1-E2,
  281	term_variables(Exp,Xs),              % original arguments, bound to TXs on call
  282	make_EQ_(Exp,TEQ),                   % original constraint with arguments
  283	% build constraint list starting with Z's and ending with TEQ and DEQ ()
  284	T::real(0,1),
  285	make_As_and_Zs_(Xs,T,As,Zs,Cs,[TEQ,DEQ]),  % T per Z
  286	% now build Taylor constraint, DEQ
  287	copy_term_nat(Exp,AExp),              % copy of original constraint with  As
  288	term_variables(AExp,As),
  289	sum_diffs(Xs, As, Zs, Zs, Exp, AExp, DEQ),  % add on D(Z)'s'
  290	% make any vars in original equation and contractor arguments finite real intervals
  291	!,
  292	Xs::real,  % all vars are intervals
  293	{Cs}.      % apply constraints
  294taylor_contractor({Es},CF) :-
  295	taylor_merged_contractor({Es},CF),  % list or sequence	
  296	!.
  297taylor_contractor(Eq,_) :-
  298	fail_msg_('Invalid constraint for Taylor contractor: ~w',[Eq]).
  299
  300make_As_and_Zs_([],_,[],[],Tail,Tail).
  301make_As_and_Zs_([X|Xs],T,[A|As],[Z|Zs],[Z==A+T*(X-A)|CZs],Tail) :-
  302	make_As_and_Zs_(Xs,T,As,Zs,CZs,Tail).
  303
  304sum_diffs([], [], [], _AllZs, _Exp, ExpIn, EQ) :- make_EQ_(ExpIn,EQ).
  305sum_diffs([X|Xs], [A|As], [Z|Zs], AllZs, Exp, AExp, DEQ) :-
  306	copy_term_nat(Exp,NExp),        % copy expression and replace Xs by Zs
  307	term_variables(NExp,AllZs),
  308	partial_derivative(NExp,Z,DZ),  % differentiate wrt. Z and add to generated expression
  309	sum_diffs(Xs, As, Zs, AllZs, Exp, AExp+DZ*(X-A), DEQ).
  310
  311% map expression Exp to an equation equivalent to Exp==0 with numeric RHS
  312make_EQ_(Exp,LHS==RHS) :-    % turn expression into equation equivalent to Exp==0.
  313	make_EQ_(Exp,LHS,RHS).
  314	
  315make_EQ_(E,E,0)         :- var(E), !.
  316make_EQ_(X+Y,X,SY)      :- number(Y), !, SY is -Y.
  317make_EQ_(X-Y,X,Y)       :- number(Y), !.
  318make_EQ_(X+Y,Y,SX)      :- number(X), !, SX is -X.
  319make_EQ_(X-Y,SY,SX)     :- number(X), !, SX is -X, negate_sum_(Y,SY).
  320make_EQ_(X+Y,LHS+Y,RHS) :- !, make_EQ_(X,LHS,RHS).
  321make_EQ_(X-Y,LHS-Y,RHS) :- !, make_EQ_(X,LHS,RHS).
  322make_EQ_(E,E,0).        % default (non +/- subexpression)
  323
  324negate_sum_(Y,-Y) :- var(Y), !.
  325negate_sum_(X+Y,NX-Y) :- !, negate_sum_(X,NX).
  326negate_sum_(X-Y,NX+Y) :- !, negate_sum_(X,NX).
  327negate_sum_(E,-E).
 taylor_merged_contractor(+Constraints, -Contractor) is semidet
Succeeds if a merged centre form contractor Contractor can be generated from each equality (== or =:=) constraint in Constraints; otherwise fails.
deprecated
-
  • use taylor_contractor/2 instead */
  336%
  337% build a cf_contractor by merging a list of cf_contractor's
  338%
  339taylor_merged_contractor({Es},T) :-
  340	cf_list(Es,Ts),
  341	cf_merge(Ts,T).
  342
  343cf_list([],[]) :- !.
  344cf_list([C|Cs],[CF|CFs]) :- !,
  345	cf_list(C, CF),
  346	cf_list(Cs,CFs).
  347cf_list((C,Cs),[CF|CFs]) :-  !,
  348	cf_list(C, CF),
  349	cf_list(Cs,CFs).
  350cf_list(C,CF) :-
  351	taylor_contractor({C},CF).
  352
  353cf_merge(CFs,CF) :- cf_merge(CFs,cf_contractor([],[]),CF).
  354
  355cf_merge([],CF,CF).
  356cf_merge([CF|CFs],CFIn,CFOut) :-
  357	cf_merge(CF,CFIn,CFNxt),
  358	cf_merge(CFs,CFNxt,CFOut).	
  359cf_merge(cf_contractor(Xs,As),cf_contractor(XsIn,AsIn),cf_contractor(XsOut,AsOut)) :-
  360	cf_add(Xs,As,XsIn,AsIn,XsOut,AsOut).
  361
  362cf_add([],[],Xs,As,Xs,As).
  363cf_add([X|Xs],[A|As],XsIn,AsIn,XsOut,AsOut) :-
  364	var_existing(XsIn,AsIn,X,A), !,
  365	cf_add(Xs,As,XsIn,AsIn,XsOut,AsOut).
  366cf_add([X|Xs],[A|As],XsIn,AsIn,XsOut,AsOut) :-
  367	cf_add(Xs,As,[X|XsIn],[A|AsIn],XsOut,AsOut).
  368
  369var_existing([Xex|Xs],[Aex|As], X,A) :- Xex==X -> Aex=A ; var_existing(Xs,As,X,A).
 integrate(+F, -X:interval, -R) is semidet
Equivalent to integrate/4 with default precision. */
 integrate(+F, -X:interval, -R, +P:integer) is semidet
Succeeds if R is the numerical integration of F over the range of interval X. F must be continuously differentiable over this range or it fails.

The number of integration steps (= 2**P) is determined by the precision parameter P (default is value of environment flag clpBNR_default_precision). Example of use with increasing precision values:

?- X::real(0.0,1.0), F=X**2, between(2,10,P),integrate(F,X,RV,P), range(RV,R), format('~w:~w\n',[P,R]), fail.
2:[0.328125,0.359375]
3:[0.33203125,0.33984375]
4:[0.3330078125,0.3349609375]
5:[0.333251953125,0.333740234375]
6:[0.33331298828125,0.33343505859375]
7:[0.3333282470703125,0.3333587646484375]
8:[0.3333320617675781,0.3333396911621094]
9:[0.33333301544189453,0.33333492279052734]
10:[0.33333325386047363,0.33333373069763184]
false.

*/

  397% integrate(F,X,R) where X is an interval over which to integrate and F = f(X)
  398% Note that integrate(F,X,R) is equivalent to boundary_values(X,[dV(_,F,0,R])
  399integrate(F,X,R) :-
  400	current_prolog_flag(clpBNR_default_precision,P),
  401	integrate(F,X,R,P,_).
  402integrate(F,X,R,P) :-
  403	integrate(F,X,R,P,_).
  404integrate(F,X,R,P,Steps) :-                    % internal arity 5 for development
  405	compound(F),                               % F must be an expression in X
  406    interval(X),                               % X must be an interval 
  407    integer(P), P>0,                           % P must be positive integer
  408	!,                                         % args OK, commit 
  409	boundary_values(X,[dV(_,F,0,R)],P,Steps). % use integration in `boundary_values`
  410integrate(F,X,R,P,_Steps) :-
  411	fail_msg_('Invalid argument(s): ~w',[integrate(F,X,R,P)]).
 boundary_values(-X:interval, +Dvars:list) is semidet
Equivalent to boundary_values/4 with default precision and discarding steps list. */
 boundary_values(-X:interval, +Dvars:list, +P:integer) is semidet
Equivalent to boundary_values/4 discarding steps list. */
 boundary_values(-X:interval, +Dvars:list, +P:integer, -Steps:list) is semidet
Succeeds if a solution can be found to the boundary value problem specified by the independent variable X and the list of dependent variables defined by Dvars. Each dependent variable definition is a compound term of the form dV(Y, Fxy, Yi, Yf). The initial and final values of the independent variable for the purposes of the boundary value problem are specified by the lower and upper values of the domain of X. The arguments of for dvar/4 are:
  461boundary_values(X,YDefs) :-
  462	current_prolog_flag(clpBNR_default_precision,P),
  463	boundary_values_(X,YDefs,P,_).
  464	
  465boundary_values(X,YDefs,P) :-
  466	boundary_values_(X,YDefs,P,_).
  467
  468boundary_values(X,YDefs,P,Steps) :-
  469	boundary_values_(X,YDefs,P,Steps/_).
  470
  471boundary_values_(X,YDefs,P,Out/[(Xf,Yfs)]) :-
  472    integer(P), P>0,                             % P must be positive integer
  473    domain(X,Xdom), Xdom =.. [_Type,Xi,Xf],      % X must be an interval 
  474	eval_dvars(YDefs,Ys,Yis,Yfs,Ydoms),          % too many args for maplist	
  475	maplist(fXY_lambda(X,Ys),YDefs,Fxys),        % list of partial derivative lambda args
  476	(maplist(total_derivative_(Fxys),Fxys,DFxys) % list of connective derivative lambda args
  477	 -> true
  478	 ;  DFxys = none                             % F non-differentiable(?), use euler step
  479	),
  480	!,                                           % args all good, commit
  481    integrate_(P,Fxys,DFxys,(Xi,Yis),(Xf,Yfs),Ydoms,Out/[(Xf,Yfs)]).
  482boundary_values_(X,YDefs,P,_) :-
  483	fail_msg_('Invalid argument(s): ~w',[boundary_values(X,YDefs,P)]).
  484
  485eval_dvars([],[],[],[],[]).
  486eval_dvars([dV(Y, _PD, Lexp, Uexp)|YDefs],[Y|Ys],[Yi|Yis],[Yf|Yfs],[Ydom|Ydoms]) :-
  487	(domain(Y,Ydom) -> true ; Ydom = real),  % Ydom defaults to real 
  488	(var(Lexp) -> Yi=Lexp, Yi::Ydom ; Yi is Lexp),
  489	(var(Uexp) -> Yf=Uexp, Yf::Ydom ; Yf is Uexp),
  490	eval_dvars(YDefs,Ys,Yis,Yfs,Ydoms).
  491
  492% construct Lambda args for Fxy
  493fXY_lambda(X,Ys,dV(_Y,Fxy,_,_),FxyArgs) :- 
  494	lambda_for_(Fxy,X,Ys,FxyArgs).
  495		
  496% construct Lambda args for derivative function of Fxy from Lambda of Fxy
  497
  498total_derivative_(Fxys,_Free/Ps,DxyArgs) :- !,  % ignore free variables
  499	total_derivative_(Fxys,Ps,DxyArgs).
  500total_derivative_(Fxys,[X,Ys,Fxy],DxyArgs) :-
  501	partial_derivative(Fxy,X,DFDX),          % clpBNR built-in
  502	sumYpartials(Fxys,Ys,Fxy,0,DYsum),
  503	simplify_sum_(DFDX, DYsum, DExp), !,
  504	lambda_for_(DExp,X,Ys,DxyArgs).
  505	
  506sumYpartials([],[],_Fxy,Acc,Acc).
  507sumYpartials([_Free/FxyI|FxyIs],YIs,Fxy,Acc,Sum) :- !,
  508	sumYpartials([FxyI|FxyIs],YIs,Fxy,Acc,Sum).
  509sumYpartials([[_X,_Ys,FxyI]|FxyIs],[YI|YIs],Fxy,Acc,Sum) :-
  510	partial_derivative(Fxy,YI,DFDYI),
  511	(number(DFDYI), DFDYI =:= 0 -> NxtAcc = Acc ; simplify_sum_(Acc,FxyI*DFDYI,NxtAcc)),
  512	!,
  513	sumYpartials(FxyIs,YIs,Fxy,NxtAcc,Sum).
  514	
  515simplify_sum_(X,Y,Y) :- number(X),X=:=0.
  516simplify_sum_(X,Y,X) :- number(Y),Y=:=0.
  517simplify_sum_(X,Y,X+Y).
  518
  519% construct args for Lambda expression
  520lambda_for_(Fxy,X,Ys,Args) :-
  521	Lambda_parms = [X,Ys,Fxy],
  522	term_variables(Fxy,FVs),
  523	exclude(var_member_([X|Ys]),FVs,EVs),    % EVs = free variables
  524	(comma_op_(EVs,EV) -> Args = {EV}/Lambda_parms ; Args = Lambda_parms).
  525
  526var_member_([L|Ls],E) :- L==E -> true ; var_member_(Ls,E).
  527
  528comma_op_([X],X).  % assumes use in if-then
  529comma_op_([X|Xs],(X,Y)) :- comma_op_(Xs,Y).	
  530
  531% integration loop
  532integrate_(0, Fxys, Dxys, Initial, Final, Ydomains, [Initial|Ps]/Ps) :- !,
  533	% select integration step
  534	( Dxys == none
  535	 -> step_euler(Fxys, Initial, Final, Ydomains)
  536	 ;  step_trap(Fxys, Dxys, Initial, Final, Ydomains)
  537	).
  538integrate_(P, Fxys, Dxys, Initial, Final, Ydomains, L/E) :-
  539    % create interpolation point and integrate two halves
  540    interpolate_(Initial, Final, Ydomains, Middle),
  541    Pn is P - 1,
  542    integrate_(Pn, Fxys, Dxys, Initial, Middle, Ydomains, L/M),
  543    integrate_(Pn, Fxys, Dxys, Middle,  Final,  Ydomains, M/E).
  544
  545interpolate_((X0,_Y0s), (X1,_Y1s), Ydomains, (XM,YMs)) :-
  546    XM is (X0 + X1)/2,            % XM is midpoint of (X0,X1)
  547	maplist(::,YMs,Ydomains).     % corresponding YMs
  548
  549step_euler(Fxys, (X0,Y0), (X1,Y1), Ydoms) :-
  550    X01:: real(X0,X1),            % range of X in step
  551    maplist(lambda_constrain_(X01,Y01),Fxys,Fs),   % approx f' over X0 
  552    Dx is X1 - X0,                % assumed (X1>X0)
  553    DX :: real(0,Dx),             % range for estimate
  554	euler_constraints(Y0,Y1,Y01,Ydoms,Dx,DX,Fs,In/In,Cs/[]),  % flatten with diff list
  555	{Cs}.
  556	
  557step_trap(Fxys, Dxys, (X0,Y0), (X1,Y1), Ydoms) :-
  558    X01:: real(X0,X1),            % range of X in step
  559    maplist(lambda_constrain_(X0,Y0),Fxys,F0s),    % F0s = slopes at X0
  560    maplist(lambda_constrain_(X1,Y1),Fxys,F1s),    % F1s = slopes at X1 
  561    maplist(lambda_constrain_(X01,Y01),Dxys,Ds),   % approx f' over X0 
  562    Dx is X1 - X0,                % assumed (X1>X0)
  563    DX :: real(0,Dx),             % range for estimate
  564	trap_constraints(Y0,Y1,Y01,Ydoms,Dx,DX,F0s,F1s,Ds,In/In,Cs/[]),  % flatten with diff list
  565	{Cs}.  %%, absolve(Y1,2).
  566
  567lambda_constrain_(X,Ys,Args,F) :-  % reorder args for yall: >>
  568	yall: >>(Args,true,X,Ys,F).    % avoid meta-call (basically just makes copy)
  569%  known safe since lambda Goal=true
  570sandbox:safe_primitive(clpBNR_toolkit:lambda_constrain_(_X,_Ys,_Args,_F)). 
  571
  572/* see Carleton notes:
  573https://www.softwarepreservation.org/projects/prolog/bnr/doc/Older-Introduction_to_CLP%28BNR%29-1995.pdf/view
  574*/
  575euler_constraints([],[],[],[],_Dx,_DX,[],In,In).
  576euler_constraints([Y0|Y0s],[Y1|Y1s],[Y01|Y01s],[Ydom|Ydoms],Dx,DX,[F|Fs],
  577	In/[FM <= F,                      % FM = slope inclusion 
  578	    Y01 - Y0 is DX*FM,
  579	    Y1  - Y0 is Dx*FM
  580	   |Cs],
  581	Out) :-
  582	Y01:: Ydom,
  583	euler_constraints(Y0s,Y1s,Y01s,Ydoms,Dx,DX,Fs,In/Cs,Out).
  584
  585trap_constraints([],[],[],[],_Dx,_DX,[],[],[],In,In).
  586trap_constraints([Y0|Y0s],[Y1|Y1s],[Y01|Y01s],[Ydom|Ydoms],Dx,DX,[F0|F0s],[F1|F1s],[D|Ds],
  587	% use `is` to circumvent `simplify`
  588	In/[FM <= (F0+F1)/2,              % FM = average slope using step endpoints (one-way)
  589	    % Note that the following must not be symbolically simplified to eliminate D
  590	    8*DR is D - D,                % 4*deltaR == (D-D)/2 (for error term)
  591	    Y01 - Y0 is DX*(FM + DR*DX),
  592	    Y1  - Y0 is Dx*(FM + DR*Dx)
  593	   |Cs],
  594	Out) :-
  595	Y01:: Ydom, DR::real,	trap_constraints(Y0s,Y1s,Y01s,Ydoms,Dx,DX,F0s,F1s,Ds,In/Cs,Out).
 lin_minimum(+ObjF, Constraints:{}, ?Min:numeric) is semidet
Succeeds if the global minimum value of the objective function ObjF subject to Constraints can be unified with Min; otherwise fails. Both the objective function and Constraints must be linear, i.e., only subexpressions summing of the form X*C (or C*X) are permitted since the actual computation is done using library(simplex). Narrowing of minimizers (variables in ObjF) is limited to that constrained by the Min result to accomodate multiple sets of minimizers. (See lin_minimize/3 to use minimizers used to derive Min.) A solution generator, e.g., clpBNR:solve/1 can be used to search for alternative sets of minimizers. "Universal Mines" example from the User Guide:
?- [M_Idays,M_IIdays,M_IIIdays]::integer(0,7),
lin_minimum(20*M_Idays+22*M_IIdays+18*M_IIIdays,
{4*M_Idays+6*M_IIdays+M_IIIdays>=54,4*M_Idays+4*M_IIdays+6*M_IIIdays>=65}, Min).
Min = 284,
M_Idays::integer(2, 7),
M_IIdays::integer(4, 7),
M_IIIdays::integer(2, 7).

?- [M_Idays,M_IIdays,M_IIIdays]::integer(0,7),
lin_minimum(20*M_Idays+22*M_IIdays+18*M_IIIdays,
{4*M_Idays+6*M_IIdays+M_IIIdays>=54,4*M_Idays+4*M_IIdays+6*M_IIIdays>=65}, Min),
solve([M_Idays,M_IIdays,M_IIIdays]).
M_Idays = 2,
M_IIdays = 7,
M_IIIdays = 5,
Min = 284 ;
false.

For linear systems, lin_minimum/3, lin_maximum/3 can be significantly faster than using the more general purpose clpBNR:global_minimum/3, clpBNR:global_maximum/3

See also
- lin_minimize/3, library(simplex) */
 lin_maximum(+ObjF, Constraints:{}, ?Max:numeric) is semidet
Succeeds if the global minimum value of the objective function ObjF subject to Constraints can be unified with Max; otherwise fails. This is the corresponding predicate to lin_minimum/3 for finding global maxima.
See also
- lin_minimum/3, lin_maximize/3 */
  633lin_minimum(ObjF,{Constraints},MinValue) :-
  634	lin_minimum_(ObjF,{Constraints},MinValue,false).
  635
  636lin_maximum(ObjF,{Constraints},MinValue) :-
  637	lin_maximum_(ObjF,{Constraints},MinValue,false).
 lin_minimize(+ObjF, Constraints:{}, ?Min:numeric) is semidet
Succeeds if the global minimum value of the objective function ObjF subject to Constraints can be unified with Min; otherwise fails. This behaves identically to lin_minimum/3 except variables in ObjF will be narrowed to the values used in calculating the final value of Min. Any other sets of minimizers corresponding to Min are removed from the solution space. "Universal Mines" example from the User Guide:
?- [M_Idays,M_IIdays,M_IIIdays]::integer(0,7),
lin_minimize(20*M_Idays+22*M_IIdays+18*M_IIIdays,
{4*M_Idays+6*M_IIdays+M_IIIdays>=54,4*M_Idays+4*M_IIdays+6*M_IIIdays>=65}, Min).
M_Idays = 2,
M_IIdays = 7,
M_IIIdays = 5,
Min = 284.
See also
- lin_minimum/3 */
 lin_maximize(+ObjF, Constraints:{}, ?Max:numeric) is semidet
Succeeds if the global maximum value of the objective function ObjF subject to Constraints can be unified with Max; otherwise fails. This behaves identically to lin_maximum/3 except variables in ObjF will be narrowed to the values used in calculating the final value of Max. Any other sets of minimizers corresponding to Min are removed from the solution space.
See also
- lin_maximum/3 */
  660lin_minimize(ObjF,{Constraints},MinValue) :-
  661	lin_minimum_(ObjF,{Constraints},MinValue,true).
  662
  663lin_maximize(ObjF,{Constraints},MinValue) :-
  664	lin_maximum_(ObjF,{Constraints},MinValue,true).
  665	
  666
  667lin_minimum_(ObjF,{Constraints},MinValue,BindVars) :-
  668	init_simplex_(ObjF,Constraints,Objective,S0,Vs),
  669	(minimize(Objective,S0,S)
  670	 -> objective(S,MinValue), {ObjF == MinValue},
  671	    (BindVars == true
  672	     -> bind_vars_(Vs,S)
  673	     ;  remove_names_(Vs),
  674	        {Constraints}  % apply constraints
  675	    )
  676	 ;  fail_msg_('Failed to minimize: ~w',[ObjF])
  677	).
  678	
  679lin_maximum_(ObjF,{Constraints},MaxValue,BindVars) :-
  680	init_simplex_(ObjF,Constraints,Objective,S0,Vs),
  681	(maximize(Objective,S0,S)
  682	 -> objective(S,MaxValue), {ObjF == MaxValue},
  683	    (BindVars == true
  684	     -> bind_vars_(Vs,S)
  685	     ;  remove_names_(Vs),
  686	        {Constraints}  % apply constraints
  687	    )
  688	 ;  fail_msg_('Failed to maximize: ~w',[ObjF])
  689	).
  690
  691init_simplex_(ObjF,Constraints,Objective,S,Vs) :-
  692	gen_state(S0),
  693	term_variables((ObjF,Constraints),Vs),
  694	(Vs = []
  695	 -> fail_msg_('No variables in expression to optimize: ~w',[ObjF])
  696	 ;  sim_constraints_(Constraints,S0,S1),
  697	    _::real(_,Max),  % max value to constrain for simplex
  698	    constrain_ints_(Vs,Max,S1,S),
  699	    (map_simplex_(ObjF,T/T,Objective/[])
  700	     -> true
  701	     ;  fail_msg_('Illegal linear objective: ~w',[ObjF])
  702	    )
  703	).
  704
  705% use an attribute to associate a var with a unique simplex variable name
  706simplex_var_(V,SV) :- var(V),
  707	(get_attr(V,clpBNR_toolkit,SV)
  708	 -> true
  709	 ;  statistics(inferences,VID), SV = var(VID), put_attr(V,clpBNR_toolkit,SV)
  710	).
  711
  712% Name attribute removed before exit.
  713remove_names_([]).
  714remove_names_([V|Vs]) :-
  715	del_attr(V,clpBNR_toolkit),
  716	remove_names_(Vs).
  717		 
  718attr_unify_hook(var(_),_).     % unification always does nothing and succeeds
  719
  720constrain_ints_([],_,S,S).
  721constrain_ints_([V|Vs],Max,Sin,Sout) :- 
  722	% Note: library(simplex) currently constrains all variables to be non-negative
  723	simplex_var_(V,SV),               % corresponding simplex variable name
  724	% if not already an interval, make it one with finite non-negative value
  725	(domain(V,D) -> true ; V::real(0,_), domain(V,D)),
  726	(D == boolean -> Dom = integer(0,1); Dom = D),
  727	Dom =.. [Type,L,H],
  728	(Type == integer -> constraint(integral(SV),Sin,S1) ; S1 = Sin),
  729	(L < 0
  730	 -> % apply non-negativity condition    
  731	    ({V >= 0} -> L1 = 0 ; fail_msg_('Negative vars not supported by \'simplex\': ~w',[V]))
  732	 ;  L1 = L
  733	),
  734	% explicitly constrain any vars not (0,Max-eps)
  735	(L1 > 0   -> constraint([SV] >= L1,S1,S2)   ; S2 = S1),    % L1 not negative
  736	(H  < Max -> constraint([SV] =< H,S2,SNxt)  ; SNxt = S2),
  737	constrain_ints_(Vs,Max,SNxt,Sout).
  738
  739bind_vars_([],_).
  740bind_vars_([V|Vs],S) :-  
  741	% Note: skip anything nonvar (already bound due to active constraints)
  742	(simplex_var_(V,SV) -> variable_value(S,SV,V) ; true),
  743	bind_vars_(Vs,S).
  744
  745% clpBNR constraints have already been applied so worst errors have been detected
  746sim_constraints_([],S,S) :- !.
  747sim_constraints_([C|Cs],Sin,Sout) :- !,
  748	sim_constraints_(C, Sin,Snxt),
  749	sim_constraints_(Cs,Snxt,Sout).
  750sim_constraints_((C,Cs),Sin,Sout) :-  !,
  751	sim_constraints_(C, Sin,Snxt),
  752	sim_constraints_(Cs,Snxt,Sout).
  753sim_constraints_(C,Sin,Sout) :- 
  754	sim_constraint_(C,SC),
  755	constraint(SC,Sin,Sout).  % from simplex
  756
  757sim_constraint_(C,SC) :-
  758	C=..[Op,LHS,RHS],              % decompose
  759	constraint_op(Op,COp),         % acceptable to simplex
  760	number(RHS), RHS >= 0,         % requirement of simplex
  761	map_simplex_(LHS,T/T,Sim/[]),  % map to simplex list of terms
  762	!,
  763	SC=..[COp,Sim,RHS].            % recompose
  764sim_constraint_(C,_) :-
  765	fail_msg_('Illegal linear constraint: ~w',[C]).	
  766
  767map_simplex_(T,CT/[S|Tail],CT/Tail) :-
  768	map_simplex_term_(T,S),
  769	!.
  770map_simplex_(A+B,Cin,Cout) :- !,
  771	map_simplex_(A,Cin,Cnxt),
  772	map_simplex_(B,Cnxt,Cout).
  773map_simplex_(A-B,Cin,Cout) :- !,
  774	map_simplex_(A,Cin,Cnxt),
  775	map_simplex_(-B,Cnxt,Cout).
  776			
  777map_simplex_term_(V,1*SV)   :- simplex_var_(V,SV), !.
  778map_simplex_term_(-T,NN*V)  :- !,
  779	map_simplex_term_(T,N*V),
  780	NN is -N.
  781map_simplex_term_(N*V,N*SV) :- number(N), simplex_var_(V,SV), !.
  782map_simplex_term_(V*N,N*SV) :- number(N), simplex_var_(V,SV).
  783
  784constraint_op(==,=).
  785constraint_op(=<,=<).
  786constraint_op(>=,>=).
 local_minima(+ObjF) is semidet
Succeeds if the value of objective function ObjF can be constrained to be a local minimum, i.e, it's "slope" is 0 in every dimension; otherwise fails. This requires that a partial derivative of ObjF exists for each variable. local_minima should be executed prior to a call to clpBNR:global_minimum using the same objective function, e.g.,
?- X::real(0,10), OF=X**3-6*X**2+9*X+6, local_minima(OF), global_minimum(OF,Z).
OF = X**3-6*X**2+9*X+6,
X:: 3.00000000000000...,
Z:: 6.000000000000... .

Using any local optima predicate can significantly improve performance compared to searching for global optima (clpBNR:global_*) without local constraints.

See also
- clpBNR:local_minima/2 */
 local_maxima(+ObjF) is semidet
Succeeds if the value of objective function ObjF can be constrained to be a local maximum, i.e, it's "slope" is 0 in every dimension; otherwise fails. This requires that a partial derivative of ObjF exists for each variable. local_maxima should be executed prior to a call to clpBNR:global_maximum using the same objective function, e.g.,
?- X::real(0,10), OF=X**3-6*X**2+9*X+6, local_maxima(OF), global_maximum(OF,Z).
OF = X**3-6*X**2+9*X+6,
X:: 1.000000000000000...,
Z:: 10.0000000000000... .
See also
- clpBNR:local_maxima/2 */
  814%
  815%	local_minima/1,       % apply KT constraints for objective function expression (OFE)
  816%	local_maxima/1,       % semantically equivalent to local_minima/1
  817%
  818local_minima(ObjExp) :-
  819	local_optima_(min,ObjExp,[]).
  820
  821local_maxima(ObjExp) :-
  822	local_optima_(max,ObjExp,[]).
 local_minima(+ObjF, +Constraints:{}) is semidet
Succeeds if the value of objective function ObjF can be constrained to be a local minimum, i.e, it's "slope" is 0 in every dimension, subject to Constraints; otherwise fails. This requires that a partial derivative of ObjF, and any subexpression in Constraints, exists for each variable. local_minima should be executed prior to a call to clpBNR:global_minimum using the same objective function, e.g.,
?- [X1,X2]::real, OF=X1**4*exp(-0.01*(X1*X2)**2),
local_minima(OF,{2*X1**2+X2**2==10}), global_minimum(OF,Z), solve([X1,X2]).
OF = X1**4*exp(-0.01*(X1*X2)**2),
X1::real(-1.703183936003284e-108, 1.703183936003284e-108),
X2:: -3.16227766016838...,
Z:: 0.0000000000000000... ;
OF = X1**4*exp(-0.01*(X1*X2)**2),
X1::real(-1.703183936003284e-108, 1.703183936003284e-108),
X2:: 3.16227766016838...,
Z:: 0.0000000000000000... .
See also
- clpBNR:local_minima/1 */
 local_maxima(+ObjF, +Constraints:{}) is semidet
Succeeds if the value of objective function ObjF can be constrained to be a local maximum, i.e, it's "slope" is 0 in every dimension; otherwise fails. This requires that a partial derivative of ObjF, and any subexpression in Constraints, exists for each variable. local_maxima should be executed prior to a call to clpBNR:global_maximum using the same objective function, e.g.,
?- [X1,X2]::real,OF=X1**4*exp(-0.01*(X1*X2)**2),
local_maxima(OF,{2*X1**2+X2**2==10}), global_maximum(OF,Z),solve([X1,X2]).
OF = X1**4*exp(-0.01*(X1*X2)**2),
X1:: -2.23606797749979...,
X2:: 0.0000000000000000...,
Z:: 25.0000000000000... ;
OF = X1**4*exp(-0.01*(X1*X2)**2),
X1:: 2.23606797749979...,
X2:: 0.0000000000000000...,
Z:: 25.0000000000000... .
See also
- clpBNR:local_maxima/1 */
  861%
  862%	local_minima/2,       % apply KT constraints for minima with constraints
  863%	local_maxima/2        % apply KT constraints for maxima with constraints
  864%
  865local_minima(ObjExp,{Constraints}) :-
  866	local_optima_(min,ObjExp,Constraints).
  867	
  868local_maxima(ObjExp,{Constraints}) :-
  869	local_optima_(max,ObjExp,Constraints).
  870	
  871	
  872local_optima_(MinMax,ObjF,Constraints) :-
  873	local_optima_(MinMax,ObjF,Constraints,Cs),  % generate constraints
  874	{Cs}.                                       % then apply
  875
  876local_optima_(MinMax,ObjF,Constraints,[Constraints,GCs,LCs]) :-
  877	lagrangian_(Constraints,MinMax,ObjF,LObjF,LCs),
  878	term_variables((Constraints,ObjF),Vs),
  879	gradient_constraints_(Vs,GCs,LObjF).
  880
  881gradient_constraints_([],[],_Exp).
  882gradient_constraints_([X|Xs],[C|Cs],Exp) :-
  883	partial_derivative(Exp,X,D),
  884	(number(D) -> C=[] ; C=(D==0)),  % no constraint if PD is a constant
  885	gradient_constraints_(Xs,Cs,Exp).
  886	
  887% for each constraint add to Lagrangian expression with auxiliary KKT constraints
  888lagrangian_(C,MinMax,Exp,LExp, LC) :- nonvar(C),
  889	kt_constraint_(C,CExp, LC), % generate langrange term with multiplier
  890	lexp(MinMax,Exp,CExp,LExp),
  891	!.
  892lagrangian_([],_,L,L,[]).
  893lagrangian_([C|Cs],MinMax,Exp,LExp,[LC|LCs]) :-
  894	lagrangian_(C, MinMax,Exp,NExp,LC),
  895	lagrangian_(Cs,MinMax,NExp,LExp,LCs).	
  896lagrangian_((C,Cs),MinMax,Exp,LExp,[LC|LCs]) :-
  897	lagrangian_(C,MinMax,Exp,NExp,LC),
  898	lagrangian_(Cs,MinMax,NExp,LExp,LCs).
  899		
  900lexp(min,Exp,CExp,Exp+CExp).
  901lexp(max,Exp,CExp,Exp-CExp).
  902
  903kt_constraint_(LHS == RHS, M*(LHS-RHS), []) :-
  904	M::real.                          % finite multiplier only
  905kt_constraint_(LHS =< RHS, MGx,     MGx==0) :-
  906	MGx = M*(LHS-RHS), M::real(0,_).  % positive multiplier and KKT non-negativity condition
  907kt_constraint_(LHS >= RHS, Exp,         LC) :-
  908	kt_constraint_(RHS =< LHS, Exp, LC).  % >= convert to =<