File: wrap.lisp

package info (click to toggle)
clisp 1%3A2.49.20241228.gitc3ec11b-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 57,724 kB
  • sloc: lisp: 124,909; ansic: 83,890; xml: 27,431; sh: 11,074; fortran: 7,307; makefile: 1,456; perl: 164; sed: 13
file content (327 lines) | stat: -rw-r--r-- 13,462 bytes parent folder | download | duplicates (4)
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
;;; Lisp wrappers for the Netica API
;;; <http://norsys.com/netica_c_api.htm>, version 3.25
;;;
;;; Copyright (C) 2003-2008 by Sam Steingold
;;; This is Free Software, distributed under the GNU GPL v2+
;;; See http://www.gnu.org/copyleft/gpl.html

(require "netica")

(in-package "NETICA")

(pushnew :netica *features*)

;;; low level wrappers

(eval-when (compile eval)
  (cl:defmacro make-node-wrapper (func &rest more-args)
    (let* ((fun (if (consp func) (first func) func))
           (orig (symbol-name fun)) (node (gensym orig)) (vec (gensym orig))
           (length-form (if (consp func)
                            (subst node '<node> (second func))
                            `(netica::GetNodeNumberStates_bn ,node)))
           (name (intern (subseq orig 0 (position #\_ orig)) "NETICA")))
      `(defun ,name (,node ,@more-args)
         ,(concatenate 'string "A low-level wrapper for " orig)
         (ffi:with-c-var (,vec 'ffi:c-pointer (,fun ,node ,@more-args))
           (ffi:cast ,vec `(ffi:c-ptr (ffi:c-array netica::prob_bn
                                                   ,,length-form))))))))

(cl:defun adjust-number-of-states (num-states type)
  (+ num-states
     (gethash type
              (load-time-value
               (let ((ht (make-hash-table)))
                 (setf (gethash netica::CONTINUOUS_TYPE ht) 1)
                 (setf (gethash netica::DISCRETE_TYPE ht) 0)
                 ht)))))

(make-node-wrapper netica::GetNodeBeliefs_bn)
(make-node-wrapper netica::GetNodeExpectedUtils_bn)
(make-node-wrapper netica::GetNodeLikelihood_bn)
(make-node-wrapper netica::GetNodeProbs_bn parent-states)
(make-node-wrapper (netica::GetNodeLevels_bn
                    (adjust-number-of-states
                     (netica::GetNodeNumberStates_bn <node>)
                     (netica::GetNodeType_bn <node>))))

;;; user interface variables
(defvar *verbose* nil "the netica log stream")
(defvar *env* nil "the current netica environment")
(defvar *license* "" "the netica license key - ask norsys")

;;; helpers
(defun error-category (err)
  "return the list of categories where the error belongs"
  (mapcan (lambda (c)
            (unless (zerop (netica::ErrorCategory_ns (symbol-value c) err))
              (list c)))
          '(netica::OUT_OF_MEMORY_CND
            netica::USER_ABORTED_CND
            netica::FROM_WRAPPER_CND
            netica::FROM_DEVELOPER_CND
            netica::INCONS_FINDING_CND)))

(defun error-message (err)
  "Convert netica error to a string"
  (format nil "~s(~s)~@[ ~s~]: ~s~%"
          (ffi:enum-from-value 'netica::errseverity_ns
                               (netica::ErrorSeverity_ns err))
          (netica::ErrorNumber_ns err)
          (netica::error-category err)
          (netica::ErrorMessage_ns err)))

(defun check-errors (&key ((:env *env*) *env*) (clear t)
                          (severity netica::NOTHING_ERR))
  "Check all errors of the given severity and optionally clear them."
  (let ((err nil))
    (loop (setq err (netica::GetError_ns *env* severity err))
      (unless err (return))
      (if (>= (netica::ErrorSeverity_ns err) netica::ERROR_ERR)
          (cerror (if clear "clear and proceed" "show next error")
                  (error-message err))
          (warn (error-message err)))
      (when clear
        (netica::ClearError_ns err) (setq err nil)
        (format *error-output* "~&...cleared~%")))))

(defun start-netica (&key ((:license *license*) *license*)
                          ((:verbose *verbose*) *verbose*))
  "Start netica, initialize it, and return the new environment.
Sets *env* to this environment on success."
  (let ((env (netica::NewNeticaEnviron_ns *license* nil nil))
        status message)
    (when *verbose*
      (format *verbose* "~&;; new environment: ~s~%" env))
    (multiple-value-setq (status message) (netica::InitNetica2_bn env))
    (when *verbose*
      (format *verbose* ";; init status=~s~%~a~%" status message))
    (multiple-value-setq (status message) (netica::GetNeticaVersion_bn env))
    (when *verbose*
      (format *verbose* ";; version=~s (~s)~%" status message))
    (setq status (netica::ArgumentChecking_ns netica::REGULAR_CHECK env))
    (when *verbose*
      (format *verbose* ";; checking level: ~s --> ~s~%"
              status netica::REGULAR_CHECK))
    (setq status (netica::LimitMemoryUsage_ns
                  (float netica::QUERY_CHECK 1d0) env))
    (when *verbose*
      (format *verbose* ";; memory usage: ~s bytes~%" status))
    (check-errors :env env)
    (setq *env* env)))

(defun close-netica (&key (env *env*)
                          ((:verbose *verbose*) *verbose*))
  "Terminate the netica session.
Sets *env* to NIL when it was closed."
  (check-errors)
  (multiple-value-bind (status message) (netica::CloseNetica_bn *env*)
    (when *verbose*
      (format *verbose* "~&;; close status=~s~%~a~%" status message)))
  (when (eq env *env*)
    (setq *env* nil)))

(cl:defun required-argument (f a) (error "~s: missing ~s argument" f a))

(defun make-net (&key (name (symbol-name (gensym)))
                      (comment nil) (title nil)
                      ((:env *env*) *env*)
                      ((:verbose *verbose*) *verbose*))
  "Make a network with a given name and return it."
  (let ((net (netica::NewNet_bn name *env*)))
    (when *verbose*
      (format *verbose* "~&;; new net ~s: ~s~%" name net))
    (check-errors)
    (when comment
      (netica::SetNetComment_bn net comment)
      (check-errors))
    (when title
      (netica::SetNetTitle_bn net title)
      (check-errors))
    net))

(defun net-info (net &key (out *standard-output*))
  "Print information about the net."
  (format out "~&net: ~s~%name: ~s~%" net (netica::GetNetName_bn net))
  (let ((title (netica::GetNetTitle_bn net)))
    (unless (zerop (length title))
      (format out "title: ~s~%" title)))
  (let ((comment (netica::GetNetComment_bn net)))
    (unless (zerop (length comment))
      (format out "comment: ~s~%" comment)))
  (let ((file-name (netica::GetNetFileName_bn net)))
    (unless (zerop (length file-name))
      (format out "file-name: ~s~%" file-name)))
  (let* ((nodes (netica::GetNetNodes_bn net))
         (count (netica::LengthNodeList_bn nodes)))
    (dotimes (ii count)
      (node-info (netica::NthNode_bn nodes ii) :header ii)))
  (check-errors))

(defun make-node (&key (name (symbol-name (gensym)))
                       (net (required-argument 'make-node :net))
                       (kind netica::NATURE_NODE)
                       (levels nil) (states nil)
                       (num-states (if levels 0 (length states)))
                       (title nil) (comment nil)
                       (parents nil) (cpt nil) x y
                       ((:env *env*) *env*)
                       ((:verbose *verbose*) *verbose*))
  "Make a network node with the given parameters and return it.
The parameters are: name, net, kind, states (state name list),
levels (vector), number of states, parents list, cpt.
CPT (conditional probability table) is a list of conses:
 ((parent-state-vector . node-state-probability-vector) ...)
one cons for each combination of possible parent states,
where parent-state-vector is a vector of parent states,
 its length being (length parents);
and node-state-probability-vector is a vector of corresponding node state
 probabilities, its length being (length states).
When LEVELS is supplied, the node is continuous.
X & Y are coordinates; both or neither must be supplied."
  (let ((node (netica::NewNode_bn name num-states net)))
    (when *verbose*
      (format *verbose* "~&;; new node ~s: ~s~%" name node))
    (check-errors)
    (when (/= kind netica::NATURE_NODE)
      (netica::SetNodeKind_bn node kind)
      (check-errors))
    (when levels
      (netica::SetNodeLevels_bn node (1- (length levels)) levels)
      (check-errors))
    (loop :for state :in states :and idx :upfrom 0
      :do (if (consp state)
              (progn
                (netica::SetNodeStateName_bn node idx (car state))
                (netica::SetNodeStateTitle_bn node idx (cdr state)))
              (netica::SetNodeStateName_bn node idx state))
      (check-errors))
    (when title
      (netica::SetNodeTitle_bn node title)
      (check-errors))
    (when comment
      (netica::SetNodeComment_bn node comment)
      (check-errors))
    (dolist (parent parents)
      (netica::AddLink_bn parent node)
      (check-errors))
    (dolist (probs cpt)
      (netica::SetNodeProbs_bn node
                               (map 'vector #'netica::GetStateNamed_bn
                                    (car probs) parents)
                               (cdr probs))
      (check-errors))
    (when (or x y)
      (if (and x y)
          (netica::SetNodeVisPosition_bn node nil x y)
          (cerror "ignore the supplied argument"
                  "If one of X (~S) and Y (~S) is supplied, both must be"
                  x y)))
    (check-errors)
    node))

(defun node-info (node &key header (out *standard-output*))
  "Print information about the node."
  (format out "~&~@[ * [~s] ~]node: ~s (net: ~s)~%name: ~s   (~s ~s)~%"
          header node (netica::GetNodeNet_bn node)
          (netica::GetNodeName_bn node)
          (ffi:enum-from-value 'netica::nodetype_bn
                               (netica::GetNodeType_bn node))
          (ffi:enum-from-value 'netica::nodekind_bn
                               (netica::GetNodeKind_bn node)))
  (let ((title (netica::GetNodeTitle_bn node)))
    (unless (zerop (length title))
      (format out "title: ~s~%" title)))
  (multiple-value-bind (x y) (netica::GetNodeVisPosition_bn node nil)
    (format out "position: (~s ~s)~%" x y))
  (let ((count (netica::GetNodeNumberStates_bn node)))
    (format out "state count: ~:d~%" count)
    (dotimes (state count)
      (let ((title (netica::GetNodeStateTitle_bn node state)))
        (format out "[~:d] name: ~s~[~:;  title: ~s~]~%" state
                (netica::GetNodeStateName_bn node state)
                (length title) title))))
  (let* ((nodes (netica::GetNodeChildren_bn node))
         (count (netica::LengthNodeList_bn nodes)))
    (if (zerop count) (format out "no children~%")
        (loop :initially (format out "~:d ~:*~[~;child~:;children~]:~%" count)
          :for ii :from 0 :to (1- count)
          :for child = (netica::NthNode_bn nodes ii)
          :do (format out "[~:d] ~s (~s)~%" ii
                      (netica::GetNodeName_bn child) child))))
  (let* ((nodes (netica::GetNodeParents_bn node))
         (count (netica::LengthNodeList_bn nodes)))
    (if (zerop count) (format out "no parents~%")
        (loop :initially (format out "~:d parent~:p:~%" count)
          :for ii :from 0 :to (1- count)
          :for parent = (netica::NthNode_bn nodes ii)
          :do (format out "[~:d] ~s (~s)~%" ii
                      (netica::GetNodeName_bn parent) parent))))
  (let ((levels (netica::GetNodeLevels node)))
    (dotimes (ii (length levels))
      (format out "[~:d] level: ~s~%" ii (aref levels ii))))
  (check-errors))

(defun get-beliefs (node
                    &key ((:env *env*) *env*)
                         ((:verbose *verbose*) *verbose*))
  "Get the belief vector for the node."
  (let ((beliefs (netica::GetNodeBeliefs node))
        (name (netica::GetNodeName_bn node)))
    (check-errors)
    (when *verbose*
      (loop :for belief :across beliefs :and index :upfrom 0 :do
        (format *verbose* "~&;; ~a: P(~s)=~f~%" name
                (netica::GetNodeStateName_bn node index) belief))
      (check-errors))
    beliefs))

(defun enter-finding (net node state
                      &key ((:env *env*) *env*)
                           ((:verbose *verbose*) *verbose*))
  "Enter a finding by node and state names"
  (let* ((nd (netica::GetNodeNamed_bn node net))
         (st (netica::GetStateNamed_bn state nd)))
    (netica::EnterFinding_bn nd st)
    (check-errors)
    (when *verbose*
      (format *verbose* "~&;; ~s: set to ~s~%" node state))))

(cl:defun open-dne-file (file &key ((:env *env*) *env*)
                         ((:verbose *verbose*) *verbose*))
  (let ((out (netica::NewFileStream_ns
              (namestring (translate-logical-pathname
                           (merge-pathnames
                            file #.(make-pathname :type "dne"))))
              *env* nil)))
    (when *verbose*
      (format *verbose* "~&;; new stream: ~s~&" out))
    (check-errors)
    out))

(defmacro with-open-dne-file ((var file &rest opts &key &allow-other-keys)
                              &body body)
  `(let ((,var (open-dne-file ,file ,@opts)))
     (unwind-protect (progn ,@body)
       (netica::DeleteStream_ns ,var)
       (check-errors))))

(defun save-net (net &key (file (netica::GetNetFileName_bn net))
                          ((:env *env*) *env*)
                          ((:verbose *verbose*) *verbose*))
  "Save the network to the file."
  (with-open-dne-file (out file)
    (netica::WriteNet_bn net out)
    (check-errors)
    (when *verbose*
      (format *verbose* ";; saved ~s to ~s~%" net
              (netica::GetNetFileName_bn net)))))

(defun read-net (file &key ((:env *env*) *env*)
                           ((:verbose *verbose*) *verbose*))
  (with-open-dne-file (in file)
    (let ((net (netica::ReadNet_bn in netica::NO_WINDOW)))
      (check-errors)
      net)))

(pushnew "NETICA" custom:*system-package-list* :test #'string=)