diff --git a/pyqn/dimensions.py b/pyqn/dimensions.py
index 168c250..319de59 100644
--- a/pyqn/dimensions.py
+++ b/pyqn/dimensions.py
@@ -1,3 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
# dimensions.py
# A class representing the dimensions of a physical quantity's units, in
# terms of powers of length (L), mass (M), time (T), temperature (Theta),
@@ -22,9 +25,15 @@
# You should have received a copy of the GNU General Public License
# along with PyQn. If not, see
+class DimensionsError(Exception):
+ def __init__(self, error_str):
+ self.error_str = error_str
+ def __str__(self):
+ return self.error_str
+
class Dimensions(object):
# these are the abbreviations for Length, Mass, Time, Temperature,
- # Quantity (amount of substance), Current, and Luminous Intensity:
+ # Quantity (amount of substance), Current, and Luminous Intensity
dim_names = ['L', 'M', 'T', 'Theta', 'Q', 'C', 'I']
dim_desc = ['length', 'mass', 'time', 'temperature', 'amount',
'current', 'luminous intensity']
@@ -37,10 +46,12 @@ def __init__(self, dims=None, **kwargs):
if dims:
# initialize by dims array
if not kwargs:
- self.dims = dims
+ if (len(dims) == 7) and (type(dims[0]) is not str):
+ self.dims = dims
+ else:
+ raise DimensionsError('Inconsistent number of dimensions in input or non-value elements of input array')
else:
- print('bad initialisation of Dimensions object')
- sys.exit(1)
+ raise DimensionsError('Bad initialisation of Dimensions object')
else:
# initialize by keyword arguments
for dim_name in kwargs:
@@ -104,4 +115,4 @@ def __ne__(self, other):
d_voltage = d_energy / d_charge # 1 V = 1 J/C
d_magfield_strength = d_voltage * d_time / d_area # 1 T = 1 V.s/m^2
d_magnetic_flux = d_voltage * d_time # 1 Wb = 1 V.s
-d_temperature = Dimensions(Theta=1)
\ No newline at end of file
+d_temperature = Dimensions(Theta=1)
diff --git a/pyqn/qn_array.py b/pyqn/qn_array.py
index a14b43b..5f1ef4a 100644
--- a/pyqn/qn_array.py
+++ b/pyqn/qn_array.py
@@ -1,19 +1,274 @@
-from .symbol import Symbol
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# qn_array.py
+# A class representing an array of physical quantities, with units and
+# uncertainty.
+#
+# Copyright (C) 2012-2017 Christian Hill
+# Department of Physics and Astronomy, University College London
+# christian.hill@ucl.ac.uk
+#
+# This file is part of PyQn
+#
+# PyQn is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PyQn is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PyQn. If not, see
+
import numpy as np
+from .units import Units
+from .quantity import Quantity
+from .ufunc_dictionaries import *
+import matplotlib.pyplot as plt
+import csv
-class qnArrayError(Exception):
+class QnArrayError(Exception):
+ """
+ An Exception class for errors that might occur whilst manipulating
+ QnArray objects.
+
+ """
+
def __init__(self, error_str):
self.error_str = error_str
def __str__(self):
return self.error_str
-class qnArray(Symbol):
- def __init__(self, name=None, latex=None, html=None, values=None,
- units=None, sd=None, definition=None)
- Symbol.__init__(self, name, latex, html, definition)
- if type(values)==list:
- self.values = np.array(values)
- elif type(values)==numpy.ndarray:
- self.values = values
+class QnArray(np.ndarray):
+ """
+ A Python class representing an array of values, their units and
+ errors. This class extends the numpy ndarray class such that
+ QnArray objects can be used with numpy ufuncs and the errors are
+ propagated using the functions defined in the ufunc_dictionaries.py
+ file.
+
+ """
+
+ def __new__(cls, input_array, info=None, units='1', sd=None):
+ """
+ Initialises a qnArray as a child class derived from the numpy
+ ndarray. The units are set to default to be unitless, but can
+ be set to be a string (which will be converted to a Units
+ object) or set straight away to be a Units object, the standard
+ deviation is set to default to an array of zeros.
+
+ """
+
+ obj = np.asarray(input_array).view(cls)
+ obj.info = info
+ if sd is None:
+ obj.sd = np.zeros(len(input_array))
else:
- raise qnArrayError
\ No newline at end of file
+ obj.sd = np.array(sd)
+
+ if type(units) is str:
+ obj.units = Units(units) #records units as Unit class
+ elif type(units) is Units:
+ obj.units = units
+ return obj
+
+ def __array_finalize__(self, obj):
+ """
+ Function which is called each time that a function is operated
+ on the QnArray object.
+
+ """
+
+ if obj is None: return
+ self.info = getattr(obj, 'info', None)
+ self.sd = getattr(obj, 'sd', None)
+
+ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
+ """
+ Function which is called when numpy ufuncs are called on the
+ QnArray object. It uses predefined equations to calculate the
+ new errors of the returned QnArray objects.
+
+ """
+
+ if ufunc in ufunc_dict_alg:
+ alg_func = ufunc_dict_alg[ufunc][0]
+ sd_func = ufunc_dict_alg[ufunc][1]
+ units_func = ufunc_dict_alg[ufunc][2]
+ alg_func_reverse = ufunc_dict_alg[ufunc][3]
+
+ if all([hasattr(x, 'units') for x in inputs]):
+ result_units = units_func(inputs[0].units,
+ inputs[1].units)
+
+ # if both are qn arrays
+ if (type(inputs[1]) is QnArray) and \
+ (type(inputs[0]) is QnArray):
+ result_val = getattr(np.asarray(inputs[0]),
+ alg_func)(np.asarray(inputs[1]))
+ result_sd = sd_func(result_val, np.asarray(inputs[0]),
+ np.asarray(inputs[1]),
+ inputs[0].sd,
+ inputs[1].sd)
+
+ # if one input is a quantity
+ elif type(inputs[1]) is Quantity:
+ result_val = getattr(np.asarray(inputs[0]),
+ alg_func)(inputs[1].value)
+ result_sd = sd_func(result_val, np.asarray(inputs[0]),
+ inputs[1].value,
+ inputs[0].sd,
+ inputs[1].sd)
+
+ elif type(inputs[0]) is Quantity:
+ result_val = getattr(np.asarray(inputs[1]),
+ alg_func_reverse)(inputs[0].value)
+ result_sd = sd_func(result_val, inputs[0].value,
+ np.asarray(inputs[1]),
+ inputs[0].sd,
+ inputs[1].sd)
+
+ # for all other object types
+ elif type(inputs[0]) is QnArray:
+ result_val = getattr(np.asarray(inputs[0]),
+ alg_func)(inputs[1])
+ result_sd = sd_func(result_val, np.asarray(inputs[0]),
+ inputs[1],
+ inputs[0].sd, 0)
+ result_units = units_func(inputs[0].units, Units('1'))
+
+ else:
+ result_val = getattr(np.asarray(inputs[1]),
+ alg_func_reverse)(inputs[0])
+ result_sd = sd_func(result_val, inputs[0],
+ np.asarray(inputs[1]),
+ 0, inputs[1].sd)
+ result_units = units_func(Units('1'), inputs[1].units)
+
+ return QnArray(result_val, units = result_units,
+ sd = result_sd)
+
+ elif ufunc in ufunc_dict_one_input:
+ #checks if units of input are valid
+ unit_test_func = ufunc_dict_one_input[ufunc][1]
+ inputs_checked = unit_test_func(inputs[0])
+
+ #extracts functions for finding sd and units
+ sd_func = ufunc_dict_one_input[ufunc][0]
+ units_func = ufunc_dict_one_input[ufunc][2]
+
+ #calculates results
+ result_val = ufunc(np.asarray(inputs_checked))
+ result_sd = sd_func(result_val,
+ np.asarray(inputs_checked),
+ np.asarray(inputs_checked.sd))
+ result_units = units_func(inputs[0].units)
+ return QnArray(result_val, units = result_units,
+ sd = result_sd)
+
+ elif ufunc in ufunc_dict_two_inputs:
+ unit_test_func = ufunc_dict_two_inputs[ufunc][1]
+ input1 = unit_test_func(inputs[0])
+ input2 = unit_test_func(inputs[1])
+ sd_func = ufunc_dict_two_inputs[ufunc][0]
+ units_func = ufunc_dict_two_inputs[ufunc][2]
+ result_val = ufunc(np.asarray(input1),np.asarray(input2))
+ result_sd = sd_func(result_val, np.asarray(input1),
+ np.asarray(input2),
+ input1.sd, input2.sd)
+ result_units = units_func(input1.units, input2.units)
+ return QnArray(result_val, units = result_units,
+ sd = result_sd)
+
+ def __eq__(self, other):
+ """
+ Checks if two QnArray objects are the same, given that their
+ values and units are the same.
+
+ """
+
+ if all(super(QnArray, self).__eq__(super(QnArray, other))) and \
+ (self.units == other.units):
+ return True
+ else:
+ return False
+
+ @property
+ def html_str(self):
+ """
+ Creates a representation of the array in the HTML format.
+
+ """
+
+ html_chunks = []
+ for i in range(len(self)):
+ html_chunks.append('{} {}'.format(self[i],self.units))
+ return ', '.join(html_chunks)
+
+ def convert_units_to(self, new_units, force=None):
+ """
+ Converts the units of the array given a new unit. It uses the
+ conversion factor generator of the Units class.
+
+ """
+
+ to_units = Units(new_units)
+ fac = self.units.conversion(to_units, force)
+ new_vals = np.asarray(self)*fac
+ new_sd = self.sd*fac
+ return QnArray(new_vals, units = new_units, sd = new_sd)
+
+ def append(self, input_quantity):
+ """
+ Appends a Quantity object to the QnArray object.
+
+ """
+
+ if self.units != input_quantity.units:
+ raise QnArrayError('Same units expected')
+ return QnArray(np.append(np.asarray(self),
+ input_quantity.value),
+ units = self.units,
+ sd = np.append(self.sd,input_quantity.sd))
+
+def plot_qn_arrays(qn_arr_1, qn_arr_2):
+ """
+ Plots two QnArray objects using matplotlib with their errors.
+
+ """
+
+ plt.errorbar(np.asarray(qn_arr_1),np.asarray(qn_arr_2),
+ xerr = qn_arr_1.sd, yerr = qn_arr_2.sd)
+ plt.show()
+
+def load_data(filename, file_type, errors=False):
+ """
+ Function which generates two QnArray objects given a csv file.
+
+ """
+
+ vals1 = []
+ vals2 = []
+ sd1 = []
+ sd2 = []
+ if file_type is 'csv':
+ with open(filename,'rb') as f:
+ reader = csv.reader(f)
+ if errors is False:
+ for row in reader:
+ vals1.append(row[0])
+ vals2.append(row[1])
+ sd1.append(0)
+ sd2.append(0)
+ else:
+ for row in reader:
+ vals1.append(row[0])
+ sd1.append(row[1])
+ vals2.append(row[2])
+ sd2.append(row[3])
+ return QnArray(vals1, units = '1', sd = sd1), \
+ QnArray(vals2, units = '1', sd = sd2)
diff --git a/pyqn/quantity.py b/pyqn/quantity.py
index 61d38ce..72aed73 100644
--- a/pyqn/quantity.py
+++ b/pyqn/quantity.py
@@ -1,3 +1,6 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
# quantity.py
# A class representing physical quantity, with name, units and uncertainty.
#
@@ -59,7 +62,10 @@ def __init__(self, name=None, latex=None, html=None, value=None,
Symbol.__init__(self, name, latex, html, definition)
self.value = value
- self.sd = sd
+ if sd is None:
+ self.sd = 0
+ else:
+ self.sd = sd
if units is None:
self.units = Units([])
else:
@@ -182,6 +188,12 @@ def draw_from_dist(self, shape=None):
' deviation.'.format(self.name))
return np.random.normal(loc=self.value, scale=self.sd, size=shape)
+
+ def __eq__(self, other):
+ if (self.value == other.value) and (self.units == other.units):
+ return True
+ else:
+ return False
def __add__(self, other):
"""
@@ -189,20 +201,24 @@ def __add__(self, other):
are propagated, but assumed to be uncorrelated.
"""
-
- if type(other) != Quantity:
- raise TypeError
- if self.value is None or other.value is None:
- raise ValueError
- if self.units != other.units:
- raise UnitsError('Can\'t add two quantities with different'
+ if type(other) is Quantity:
+ if self.value is None or other.value is None:
+ raise ValueError
+ if self.units != other.units:
+ raise UnitsError('Can\'t add two quantities with different'
' units: %s and %s' % (self.units, other.units))
- if self.sd is None or other.sd is None:
- sd = None
+ if self.sd is None or other.sd is None:
+ sd = None
+ else:
+ sd = math.hypot(self.sd, other.sd)
+ return Quantity(value=self.value+other.value, units=self.units, sd=sd)
else:
- sd = math.hypot(self.sd, other.sd)
- return Quantity(value=self.value+other.value, units=self.units, sd=sd)
-
+ try:
+ result = other.__radd__(self)
+ except TypeError:
+ print('These objects are not compatible')
+ return result
+
def __sub__(self, other):
"""
Subtract two Quantity objects; they must have the same units. Errors
@@ -210,18 +226,23 @@ def __sub__(self, other):
"""
- if type(other) != Quantity:
- raise TypeError
- if self.value is None or other.value is None:
- raise ValueError
- if self.units != other.units:
- raise UnitsError('Can\'t subtract two quantities with different'
+ if type(other) is Quantity:
+ if self.value is None or other.value is None:
+ raise ValueError
+ if self.units != other.units:
+ raise UnitsError('Can\'t subtract two quantities with different'
' units: %s and %s' % (self.units, other.units))
- if self.sd is None or other.sd is None:
- sd = None
+ if self.sd is None or other.sd is None:
+ sd = None
+ else:
+ sd = math.hypot(self.sd, other.sd)
+ return Quantity(value=self.value-other.value, units=self.units, sd=sd)
else:
- sd = math.hypot(self.sd, other.sd)
- return Quantity(value=self.value-other.value, units=self.units, sd=sd)
+ try:
+ result = other.__rsub__(self)
+ except TypeError:
+ print('These objects are not compatible')
+ return result
def __mul__(self, other):
"""
@@ -239,7 +260,7 @@ def __mul__(self, other):
sd = abs(other) * self.sd
return Quantity(value=self.value*other, units=self.unit, sd=sd)
else:
- if type(other) != Quantity:
+ if (type(other) is not Quantity) and (type(other) is not qnArray):
raise TypeError
if other.value is None:
raise ValueError
@@ -258,19 +279,8 @@ def __truediv__(self, other):
propagated, but assumed to be uncorrelated.
"""
-
- if self.value is None:
- raise ValueError
- if type(other) in (int, float):
- if self.sd is None:
- sd = None
- else:
- sd = abs(other) / self.sd
- return Quantity(value=self.value/other, units=self.units, sd=sd)
- else:
- if type(other) != Quantity:
- raise TypeError
- if other.value is None:
+ if type(other) is Quantity:
+ if (self.value is None) or other.value is None:
raise ValueError
value = self.value / other.value
if not self.sd or not other.sd:
@@ -280,13 +290,28 @@ def __truediv__(self, other):
other.sd/other.value)
units = self.units / other.units
return Quantity(value=value, units=units, sd=sd)
+ if type(other) in (int, float):
+ if self.sd is None:
+ sd = None
+ else:
+ sd = abs(other) / self.sd
+ return Quantity(value=self.value/other, units=self.units, sd=sd)
+ else:
+ try:
+ result = other.__rtruediv__(self)
+ except TypeError:
+ print('These objects are not compatible')
+ return result
def __rtruediv__(self, other):
return self.__truediv__(other)
def __pow__(self, power):
- return Quantity(value = self.value**power,
- units = self.units**power)
+ new_quantity = Quantity(value = self.value**power,
+ units = self.units**power)
+ if self.sd:
+ new_quantity.sd = self.value**power*math.hypot(self.sd/self.value, self.sd/self.value)
+ return new_quantity
@classmethod
def parse(self, s_quantity, name=None, units=None, sd=None,
@@ -327,3 +352,14 @@ def parse(self, s_quantity, name=None, units=None, sd=None,
ndp = 0
sd = float(s_sd) * 10**(exp-ndp)
return Quantity(name=name, value=value, units=units, sd=sd)
+
+ @property
+ def html_str(self):
+ html_chunks = []
+ if self.name:
+ html_chunks.append('{:s} ='.format(self.name))
+ html_chunks.append('{:}'.format(self.value))
+ if self.sd:
+ html_chunks.append('± {:}'.format(self.sd))
+ html_chunks.append(self.units.html)
+ return ' '.join(html_chunks)
diff --git a/pyqn/si.py b/pyqn/si.py
index 16f8db2..4527020 100644
--- a/pyqn/si.py
+++ b/pyqn/si.py
@@ -29,6 +29,12 @@ def __init__(self, prefix, name, power):
self.name = name
self.power = power
self.fac = 10**power
+
+ def __eq__(self, other):
+ if (self.prefix == other.prefix) and (self.power == other.power):
+ return True
+ else:
+ return False
# Here are the SI prefixes that we recognise.
si_prefixes = { 'y': SIPrefix('y', 'yocto', -24),
diff --git a/pyqn/symbol.py b/pyqn/symbol.py
index 4b7115e..19a58be 100644
--- a/pyqn/symbol.py
+++ b/pyqn/symbol.py
@@ -49,4 +49,7 @@ def __str__(self):
"""
Return the string representation of the Symbol, its name attribute.
"""
- return self.name
+ if self.name:
+ return self.name
+ else:
+ return '[undefined]'
diff --git a/pyqn/tests/test_atom_unit.py b/pyqn/tests/test_atom_unit.py
new file mode 100644
index 0000000..72b8666
--- /dev/null
+++ b/pyqn/tests/test_atom_unit.py
@@ -0,0 +1,31 @@
+import unittest
+from ..atom_unit import AtomUnit
+from ..base_unit import BaseUnit, base_units
+from ..dimensions import d_length, d_energy
+from ..si import SIPrefix
+
+class AtomUnitCheck(unittest.TestCase):
+ def test_atom_unit_init(self):
+ au1 = AtomUnit('m', BaseUnit('m', 'metre', 'length', 1., '', 'm', d_length), -2)
+ self.assertEqual(au1.base_unit, BaseUnit('m', 'metre', 'length', 1., '', 'm', d_length))
+ self.assertEqual(au1.exponent, -2)
+ self.assertEqual(au1.si_prefix, SIPrefix('m', 'milli', -3))
+ self.assertEqual(au1.si_fac, 1000000.0)
+
+ def test_atom_unit_parse(self):
+ au1 = AtomUnit.parse('μJ')
+ self.assertEqual(au1.base_unit, BaseUnit('J','','',1,'','',d_energy))
+ self.assertEqual(au1.exponent, 1)
+ self.assertEqual(au1.si_prefix, SIPrefix('μ', 'micro', -6))
+ self.assertEqual(au1.si_fac, 1e-6)
+
+ def test_atom_unit_pow(self):
+ au1 = AtomUnit.parse('mm')
+ au2 = au1 ** 3
+ self.assertEqual(au2.base_unit, BaseUnit('m', 'metre', 'length', 1., '', 'm', d_length), -2)
+ self.assertEqual(au2.exponent, 3)
+ self.assertEqual(au2.si_prefix, SIPrefix('m', 'milli', -3))
+ self.assertEqual(au2.si_fac, 1e-9)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pyqn/tests/test_base_unit.py b/pyqn/tests/test_base_unit.py
new file mode 100644
index 0000000..4858568
--- /dev/null
+++ b/pyqn/tests/test_base_unit.py
@@ -0,0 +1,52 @@
+import unittest
+from ..base_unit import BaseUnit
+from ..dimensions import d_length, Dimensions, d_voltage, d_current, d_time, d_mass
+
+class BaseUnitCheck(unittest.TestCase):
+ def test_base_unit_init(self):
+ bu1 = BaseUnit('m', 'metre', 'length', 1., '', 'm', d_length)
+ self.assertEqual(bu1.stem, 'm')
+ self.assertEqual(bu1.name, 'metre')
+ self.assertEqual(bu1.unit_type, 'length')
+ self.assertEqual(bu1.fac, 1)
+ self.assertEqual(bu1.description, '')
+ self.assertEqual(bu1.latex, 'm')
+ self.assertEqual(bu1.dims, Dimensions(L=1))
+
+ bu2 = BaseUnit('Ω', 'ohm','electric resistance', 1, '',r'\Omega',d_voltage / d_current)
+ self.assertEqual(bu2.stem, 'Ω')
+ self.assertEqual(bu2.name, 'ohm')
+ self.assertEqual(bu2.unit_type, 'electric resistance')
+ self.assertEqual(bu2.fac, 1)
+ self.assertEqual(bu2.description, '')
+ self.assertEqual(bu2.latex, r'\Omega')
+ self.assertEqual(bu2.dims, d_voltage/d_current)
+
+ def test_base_unit_eq(self):
+ bu1 = BaseUnit('m', 'metre', 'length', 1., '', 'm', d_length)
+ bu2 = BaseUnit('s', 'second', 'time', 1., '', 's', d_time)
+ bu3 = BaseUnit('g', 'gram', 'mass', 1.e-3, '', 'g', d_mass)
+ bu4 = BaseUnit('K', 'kelvin', 'temperature', 1., '', 'K', Dimensions(Theta=1))
+ self.assertFalse(bu1 == bu2)
+ self.assertFalse(bu1 == bu3)
+ self.assertFalse(bu1 == bu4)
+ self.assertFalse(bu2 == bu3)
+ self.assertFalse(bu2 == bu4)
+ self.assertFalse(bu3 == bu4)
+
+ bu5 = BaseUnit('k', '', '', 1, '', '', Dimensions(dims = []))
+ self.assertFalse(bu4 == bu5)
+
+ def test_base_unit_str(self):
+ bu1 = BaseUnit('m', 'metre', 'length', 1., '', 'm', d_length)
+ bu2 = BaseUnit('s', 'second', 'time', 1., '', 's', d_time)
+ bu3 = BaseUnit('g', 'gram', 'mass', 1.e-3, '', 'g', d_mass)
+ bu4 = BaseUnit('K', 'kelvin', 'temperature', 1., '', 'K', Dimensions(Theta=1))
+ self.assertEqual(str(bu1), 'm')
+ self.assertEqual(str(bu2), 's')
+ self.assertEqual(str(bu3), 'g')
+ self.assertEqual(str(bu4), 'K')
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/pyqn/tests/test_dimensions.py b/pyqn/tests/test_dimensions.py
new file mode 100644
index 0000000..5f49e25
--- /dev/null
+++ b/pyqn/tests/test_dimensions.py
@@ -0,0 +1,159 @@
+import unittest
+from ..dimensions import Dimensions, DimensionsError
+
+class DimensionsCheck(unittest.TestCase):
+ def test_dimensions_init(self):
+ arr1 = [1,0,0,0,0,0,1]
+ arr2 = [0,2,10,0,5,0,0]
+ arr3 = [1,1,-1,1,-1,1,-1]
+ d1 = Dimensions(dims = arr1)
+ d2 = Dimensions(dims = arr2)
+ d3 = Dimensions(dims = arr3)
+ d4 = Dimensions(dims = [])
+
+ for i in range(7):
+ self.assertEqual(d1.dims[i], arr1[i])
+ self.assertEqual(d2.dims[i], arr2[i])
+ self.assertEqual(d3.dims[i], arr3[i])
+ self.assertEqual(d4.dims[i], 0)
+
+ d5 = Dimensions(M = 1)
+ self.assertEqual(d5.dims[1], 1)
+
+ d6 = Dimensions(Q = -2, Theta = -100)
+ self.assertEqual(d6.dims[4], -2)
+ self.assertEqual(d6.dims[3], -100)
+
+ d7 = Dimensions(C = 10, I = 0)
+ self.assertEqual(d7.dims[5], 10)
+ self.assertEqual(d7.dims[6], 0)
+
+ with self.assertRaises(DimensionsError) as e:
+ d = Dimensions(dims = [1])
+ with self.assertRaises(DimensionsError) as e:
+ d = Dimensions(dims = [1,1,1,1,1,1,1,1,1,1,1])
+ with self.assertRaises(DimensionsError) as e:
+ d = Dimensions(dims = ['a','b','c','d','e','f','g'])
+ with self.assertRaises(KeyError) as e:
+ d = Dimensions(Mass = 1)
+ with self.assertRaises(KeyError) as e:
+ d = Dimensions(time = -10)
+
+ def test_dimensions_str(self):
+ self.assertEqual(str(Dimensions(dims = [])),'[dimensionless]')
+ self.assertEqual(str(Dimensions(dims = [1,2,3,4,5,6,7])),'L.M2.T3.Theta4.Q5.C6.I7')
+ self.assertEqual(str(Dimensions(dims = [1,0,0,-2,0,1,2])),'L.Theta-2.C.I2')
+
+ def test_dimensions_mul(self):
+ arr1 = [1,0,0,0,0,0,0]
+ arr2 = [0,1,1,0,1,0,0]
+ arr3 = [-1,10,5,2,0,-4,0]
+ d1 = Dimensions(dims = arr1)
+ d2 = Dimensions(dims = arr2)
+ d3 = Dimensions(dims = arr3)
+
+ d12 = d1 * d2
+ d13 = d1 * d3
+ d23 = d2 * d3
+ for i in range(7):
+ self.assertEqual(d12.dims[i], arr1[i]+arr2[i])
+ self.assertEqual(d13.dims[i], arr1[i]+arr3[i])
+ self.assertEqual(d23.dims[i], arr2[i]+arr3[i])
+
+ def test_dimensions_truediv(self):
+ arr1 = [1,0,0,0,0,0,0]
+ arr2 = [0,1,1,0,1,0,0]
+ arr3 = [-1,10,5,2,0,-4,0]
+ d1 = Dimensions(dims = arr1)
+ d2 = Dimensions(dims = arr2)
+ d3 = Dimensions(dims = arr3)
+
+ d12 = d1 / d2
+ d21 = d2 / d1
+ d13 = d1 / d3
+ d31 = d3 / d1
+ d23 = d2 / d3
+ d32 = d3 / d2
+ for i in range(7):
+ self.assertEqual(d12.dims[i], arr1[i]-arr2[i])
+ self.assertEqual(d21.dims[i], arr2[i]-arr1[i])
+ self.assertEqual(d13.dims[i], arr1[i]-arr3[i])
+ self.assertEqual(d31.dims[i], arr3[i]-arr1[i])
+ self.assertEqual(d23.dims[i], arr2[i]-arr3[i])
+ self.assertEqual(d32.dims[i], arr3[i]-arr2[i])
+
+ def test_dimensions_pow(self):
+ arr1 = [1,0,0,0,0,0,0]
+ arr2 = [0,1,1,0,1,0,0]
+ arr3 = [-1,10,5,2,0,-4,0]
+ d1 = Dimensions(dims = arr1)
+ d2 = Dimensions(dims = arr2)
+ d3 = Dimensions(dims = arr3)
+
+ d1_1 = d1 ** 1
+ d1_2 = d1 ** 2
+ d1__10 = d1 ** (-10)
+ d2_1 = d2 ** 1
+ d2_2 = d2 ** 2
+ d2__10 = d2 ** (-10)
+ d3_1 = d3 ** 1
+ d3_2 = d3 ** 2
+ d3__10 = d3 ** (-10)
+
+ for i in range(7):
+ self.assertEqual(d1_1.dims[i], arr1[i])
+ self.assertEqual(d1_2.dims[i], arr1[i]*2)
+ self.assertEqual(d1__10.dims[i], arr1[i]*(-10))
+ self.assertEqual(d2_1.dims[i], arr2[i])
+ self.assertEqual(d2_2.dims[i], arr2[i]*2)
+ self.assertEqual(d2__10.dims[i], arr2[i]*(-10))
+ self.assertEqual(d3_1.dims[i], arr3[i])
+ self.assertEqual(d3_2.dims[i], arr3[i]*2)
+ self.assertEqual(d3__10.dims[i], arr3[i]*(-10))
+
+ def test_dimensions_eq(self):
+ d1 = Dimensions(dims = [1,1,1,0,1,1,1])
+ d2 = Dimensions(dims = [1,1,1,0,1,1,1])
+ d3 = Dimensions(dims = [])
+ d4 = Dimensions(dims = [0,0,0,0,0,0,0])
+ self.assertTrue(d1 == d2)
+ self.assertTrue(d3 == d4)
+
+ def test_dimensions_neq(self):
+ d1 = Dimensions(dims = [1,0,0,0,0,0,0])
+ d2 = Dimensions(dims = [0,1,1,0,1,0,0])
+ d3 = Dimensions(dims = [-1,10,5,2,0,-4,0])
+ d4 = Dimensions(dims = [1,1,1,0,1,1,1])
+ d5 = Dimensions(dims = [1,1,1,0,1,1,1])
+ d6 = Dimensions(dims = [])
+ d7 = Dimensions(dims = [0,0,0,0,0,0,0])
+
+ self.assertTrue(d1 != d2)
+ self.assertTrue(d1 != d3)
+ self.assertTrue(d1 != d4)
+ self.assertTrue(d1 != d5)
+ self.assertTrue(d1 != d6)
+ self.assertTrue(d1 != d7)
+
+ self.assertTrue(d2 != d3)
+ self.assertTrue(d2 != d4)
+ self.assertTrue(d2 != d5)
+ self.assertTrue(d2 != d6)
+ self.assertTrue(d2 != d7)
+
+ self.assertTrue(d3 != d4)
+ self.assertTrue(d3 != d5)
+ self.assertTrue(d3 != d6)
+ self.assertTrue(d3 != d7)
+
+ self.assertFalse(d4 != d5)
+ self.assertTrue(d4 != d6)
+ self.assertTrue(d4 != d7)
+
+ self.assertTrue(d5 != d6)
+ self.assertTrue(d5 != d7)
+
+ self.assertFalse(d6 != d7)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pyqn/tests/test_qn_array.py b/pyqn/tests/test_qn_array.py
new file mode 100644
index 0000000..1262c65
--- /dev/null
+++ b/pyqn/tests/test_qn_array.py
@@ -0,0 +1,292 @@
+import unittest
+from ..qn_array import QnArray, QnArrayError
+from ..units import Units
+from ..quantity import Quantity
+from ..ufunc_dictionaries import *
+import numpy as np
+
+class QnArrayTest(unittest.TestCase):
+ def test_qn_array_init(self):
+ vals1 = [1,2,3,4]
+ sd1 = [0.1,0.2,0.3,0.4]
+ qnarr1 = QnArray(vals1,units='m',sd=sd1)
+ self.assertEqual(qnarr1.units, Units('m'))
+ for i in range(len(vals1)):
+ self.assertEqual(qnarr1[i], vals1[i])
+ self.assertEqual(qnarr1.sd[i], sd1[i])
+
+ vals2 = [-10,-20,-30,0,5]
+ qnarr2 = QnArray(vals2, units = 'J')
+ self.assertEqual(qnarr2.units, Units('J'))
+ for i in range(len(vals2)):
+ self.assertEqual(qnarr2[i], vals2[i])
+ self.assertEqual(qnarr2.sd[i], 0)
+
+ def test_qn_array_add(self):
+ vals1 = [1,2,3,4]
+ sd1 = [0.1,0.2,0.3,0.4]
+ qnarr1 = QnArray(vals1,units='m',sd=sd1)
+
+ vals2 = [2,3,4,1]
+ sd2 = [0.2,0.3,0.4,0.1]
+ qnarr2 = QnArray(vals2, units='m',sd=sd2)
+
+ qnarr3 = qnarr1 + qnarr2
+ self.assertEqual(qnarr3.units, qnarr1.units)
+ for i in range(len(vals2)):
+ self.assertEqual(qnarr3[i], vals1[i]+vals2[i])
+ self.assertAlmostEqual(qnarr3.sd[i], (sd1[i]**2+sd2[i]**2)**0.5)
+
+ vals3 = [1,1,1]
+ qnarr3 = QnArray(vals3, units='m')
+ with self.assertRaises(ValueError) as e:
+ qnarr = qnarr1 + qnarr3
+
+ q1 = Quantity(value = 10, units = 'm')
+ qnarr4 = qnarr1 + q1
+ qnarr5 = q1 + qnarr1
+ qnarr6 = qnarr2 + 15
+ qnarr7 = 15 + qnarr2
+ self.assertEqual(qnarr4.units, Units('m'))
+ self.assertEqual(qnarr5.units, Units('m'))
+ self.assertEqual(qnarr6.units, Units('m'))
+ self.assertEqual(qnarr7.units, Units('m'))
+ for i in range(len(qnarr4)):
+ self.assertEqual(qnarr4[i], qnarr1[i]+10)
+ self.assertEqual(qnarr5[i], 10+qnarr1[i])
+ self.assertEqual(qnarr6[i], qnarr2[i]+15)
+ self.assertEqual(qnarr7[i], 15+qnarr2[i])
+
+ def test_qn_array_sub(self):
+ vals1 = [1,2,3,4]
+ qnarr1 = QnArray(vals1,units='m')
+
+ vals2 = [2,3,4,1]
+ qnarr2 = QnArray(vals2, units='m')
+
+ qnarr3 = qnarr1 - qnarr2
+ for i in range(len(vals2)):
+ self.assertEqual(qnarr3[i], vals1[i]-vals2[i])
+
+ vals3 = [1,1,1]
+ qnarr3 = QnArray(vals3, units='m')
+ with self.assertRaises(ValueError) as e:
+ qnarr = qnarr1 - qnarr3
+
+ q1 = Quantity(value = 10, units = 'm')
+ qnarr4 = qnarr1 - q1
+ qnarr5 = q1 - qnarr1
+ qnarr6 = qnarr2 - 15
+ qnarr7 = 15 - qnarr2
+ self.assertEqual(qnarr4.units, Units('m'))
+ self.assertEqual(qnarr5.units, Units('m'))
+ self.assertEqual(qnarr6.units, Units('m'))
+ self.assertEqual(qnarr7.units, Units('m'))
+ for i in range(len(qnarr4)):
+ self.assertEqual(qnarr4[i], qnarr1[i]-10)
+ self.assertEqual(qnarr5[i], 10-qnarr1[i])
+ self.assertEqual(qnarr6[i], qnarr2[i]-15)
+ self.assertEqual(qnarr7[i], 15-qnarr2[i])
+
+ def test_qn_array_mul(self):
+ vals1 = [1,2,3,4]
+ qnarr1 = QnArray(vals1,units='m')
+
+ vals2 = [2,3,4,1]
+ qnarr2 = QnArray(vals2, units='s')
+
+ qnarr3 = qnarr1 * qnarr2
+ self.assertEqual(qnarr3.units, Units('m.s'))
+ for i in range(len(vals2)):
+ self.assertEqual(qnarr3[i], vals1[i]*vals2[i])
+
+ vals3 = [1,1,1]
+ qnarr3 = QnArray(vals3, units='m')
+ with self.assertRaises(ValueError) as e:
+ qnarr = qnarr1 * qnarr3
+
+ q1 = Quantity(value = 10, units = 'J')
+ qnarr4 = qnarr1 * q1
+ qnarr5 = q1 * qnarr1
+ qnarr6 = qnarr2 * 15
+ qnarr7 = 15 * qnarr2
+ self.assertEqual(qnarr4.units, Units('m.J'))
+ self.assertEqual(qnarr5.units, Units('m.J'))
+ self.assertEqual(qnarr6.units, Units('s'))
+ self.assertEqual(qnarr7.units, Units('s'))
+ for i in range(len(qnarr4)):
+ self.assertEqual(qnarr4[i], qnarr1[i]*10)
+ self.assertEqual(qnarr5[i], 10*qnarr1[i])
+ self.assertEqual(qnarr6[i], qnarr2[i]*15)
+ self.assertEqual(qnarr7[i], 15*qnarr2[i])
+
+ def test_qn_array_mul(self):
+ vals1 = [1,2,3,4]
+ qnarr1 = QnArray(vals1,units='m')
+
+ vals2 = [2,3,4,1]
+ qnarr2 = QnArray(vals2, units='s')
+
+ qnarr3 = qnarr1 / qnarr2
+ self.assertEqual(qnarr3.units, Units('m.s-1'))
+ for i in range(len(vals2)):
+ self.assertEqual(qnarr3[i], vals1[i]/vals2[i])
+
+ vals3 = [1,1,1]
+ qnarr3 = QnArray(vals3, units='m')
+ with self.assertRaises(ValueError) as e:
+ qnarr = qnarr1 / qnarr3
+
+ q1 = Quantity(value = 10, units = 'J')
+ qnarr4 = qnarr1 / q1
+ qnarr5 = q1 / qnarr1
+ qnarr6 = qnarr2 / 15
+ qnarr7 = 15 / qnarr2
+ self.assertEqual(qnarr4.units, Units('m.J-1'))
+ self.assertEqual(qnarr5.units, Units('m-1.J'))
+ self.assertEqual(qnarr6.units, Units('s'))
+ self.assertEqual(qnarr7.units, Units('s-1'))
+ for i in range(len(qnarr4)):
+ self.assertEqual(qnarr4[i], qnarr1[i]/10)
+ self.assertEqual(qnarr5[i], 10/qnarr1[i])
+ self.assertEqual(qnarr6[i], qnarr2[i]/15)
+ self.assertEqual(qnarr7[i], 15/qnarr2[i])
+
+ def test_qn_array_conversion(self):
+ a1 = [1,2,3]
+ sd1 = [0.1,0.2,0.3]
+ q1 = QnArray(a1, units = 'm', sd = sd1)
+ q2 = q1.convert_units_to('inch')
+ r_wanted = [a*39.37007874 for a in a1]
+ sd_wanted = [s*39.37007874 for s in sd1]
+
+ self.assertEqual(q2.units, Units('inch'))
+ for i in range(3):
+ self.assertAlmostEqual(q2[i], r_wanted[i])
+ self.assertAlmostEqual(q2.sd[i], sd_wanted[i])
+
+ def test_qn_array_append(self):
+ a1 = [1,2,3]
+ sd1 = [0.1,0.2,0.3]
+ q1 = QnArray(a1, units = 'm', sd = sd1)
+ quant1 = Quantity(value=10,units='m')
+ quant2 = Quantity(value=5,units='m',sd=0.5)
+ quant3 = Quantity(value=2,units='J',sd=0.1)
+
+ result1 = q1.append(quant1)
+ result2 = q1.append(quant2)
+ with self.assertRaises(QnArrayError) as e:
+ result3 = q1.append(quant3)
+ self.assertEqual(len(result1),4)
+ self.assertEqual(result1[-1],10)
+ self.assertEqual(len(result2),4)
+ self.assertEqual(result2[-1],5)
+
+ def test_qn_array_ufunc(self):
+ a1 = [1,2,3]
+ sd1 = [0.1,0.2,0.3]
+ a2 = [0.1,0.2,0.3]
+ sd2 = [0.01,0.02,0.03]
+ q1 = QnArray(a1,units = 'm', sd = sd1)
+ q2 = QnArray(a2,units = 'm', sd = sd2)
+ q3 = QnArray(a2,units = 's', sd = sd2)
+
+ for ufunc in ufunc_dict_alg:
+ if (ufunc is np.multiply) or (ufunc is np.divide):
+ in1 = q1
+ in2 = q3
+ elif (ufunc is np.add) or (ufunc is np.subtract):
+ in1 = q1
+ in2 = q2
+ result = ufunc(in1, in2)
+
+ sd_func = ufunc_dict_alg[ufunc][1]
+ units_func = ufunc_dict_alg[ufunc][2]
+
+ self.assertTrue(result.units, units_func(in1.units, in2.units))
+ for i in range(3):
+ self.assertEqual(result[i], ufunc(np.asarray(in1), np.asarray(in2))[i])
+ self.assertEqual(result.sd[i], sd_func(np.asarray(result), np.asarray(in1), np.asarray(in2), in1.sd, in2.sd)[i])
+
+ print('{} tested!'.format(ufunc))
+
+ a1 = [0.1,0.2,0.3]
+ sd1 = [0.01,0.02,0.03]
+ a2 = [0.4,0.5,0.6]
+ sd2 = [0.04,0.05,0.06]
+ q3 = QnArray(a1, units = '1', sd = sd1)
+ q4 = QnArray(a1, units = 'rad', sd = sd1)
+ q5 = QnArray(a1, units = 'deg', sd = sd1)
+ q6 = QnArray(a2, units = '1', sd = sd2)
+
+ for ufunc in ufunc_dict_one_input:
+ unit_test = ufunc_dict_one_input[ufunc][1]
+ if unit_test is units_check_unitless:
+ with self.assertRaises(Exception) as e:
+ ufunc(q1)
+ with self.assertRaises(Exception) as e:
+ ufunc(q2)
+ with self.assertRaises(Exception) as e:
+ ufunc(q4)
+ with self.assertRaises(Exception) as e:
+ ufunc(q5)
+ in1 = [unit_test(q3)]
+ r = [ufunc(in1[0])]
+ elif unit_test is units_check_unitless_deg_rad:
+ with self.assertRaises(Exception) as e:
+ ufunc(q1)
+ with self.assertRaises(Exception) as e:
+ ufunc(q2)
+ in1 = [unit_test(q3), unit_test(q4), unit_test(q5)]
+ r = [ufunc(in1[0]), ufunc(in1[1]), ufunc(in1[2])]
+ elif unit_test is units_check_any:
+ in1 = [unit_test(q1), unit_test(q2), unit_test(q3), unit_test(q4), unit_test(q5)]
+ r = [ufunc(i) for i in in1]
+ sd_func = ufunc_dict_one_input[ufunc][0] #(result, vals, sd)
+ units_func = ufunc_dict_one_input[ufunc][2] #(units)
+
+ for result, input1 in zip(r,in1):
+ self.assertEqual(result.units, units_func(input1.units))
+ for i in range(3):
+ self.assertAlmostEqual(result[i], ufunc(np.asarray(input1))[i], places = 2)
+ self.assertAlmostEqual(result.sd[i], sd_func(result, input1, input1.sd)[i],places = 1)
+
+ print('{} tested!'.format(ufunc))
+
+ for ufunc in ufunc_dict_two_inputs:
+ unit_test = ufunc_dict_two_inputs[ufunc][1]
+ if unit_test is units_check_unitless:
+ with self.assertRaises(Exception) as e:
+ ufunc(q1,q3)
+ with self.assertRaises(Exception) as e:
+ ufunc(q2,q6)
+ with self.assertRaises(Exception) as e:
+ ufunc(q4,q3)
+ with self.assertRaises(Exception) as e:
+ ufunc(q5,q3)
+ in1 = [unit_test(q3)]
+ in2 = [unit_test(q6)]
+ r = [ufunc(i1,i2) for i1,i2 in zip(in1,in2)]
+ elif unit_test is units_check_any:
+ in1 = [unit_test(q1), unit_test(q2), unit_test(q3), unit_test(q4), unit_test(q5), unit_test(q6)]
+ in2 = [unit_test(q6), unit_test(q5), unit_test(q4), unit_test(q3), unit_test(q2), unit_test(q1)]
+ r = [ufunc(i1,i2) for i1,i2 in zip(in1,in2)]
+ sd_func = ufunc_dict_two_inputs[ufunc][0]
+ units_func = ufunc_dict_two_inputs[ufunc][2]
+
+ for result, input1, input2 in zip(r, in1, in2):
+ self.assertEqual(result.units, units_func(input1.units, input2.units))
+ result_got = result
+ result_wanted = ufunc(np.asarray(input1),np.asarray(input2))
+ sd_got = result.sd
+ sd_wanted = sd_func(np.asarray(result),
+ np.asarray(input1), np.asarray(input2),
+ input1.sd, input2.sd)
+ for i in range(3):
+ self.assertAlmostEqual(result_got[i], result_wanted[i])
+ self.assertAlmostEqual(sd_got[i], sd_wanted[i])
+
+ print('{} tested!'.format(ufunc))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pyqn/tests/test_quantity.py b/pyqn/tests/test_quantity.py
index c0111b4..d1605bc 100644
--- a/pyqn/tests/test_quantity.py
+++ b/pyqn/tests/test_quantity.py
@@ -1,12 +1,29 @@
import unittest
from ..quantity import Quantity, QuantityError
from ..dimensions import Dimensions, d_energy
-from ..units import UnitsError
+from ..units import UnitsError, Units
class QuantityManipulations(unittest.TestCase):
def test_quantity_init(self):
pass
+ def test_quantity_parse(self):
+ q1 = Quantity.parse("a = 10 m/s")
+ q2 = Quantity.parse("lambda = 300.15(10) nm")
+ q3 = Quantity.parse("1e5 J")
+
+ self.assertEqual(q1.name, 'a')
+ self.assertEqual(q1.value, 10)
+ self.assertEqual(q1.units, Units('m.s-1'))
+
+ self.assertEqual(q2.name, 'lambda')
+ self.assertEqual(q2.value, 300.15)
+ self.assertEqual(q2.sd, 0.1)
+ self.assertEqual(q2.units, Units('nm'))
+
+ self.assertEqual(q3.value, 1e5)
+ self.assertEqual(q3.units, Units('J'))
+
def test_quantity_multiplication(self):
q1 = Quantity(value=22.4,units='m/s')
q2 = Quantity(value=2,units='s')
@@ -76,5 +93,16 @@ def test_quantity_conversion(self):
#self.assertAlmostEqual(q2.value,1.2483019242e+21,places=2)
pass
+ def test_quantity_html(self):
+ q1 = Quantity(name = 'E', value = 1.2, units = 'J')
+ q2 = Quantity(value = -5, units = 's', sd = 0.3)
+ q3 = Quantity(value = 30.7, units = 'kg.m2.s-2')
+ q4 = Quantity(value = 22.4,units = 'm/s')
+
+ self.assertEqual(q1.html_str, 'E = 1.2 J')
+ self.assertEqual(q2.html_str, '-5 ± 0.3 s')
+ self.assertEqual(q3.html_str, '30.7 kg m2 s-2')
+ self.assertEqual(q4.html_str, '22.4 m s-1')
+
if __name__ == '__main__':
- unittest.main()
\ No newline at end of file
+ unittest.main()
diff --git a/pyqn/tests/test_si.py b/pyqn/tests/test_si.py
new file mode 100644
index 0000000..9bb682c
--- /dev/null
+++ b/pyqn/tests/test_si.py
@@ -0,0 +1,33 @@
+import unittest
+from ..si import SIPrefix, si_prefixes
+
+class SICheck(unittest.TestCase):
+ def test_siprefix_init(self):
+ s = SIPrefix('PREFIX','NAME',2.5)
+ self.assertEqual(s.prefix, 'PREFIX')
+ self.assertEqual(s.name, 'NAME')
+ self.assertEqual(s.power, 2.5)
+ self.assertEqual(s.fac, 10**2.5)
+
+ def test_siprefixes(self):
+ self.assertEqual(si_prefixes['y'].prefix, 'y')
+ self.assertEqual(si_prefixes['y'].name, 'yocto')
+ self.assertEqual(si_prefixes['y'].power, -24)
+ self.assertEqual(si_prefixes['y'].fac, 1e-24)
+
+ self.assertEqual(si_prefixes['μ'].prefix, 'μ')
+ self.assertEqual(si_prefixes['μ'].name, 'micro')
+ self.assertEqual(si_prefixes['μ'].power, -6)
+ self.assertEqual(si_prefixes['μ'].fac, 1e-6)
+
+ self.assertEqual(si_prefixes['M'].prefix, 'M')
+ self.assertEqual(si_prefixes['M'].name, 'mega')
+ self.assertEqual(si_prefixes['M'].power, 6)
+ self.assertEqual(si_prefixes['M'].fac, 1e6)
+
+ def test_siprefix_eq(self):
+ self.assertTrue(SIPrefix('PREFIX','NAME',2.0) == SIPrefix('PREFIX','',2))
+ self.assertFalse(SIPrefix('P','NAME',3) == SIPrefix('P','NAME',-3))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pyqn/tests/test_symbol.py b/pyqn/tests/test_symbol.py
new file mode 100644
index 0000000..1681a2b
--- /dev/null
+++ b/pyqn/tests/test_symbol.py
@@ -0,0 +1,44 @@
+import unittest
+from ..symbol import Symbol
+
+class SymbolCheck(unittest.TestCase):
+ def test_symbol_init(self):
+ s = Symbol()
+ self.assertTrue(s.name == None)
+ self.assertTrue(s.latex == None)
+ self.assertTrue(s.html == None)
+ self.assertTrue(s.definition == None)
+
+ s = Symbol(name = 'NAME')
+ self.assertTrue(s.name == 'NAME')
+ self.assertTrue(s.latex == 'NAME')
+ self.assertTrue(s.html == 'NAME')
+ self.assertTrue(s.definition == None)
+
+ s = Symbol(name = 'NAME', html = 'HTML')
+ self.assertTrue(s.name == 'NAME')
+ self.assertTrue(s.html == 'HTML')
+ self.assertTrue(s.latex == 'NAME')
+ self.assertTrue(s.definition == None)
+
+ s = Symbol(definition = 'DEFINITION')
+ self.assertTrue(s.name == None)
+ self.assertTrue(s.html == None)
+ self.assertTrue(s.latex == None)
+ self.assertTrue(s.definition == 'DEFINITION')
+
+ s = Symbol(name = 'NAME', latex = 'LATEX')
+ self.assertTrue(s.name == 'NAME')
+ self.assertTrue(s.html == 'NAME')
+ self.assertTrue(s.latex == 'LATEX')
+ self.assertTrue(s.definition == None)
+
+ def test_symbol_str(self):
+ self.assertTrue(str(Symbol()), '[undefined]')
+ self.assertTrue(str(Symbol(name='NAME')),'NAME')
+ self.assertTrue(str(Symbol(latex='LATEX')),'[undefined]')
+ self.assertTrue(str(Symbol(name='NAME',latex='LATEX')),'NAME')
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/pyqn/ufunc_dictionaries.py b/pyqn/ufunc_dictionaries.py
new file mode 100644
index 0000000..7dfb611
--- /dev/null
+++ b/pyqn/ufunc_dictionaries.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# ufunc_dictionaries.py
+# A set of predefined error propagation functions and functions which
+# return the resultant units given numpy ufuncs operating on QnArray
+# objects.
+#
+# Copyright (C) 2012-2017 Christian Hill
+# Department of Physics and Astronomy, University College London
+# christian.hill@ucl.ac.uk
+#
+# This file is part of PyQn
+#
+# PyQn is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# PyQn is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with PyQn. If not, see
+
+from .units import Units, UnitsError
+import numpy as np
+
+def sd_add_sub(result, vals1, vals2, sd1, sd2):
+ return np.sqrt(sd1**2+sd2**2)
+def sd_mul_div(result, vals1, vals2, sd1, sd2):
+ return result*np.sqrt((sd1/vals1)**2+(sd2/vals2)**2)
+
+def sd_logaddexp(result, vals1, vals2, sd1, sd2):
+ return np.sqrt(np.exp(vals1)**2*sd1**2+np.exp(vals2)**2*sd2**2)/(np.exp(vals1)+np.exp(vals2))
+def sd_logaddexp2(result, vals1, vals2, sd1, sd2):
+ return np.sqrt(np.exp(vals1)**2*sd1**2+np.exp(vals2)**2*sd2**2)/((np.log(2))*(np.exp(vals1)+np.exp(vals2)))
+def sd_power(result, vals1, vals2, sd1, sd2):
+ return np.sqrt(sd1**2*(vals2*vals1**(vals2-1))**2 + sd2**2*(result*np.log(vals1))**2)
+
+def sd_exp(result, vals, sd):
+ return result * sd
+def sd_exp2(result, vals, sd):
+ return result * sd * np.log(2)
+def sd_expm1(result, vals, sd):
+ return sd*np.exp(vals)
+def sd_sin(result, vals, sd):
+ return np.cos(vals) * sd
+def sd_cos(result, vals, sd):
+ return np.sin(vals) * sd
+def sd_tan(result, vals, sd):
+ return np.cos(np.asarray(vals))**(-2) * np.asarray(sd)
+def sd_arcsin_arccos(result, vals, sd):
+ return np.asarray(sd)/np.sqrt(1-np.asarray(vals)**2)
+def sd_arctan(result, vals, sd):
+ return np.asarray(sd)/(1.0+np.asarray(vals)**2)
+def sd_sinh(result, vals, sd):
+ return sd*np.cosh(vals)
+def sd_cosh(result, vals, sd):
+ return sd*np.sinh(vals)
+def sd_tanh(result, vals, sd):
+ return np.asarray(sd)*np.cosh(np.asarray(vals))**(-2)
+def sd_arcsinh(result, vals, sd):
+ return np.asarray(sd)/np.sqrt(1+np.asarray(vals)**2)
+def sd_arccosh(result, vals, sd):
+ return sd/(np.sqrt(vals-1)*np.sqrt(vals+1))
+def sd_arctanh(result, vals, sd):
+ return np.asarray(sd)/(1.0-np.asarray(vals)**2)
+def sd_log(result, vals, sd):
+ return sd/vals
+def sd_log2(result, vals, sd):
+ return sd/(vals*np.log(2))
+def sd_log10(result, vals, sd):
+ return sd/(vals*np.log(10))
+def sd_log1p(result, vals, sd):
+ return sd/(1+vals)
+def sd_sqrt(result, vals, sd):
+ return sd/(2*result)
+def sd_square(result, vals, sd):
+ return sd*2*vals
+def sd_cbrt(result, vals, sd):
+ return np.asarray(sd)/(3*np.asarray(vals)**(2/3))
+def sd_reciprocal(result, vals, sd):
+ return sd/vals**2
+def sd_nochange(result, vals, sd):
+ return sd
+
+def units_check_unitless(input_arr):
+ if input_arr.units.has_units() is True:
+ raise UnitsError('qnArray must be unitless')
+ else:
+ return input_arr
+
+def units_check_unitless_deg_rad(input_arr):
+ if input_arr.units == Units('deg'):
+ return input_arr.convert_units_to('rad')
+ elif (input_add.units == Units('rad')) or (input_arr.units.has_units() is False):
+ return input_arr
+ else:
+ raise UnitsError('qnArray must have units: deg, rad, unitless')
+
+def units_check_any(input_arr):
+ return input_arr
+
+def units_add_sub(u1, u2):
+ if u1 == u2:
+ return u1
+ else:
+ raise UnitsError('units must match')
+
+def units_mul(u1,u2):
+ return u1*u2
+def units_div(u1,u2):
+ return u1/u2
+def units_sqrt(u):
+ return u**(0.5)
+def units_square(u):
+ return u**2
+def units_cbrt(u):
+ return u**(1/3)
+def units_reciprocal(u):
+ return u**(-1)
+def units_unitless(u):
+ return Units('1')
+def units_self(u):
+ return u
+def units_unitless2(u1,u2):
+ return Units('1')
+
+ufunc_dict_alg = { np.add: ('__add__', sd_add_sub, units_add_sub, '__radd__'),
+ np.subtract: ('__sub__', sd_add_sub, units_add_sub, '__rsub__'),
+ np.multiply: ('__mul__', sd_mul_div, units_mul, '__rmul__'),
+ np.divide: ('__truediv__', sd_mul_div, units_div, '__rtruediv__')}
+
+ufunc_dict_one_input = {np.exp: (sd_exp, units_check_unitless, units_unitless),
+ np.exp2: (sd_exp2, units_check_unitless, units_unitless),
+ np.expm1: (sd_expm1, units_check_unitless, units_unitless),
+ np.log: (sd_log, units_check_unitless, units_unitless),
+ np.log2: (sd_log2, units_check_unitless, units_unitless),
+ np.log10: (sd_log10, units_check_unitless, units_unitless),
+ np.log1p: (sd_log1p, units_check_unitless, units_unitless),
+ np.sin: (sd_sin, units_check_unitless_deg_rad, units_unitless),
+ np.cos: (sd_cos, units_check_unitless_deg_rad, units_unitless),
+ np.tan: (sd_tan, units_check_unitless_deg_rad, units_unitless),
+ np.arcsin: (sd_arcsin_arccos, units_check_unitless, units_unitless),
+ np.arccos: (sd_arcsin_arccos, units_check_unitless, units_unitless),
+ np.arctan: (sd_arctan, units_check_unitless, units_unitless),
+ np.sinh: (sd_sinh, units_check_unitless, units_unitless),
+ np.cosh: (sd_cosh, units_check_unitless, units_unitless),
+ np.tanh: (sd_tanh, units_check_unitless, units_unitless),
+ #np.arcsinh: (sd_arcsinh, units_check_unitless, units_unitless),
+ #np.arccosh: (sd_arccosh, units_check_unitless, units_unitless),
+ #np.arctanh: (sd_arctanh, units_check_unitless, units_unitless),
+ np.negative: (sd_nochange, units_check_any, units_self),
+ np.positive: (sd_nochange, units_check_any, units_self),
+ np.absolute: (sd_nochange, units_check_any, units_self),
+ np.fabs: (sd_nochange, units_check_any, units_self),
+ np.sqrt: (sd_sqrt, units_check_any, units_sqrt),
+ np.square: (sd_square, units_check_any, units_square),
+ np.cbrt: (sd_cbrt, units_check_any, units_cbrt),
+ np.reciprocal: (sd_reciprocal, units_check_any, units_reciprocal),
+ }
+
+ufunc_dict_two_inputs = {np.logaddexp: (sd_logaddexp, units_check_unitless, units_unitless2),
+ np.logaddexp2: (sd_logaddexp2, units_check_unitless, units_unitless2),
+ np.power: (sd_power, units_check_unitless, units_unitless2),
+ np.true_divide: (sd_mul_div, units_check_any, units_div),
+ np.floor_divide: (sd_mul_div, units_check_any, units_div)
+ }
+
diff --git a/pyqn/units.py b/pyqn/units.py
index 6e8aada..32a00a3 100644
--- a/pyqn/units.py
+++ b/pyqn/units.py
@@ -280,7 +280,7 @@ def mol_conversion(self, other):
fac = fac/(NA**(from_dims.dims[4]-to_dims.dims[4]))
else:
fac = fac*(NA**(to_dims.dims[4]-from_dims.dims[4]))
- return fac/other.to_si()
+ return (fac/other.to_si())**(-1)
def spec_conversion(self, other):
d_wavenumber = d_length**-1