home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
GEMini Atari
/
GEMini_Atari_CD-ROM_Walnut_Creek_December_1993.iso
/
files
/
language
/
examples
/
simplex.lsp
< prev
next >
Wrap
Lisp/Scheme
|
1993-10-23
|
27KB
|
578 lines
;; Simplex-Algorithmus und dessen Demonstration
;; Bruno Haible 25.09.1986, 30.5.1991
;; Version für Common Lisp
(provide 'simplex)
; Typ einer Zahl: hier rationale Zahlen
(deftype Zahl () 'rational) ; abgekürzt: R
(deftype Zahl-Vektor () '(simple-array Zahl 1)) ; abgekürzt: R^n
(deftype Zahl-Matrix () '(simple-array Zahl 2)) ; abgekürzt: R^mxn
; Input: A, eine mxn-Matrix von Zahlen,
; b, ein m-Vektor von Zahlen,
; c, ein n-Vektor von Zahlen.
; Löst eine kanonische Optimierungsaufgabe :
; ( C ) Finde unter den y ∈ R^n mit A*y=b, y≥0 diejenigen mit <c,y> = Min
; ( C* ) Finde unter den z ∈ R^m mit x=A`*z+c≥0 diejenigen mit <b,z> = Min
; Siehe Koulikov, Algèbre et théorie des nombres, Chap. 6.1.
; Output: 1. Wert: ob lösbar, NIL oder T,
; falls lösbar:
; 2. Wert: Lösung von ( C ), dem LP: Liste, bestehend aus
; - der Lösung y
; - dem optimalen Wert von <c,y>
; 3. Wert: Lösung von ( C* ), dem Dualproblem DP: Liste, bestehend aus
; - der Lösung x
; - den konstanten Gliedern Z für die z's
; - den Abhängigkeiten ZZ zwischen den z's
; - dem optimalen Wert von <b,z>
; Die allgemeine Lösung ist so aufgebaut:
; Falls ZZ[i,i]=T, ist die Variable z_i frei wählbar.
; Sonst ist ZZ[i,i]=NIL, und die Variable z_i errechnet sich als
; z_i = Z[i] + Summe(j=1,...,m mit Z[j,j]=T , ZZ[i,j]*z_j) .
; 4. Wert: ob die angegebenen Lösungen die gesamte Lösungsmenge
; ausmachen (T) oder ob möglicherweise noch eine andere
; Lösung existiert.
; In jedem Fall ist die Lösungsmenge konvex.
; Methode:
; Methode von Dantzig, siehe Koulikov-Buch;
; Zusatz für entarteten Fall nach Bronstein/Semendjajew.
(defun simplex (A b c)
; Input überprüfen:
(let ((m (length b))
(n (length c)))
(declare (type fixnum m n))
(assert (typep A `(array * (,m ,n))))
; m = die Höhe der Matrix, in der gearbeitet wird.
; n = die Breite der Matrix, in der gearbeitet wird.
; Initialisierung:
(let ((AA (let ((h (make-array `(,m ,n))))
(dotimes (i m)
(dotimes (j n)
(setf (aref h i j) (rational (aref A i j)))
) )
h
) )
(AB (let ((h (make-array m)))
(dotimes (i m) (setf (aref h i) (rational (elt b i))))
h
) )
(AC (let ((h (make-array n)))
(dotimes (j n) (setf (aref h j) (rational (elt c j))))
h
) )
(AD 0)
; AA ∈ R^mxn : das Tableau, in dem gearbeitet wird
; AB ∈ R^m : der rechte Rand
; AC ∈ R^n : der untere Rand
; AD ∈ R : die rechte untere Ecke
(Extend nil)
(AAE nil)
(ACE nil)
; Extend ∈ Boolean : sind die Zusatzspalten (Entartungsfall) aktiv?
; AAE ∈ R^mxm : Zusatzspalten für den Entartungsfall
; ACE ∈ R^m : unterer Rand der Zusatzspalten
(Zeile (let ((h (make-array m)))
(dotimes (i m) (setf (aref h i) (cons 'ZO i)))
h
) )
(Spalte (let ((h (make-array n)))
(dotimes (j n) (setf (aref h j) (cons 'XY j)))
h
) )
; Zeile ∈ Cons^m : linke Markierungen
; Spalte ∈ Cons^n : obere Markierungen
;
; Skizze des Tableaus:
;
; | Zeile[0] Zeile[1] ... Zeile[n-1] |
; ------------+-------------------------------------+---------
; Spalte[0] | AA[0,0] AA[0,1] ... AA[0,n-1] | AB[0]
; Spalte[1] | AA[1,0] AA[1,1] ... AA[1,n-1] | AB[1]
; ... | ... ... ... | ...
; Spalte[m-1] | AA[m-1,0] AA[m-1,1] ... AA[m-1,n-1] | AB[m-1]
; ------------+-------------------------------------+---------
; | AC[0] AC[1] ... AC[n-1] | AD
;
; Markierung von Spalten: 0 oder Y
; ... ...
; Z X
;
; Markierung von Zeilen: Z ... 0 oder X ... -Y.
;
; für alle i=0,...,m-1:
; Summe(j=0,...,n-1; AA[i,j]*(0 oder Y[Spalte[j].Nr])) - AB[i] =
; = (0 oder -Y[Zeile[i].Nr])
; für alle j=0,...,n-1:
; Summe(i=0,...,m-1; AA[i,j]*(Z[Zeile[i].Nr] oder X[Zeile[i].Nr])) + AC[j] =
; = (Z[Spalte[j].Nr] oder X[Spalte[j].Nr])
; Summe(j=1,...,N; AC[j]*(0 oder Y[Spalte[j].Nr])) - AD = <c,y>
; Summe(i=1,...,M; AB[i]*(Z[Zeile[i].Nr] oder X[Zeile[i].Nr])) + AD = <b,z>
; Dieses ist als Gleichung mit den Unbekannten X,Y,Z zu verstehen.
;
; Die Zusatzspalten sind - wenn vorhanden - rechts anzufügen.
;
(ZZ (let ((h (make-array `(,m ,m) :initial-element 0)))
(dotimes (i m) (setf (aref h i i) nil))
h
) )
(Z (make-array m))
(Y (make-array n))
(X (make-array n))
)
(declare (type Zahl-Matrix AA) (type Zahl-Vektor AB AC) (type Zahl AD))
(flet
((pivot (k l)
(declare (type fixnum k l))
; pivotiert das Tableau das Element AA[k,l] mit 0≤k<m, 0≤l<n.
; Invariante ist, daß vor und nach dem Pivotieren die obigen
; Gleichungen gelten.
(let ((r (/ (aref AA k l))))
(declare (type Zahl r))
; Spalte l :
(progn
(dotimes (i m)
(unless (eql i k)
(setf (aref AA i l) (- (* r (aref AA i l))))
) ) )
(setf (aref AC l) (- (* r (aref AC l))))
; alles außer Zeile k, Spalte l :
(dotimes (j n)
(unless (eql j l)
(let ((s (aref AA k j)))
(dotimes (i m)
(unless (eql i k)
(setf (aref AA i j) (+ (aref AA i j) (* s (aref AA i l))))
) )
(setf (aref AC j) (+ (aref AC j) (* s (aref AC l))))
) ) )
(let ((s (aref AB k)))
(dotimes (i m)
(unless (eql i k)
(setf (aref AB i) (+ (aref AB i) (* s (aref AA i l))))
) )
(setf AD (+ AD (* s (aref AC l))))
)
(when Extend
(locally (declare (type Zahl-Matrix AAE) (type Zahl-Vektor ACE))
(dotimes (j m)
(let ((s (aref AAE k j)))
(dotimes (i m)
(unless (eql i k)
(setf (aref AAE i j) (+ (aref AAE i j) (* s (aref AA i l))))
) )
(setf (aref ACE j) (+ (aref ACE j) (* s (aref AC l))))
) ) ) )
; Zeile k :
(progn
(dotimes (j n)
(unless (eql j l)
(setf (aref AA k j) (* (aref AA k j) r))
) )
(setf (aref AB k) (* (aref AB k) r))
)
(when Extend
(locally (declare (type Zahl-Matrix AAE))
(dotimes (j m)
(setf (aref AAE k j) (* (aref AAE k j) r))
) ) )
; Element (k,l) :
(setf (aref AA k l) r)
; Markierungen vertauschen:
(rotatef (aref Zeile k) (aref Spalte l))
)) )
; Variablen Z nach unten schaffen, evtl. Matrix verkleinern:
(let ((elbar (make-array m :fill-pointer 0))
(not-elbar (make-array m :fill-pointer 0)))
; elbar = Menge der eliminierbaren z,
; not-elbar = Menge der nicht eliminierbaren z.
(dotimes (i m)
; Suche Betragsmaximum in Zeile i:
(let ((hmax 0) (l nil))
(dotimes (j n)
(when (eq (car (aref Spalte j)) 'XY)
(let ((h (abs (aref AA i j))))
(when (> h hmax) (setq hmax h l j))
) ) )
(if l
; AA[i,l] war betragsmäßig maximal
(progn
(vector-push i not-elbar)
(pivot i l)
)
; triviale Zeile
(if (zerop (aref AB i))
; Führe diese Zeile noch pro forma weiter; sie wird sich
; aber nicht weiter ändern, weil stets nur um XY-Spalten
; pivotiert werden wird und in diesen Spalten in Zeile i
; überall 0 steht.
; Nicht-Null-Elemente stehen nur in ZO-Spalten, und die
; verändern sich nicht mehr.
(vector-push i elbar)
; Zeile fürs LP widerspruchsvoll -> unlösbar
(return-from simplex NIL)
) ) ) )
; eliminierbare Zeilen in der ZZ-Matrix vormerken:
; Die nicht eliminierbaren Zeilen haben mit den X ihre Plätze
; getauscht, so daß also Spalte[(cdr Zeile[i])] = (ZO . i) gilt,
; falls Zeile i nicht eliminierbar ( <==> (car Zeile[i]) = XY ).
(dotimes (i0h (fill-pointer elbar))
(let ((i0 (aref elbar i0h)))
(setf (aref Z i0) 0) ; nur wenn man Z[i0]=0 setzt, stimmen die anderen Z's!
(setf (aref ZZ i0 i0) T) ; z_i0 frei wählbar
(dotimes (ih (fill-pointer not-elbar))
(let* ((i (aref not-elbar ih)) ; es muß (car Zeile[i])=XY sein
(j (cdr (aref Zeile i)))) ; Spalte, mit der Zeile i pivotiert wurde
(setf (aref ZZ i i0) (aref AA i0 j))
) ) ) )
; Zeilen streichen: (benutze dabei, daß alle not-elbar[i]>=i)
(dotimes (ih (fill-pointer not-elbar))
(let ((i (aref not-elbar ih)))
(unless (eql ih i)
(dotimes (j n) (setf (aref AA ih j) (aref AA i j)))
(setf (aref AB ih) (aref AB i))
) ) )
(setq m (fill-pointer not-elbar)) ; neue Zeilenzahl = Anzahl der ZO-Spalten
)
; Spalten umsortieren: XY links, ZO rechts bringen.
; Damit werden am Schluß die nicht eliminierbaren z aus den X errechnet.
(let ((l 0) ; linke Spalte
(r (1- n))) ; rechte Spalte
(loop
(unless (< l r) (return))
(cond ((eq (car (aref Spalte l)) 'XY) (incf l))
((eq (car (aref Spalte r)) 'ZO) (decf r))
(t ; Vertausche Spalten r und l
(dotimes (i m) (rotatef (aref AA i l) (aref AA i r)))
(rotatef (aref AC l) (aref AC r))
(rotatef (aref Spalte l) (aref Spalte r))
) )
) )
; Verstecke diese M Spalten vor dem Pivotieren:
(setq n (- n m))
; Die Elemente AA[0..m-1,n..n+m-1], AC[n..n+m-1], Spalte[n,n+m-1]
; werden erst am Schluß von Teil 6 wieder benötigt.
(let ((Zeile_save (copy-seq Zeile)))
(flet
((SuchePivotZeile (AWZM l)
; Bestimmt zur Auswahlmenge AWZM der zu untersuchenden Zeilen
; und zur Spalte l die Zeile k so:
; Vorausgesetzt ist, daß
; für i∈AWZM gilt 0≤i<m und AB[i]≥0 und AA[i,l]>0.
; k ist unter allen i∈AWZM dasjenige, für das der Quotient
; AB[i]/AA[i,l] minimal ist. Sollte er =0 sein oder ist sowieso
; Extend=true, so ist nachher Extend=true, und es wurde der Vek-
; tor (AB[i]/AA[i,l], AAE[i,1]/AA[i,l], ..., AAE[i,m-1]/AA[i,l])
; unter allen i∈AWZM lexikographisch minimiert. Falls AWZM leer
; ist, ist NIL das Ergebnis.
(if (eql (fill-pointer AWZM) 0)
NIL
(let (k)
(unless Extend ; versuche k passend zu wählen
(let (hmax)
(dotimes (ih (fill-pointer AWZM))
(let* ((i (aref AWZM ih))
(h (/ (aref AB i) (aref AA i l))))
(when (or (eql ih 0) (< h hmax)) (setq hmax h k i))
) )
(when (zerop hmax)
; entarteter Fall
(setq Extend T)
(setq AAE (make-array `(,m ,m) :initial-element 0))
(dotimes (i m) (setf (aref AAE i i) 1))
(setq ACE (make-array m :initial-element 0))
) ) )
(when Extend
; Der Entartungsfall liegt entweder seit längerem vor
; oder ist erst jetzt aufgetreten (dann war das alte k
; vielleicht schlecht).
(let (hmax hmaxe)
(dotimes (ih (fill-pointer AWZM))
(let ((i (aref AWZM ih)))
(let ((h (/ (aref AA i l)))
(he (make-array m)))
(dotimes (j m) (setf (aref he j) (* (aref AAE i j) h)))
(setq h (* (aref AB i) h))
; (h,he[1],...,he[M]) ist jetzt der Quotientenvektor
(when (or (eql ih 0)
; statt (< h hmax) jetzt lexikographisch
; (h,he[1],...,he[M]) < (hmax,hmaxe[1],...,hmaxe[M])
; vergleichen:
(or (< h hmax)
(and (= h hmax)
(dotimes (ie m NIL)
(let* ((he_ie (aref he ie))
(hmaxe_ie (aref hmaxe ie)))
(when (< he_ie hmaxe_ie) (return T))
(when (> he_ie hmaxe_ie) (return NIL))
) ) ) ) )
(setq hmax h hmaxe he k i)
) ) ) ) ) )
k
)) ) )
(let ((PZM (make-array m :fill-pointer 0)))
; PZM = Menge der i mit AB[i]≥0
(loop ; Spalte AB mit positiven Zahlen füllen
#|
; Rundungsfehler ausmerzen:
(dotimes (ih (fill-pointer PZM))
(let ((i (aref PZM ih)))
(when (minusp (aref AB i)) (setq (aref AB i) 0))
) )
|#
(let ((NZM (make-array m :fill-pointer 0)))
; NZM = Menge der i mit AB[i]<0
; PZM und NZM neu berechnen:
(let ((old-PZM-count (fill-pointer PZM))) ; alte Mächtigkeit von PZM
(setf (fill-pointer PZM) 0)
(dotimes (i m)
(if (>= (aref AB i) 0)
(vector-push i PZM)
(vector-push i NZM)
) )
; Streiche die Zusatzspalten, wenn sich PZM echt vergrößert
; hat und die Entartung daher vielleicht verschwunden ist.
(when (> (fill-pointer PZM) old-PZM-count) (setq Extend nil))
)
(when (eql (fill-pointer NZM) 0) (return)) ; alle AB[i]≥0 ?
; wähle p aus: p:= dasjenige i mit AB[i]<0,
; bei dem die Anzahl der AA[i,j]≥0 maximal ist.
(let ((countmax -1) (p nil))
(dotimes (ih (fill-pointer NZM))
(let ((i (aref NZM ih))
(count 0))
(dotimes (j n) (when (>= (aref AA i j) 0) (incf count)))
(when (> count countmax) (setq countmax count p i))
) )
(when (eql countmax n)
; alle AA[p,j]≥0, aber AB[p]<0
; ==> Zeile fürs LP widerspruchsvoll ==> unlösbar
(return-from simplex NIL)
)
; Wähle l aus: maximales abs(AA[p,j]) unter allen j mit AA[p,j]<0
; (solche gibt es, da countmax<n).
(let ((hmin 0) (l nil))
(dotimes (j n)
(let ((h (aref AA p j)))
(when (< h hmin) (setq hmin h l j))
) )
; AWZM aufbauen:
(let ((AWZM (make-array m :fill-pointer 0)))
(dotimes (ih (fill-pointer PZM))
(let ((i (aref PZM ih)))
(when (> (aref AA i l) 0) (vector-push i AWZM))
) )
(let ((k (SuchePivotZeile AWZM l)))
(if (null k)
; Durch Pivotieren um AA[p,l] wächst PZM um
; mindestens das eine Element p, denn:
; für i∈PZM gilt AB[i]≥0 und wegen AZWM=ϕ
; auch AA[i,l]≤0, also nachher
; AB[i]=AB[i]-AB[p]*AA[i,l]/AA[p,l] ≥0,
; ≥0 <0 ≤0 <0
; also wieder i∈PZM.
; Aber nachher auch AB[p]=AB[p]/AA[p,l] (<0/<0) >0,
; also p∈PZM.
(progn
(setq Extend nil) ; brauche die Zusatzspalten jetzt nicht mehr
(pivot p l)
)
; Pivotzeile k ausgewählt; fertig zum Pivotieren
; Durch Pivotieren um AA[k,l] wird PZM nicht
; verkleinert, denn:
; für i∈PZM gilt AB[i]≥0.
; Falls AA[i,l]≤0, gilt nachher
; AB[i]=AB[i]-AB[k]*AA[i,l]/AA[k,l] ≥0.
; ≥0 ≥0 ≤0 >0
; Falls jedoch AA[i,l]>0, so gilt für i<>k nachher
; AB[i]=AA[i,l]*(AB[i]/AA[i,l]-AB[k]/AA[k,l]) ≥0
; >0 ≥0 nach Wahl von k wegen i∈AWZM
; und für i=k nachher AB[k]=AB[k]/AA[k,l] (≥0/>0) ≥0.
; Also nachher stets wieder AB[i]≥0, d.h. i∈PZM.
(pivot k l)
) ) ) ) ) )
) )
; Jetzt ist Extend=false, weil (fill-pointer PZM) sich gerade
; auf m erhöht haben muß.
; Ab hier sind alle AB[i]≥0, und diese Eigenschaft geht nicht verloren.
(loop
; Suche ein l mit AC[l]<0 :
(let (l)
(when
(dotimes (j n T)
(when (< (aref AC j) 0) (setq l j) (return NIL))
)
(return) ; alle AC[j]≥0 ==> lösbar
)
; AWZM := Menge der i mit AA[i,l]>0 :
(let ((AWZM (make-array m :fill-pointer 0)))
(dotimes (i m)
(when (> (aref AA i l) 0) (vector-push i AWZM))
)
(let ((k (SuchePivotZeile AWZM l)))
(if (null k)
; alle AA[i,l]≤0 und AC[l]<0 ==> Spalte fürs DP widerspruchsvoll
(return-from simplex NIL)
(pivot k l)
; weiterhin alle AB[i]≥0.
; AD:=AD-AB[k]*AC[l]/AA[k,l] ≥ AD wird nicht verkleinert.
; ≥0 <0 >0
) ) ) )
)
; Lösbar! Lösung bauen:
(let ((vollständig t))
(dotimes (i m)
(let ((s (aref AB i))
(index (cdr (aref Zeile i))))
(setf (aref X index) 0 (aref Y index) s)
(setq vollständig (and vollständig (> s 0)))
) )
(dotimes (j n)
(let ((s (aref AC j))
(index (cdr (aref Spalte j))))
(setf (aref X index) s (aref Y index) 0)
(setq vollständig (and vollständig (> s 0)))
) )
; Die nicht eliminierbaren z-Werte berechnen sich aus dem
; verstecken Teil von AA und AC und den Werten von X und Y :
(do ((j n (1+ j)))
((>= j (+ n m)))
(let ((s (aref AC j)))
(dotimes (i m)
(setq s (+ s (* (aref AA i j) (aref X (cdr (aref Zeile_save i))))))
)
(setf (aref Z (cdr (aref Spalte j))) s)
) )
(values T (list Y (- AD)) (list X Z ZZ AD) vollständig)
) ) ) ) ) ) )
(defun test-simplex (aufg)
(multiple-value-bind (m n) (values-list (array-dimensions (first aufg)))
(let ((x-list
(mapcar #'(lambda (i) (format nil "X[~D]" i))
(make-sequence 'list n :initial-element 1 :update #'1+)
) )
(y-list
(mapcar #'(lambda (i) (format nil "Y[~D]" i))
(make-sequence 'list n :initial-element 1 :update #'1+)
) )
(z-list
(mapcar #'(lambda (i) (format nil "Z[~D]" i))
(make-sequence 'list m :initial-element 1 :update #'1+)
)) )
; Aufgabe ausgeben:
(multiple-value-bind (A b c) (values-list aufg)
(format t "~%~%Lineares Problem:")
(dotimes (i m)
(let ((zeile (make-array n)))
(dotimes (j n) (setf (aref zeile j) (aref A i j)))
(format t "~%~{~1{~S * ~A~:}~^ + ~} = ~S"
(remove 0 (mapcar #'list (coerce zeile 'list) y-list)
:key #'first :test #'=
)
(aref b i)
) ) )
(format t "~%mit ~{~A~^, ~} ≥ 0" y-list)
(format t "~%Minimiere ~{~1{~S * ~A~:}~^ + ~}."
(remove 0 (mapcar #'list (coerce c 'list) y-list)
:key #'first :test #'=
) )
(format t "~%Dualproblem:")
(dotimes (j n)
(let ((spalte (make-array m)))
(dotimes (i m) (setf (aref spalte i) (aref A i j)))
(format t "~%~A = ~:{~S * ~A + ~} ~S ≥ 0"
(elt x-list j)
(remove 0 (mapcar #'list (coerce spalte 'list) z-list)
:key #'first :test #'=
)
(aref c j)
) ) )
(format t "~%Minimiere ~{~1{~S * ~A~:}~^ + ~}."
(remove 0 (mapcar #'list (coerce b 'list) z-list)
:key #'first :test #'=
) )
)
(let ((lösung (multiple-value-list (apply #'simplex aufg))))
; Lösung ausgeben:
(if (first lösung)
(progn
(format t "~%~%~:[Eine~;Die einzige~] Lösung ist:" (fourth lösung))
(let ((LP-lösung (second lösung)))
(format t "~%~{~1{~A = ~S~:}~^, ~}~%Minimum = ~S"
(mapcar #'list y-list (coerce (first LP-lösung) 'list))
(second LP-lösung)
) )
(let ((DP-lösung (third lösung)))
(let ((Z (second DP-lösung))
(ZZ (third DP-lösung)))
(dotimes (i m)
(format t "~%~A = " (elt z-list i))
(if (aref ZZ i i)
(format t "beliebig")
(format t "~S~:{ + ~S * ~A~}"
(aref Z i)
(let ((L nil))
(dotimes (j m)
(when (aref ZZ j j)
(unless (zerop (aref ZZ i j))
(push (list (aref ZZ i j) (elt z-list j))
L
) ) ) )
(nreverse L)
) )
) ) )
(format t "~%~{~1{~A = ~S~:}~^, ~}~%Minimum = ~S"
(mapcar #'list x-list (coerce (first DP-lösung) 'list))
(fourth DP-lösung)
) ) )
(format t "~%unlösbar")
)
lösung
) ) ) )
(defun test ()
(mapcar #'test-simplex
'((#2A((1 0 1 -1 -1 3)
(0 1 2 -1 -1/2 1))
#(0 0)
#(0 0 -1 -1 -3 8)
)
(#2A((1 -1 -1 3 1 0)
(2 -1 -1/2 1 0 1))
#(0 0)
#(-1 -1 -3 8 0 0)
)
(#2A((1 1 0 1)
(2 0 1 0))
#(1 1)
#(-1 1 -2 0)
)
(#2A((1 1 2 1 3 -1 0)
(2 -2 3 1 1 0 -1))
#(4 3)
#(2 3 5 2 3 0 0)
)
(#2A(( 1 8 0 -1 0 0 0)
(-3 -7 -20 0 -1 0 0)
( 1 0 0 0 0 -1 0)
( 0 -1 0 0 0 0 -1)
( 1 1 1 0 0 0 0))
#(3 -6 2/5 -1/2 1)
#(5 2 1/4 0 0 0 0)
)
(#2A((1 2 1 1 -2)
(1 1 -4 -2 -3)
(1 2 5 -4 6))
#(4 2 3)
#(3 5 4 5 6)
)
(#2A((1 2 -1 1)
(2 -2 3 3)
(1 -1 2 -1))
#(0 9 6)
#(-3 1 3 -1)
))
) )