[Numpy-discussion] subclassing matrix

Basilisk96 basilisk96 at gmail.com
Thu Jan 10 19:55:46 EST 2008


Yes, that certainly meets the need.

In previous applications, I never thought to use Vector because it was
sufficient for me to adopt the convention of a column vector to
represent XY points.  That way, I could do things like c * R * u + t.
Isn't that simple? Not only is it easy on the eyes, but it follows
most academic definitions that I know. Furthermore, I was able to
apply linear operators to a whole set of XY points by doing something
to the tune of:
    u = numpy.hstack(tuple_of_points_from_ascii_file)    # u.shape is
(2, n)
    R = some_2d_numpy_rotation_matrix    # R.shape is (2, 2)
    t = numpy.mat("1.; 3.")
    v = R * u + t   # v.shape is the same as u.shape

So, the need arose for a self-contained Vector class that inherently
supports a number of common geometric operations, and uses numpy for
them. It seemed natural to subclass matrix because numpy.array is a
general-case array; numpy.matrix restricts that to an array of rank 2;
and Vector further restricts that to a *column* array of rank 2.

Now, if I were to write a custom library of my common vector
operations using module functions, I'd have to always keep in mind
that they must operate on either column or row vectors, but not both.
Instead, I chose to write a subclass of a well-tested class and
explicitly forced all Vector instances to be columns.

Vector has a number of methods that wrap numpy functions within the
Vector context. For example:
    def cross(self, other):
        """Cross product of this vector and another vector"""
        if isinstance(other, self.__class__):
            return Vector(_N.cross(self, other, axis=0))
        else:
            raise TypeError('Both arguments must be of type Vector')

I think that a call like w = u.cross(v) is less verbose than having to
remember the `axis` argument in numpy.cross(u, v, axis=0). Yes, I
realize that it would not be required if the vectors were row
matrices, but then I'd have to live with using u.T * A, which I don't
particularly like either.

But now I can do things like:

   >>> from vector import Vector
   >>> u = Vector('1 2 3')
   >>> v = Vector('3 2 1')
   >>> w = u.cross(v)
   >>> print w
   (-4.00000, +8.00000, -4.00000)
   >>> alpha = u.angle(w)
   >>> print alpha
   1.57079632679
..and so on.

However, my original question still stands: is
    ret.__class__ = cls
a good idea?

Cheers,
-Basilisk96


On Jan 10, 10:03 am, "Colin J. Williams" <c... at sympatico.ca> wrote:
> Basilisk96 wrote:
> > Hello folks,
>
> > In the course of a project that involved heavy use of geometry and
> > linear algebra, I found it useful to create a Vector subclass of
> > numpy.matrix (represented as a column vector in my case).
>
> Why not consider a matrix with a shape
> of (1, n) as a row vector and
> one with (n, 1) as a column vector?
>
> Then you can simply write A * u or u.T * A.
>
> Does this not meet the need?
>
> You could add methods isRowVector and
> isColumnVector to the Matrix class.
>
> Colin W.
>
>
>
>
>
> > I'd like to hear comments about my use of this "class promotion"
> > statement in __new__:
> >     ret.__class__ = cls
>
> > It seems to me that it is hackish to just change an instance's class
> > on the fly, so perhaps someone could clue me in on a better practice.
> > Here is my reason for doing this:
> > Many applications of this code involve operations between instances of
> > numpy.matrix and instances of Vector, such as applying a linear-
> > operator matrix on a vector. If I omit that "class promotion"
> > statement, then the results of such operations cannot be instantiated
> > as Vector types:
> >     >>> from vector import Vector
> >     >>> import numpy
> >     >>> u = Vector('1 2 3')
> >     >>> A = numpy.matrix('2 0 0; 0 2 0; 0 0 2')
> >     >>> p = Vector(A * u)
> >     >>> p.__class__
> >     <class 'numpy.core.defmatrix.matrix'>
>
> > This is undesirable because the calculation result loses the custom
> > Vector methods and attributes that I want to use. However, if I use
> > that "class promotion" statement, the p.__class__ lookup returns what
> > I want:
> >     >>> p.__class__
> >     <class 'vector.Vector'>
>
> > Is there a better way to achieve that?
>
> > Here is the partial subclass code:
> > #---------- vector.py
> > import numpy as _N
> > import math as _M
> > #default tolerance for equality tests
> > TOL_EQ = 1e-6
> > #default format for pretty-printing Vector instances
> > FMT_VECTOR_DEFAULT = "%+.5f"
>
> > class Vector(_N.matrix):
> >     """
> >     2D/3D vector class that supports numpy matrix operations and more.
>
> >     Examples:
> >         u = Vector([1,2,3])
> >         v = Vector('3 4 5')
> >         w = Vector([1, 2])
> >     """
> >     def __new__(cls, data="0. 0. 0.", dtype=_N.float64):
> >         """
> >         Subclass instance constructor.
>
> >             If data is not specified, a zero Vector is constructed.
> >             The constructor always returns a Vector instance.
> >             The instance gets a customizable Format attribute, which
> > controls the printing precision.
> >         """
> >         ret = super(Vector, cls).__new__(cls, data, dtype=dtype)
> >         #promote the instance to cls type.
> >         ret.__class__ = cls
> >         assert ret.size in (2, 3), 'Vector must have either two or
> > three components'
> >         if ret.shape[0] == 1:
> >             ret = ret.T
> >         assert ret.shape == (ret.shape[0], 1), 'could not express
> > Vector as a Mx1 matrix'
> >         if ret.shape[0] == 2:
> >             ret = _N.vstack((ret, 0.))
> >         ret.Format = FMT_VECTOR_DEFAULT
> >         return ret
>
> >     def __str__(self):
> >         fmt = getattr(self, "Format", FMT_VECTOR_DEFAULT)
> >         fmt = ', '.join([fmt]*3)
> >         return ''.join(["(", fmt, ")"]) % (self.X, self.Y, self.Z)
>
> >     def __repr__(self):
> >         fmt = ', '.join(['%s']*3)
> >         return ''.join(["%s([", fmt, "])"]) %
> > (self.__class__.__name__, self.X, self.Y, self.Z)
>
> >     #### the remaining methods are Vector-specific math operations,
> > including the X,Y,Z properties...
>
> > Cheers,
> > -Basilisk96
>
> _______________________________________________
> Numpy-discussion mailing list
> Numpy-discuss... at scipy.orghttp://projects.scipy.org/mailman/listinfo/numpy-discussion



More information about the NumPy-Discussion mailing list