6
\$\begingroup\$

I have had little experience with OO programming, being a typical engineer I tend to hack together functional code. However, I'm trying to change that and I see value in implementing OO when modelling the physical world. I've written my first ever class for a position vector. I want an instance of the class to be able to store both the cartesian [x, y, z] and polar [z, theta, phi] coordinates of a position. Updating one attribute automatically updates the others. This class will be later be reused in other classes via composition.

I want an instance of my class to do the following:

>> p = PositionVector([15,5,5])
>> p.cartesian
[15,5,5]
>> p.polar
[16.583123951777, 0.0, 1.2645189576252271]
>> p.x = 3
>> p.cartesian
[3,5,5]
>> p.polar
[7.681145747868608, 0.7853981633974483, 0.8619682853367363]
>> p.theta = 0.5
>> p.cartesian
[5.117141702137862, 2.7955072527614058, 5.0]
>> p.polar
[7.681145747868608, 0.5, 0.8619682853367363]

The class I've written so far is below and while it works as expected, my limited understanding of Python classes and the property function tells me there's a better way to do this. I don't know if the methods for x y z r theta and phi need to be fully written since they are simply elements within each list/array but can't figure out how to get around this. I also don't know if this is the safest and most pythonic way of structuring a class object:

class PositionVector(object):
  def __init__(self, vector, system='cartesian'):
    if system == 'cartesian':
      self.cartesian = vector
      self.polar = self.position_vector_polar(self.cartesian)
    if system == 'polar':
      self.polar = vector
      self.cartesian = self.position_vector_cartesian(self.polar)

  @property
  def cartesian(self):
    return self._cartesian
  @cartesian.setter
  def cartesian(self, value):
    self._cartesian = value
    self._polar = self.position_vector_polar(self._cartesian)
    self._x, self._y, self._z = self._cartesian
    self._r, self._theta, self._phi = self._polar

  @property
  def polar(self):
    return self._polar
  @polar.setter
  def polar(self, value):
    self._polar = value
    self._cartesian  = self.position_vector_cartesian(self._polar)
    self._x, self._y, self._z = self.cartesian
    self._r, self._theta, self._phi = self.polar

  @property
  def x(self):
    return self._x
  @x.setter
  def x(self, value):
    self._x = value
    self._cartesian[0]  = self._x
    self._polar = self.position_vector_polar(self._cartesian)

  # repeat above for y and z variables

  @property
  def r(self):
    return self._r
  @r.setter
  def r(self, value):
    self._r = value
    self._polar[0]  = self._r
    self._cartesian = self.position_vector_cartesian(self._polar)

  # repeat above for thets and phi variables

  def position_vector_cartesian(self, polar):
    r, theta, phi = polar
    x = r * math.cos(theta) * math.sin(phi)
    y = r * math.sin(theta) * math.sin(phi)
    z = r * math.cos(phi)
    return [x, y, z]

  def position_vector_polar(self, cartesian):
    x, y, z = cartesian
    r = math.sqrt(x ** 2 + y ** 2 + z ** 2)
    theta = math.atan(y/x)
    phi = math.acos(z/r)
    return [r, theta, phi]

Note that the lists will be be replaced with NumPy arrays so I can perform algebra on the vectors, but for now I just want to get the class structure correct.

\$\endgroup\$
3
\$\begingroup\$

As far as I understand the code, a PositionVector object holds both cartesian and polar coordinates. This is surely redundant: multiple representations of the same position hold no more information than a single one (note that there are many more coordinate systems, and you cannot possibly account for all of them). I recommend to keep the position in a preferred coordinate system, and provide methods to convert it to and from other representations.

Edit: addressing concerns in the comment:

What about if I want to set a new radius, r? What would be the most appropriate way to update [x, y, z]?

This is a very important question I didn't elaborate on in the answer. In the algebraic world we want to reason in terms of objects, as opposed to coordinates. Coordinates are secondary - they are just instrumental for calculations.

In other words, you do not want to set new radius. You want to apply a transformation (translate, rotate, scale, etc).

I wrote the class with the ability to apply vector algebra in mind, if I follow your suggestion, would vector algebra be straight forward, regardless of which ordinates I am working with?

I seriously hope so.

\$\endgroup\$
  • \$\begingroup\$ you are absolutely right. Though I only need cartesian and polar/spherical coordinates at this stage. So, I should just store a single cartesian array [x,y,z] and then if I want x, y, z, r, theta, phi or [r, theta, phi] I should just use methods to extract/convert them from [x, y, z]? What about if I want to set a new radius, r? What would be the most appropriate way to update [x, y, z]? I wrote the class with the ability to apply vector algebra in mind, if I follow your suggestion, would vector algebra be straight forward, regardless of which ordinates I am working with? \$\endgroup\$ – user162390 Dec 7 '14 at 0:11
  • \$\begingroup\$ @user162390 Please see update \$\endgroup\$ – vnp Dec 7 '14 at 0:37
  • \$\begingroup\$ Thanks @vnp, I think I understand. I'll try and build or at least redesign it to later allow for methods for handling transformations. I guess I was holding off on this step until I had the basic class working and had a better idea of how to implement it, however, I should really have transformations in mind from the beginning. I'll try and post amendments tonight or tomorrow. \$\endgroup\$ – user162390 Dec 7 '14 at 3:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.