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