File: bf-cobyla-interface.lisp

package info (click to toggle)
maxima 5.47.0-9
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 193,104 kB
  • sloc: lisp: 434,678; fortran: 14,665; tcl: 10,990; sh: 4,577; makefile: 2,763; ansic: 447; java: 328; python: 262; perl: 201; xml: 60; awk: 28; sed: 15; javascript: 2
file content (170 lines) | stat: -rw-r--r-- 5,995 bytes parent folder | download | duplicates (2)
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
;;; -*- Mode: lisp -*-

;;; Simple Maxima interface to COBYLA, Constrained Optimization BY
;;; Linear Approximations.

(in-package #:maxima)

;; Variable that will hold the function that calculates the function
;; value and the constraints.
(defvar *calcfc*)

(in-package #:bigfloat)

;; COBYLA always calls CALCFC to compute the function value and the
;; constraint equations.  But we want to be able to specify different
;; versions.  So, COBYLA calls CALCFC, which then calls *CALCFC* to
;; do the real computation.
(cl:defun calcfc (n m x f con)
  (cl:declare (cl:ignore f))
  (cl:funcall maxima::*calcfc* n m x con))

(cl:in-package #:maxima)

;; The actual interface to COBYLA.  Take the inputs from maxima,
;; massage them into a suitable Lisp form, and call COBYLA to find the
;; answer.
(defmfun $bf_fmin_cobyla
    (f vars init-x
       &key constraints
       (rhobeg 1d0)
       (rhoend nil rhoendp)
       (iprint 0)
       (maxfun 1000))
  (unless (listp vars)
    (merror "~M: vars must be a list of variables. Got: ~M"
	    %%pretty-fname vars))
  (unless (listp init-x)
    (merror "~M: Initial values must be a list of values. Got: ~M"
	    %%pretty-fname init-x))

  (unless (= (length (cdr vars))
	     (length (cdr init-x)))
    (merror
     "~M: Number of initial values (~M) does not match the number of variables ~M~%"
     %%pretty-fname
     (length (cdr init-x))
     (length (cdr vars))))

  (unless (and (integerp iprint)
	       (<= 0 iprint 3))
    (merror
     "~M: iprint must be an integer between 0 and 3, inclusive, not: ~M~%"
     %%pretty-fname iprint))

  (unless (and (integerp maxfun) (plusp maxfun))
    (merror
     "~M: maxfun must be a positive integer, not: ~M~%"
     %%pretty-fname maxfun))

  (unless rhoendp
    (setf rhoend (bigfloat:sqrt (bigfloat:expt (bigfloat:bigfloat 10) (- $fpprec)))))
  
  ;; Go through constraints and convert f >= g to f - g, f <= g to g -
  ;; f, and f = g to f - g and g - f.  This is because cobyla expects
  ;; all constraints to of the form h>=0.
  (let (normalized-constraints)
    (dolist (c (cdr constraints))
      (let ((op ($op c)))
	(cond ((string-equal op ">=")
	       (push (sub ($lhs c) ($rhs c)) normalized-constraints))
	      ((string-equal op "<=")
	       (push (sub ($rhs c) ($lhs c)) normalized-constraints))
	      ((string-equal op "=")
	       (push (sub ($lhs c) ($rhs c)) normalized-constraints)
	       (push (sub ($rhs c) ($lhs c)) normalized-constraints))
	      (t
	       (merror "~M: Constraint equation must be =, <= or >=: got ~M"
		       %%pretty-fname op)))))

    (setf normalized-constraints
	  (list* '(mlist)
		 (nreverse normalized-constraints)))
    #+nil
    (mformat t "cons = ~M~%" normalized-constraints)

    (let* ((n (length (cdr vars)))
	   (m (length (cdr normalized-constraints)))
	   (x (make-array n
			  :initial-contents (mapcar #'(lambda (z)
							(let ((r (bigfloat:to ($bfloat z))))
							  r))
						    (cdr init-x))))
	   ;; Real work array for cobyla.
	   (w (make-array (+ (* n (+ (* 3 n)
				     (* 2 m)
				     11))
			     (+ (* 4 m) 6)
			     6)))
	   ;; Integer work array for cobyla.
	   (iact (make-array (+ m 1) :element-type 'f2cl-lib::integer4))
	   (fv (coerce-bfloat-fun f vars))
	   (cv (coerce-bfloat-fun normalized-constraints vars))
	   (*calcfc* #'(lambda (nn mm xval cval)
			 ;; Compute the function and the constraints at
			 ;; the given xval.  The function value is
			 ;; returned is returned, and the constraint
			 ;; values are stored in cval.
			 (declare (fixnum nn mm)
				  (type (cl:array t (*)) xval cval))
			 (let* ((x-list (map 'list #'to xval))
				(f (bigfloat:maybe-to (apply fv x-list)))
				(c (apply cv x-list)))
			   ;; Do we really need these checks?
			   #+nil
			   (progn
			     (format t "xval = ~S~%" xval)
			     (format t "f = ~A~%" f)
			     (format t "c = ~A~%" c))
			   (setf c (mapcar #'bigfloat:maybe-to (cdr c)))
			   ;;(format t "c = ~A~%" c)
			   (unless (or (floatp f) ($bfloatp f) (typep f 'bigfloat:bigfloat))
			     (merror "At the point ~M:~%The objective function did not evaluate to a number: ~M"
				     (list* '(mlist) x-list)
				     f))
			   (let ((bad-cons (loop for cval in c
						 for k from 1
						 unless (or (floatp cval)
							    ($bfloatp cval)
							    (typep cval 'bigfloat:bigfloat))
						   collect k)))
			     (when bad-cons
			       ;; List the constraints that did not
			       ;; evaluate to a number to make it easier
			       ;; for the user to figure out which
			       ;; constraints were bad.
			       (mformat t "At the point ~M:~%" (list* '(mlist) x-list))
			       (merror
				(with-output-to-string (msg)
				  (loop for index in bad-cons
					do
					   (mformat msg "Constraint ~M did not evaluate to a real: ~M~%"
						    index
						    (elt normalized-constraints index)))))))
			   (replace cval c)
			   ;; This is the f2cl calling convention for
			   ;; CALCFC.  For some reason, f2cl thinks n
			   ;; and m are modified, so they must be
			   ;; returned.
			   (values nn mm nil
				   f nil)))))
      (multiple-value-bind (null-0 null-1 null-2 null-3 null-4 null-5 neval null-6 null-7 ierr)
	  (bigfloat::cobyla n m x (bigfloat:to rhobeg) (bigfloat:to rhoend) iprint maxfun w iact 0)
	(declare (ignore null-0 null-1 null-2 null-3 null-4 null-5 null-6 null-7))
	;; Should we put a check here if the number of function
	;; evaluations equals maxfun?  When iprint is not 0, the output
	;; from COBYLA makes it clear that something bad happened.
	(let ((x-list (map 'list #'to x)))
	  ;; Return the optimum function value, the point that gives the
	  ;; optimum, the value of the constraints, and the number of
	  ;; function evaluations.  For convenience.  Only the point and
	  ;; the number of evaluations is really needed.
	  (make-mlist
	   (list* '(mlist)
		  (mapcar #'(lambda (var val)
			      `((mequal) ,var ,val))
			  (cdr vars)
			  (coerce x 'list)))
	   (apply fv x-list)
	   neval
	   ierr))))))