2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2004 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 import Numeric
, LinearAlgebra
28 # we can assume len(list) != 0 here (and do not start from the scalar 0)
35 # we can assume len(list) != 0 here (and do not start from the scalar 1)
43 # represents a scalar variable or constant
45 def __init__(self
, value
=None, name
="unnamed_scalar"):
58 return self
.addend().polynom()
63 def __add__(self
, other
):
64 return self
.polynom() + other
68 def __sub__(self
, other
):
69 return self
.polynom() - other
71 def __rsub__(self
, other
):
72 return -self
.polynom() + other
74 def __mul__(self
, other
):
75 return self
.addend()*other
79 def __div__(self
, other
):
80 return self
.addend()/other
83 return self
._scalar
is not None
87 raise RuntimeError("scalar already defined")
89 self
._scalar
= float(value
)
91 raise RuntimeError("float expected")
95 raise RuntimeError("scalar not yet defined")
103 return "%s{=%s}" % (self
.name
, self
._scalar
)
109 # represents a addend, i.e. list of scalars to be multiplied by each other
111 def __init__(self
, scalars
):
112 self
._scalars
= [scalar
.scalar() for scalar
in scalars
]
113 if not len(self
._scalars
):
114 raise RuntimeError("empty scalars not allowed")
120 return polynom([self
])
123 return addend([scalar(-1)] + self
._scalars
)
125 def __add__(self
, other
):
126 return self
.polynom() + other
130 def __sub__(self
, other
):
131 return self
.polynom() - other
133 def __rsub__(self
, other
):
134 return -self
.polynom() + other
136 def __mul__(self
, other
):
138 other
= other
.addend()
139 except (TypeError, AttributeError):
141 other
= scalar(other
)
145 return addend(self
._scalars
+ [other
])
147 return addend(self
._scalars
+ other
._scalars
)
151 def __div__(self
, other
):
152 return addend([scalar(1/other
)] + self
._scalars
)
155 return product([float(scalar
) for scalar
in self
._scalars
])
158 return len([scalar
for scalar
in self
._scalars
if not scalar
.is_set()]) < 2
161 assert self
.is_linear()
162 setscalars
= [scalar
for scalar
in self
._scalars
if scalar
.is_set()]
164 return float(addend(setscalars
))
169 assert self
.is_linear()
170 unsetscalars
= [scalar
for scalar
in self
._scalars
if not scalar
.is_set()]
171 if len(unsetscalars
):
172 assert len(unsetscalars
) == 1
173 return unsetscalars
[0]
178 return " * ".join([str(scalar
) for scalar
in self
._scalars
])
182 # represents a polynom, i.e. a list of addends to be summed up
184 def __init__(self
, polynom
):
185 self
._addends
= [addend
.addend() for addend
in polynom
]
186 if not len(self
._addends
):
187 raise RuntimeError("empty polynom not allowed")
193 return polynom([-addend
for addend
in self
._addends
])
195 def __add__(self
, other
):
197 other
= other
.polynom()
198 except (TypeError, AttributeError):
199 other
= scalar(other
).polynom()
200 return polynom(self
._addends
+ other
._addends
)
204 def __sub__(self
, other
):
207 def __rsub__(self
, other
):
210 def __mul__(self
, other
):
211 return sum([addend
*other
for addend
in self
._addends
])
215 def __div__(self
, other
):
216 return polynom([addend
/other
for addend
in self
._addends
])
219 return sum([float(addend
) for addend
in self
._addends
])
223 for addend
in self
._addends
:
224 is_linear
= is_linear
and addend
.is_linear()
228 return " + ".join([str(addend
) for addend
in self
._addends
])
230 def solve(self
, solver
):
231 solver
.addequation(self
)
235 # represents a vector, i.e. a list of scalars (or polynoms)
237 def __init__(self
, dimension_or_values
, name
="unnamed_vector"):
241 raise RuntimeError("a vectors name should be a string (you probably wanted to write vector([x, y]) instead of vector(x, y))")
243 for value
in dimension_or_values
:
247 self
._items
= [scalar(name
="%s[%i]" % (name
, i
))
248 for i
in range(dimension_or_values
)]
252 for value
in dimension_or_values
:
255 except (TypeError, AttributeError):
256 self
._items
.append(scalar(value
=value
, name
="%s[%i]" % (name
, len(self
._items
))))
258 self
._items
.append(value
)
259 if not len(self
._items
):
260 raise RuntimeError("empty vector not allowed")
264 return len(self
._items
)
266 def __getitem__(self
, i
):
267 return self
._items
[i
]
269 def __getattr__(self
, attr
):
277 raise AttributeError(attr
)
283 return vector([-item
for item
in self
._items
])
285 def __add__(self
, other
):
286 other
= other
.vector()
287 if len(self
) != len(other
):
288 raise RuntimeError("vector length mismatch in add")
289 return vector([selfitem
+ otheritem
for selfitem
, otheritem
in zip(self
._items
, other
._items
)])
293 def __sub__(self
, other
):
296 def __rsub__(self
, other
):
299 def __mul__(self
, other
):
301 other
= other
.vector()
302 except (TypeError, AttributeError):
303 return vector([item
*other
for item
in self
._items
])
306 if len(self
) != len(other
):
307 raise RuntimeError("vector length mismatch in scalar product")
308 return sum([selfitem
*otheritem
for selfitem
, otheritem
in zip(self
._items
, other
._items
)])
310 def __rmul__(self
, other
):
311 return vector([other
*item
for item
in self
._items
])
312 # We do not allow for vector * <matrix-like-object> here (i.e. a
313 # simulating the behaviour of a dual vector). In principle we could do
314 # that, but for transformations it would lead to (a*X)*c != a*(X*c).
315 # Hence we disallow vector*X for X being something else that a scalar.
317 def __div__(self
, other
):
318 return vector([item
/other
for item
in self
._items
])
321 return "%s{=(%s)}" % (self
.name
, ", ".join([str(item
) for item
in self
._items
]))
323 def solve(self
, solver
):
324 for item
in self
._items
:
325 solver
.addequation(item
)
328 class zerovector(vector
):
330 def __init__(self
, dimension
, name
="0"):
331 vector
.__init
__(self
, [0 for i
in range(dimension
)], name
)
335 # represents a matrix, i.e. a 2d list of scalars (or polynoms)
337 def __init__(self
, dimensions_or_values
, name
="unnamed_matrix"):
341 raise RuntimeError("a matrix name should be a string (you probably wanted to write matrix([x, y]) instead of matrix(x, y))")
343 for row
in dimensions_or_values
:
348 self
._numberofrows
, self
._numberofcols
= [int(x
) for x
in dimensions_or_values
]
349 self
._rows
= [[scalar(name
="%s[%i, %i]" % (name
, row
, col
))
350 for col
in range(self
._numberofcols
)]
351 for row
in range(self
._numberofrows
)]
355 self
._numberofcols
= None
356 for row
in dimensions_or_values
:
361 except (TypeError, AttributeError):
362 _cols
.append(scalar(value
=col
, name
="%s[%i, %i]" % (name
, len(self
._rows
), len(_cols
))))
365 self
._rows
.append(_cols
)
366 if self
._numberofcols
is None:
367 self
._numberofcols
= len(_cols
)
368 elif self
._numberofcols
!= len(_cols
):
369 raise RuntimeError("column length mismatch")
370 self
._numberofrows
= len(self
._rows
)
371 if not self
._numberofrows
or not self
._numberofcols
:
372 raise RuntimeError("empty matrix not allowed")
375 # instead of __len__ two methods to fetch the matrix dimensions
376 def getnumberofrows(self
):
377 return self
._numberofrows
379 def getnumberofcols(self
):
380 return self
._numberofcols
382 def __getitem__(self
, (row
, col
)):
383 return self
._rows
[row
][col
]
389 return matrix([[-col
for col
in row
] for row
in self
._rows
])
391 def __add__(self
, other
):
392 other
= other
.matrix()
393 if self
._numberofrows
!= other
._numberofrows
or self
._numberofcols
!= other
._numberofcols
:
394 raise RuntimeError("matrix geometry mismatch in add")
395 return matrix([[selfcol
+ othercol
396 for selfcol
, othercol
in zip(selfrow
, otherrow
)]
397 for selfrow
, otherrow
in zip(self
._rows
, other
._rows
)])
401 def __sub__(self
, other
):
404 def __rsub__(self
, other
):
407 def __mul__(self
, other
):
409 other
= other
.matrix()
410 except (TypeError, AttributeError):
412 other
= other
.vector()
413 except (TypeError, AttributeError):
414 return matrix([[col
*other
for col
in row
] for row
in self
._rows
])
416 if self
._numberofcols
!= len(other
):
417 raise RuntimeError("size mismatch in matrix vector product")
418 return vector([sum([col
*otheritem
419 for col
, otheritem
in zip(row
, other
)])
420 for row
in self
._rows
])
422 if self
._numberofcols
!= other
._numberofrows
:
423 raise RuntimeError("size mismatch in matrix product")
424 return matrix([[sum([self
._rows
[row
][i
]*other
._rows
[i
][col
]
425 for i
in range(self
._numberofcols
)])
426 for col
in range(other
._numberofcols
)]
427 for row
in range(self
._numberofrows
)])
429 def __rmul__(self
, other
):
431 other
= other
.vector()
432 except (TypeError, AttributeError):
433 return matrix([[other
*col
for col
in row
] for row
in self
._rows
])
435 if self
._numberofrows
!= len(other
):
436 raise RuntimeError("size mismatch in matrix vector product")
437 return vector([sum([other
[i
]*self
._rows
[i
][col
]
438 for i
in range(self
._numberofrows
)])
439 for col
in range(self
._numberofcols
)])
441 def __div__(self
, other
):
442 return matrix([[col
/other
for col
in row
] for row
in self
._rows
])
445 return "%s{=(%s)}" % (self
.name
, ", ".join(["(" + ", ".join([str(col
) for col
in row
]) + ")" for row
in self
._rows
]))
447 def solve(self
, solver
):
448 for row
in self
._rows
:
450 solver
.addequation(col
)
453 class identitymatrix(matrix
):
455 def __init__(self
, dimension
, name
="I"):
461 matrix
.__init
__(self
, [[eq(row
, col
) for col
in range(dimension
)] for row
in range(dimension
)], name
)
465 # represents a transformation, i.e. matrix and a constant vector
467 def __init__(self
, dimensions_or_values
, name
="unnamed_trafo"):
471 raise RuntimeError("a trafo name should be a string (you probably wanted to write trafo([x, y]) instead of trafo(x, y))")
472 if len(dimensions_or_values
) != 2:
473 raise RuntimeError("first parameter of a trafo must contain two elements: either two dimensions or a matrix and a vector")
475 numberofrows
, numberofcols
= [int(x
) for x
in dimensions_or_values
]
477 self
._matrix
= dimensions_or_values
[0].matrix()
478 self
._vector
= dimensions_or_values
[1].vector()
479 if self
._matrix
.getnumberofrows() != len(self
._vector
):
480 raise RuntimeError("size mismatch between matrix and vector")
482 self
._matrix
= matrix((numberofrows
, numberofcols
), name
=name
+ "_matrix")
483 self
._vector
= vector(numberofrows
, name
=name
+ "_vector")
496 return trafo((-self
._matrix
, -self
._vector
))
498 def __add__(self
, other
):
499 other
= other
.trafo()
500 return trafo((self
._matrix
+ other
._matrix
, self
._vector
+ other
._vector
))
504 def __sub__(self
, other
):
507 def __rsub__(self
, other
):
510 def __mul__(self
, other
):
512 other
= other
.trafo()
513 except (TypeError, AttributeError):
515 other
= other
.vector()
516 except (TypeError, AttributeError):
517 return trafo((self
._matrix
* other
, self
._vector
* other
))
519 return self
._matrix
* other
+ self
._vector
521 return trafo((self
._matrix
* other
._matrix
, self
._vector
+ self
._matrix
* other
._vector
))
523 def __rmul__(self
, other
):
524 return trafo((other
* self
._matrix
, other
* self
._vector
))
526 def __div__(self
, other
):
527 return trafo((self
._matrix
/other
, self
._vector
/other
))
530 return "%s{=(matrix: %s, vector: %s)}" % (self
.name
, self
._matrix
, self
._vector
)
532 def solve(self
, solver
):
533 self
._matrix
.solve(solver
)
534 self
._vector
.solve(solver
)
537 class identitytrafo(trafo
):
539 def __init__(self
, dimension
, name
="I"):
540 trafo
.__init
__(self
, (identitymatrix(dimension
, name
=name
),
541 zerovector(dimension
, name
=name
)), name
)
545 # linear equation solver
548 self
.eqs
= [] # scalar equations not yet solved (a equation is a polynom to be zero)
550 def eq(self
, lhs
, rhs
=None):
557 def addequation(self
, equation
):
558 # the equation is just a polynom which should be zero
559 self
.eqs
.append(equation
.polynom())
561 # try to solve some combinations of linear equations
563 for eqs
in self
.combine(self
.eqs
):
565 break # restart for loop
567 break # quit while loop
569 def combine(self
, eqs
):
570 # create combinations of linear equations
574 for x
in self
.combine(eqs
[1:]):
576 if eqs
[0].is_linear():
577 for x
in self
.combine(eqs
[1:]):
580 def solve(self
, eqs
):
581 # try to solve a set of linear equations
586 for addend
in eq
._addends
:
587 var
= addend
.variable()
588 if var
is not None and var
not in vars:
591 a
= Numeric
.zeros((l
, l
), Numeric
.Float
)
592 b
= Numeric
.zeros((l
, ), Numeric
.Float
)
593 for i
, eq
in enumerate(eqs
):
594 for addend
in eq
._addends
:
595 var
= addend
.variable()
597 a
[i
, vars.index(var
)] += addend
.prefactor()
599 b
[i
] -= addend
.prefactor()
600 for i
, value
in enumerate(LinearAlgebra
.solve_linear_equations(a
, b
)):
603 i
, = [i
for i
, selfeq
in enumerate(self
.eqs
) if selfeq
== eq
]
607 raise RuntimeError("equations are overdetermined")