Skip to content

Commit

Permalink
Refs #4333. Export Run along with the dict-style interface.
Browse files Browse the repository at this point in the history
  • Loading branch information
martyngigg committed Dec 20, 2011
1 parent b68b143 commit 0586cc9
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 6 deletions.
1 change: 1 addition & 0 deletions Code/Mantid/Framework/PythonInterface/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ set ( TEST_PY_FILES
test/python/PythonPluginsTest.py
test/python/PropertyWithValueTest.py
test/python/PythonAlgorithmTest.py
test/python/RunTest.py
test/python/SimpleAPITest.py
test/python/QuatTest.py
test/python/V3DTest.py
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ namespace Mantid
* @return A string representation of the value for printing within a sequence
*/
template<typename ElementType>
std::string toString(const ElementType & value)
inline std::string toString(const ElementType & value)
{
std::ostringstream os;
os << value;
Expand All @@ -58,7 +58,7 @@ namespace Mantid
* wrapped in single quotes to emulate printing a python sequence of strings
*/
template<>
std::string toString(const std::string & value)
inline std::string toString(const std::string & value)
{
std::ostringstream os;
os << "'" << value << "'";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ set ( EXPORT_FILES
src/BoxController.cpp
src/FileFinder.cpp
src/Sample.cpp
src/Run.cpp
)

# Files containing additional helper code that are not related to exporting class/functions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ void export_ExperimentInfo()
"Return the Sample object. This cannot be modified, use mutableSample to modify.")
.def("mutableSample", &ExperimentInfo::mutableSample, return_value_policy<reference_existing_object>(),
"Return a modifiable Sample object.")
.def("run", &ExperimentInfo::run, return_value_policy<copy_const_reference>(),
.def("run", &ExperimentInfo::run, return_value_policy<reference_existing_object>(),
"Return the sample object. This cannot be modified, use mutableSample to modify.")
.def("mutableRun", &ExperimentInfo::mutableRun, return_value_policy<reference_existing_object>(),
"Return a modifiable Run object.")
Expand Down
122 changes: 122 additions & 0 deletions Code/Mantid/Framework/PythonInterface/mantid/api/src/Run.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#include "MantidAPI/Run.h"
#include <boost/python/class.hpp>
#include <boost/python/register_ptr_to_python.hpp>
#include <boost/python/overloads.hpp>
#include <boost/python/args.hpp>

using Mantid::API::Run;
using namespace boost::python;

namespace
{
namespace bpl = boost::python;

/**
* Emulate dict.get. Returns the value pointed to by the key or the default given
* @param self The object called on
* @param key The key
* @param default_ The default to return if it does not exist
*/
bpl::object getWithDefault(bpl::object self, bpl::object key, bpl::object default_)
{
bpl::object exists(self.attr("__contains__"));
if( extract<bool>(exists(key))() )
{
return self.attr("__getitem__")(key);
}
else
{
return default_;
}
}

/**
* Emulate dict.get. Returns the value pointed to by the key or None if it doesn't exist
* @param self The bpl::object called on
* @param key The key
*/
bpl::object get(bpl::object self, bpl::object key)
{
return getWithDefault(self, key, bpl::object());
}


/**
* Add a property with the given name and value
* @param self A reference to the run object that we have been called on
* @param name The name of the new property
* @param value The value of the property
* @param units A string representing a unit
* @param replace If true, replace an existing property with this one else raise an error
*/
void addPropertyWithUnit(Run & self, const std::string & name, PyObject *value, const std::string & units, bool replace)
{
if( PyFloat_Check(value) )
{
self.addProperty(name, extract<double>(value)(), units, replace);
}
else if( PyInt_Check(value) )
{
self.addProperty(name, extract<long>(value)(), units, replace);
}
else if( PyString_Check(value) )
{
self.addProperty(name, extract<std::string>(value)(), units, replace);
}
else
{
std::ostringstream msg;
msg << "Run::addProperty - Unknown value type given: " << value->ob_type->tp_name;
throw std::invalid_argument(msg.str());
}
}

/**
* Add a property with the given name and value
* @param self A reference to the run object that we have been called on
* @param name The name of the new property
* @param value The value of the property
* @param replace If true, replace an existing property with this one else raise an error
*/
void addProperty(Run & self, const std::string & name, PyObject *value, bool replace)
{
addPropertyWithUnit(self, name, value, "", replace);
}

/**
* Add a property with the given name and value. An existing property is overwritten if it exists
* @param self A reference to the run object that we have been called on
* @param name The name of the new property
* @param value The value of the property
*/
void addOrReplaceProperty(Run & self, const std::string & name, PyObject *value)
{
addProperty(self, name, value, true);
}


}

void export_Run()
{
//Pointer
register_ptr_to_python<Run*>();

//Run class
class_< Run, boost::noncopyable >("Run", no_init)
.def("getProtonCharge", &Run::getProtonCharge, "Return the total good proton charge for the run")
.def("hasProperty", &Run::hasProperty, "Returns True if the given log value is contained within the run")
.def("getProperty", &Run::getProperty, return_value_policy<return_by_value>(), "Returns the named property (log value). Use '.value' to return the value.")
.def("getProperties", &Run::getProperties, return_internal_reference<>(), "Return the list of run properties managed by this object.")
.def("addProperty", &addProperty, "Adds a property with the given name and value. If replace=True then an existing property is overwritten")
.def("addProperty", &addPropertyWithUnit, "Adds a property with the given name, value and unit. If replace=True then an existing property is overwritten")
//--------------------------- Dictionary access----------------------------
.def("get", &getWithDefault, "Returns the value pointed to by the key or None if it does not exist")
.def("get", &get, "Returns the value pointed to by the key or the default value given")
.def("__contains__", &Run::hasProperty)
.def("__getitem__", &Run::getProperty, return_value_policy<return_by_value>())
.def("__setitem__", &addOrReplaceProperty)

;
}

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "MantidKernel/Property.h"
#include "MantidPythonInterface/kernel/StlExportDefinitions.h"

#include <boost/python/class.hpp>
#include <boost/python/register_ptr_to_python.hpp>
Expand All @@ -9,14 +10,17 @@

using Mantid::Kernel::Property;
using Mantid::Kernel::Direction;
using Mantid::PythonInterface::std_vector_exporter;
using namespace boost::python;


void export_Property()
{
// Ptr<->Object conversion
register_ptr_to_python<Property*>();

// vector of properties
std_vector_exporter<Property*>::wrap("std_vector_property");

//Direction
enum_<Direction::Type>("Direction")
.value("Input", Direction::Input)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
###############################################################################
from testhelpers import run_algorithm
from mantid.geometry import Instrument
from mantid.api import Sample
from mantid.api import Sample, Run

class ExperimentInfoTest(unittest.TestCase):

Expand All @@ -23,4 +23,8 @@ def test_information_access(self):

def test_sample_access_returns_sample_object(self):
sample = self._expt_ws.sample()
self.assertTrue(isinstance(sample, Sample))
self.assertTrue(isinstance(sample, Sample))

def test_run_access_returns_run_object(self):
run = self._expt_ws.run()
self.assertTrue(isinstance(run, Run))
66 changes: 66 additions & 0 deletions Code/Mantid/Framework/PythonInterface/test/python/RunTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import unittest
from testhelpers import run_algorithm

class RunTest(unittest.TestCase):

_expt_ws = None

def setUp(self):
if self.__class__._expt_ws is None:
alg = run_algorithm('Load', Filename='LOQ48127.raw', SpectrumMax=1, child=True)
self.__class__._expt_ws = alg.getProperty("OutputWorkspace").value

def test_proton_charge_returns_a_double(self):
run = self._expt_ws.run()
charge = run.getProtonCharge()
self.assertEquals(type(charge), float)
self.assertAlmostEquals(charge, 10.040912628173828, 15)

def test_run_hasProperty(self):
self.assertTrue(self._expt_ws.run().hasProperty('run_start'))
self.assertTrue('run_start' in self._expt_ws.run())
self.assertFalse('not a log' in self._expt_ws.run())

def test_run_getProperty(self):
run_start = self._expt_ws.run().getProperty('run_start')
self.assertEquals(type(run_start.value), str)
self.assertEquals(run_start.value, "2008-12-18T17:58:38")

def do_spectra_check(nspectra):
self.assertEquals(type(nspectra.value), int)
self.assertEquals(nspectra.value, 8)
self.assertRaises(RuntimeError, self._expt_ws.run().getProperty, 'not_a_log')

do_spectra_check(self._expt_ws.run().getProperty('nspectra'))
do_spectra_check(self._expt_ws.run()['nspectra'])
do_spectra_check(self._expt_ws.run().get('nspectra'))

# get returns the default if key does not exist, or None if no default
self.assertEquals(self._expt_ws.run().get('not_a_log'), None)
self.assertEquals(self._expt_ws.run().get('not_a_log', 5.), 5.)

def test_add_property_with_known_type_succeeds(self):
run = self._expt_ws.run()
nprops = len(run.getProperties())
run.addProperty('int_t', 1, False)
self.assertEquals(len(run.getProperties()), nprops + 1)
run.addProperty('float_t', 2.4, False)
self.assertEquals(len(run.getProperties()), nprops + 2)
run.addProperty('str_t', 'from_python', False)
self.assertEquals(len(run.getProperties()), nprops + 3)
run['int_t'] = 6.5
self.assertEquals(len(run.getProperties()), nprops + 3)
self.assertEquals(run.getProperty('int_t').value, 6.5)

def test_add_propgates_units_correctly(self):
run = self._expt_ws.run()
run.addProperty('float_t', 2.4, 'metres', True)
prop = run.getProperty('float_t')
self.assertEquals(prop.units, 'metres')

def test_add_property_with_unknown_type_raises_error(self):
run = self._expt_ws.run()
self.assertRaises(ValueError, run.addProperty, 'dict_t', {}, False)

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

0 comments on commit 0586cc9

Please sign in to comment.