Skip to content

Commit

Permalink
Finish export of VMD to Python.
Browse files Browse the repository at this point in the history
Added an additional norm2 method to Kernel::VMD to bring it inline with
the V3D export so that the interfaces look similar.
Refs #6027
  • Loading branch information
martyngigg committed Aug 5, 2013
1 parent 608c45f commit e26c886
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 13 deletions.
7 changes: 6 additions & 1 deletion Code/Mantid/Framework/Kernel/inc/MantidKernel/VMD.h
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ namespace Kernel
/** @return the length of this vector */
TYPE length() const
{
return TYPE(sqrt(this->scalar_prod(*this)));
return TYPE(std::sqrt(this->norm2()));
}

/** @return the length of this vector */
Expand All @@ -508,6 +508,11 @@ namespace Kernel
return this->length();
}

/** @return the length of this vector */
TYPE norm2() const
{
return this->scalar_prod(*this);
}

//-------------------------------------------------------------------------------------------
/** Normalize this vector to unity length
Expand Down
1 change: 1 addition & 0 deletions Code/Mantid/Framework/Kernel/test/VMDTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class VMDTest : public CxxTest::TestSuite
VMD a(3,4,sqrt(39.0));
TS_ASSERT_EQUALS( a.length(), 8.0);
TS_ASSERT_EQUALS( a.norm(), 8.0);
TS_ASSERT_EQUALS( a.norm2(), 64.0);
}

void test_normalize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,50 @@
#include <boost/python/copy_const_reference.hpp>
#include <boost/python/make_constructor.hpp>
#include <boost/python/operators.hpp>
#include <boost/python/return_internal_reference.hpp>
#include <boost/python/return_value_policy.hpp>

using Mantid::Kernel::VMD;
using Mantid::Kernel::VMD_t;
using namespace boost::python;

namespace
{
/**
* Safe operator access. Returns the value at the given index
* checking whether the index is valid. VMD does no checking
* @param self The calling python object
* @param index An index whose value is to be returned
* @throws An out_of_range error if the index is out of range
*/
VMD_t getItem(const VMD & self, const size_t index)
{
if( index < self.getNumDims() )
{
return self[index];
}
else throw std::out_of_range("VMD index out of range. index=" + \
boost::lexical_cast<std::string>(index) + ", len=" + boost::lexical_cast<std::string>(self.getNumDims()));
}

/**
* Set the value at the given index
* @param self The calling python object
* @param index An index whose value is to be set
* @param value The new value for the index
* @throws An out_of_range error if the index is out of range
*/
void setItem(VMD & self, const size_t index, const VMD_t value)
{
if( index < self.getNumDims() )
{
self[index] = value;
}
else throw std::out_of_range("VMD index out of range. index=" + \
boost::lexical_cast<std::string>(index) + ", len=" + boost::lexical_cast<std::string>(self.getNumDims()));
}
}

void export_VMD()
{
class_<VMD>("VMD", init<>("Default constructor gives an object with 1 dimension"))
Expand All @@ -24,11 +62,28 @@ void export_VMD()

.def("getNumDims", &VMD::getNumDims, "Returns the number of dimensions the contained in the vector")

.def("scalar_prod", &VMD::scalar_prod,
"Returns the scalar product of this vector with another. If the number of dimensions do not match a RuntimeError is raised")

.def("cross_prod", &VMD::cross_prod,
"Returns the cross product of this vector with another. If the number of dimensions do not match a RuntimeError is raised")

.def("norm", &VMD::norm, "Returns the length of the vector")

.def("norm2", &VMD::norm2, "Returns the the squared length of the vector")

.def("normalize", &VMD::normalize, "Normalizes the length of the vector to unity and returns the length before it was normalized")

.def("angle", &VMD::angle, "Returns the angle between the vectors in radians (0 < theta < pi). If the dimensions do not match a RuntimeError is raised")

//----------------------------- special methods --------------------------------
.def("__getitem__", (const VMD_t &(VMD::*)(size_t)const)&VMD::operator[], return_value_policy<copy_const_reference>())
.def("__getitem__", &getItem)
.def("__setitem__", &setItem)
// cppcheck-suppress duplicateExpression
.def(self == self)
.def(self + self)
.def(self += self)
// cppcheck-suppress duplicateExpression
// cppcheck-suppress duplicateExpression
.def(self - self)
.def(self -= self)
.def(self * self)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import unittest
from mantid.kernel import VMD
import math
import unittest

class VMDTest(unittest.TestCase):

Expand All @@ -14,20 +15,119 @@ def test_constructors_with_dimension_pts(self):
vector = VMD(*pts) #unpack list
self.assertEquals(i, vector.getNumDims())

def test_value_access_is_read_only(self):
def test_scalar_prod_returns_expected_value(self):
a = VMD(1.0,2.0,1.0)
b = VMD(1.0,-2.0,-1.0)
sp = a.scalar_prod(b)
self.assertAlmostEquals(sp,-4.0)

def test_crossprod(self):
a = VMD(1.0,0.0,0.0)
b = VMD(0.0,1.0,0.0)
c = a.cross_prod(b)
self.assertAlmostEquals(c[0],0.0)
self.assertAlmostEquals(c[1],0.0)
self.assertAlmostEquals(c[2],1.0)

def test_norm(self):
p = VMD(1.0,-5.0,8.0);
self.assertAlmostEquals(p.norm(), math.sqrt(90.0),places=6)

def test_norm2(self):
p = VMD(1.0,-5.0,8.0);
self.assertAlmostEquals(p.norm2(), 90.0, places=6)

def test_normalize(self):
a = VMD(3,4, math.sqrt(39.0))
pre_norm = a.norm()
self.assertEquals(pre_norm, a.normalize())

b = VMD(3./8,4./8, math.sqrt(39.0)/8.) # normalized version
self.assertAlmostEquals(b, a, places=6)

def test_angle(self):
a = VMD(1,0,0);
b = VMD(0,1,0);
self.assertAlmostEqual(a.angle(b), math.pi/2, places=4);

def test_value_read_access_succeeds_for_valid_indices(self):
vector = VMD(1.0,2.0)
self.assertAlmostEqual(1.0, vector[0])
self.assertAlmostEqual(2.0, vector[1])

try:
vector[1] = 5.0
except TypeError:
pass
except:
self.fail("Operator setters have thrown an unexpected exception type. Expected TypeError.")
else:
self.fail("Operator setters have not thrown an exception. Expected to be read only.")
def test_value_write_access_succeeds_for_valid_indices(self):
vector = VMD(1.0,2.0)
vector[0] = 1.5
vector[1] = 1.6
self.assertAlmostEqual(1.5, vector[0])
self.assertAlmostEqual(1.6, vector[1])

def test_standard_mathematical_operators(self):
v1 = VMD(1.0,2.0)
v2 = VMD(5.0,-1.0)

v3 = v1 + v2
self.assertAlmostEqual(6.0, v3[0])
self.assertAlmostEqual(1.0, v3[1])
v3 = v1 - v2
self.assertAlmostEqual(-4.0, v3[0])
self.assertAlmostEqual(3.0, v3[1])
v3 = v1 * v2
self.assertAlmostEqual(5.0, v3[0])
self.assertAlmostEqual(-2.0, v3[1])
v3 = v1 / v2
self.assertAlmostEqual(1.0/5.0, v3[0])
self.assertAlmostEqual(-2.0, v3[1])

def test_inplace_mathematical_operators(self):
v1 = VMD(1.0,2.0)
v2 = VMD(5.0,-1.0)

v1 += v2
self.assertAlmostEqual(6.0, v1[0])
self.assertAlmostEqual(1.0, v1[1])
v1 = VMD(1.0,2.0)
v1 -= v2
self.assertAlmostEqual(-4.0, v1[0])
self.assertAlmostEqual(3.0, v1[1])
v1 = VMD(1.0,2.0)
v1 *= v2
self.assertAlmostEqual(5.0, v1[0])
self.assertAlmostEqual(-2.0, v1[1])
v1 = VMD(1.0,2.0)
v1 /= v2
self.assertAlmostEqual(1.0/5.0, v1[0])
self.assertAlmostEqual(-2.0, v1[1])

def test_equality_operators(self):
v1 = VMD(1.0,2.0)
self.assertTrue(v1 == v1)
v2 = VMD(5.0,3.0)
self.assertTrue(v1 != v2)

#==================== Failure cases =======================================
def test_value_read_access_raises_error_for_invalid_indices(self):
vector = VMD(1.0,2.0)
self.assertRaises(IndexError, vector.__getitem__, 2)

def test_value_write_access_raises_error_for_invalid_indices(self):
vector = VMD(1.0,2.0)
self.assertRaises(IndexError, vector.__setitem__, 2, 5.0)

def test_scalar_prod_raises_error_with_dimension_mismatch(self):
v1 = VMD(1,2,3)
v2 = VMD(1,2,3,4)
self.assertRaises(RuntimeError, v1.scalar_prod, v2)

def test_cross_prod_raises_error_with_dimension_mismatch(self):
v1 = VMD(1,2,3)
v2 = VMD(1,2,3,4)
self.assertRaises(RuntimeError, v1.cross_prod, v2)

def test_angle_raises_error_with_dimension_mismatch(self):
v1 = VMD(1,2,3)
v2 = VMD(1,2,3,4)
self.assertRaises(RuntimeError, v1.angle, v2)

if __name__ == '__main__':
unittest.main()

0 comments on commit e26c886

Please sign in to comment.