def test_retrieve_data(self): ptree = PropertyTree() ptree.put_string('type', 'SeriesRC') ptree.put_double('series_resistance', 100e-3) ptree.put_double('capacitance', 2.5) device = EnergyStorageDevice(ptree) ptree = PropertyTree() ptree.put_string('type', 'ElectrochemicalImpedanceSpectroscopy') ptree.put_double('frequency_upper_limit', 1e+2) ptree.put_double('frequency_lower_limit', 1e-1) ptree.put_int('steps_per_decade', 1) ptree.put_int('steps_per_cycle', 64) ptree.put_int('cycles', 2) ptree.put_int('ignore_cycles', 1) ptree.put_double('dc_voltage', 0) ptree.put_string('harmonics', '3') ptree.put_string('amplitudes', '5e-3') ptree.put_string('phases', '0') eis = Experiment(ptree) with File('trash.hdf5', 'w') as fout: eis.run(device, fout) spectrum_data = eis._data with File('trash.hdf5', 'r') as fin: retrieved_data = retrieve_impedance_spectrum(fin) print(spectrum_data['impedance'] - retrieved_data['impedance']) print(retrieved_data) self.assertEqual(linalg.norm(spectrum_data['frequency'] - retrieved_data['frequency'], inf), 0.0) # not sure why we don't get equality for the impedance self.assertLess(linalg.norm(spectrum_data['impedance'] - retrieved_data['impedance'], inf), 1e-10)
def test_evolve_constant_current(self): ptree = PropertyTree() ptree.put_string('mode', 'constant_current') ptree.put_double('current', 100e-3) evolve_one_time_step = TimeEvolution.factory(ptree) evolve_one_time_step(device, 0.1) self.assertEqual(device.get_current(), 100e-3)
def test_evolve_constant_load(self): ptree = PropertyTree() ptree.put_string('mode', 'constant_load') ptree.put_double('load', 120) evolve_one_time_step = TimeEvolution.factory(ptree) evolve_one_time_step(device, 0.1) self.assertAlmostEqual(device.get_voltage()/device.get_current(), -120)
def test_abstract_class(self): # Declare a concrete Experiment class DummyExperiment(Experiment): def __new__(cls, *args, **kwargs): return object.__new__(DummyExperiment) def __init__(self, ptree): Experiment.__init__(self) # Do not forget to register it to the builders dictionary. Observable._builders['Dummy'] = DummyExperiment # Construct directly via DummyExperiment with a PropertyTree as a # positional arguemnt ptree = PropertyTree() dummy = DummyExperiment(ptree) # ... or directly via Experiment by specifying the ``type`` of # Experiment. ptree.put_string('type', 'Dummy') dummy = Experiment(ptree) # The method run() must be overloaded. self.assertRaises(RuntimeError, dummy.run, None) # Override the method run(). def run(self, device): pass DummyExperiment.run = run # Now calling it without raising an error. dummy.run(None)
def test_evolve_constant_voltage(self): ptree = PropertyTree() ptree.put_string('mode', 'constant_voltage') ptree.put_double('voltage', 2.1) evolve_one_time_step = TimeEvolution.factory(ptree) evolve_one_time_step(device, 0.1) self.assertEqual(device.get_voltage(), 2.1)
def testParallelRC(self): # make parallel RC equivalent circuit device_database = PropertyTree() device_database.put_string('type', 'ParallelRC') device_database.put_double('series_resistance', R) device_database.put_double('parallel_resistance', R_L) device_database.put_double('capacitance', C) device = EnergyStorageDevice(device_database, MPI.COMM_WORLD) # setup experiment and measure eis_database = setup_expertiment() spectrum_data = measure_impedance_spectrum(device, eis_database) # extract data f = spectrum_data['frequency'] Z_computed = spectrum_data['impedance'] M_computed = 20*log10(absolute(Z_computed)) P_computed = angle(Z_computed)*180/pi # compute the exact solution Z_exact = R+R_L/(1+1j*R_L*C*2*pi*f) M_exact = 20*log10(absolute(Z_exact)) P_exact = angle(Z_exact)*180/pi # ensure the error is small max_phase_error_in_degree = linalg.norm(P_computed-P_exact, inf) max_magniture_error_in_decibel = linalg.norm(M_computed-M_exact, inf) print('max_phase_error_in_degree = {0}'.format(max_phase_error_in_degree)) print('max_magniture_error_in_decibel = {0}'.format(max_magniture_error_in_decibel)) self.assertLessEqual(max_phase_error_in_degree, 1) self.assertLessEqual(max_magniture_error_in_decibel, 0.2)
def test_builders(self): for AbstractClass in [Observer, Observable]: # AbstractClass takes a PropertyTree as argument. self.assertRaises(TypeError, AbstractClass) # The PropertyTree must specify what concrete class derived from # AbstractClass to instantiate. ptree = PropertyTree() self.assertRaises(RuntimeError, AbstractClass, ptree) # The derived concrete class must be registerd in the dictionary # that holds the builders. ptree.put_string('type', 'Invalid') self.assertRaises(KeyError, AbstractClass, ptree) # Now declare a concrete class. class ConcreteClass(AbstractClass): def __new__(cls, *args, **kwargs): return object.__new__(ConcreteClass) def __init__(*args, **kwargs): pass # Here is how to register a derived concrete class to the base abstract class. AbstractClass._builders['ConcreteClass'] = ConcreteClass # Now instantiation works. ptree.put_string('type', 'ConcreteClass') o = AbstractClass(ptree) # Also can build directly from derived class. o = ConcreteClass() # Remove from the dictionary. del AbstractClass._builders['ConcreteClass'] self.assertRaises(KeyError, AbstractClass, ptree)
def test_raise_exceptions(self): ptree = PropertyTree() # property tree will throw if the specified path does not exist self.assertRaises(KeyError, ptree.get_int, 'path.does.not.exist') # or if the translation fails ptree.put_string('some.path.to.a.string', 'not a double') self.assertRaises(TypeError, ptree.get_double, 'some.path.to.a.string')
def testRetrieveData(self): try: from h5py import File except ImportError: print('module h5py not found') return device_database = PropertyTree() device_database.put_string('type', 'SeriesRC') device_database.put_double('series_resistance', R) device_database.put_double('capacitance', C) device = EnergyStorageDevice(device_database, MPI.COMM_WORLD) eis_database = setup_expertiment() eis_database.put_int('steps_per_decade', 1) eis_database.put_int('steps_per_cycle', 64) eis_database.put_int('cycles', 2) eis_database.put_int('ignore_cycles', 1) fout = File('trash.hdf5', 'w') spectrum_data = measure_impedance_spectrum(device, eis_database, fout) fout.close() fin = File('trash.hdf5', 'r') retrieved_data = retrieve_impedance_spectrum(fin) fin.close() print(spectrum_data['impedance']-retrieved_data['impedance']) print(retrieved_data) self.assertEqual(linalg.norm(spectrum_data['frequency'] - retrieved_data['frequency'], inf), 0.0) # not sure why we don't get equality for the impedance self.assertLess(linalg.norm(spectrum_data['impedance'] - retrieved_data['impedance'], inf), 1e-10)
def test_get_with_default_value(self): ptree = PropertyTree() # double self.assertEqual( ptree.get_double_with_default_value('missing_double', 3.14), 3.14) ptree.put_double('present_double', 1.41) self.assertEqual( ptree.get_double_with_default_value('present_double', 3.14), 1.41) # string self.assertEqual( ptree.get_string_with_default_value('missing_string', 'missing'), 'missing') ptree.put_string('present_string', 'present') self.assertEqual( ptree.get_string_with_default_value('present_string', 'missing'), 'present') # int self.assertEqual(ptree.get_int_with_default_value('missing_int', 255), 255) ptree.put_int('present_int', 0) self.assertEqual(ptree.get_int_with_default_value('present_int', 255), 0) # bool self.assertEqual( ptree.get_bool_with_default_value('missing_bool', True), True) ptree.put_bool('present_bool', False) self.assertEqual( ptree.get_bool_with_default_value('present_bool', True), False)
def test_hold(self): ptree = PropertyTree() ptree.put_string('mode', 'hold') evolve_one_time_step = TimeEvolution.factory(ptree) device.evolve_one_time_step_constant_voltage(0.1, 1.4) evolve_one_time_step(device, 0.1) self.assertEqual(device.get_voltage(), 1.4)
def test_evolve_constant_load(self): ptree = PropertyTree() ptree.put_string('mode', 'constant_load') ptree.put_double('load', 120) evolve_one_time_step = TimeEvolution.factory(ptree) evolve_one_time_step(device, 0.1) self.assertAlmostEqual(device.get_voltage() / device.get_current(), -120)
def test_time_limit(self): ptree = PropertyTree() ptree.put_string('end_criterion', 'time') ptree.put_double('duration', 15) time_limit = EndCriterion.factory(ptree) time_limit.reset(0.0, device) self.assertFalse(time_limit.check(2.0, device)) self.assertTrue(time_limit.check(15.0, device)) self.assertTrue(time_limit.check(60.0, device))
def test_pickle_support(self): import pickle src = PropertyTree() src.put_double('pi', 3.14) src.put_string('greet', 'bonjour') p = pickle.dumps(src) dst = pickle.loads(p) self.assertEqual(dst.get_double('pi'), 3.14) self.assertEqual(dst.get_string('greet'), 'bonjour')
def test_property_tree(self): # ptree as container to store int, double, string, and bool ptree = PropertyTree() ptree.put_int('dim', 3) self.assertEqual(ptree.get_int('dim'), 3) ptree.put_double('path.to.pi', 3.14) self.assertEqual(ptree.get_double('path.to.pi'), 3.14) ptree.put_string('good.news', 'it works') self.assertEqual(ptree.get_string('good.news'), 'it works') ptree.put_bool('is.that.a.good.idea', False) self.assertEqual(ptree.get_bool('is.that.a.good.idea'), False)
def test_verification_with_equivalent_circuit(self): R = 50e-3 # ohm R_L = 500 # ohm C = 3 # farad U_i = 2.7 # volt U_f = 1.2 # volt # setup experiment ptree = PropertyTree() ptree.put_double('discharge_power_lower_limit', 1e-2) ptree.put_double('discharge_power_upper_limit', 1e+2) ptree.put_int('steps_per_decade', 5) ptree.put_double('initial_voltage', U_i) ptree.put_double('final_voltage', U_f) ptree.put_double('time_step', 15) ptree.put_int('min_steps_per_discharge', 2000) ptree.put_int('max_steps_per_discharge', 3000) ragone = RagoneAnalysis(ptree) # setup equivalent circuit database device_database = PropertyTree() device_database.put_double('series_resistance', R) device_database.put_double('parallel_resistance', R_L) device_database.put_double('capacitance', C) # analytical solutions E = {} def E_SeriesRC(P): U_0 = U_i / 2 + sqrt(U_i**2 / 4 - R * P) return C / 2 * (-R * P * log(U_0**2 / U_f**2) + U_0**2 - U_f**2) E['SeriesRC'] = E_SeriesRC def E_ParallelRC(P): U_0 = U_i / 2 + sqrt(U_i**2 / 4 - R * P) tmp = (U_f**2 / R_L + P * (1 + R / R_L)) / \ (U_0**2 / R_L + P * (1 + R / R_L)) return C / 2 * (-R_L * P * log(tmp) - R * R_L / (R + R_L) * P * log(tmp * U_0**2 / U_f**2)) E['ParallelRC'] = E_ParallelRC for device_type in ['SeriesRC', 'ParallelRC']: # create a device device_database.put_string('type', device_type) device = EnergyStorageDevice(device_database) # setup experiment and measure ragone.reset() ragone.run(device) P = ragone._data['power'] E_computed = ragone._data['energy'] # compute the exact solution E_exact = E[device_type](P) # ensure the error is small max_percent_error = 100 * linalg.norm( (E_computed - E_exact) / E_computed, inf) self.assertLess(max_percent_error, 0.1)
def test_verification_with_equivalent_circuit(self): R = 50e-3 # ohm R_L = 500 # ohm C = 3 # farad # setup EIS experiment ptree = PropertyTree() ptree.put_string('type', 'ElectrochemicalImpedanceSpectroscopy') ptree.put_double('frequency_upper_limit', 1e+4) ptree.put_double('frequency_lower_limit', 1e-6) ptree.put_int('steps_per_decade', 3) ptree.put_int('steps_per_cycle', 1024) ptree.put_int('cycles', 2) ptree.put_int('ignore_cycles', 1) ptree.put_double('dc_voltage', 0) ptree.put_string('harmonics', '3') ptree.put_string('amplitudes', '5e-3') ptree.put_string('phases', '0') eis = Experiment(ptree) # setup equivalent circuit database device_database = PropertyTree() device_database.put_double('series_resistance', R) device_database.put_double('parallel_resistance', R_L) device_database.put_double('capacitance', C) # analytical solutions Z = {} Z['SeriesRC'] = lambda f: R + 1 / (1j * C * 2 * pi * f) Z['ParallelRC'] = lambda f: R + R_L / (1 + 1j * R_L * C * 2 * pi * f) for device_type in ['SeriesRC', 'ParallelRC']: # create a device device_database.put_string('type', device_type) device = EnergyStorageDevice(device_database) # setup experiment and measure eis.reset() eis.run(device) f = eis._data['frequency'] Z_computed = eis._data['impedance'] # compute the exact solution Z_exact = Z[device_type](f) # ensure the error is small max_phase_error_in_degree = linalg.norm( angle(Z_computed) * 180 / pi - angle(Z_exact) * 180 / pi, inf) max_magniture_error_in_decibel = linalg.norm( 20 * log10(absolute(Z_exact)) - 20 * log10(absolute(Z_computed)), inf) print(device_type) print( '-- max_phase_error_in_degree = {0}'.format(max_phase_error_in_degree)) print( '-- max_magniture_error_in_decibel = {0}'.format(max_magniture_error_in_decibel)) self.assertLessEqual(max_phase_error_in_degree, 1) self.assertLessEqual(max_magniture_error_in_decibel, 0.2)
def test_consistency_pycap_simulation(self): # # weak run test; simply ensures that Dualfoil object # can be run with pycap.Charge # df1 = Dualfoil(path=path) # will use pycap df2 = Dualfoil(path=path) # manual runs im = df_manip.InputManager(path=path) # testing a charge-to-hold-const-voltage # manual # use InputManager to set the input file c = -12.0 # constant current im.add_new_leg(c, 5.0, 1) df1.run() df1.outbot.update_output() v = 4.54 # constant voltage im.add_new_leg(v, 5.0, 0) df1.run() df1.outbot.update_output() # pycap simulation # build a ptree input ptree = PropertyTree() ptree.put_double('time_step', 300.0) # 5 minutes ptree.put_string('charge_mode', 'constant_current') ptree.put_double('charge_current', 12.0) ptree.put_string('charge_stop_at_1', 'voltage_greater_than') ptree.put_double('charge_voltage_limit', 4.54) ptree.put_bool('charge_voltage_finish', True) # hold end voltage after either 5 minutes have passed # OR current falls under 1 ampere ptree.put_double('charge_voltage_finish_max_time', 300.0) ptree.put_double('charge_voltage_finish_current_limit', 1.0) const_current_const_voltage = Charge(ptree) const_current_const_voltage.run(df2) # check the output lists of both devices o1 = df1.outbot.output o2 = df2.outbot.output self.assertEqual(len(o1['time']), len(o2['time'])) for i in range(len(o1['time'])): self.assertAlmostEqual(o1['time'][i], o2['time'][i]) # BELOW: relaxed delta for voltage # REASON: dualfoil cuts off its voltages at 5 # decimal places, meaning that this end-digit # is subject to roundoff errors error = 1e-5 self.assertAlmostEqual(o1['voltage'][i], o2['voltage'][i], delta=error) self.assertAlmostEqual(o1['current'][i], o2['current'][i])
def test_get_children(self): ptree = PropertyTree() # put child child = PropertyTree() child.put_double('prune', 6.10) ptree.put_child('a.g', child) self.assertEqual(ptree.get_double('a.g.prune'), 6.10) # get child ptree.put_string('child.name', 'clement') ptree.put_int('child.age', -2) child = ptree.get_child('child') self.assertEqual(child.get_string('name'), 'clement') self.assertEqual(child.get_int('age'), -2)
def test_charge_constant_current(self): ptree = PropertyTree() ptree.put_string('charge_mode', 'constant_current') ptree.put_double('charge_current', 10e-3) ptree.put_string('charge_stop_at_1', 'voltage_greater_than') ptree.put_double('charge_voltage_limit', 1.4) ptree.put_double('time_step', 0.2) charge = Charge(ptree) data = initialize_data() charge.run(device, data) self.assertAlmostEqual(data['current'][0], 10e-3) self.assertAlmostEqual(data['current'][-1], 10e-3) self.assertGreaterEqual(data['voltage'][-1], 1.4) self.assertAlmostEqual(data['time'][1] - data['time'][0], 0.2)
def test_force_discharge(self): ptree = PropertyTree() ptree.put_string('mode', 'constant_voltage') ptree.put_double('voltage', 0.0) ptree.put_string('end_criterion', 'current_less_than') ptree.put_double('current_limit', 1e-5) ptree.put_double('time_step', 1.0) stage = Stage(ptree) data = initialize_data() steps = stage.run(device, data) self.assertGreaterEqual(steps, 1) self.assertEqual(steps, len(data['time'])) self.assertAlmostEqual(data['voltage'][-1], 0.0) self.assertLessEqual(data['current'][-1], 1e-5)
def test_constant_current_charge_for_given_time(self): ptree = PropertyTree() ptree.put_string('mode', 'constant_current') ptree.put_double('current', 5e-3) ptree.put_string('end_criterion', 'time') ptree.put_double('duration', 15.0) ptree.put_double('time_step', 0.1) stage = Stage(ptree) data = initialize_data() steps = stage.run(device, data) self.assertEqual(steps, 150) self.assertEqual(steps, len(data['time'])) self.assertAlmostEqual(data['time'][-1], 15.0) self.assertAlmostEqual(data['current'][-1], 5e-3)
def test_retrieve_data(self): ptree = PropertyTree() ptree.put_string('type', 'SeriesRC') ptree.put_double('series_resistance', 50e-3) ptree.put_double('capacitance', 3) device = EnergyStorageDevice(ptree) ptree = PropertyTree() ptree.put_string('type', 'RagoneAnalysis') ptree.put_double('discharge_power_lower_limit', 1e-1) ptree.put_double('discharge_power_upper_limit', 1e+1) ptree.put_int('steps_per_decade', 1) ptree.put_double('initial_voltage', 2.1) ptree.put_double('final_voltage', 0.7) ptree.put_double('time_step', 1.5) ptree.put_int('min_steps_per_discharge', 20) ptree.put_int('max_steps_per_discharge', 30) ragone = Experiment(ptree) with File('trash.hdf5', 'w') as fout: ragone.run(device, fout) performance_data = ragone._data fin = File('trash.hdf5', 'r') retrieved_data = retrieve_performance_data(fin) fin.close() # a few digits are lost when power is converted to string self.assertLess( linalg.norm(performance_data['power'] - retrieved_data['power'], inf), 1e-12) self.assertEqual( linalg.norm(performance_data['energy'] - retrieved_data['energy'], inf), 0.0) # TODO: probably want to move this into its own test ragoneplot = RagonePlot("ragone.png") ragoneplot.update(ragone) # check reset reinitialize the time step and empty the data ragone.reset() self.assertEqual(ragone._ptree.get_double('time_step'), 1.5) self.assertFalse(ragone._data['power']) self.assertFalse(ragone._data['energy'])
def test_setup_frequency_range(self): ptree = PropertyTree() ptree.put_string('type', 'ElectrochemicalImpedanceSpectroscopy') # specify the upper and lower bounds of the range # the number of points per decades controls the spacing on the log # scale ptree.put_double('frequency_upper_limit', 1e+2) ptree.put_double('frequency_lower_limit', 1e-1) ptree.put_int('steps_per_decade', 3) eis = Experiment(ptree) print(eis._frequencies) f = eis._frequencies self.assertEqual(len(f), 10) self.assertAlmostEqual(f[0], 1e+2) self.assertAlmostEqual(f[3], 1e+1) self.assertAlmostEqual(f[9], 1e-1) # or directly specify the frequencies frequencies = [3, 2e3, 0.1] eis = Experiment(ptree, frequencies) self.assertTrue(all(equal(frequencies, eis._frequencies)))
def test_retrieve_data(self): ptree = PropertyTree() ptree.put_string('type', 'SeriesRC') ptree.put_double('series_resistance', 50e-3) ptree.put_double('capacitance', 3) device = EnergyStorageDevice(ptree) ptree = PropertyTree() ptree.put_string('type', 'RagoneAnalysis') ptree.put_double('discharge_power_lower_limit', 1e-1) ptree.put_double('discharge_power_upper_limit', 1e+1) ptree.put_int('steps_per_decade', 1) ptree.put_double('initial_voltage', 2.1) ptree.put_double('final_voltage', 0.7) ptree.put_double('time_step', 1.5) ptree.put_int('min_steps_per_discharge', 20) ptree.put_int('max_steps_per_discharge', 30) ragone = Experiment(ptree) with File('trash.hdf5', 'w') as fout: ragone.run(device, fout) performance_data = ragone._data fin = File('trash.hdf5', 'r') retrieved_data = retrieve_performance_data(fin) fin.close() # a few digits are lost when power is converted to string self.assertLess(linalg.norm(performance_data['power'] - retrieved_data['power'], inf), 1e-12) self.assertEqual(linalg.norm(performance_data['energy'] - retrieved_data['energy'], inf), 0.0) # TODO: probably want to move this into its own test ragoneplot = RagonePlot("ragone.png") ragoneplot.update(ragone) # check reset reinitialize the time step and empty the data ragone.reset() self.assertEqual(ragone._ptree.get_double('time_step'), 1.5) self.assertFalse(ragone._data['power']) self.assertFalse(ragone._data['energy'])
def test_current_limit(self): # lower ptree = PropertyTree() ptree.put_string('end_criterion', 'current_less_than') ptree.put_double('current_limit', -5e-3) self.assertRaises(RuntimeError, EndCriterion.factory, ptree) ptree.put_double('current_limit', 0.0) self.assertRaises(RuntimeError, EndCriterion.factory, ptree) ptree.put_double('current_limit', 5e-3) current_limit = EndCriterion.factory(ptree) device.evolve_one_time_step_constant_current(5.0, 0.0) self.assertTrue(current_limit.check(NaN, device)) device.evolve_one_time_step_constant_current(5.0, 0.002) self.assertTrue(current_limit.check(NaN, device)) device.evolve_one_time_step_constant_current(5.0, -0.001) self.assertTrue(current_limit.check(180.0, device)) device.evolve_one_time_step_constant_current(5.0, 0.005) self.assertTrue(current_limit.check(180.0, device)) device.evolve_one_time_step_constant_current(5.0, 0.007) self.assertFalse(current_limit.check(180.0, device)) device.evolve_one_time_step_constant_current(5.0, -15e3) self.assertFalse(current_limit.check(180.0, device)) # upper ptree.put_string('end_criterion', 'current_greater_than') ptree.put_double('current_limit', -5e-3) self.assertRaises(RuntimeError, EndCriterion.factory, ptree) ptree.put_double('current_limit', 0.0) self.assertRaises(RuntimeError, EndCriterion.factory, ptree) ptree.put_double('current_limit', 5e-3) current_limit = EndCriterion.factory(ptree) device.evolve_one_time_step_constant_current(5.0, -1e-3) self.assertFalse(current_limit.check(NaN, device)) device.evolve_one_time_step_constant_current(5.0, 0.002) self.assertFalse(current_limit.check(NaN, device)) device.evolve_one_time_step_constant_current(5.0, 0.005) self.assertTrue(current_limit.check(NaN, device)) device.evolve_one_time_step_constant_current(5.0, -0.2) self.assertTrue(current_limit.check(NaN, device)) device.evolve_one_time_step_constant_current(5.0, 3.0) self.assertTrue(current_limit.check(NaN, device))
def testFourierAnalysis(self): ptree = PropertyTree() ptree.put_int('steps_per_cycle', 3) ptree.put_int('cycles', 1) ptree.put_int('ignore_cycles', 0) ptree.put_string('harmonics', '1') # uninitialized data data = {} self.assertRaises(KeyError, fourier_analysis, data, ptree) # empty data data = initialize_data() self.assertRaises(IndexError, fourier_analysis, data, ptree) # bad data data['time'] = array([1, 2, 3], dtype=float) data['current'] = array([4, 5, 6], dtype=float) data['voltage'] = array([7, 8], dtype=float) self.assertRaises(AssertionError, fourier_analysis, data, ptree) # poor data (size not a power of 2) data['voltage'] = array([7, 8, 9], dtype=float) with catch_warnings(): simplefilter("error") self.assertRaises(RuntimeWarning, fourier_analysis, data, ptree) # data unchanged after analyze dummy = array([1, 2, 3, 4, 5, 6, 7, 8], dtype=float) data['time'] = dummy data['current'] = dummy data['voltage'] = dummy # ptree needs to be updated self.assertRaises(AssertionError, fourier_analysis, data, ptree) ptree.put_int('steps_per_cycle', 4) ptree.put_int('cycles', 2) ptree.put_int('ignore_cycles', 0) fourier_analysis(data, ptree) try: testing.assert_array_equal(data['time'], dummy) testing.assert_array_equal(data['current'], dummy) testing.assert_array_equal(data['voltage'], dummy) except AssertionError: self.fail('data should not be changed by the fourier analyzis')
def run_discharge(device, ptree): data = initialize_data() # (re)charge the device initial_voltage = ptree.get_double('initial_voltage') charge_database = PropertyTree() charge_database.put_string('charge_mode', 'constant_current') charge_database.put_double('charge_current', 10.0) charge_database.put_string('charge_stop_at_1', 'voltage_greater_than') charge_database.put_double('charge_voltage_limit', initial_voltage) charge_database.put_bool('charge_voltage_finish', True) charge_database.put_double('charge_voltage_finish_current_limit', 1e-2) charge_database.put_double('charge_voltage_finish_max_time', 600) charge_database.put_double('charge_rest_time', 0) charge_database.put_double('time_step', 10.0) charge = Charge(charge_database) start = time() charge.run(device, data) end = time() # used for tracking time of this substep print('Charge: %s min' % ((end - start) / 60)) data['time'] -= data['time'][-1] # discharge at constant power discharge_power = ptree.get_double('discharge_power') final_voltage = ptree.get_double('final_voltage') time_step = ptree.get_double('time_step') discharge_database = PropertyTree() discharge_database.put_string('discharge_mode', 'constant_power') discharge_database.put_double('discharge_power', discharge_power) discharge_database.put_string('discharge_stop_at_1', 'voltage_less_than') discharge_database.put_double('discharge_voltage_limit', final_voltage) discharge_database.put_double('discharge_rest_time', 10 * time_step) discharge_database.put_double('time_step', time_step) discharge = Discharge(discharge_database) start = time() discharge.run(device, data) end = time() # used for tracking time of this substep print('Discharge: %s min' % ((end - start) / 60)) return data
def test_builders(self): for AbstractClass in [Observer, Observable]: # AbstractClass takes a PropertyTree as argument. self.assertRaises(TypeError, AbstractClass) # The PropertyTree must specify what concrete class derived from # AbstractClass to instantiate. ptree = PropertyTree() self.assertRaises(KeyError, AbstractClass, ptree) # The derived concrete class must be registerd in the dictionary # that holds the builders. ptree.put_string('type', 'Invalid') self.assertRaises(KeyError, AbstractClass, ptree) # Now declare a concrete class. class ConcreteClass(AbstractClass): def __new__(cls, *args, **kwargs): return object.__new__(ConcreteClass) def __init__(*args, **kwargs): pass # Here is how to register a derived concrete class to the base # abstract class. AbstractClass._builders['ConcreteClass'] = ConcreteClass # Now instantiation works. ptree.put_string('type', 'ConcreteClass') AbstractClass(ptree) # Also can build directly from derived class. ConcreteClass() # Remove from the dictionary. del AbstractClass._builders['ConcreteClass'] self.assertRaises(KeyError, AbstractClass, ptree)
def run_discharge(device, ptree): data = initialize_data() # (re)charge the device initial_voltage = ptree.get_double('initial_voltage') charge_database = PropertyTree() charge_database.put_string('charge_mode', 'constant_current') charge_database.put_double('charge_current', 10.0) charge_database.put_string('charge_stop_at_1', 'voltage_greater_than') charge_database.put_double('charge_voltage_limit', initial_voltage) charge_database.put_bool('charge_voltage_finish', True) charge_database.put_double('charge_voltage_finish_current_limit', 1e-2) charge_database.put_double('charge_voltage_finish_max_time', 600) charge_database.put_double('charge_rest_time', 0) charge_database.put_double('time_step', 10.0) charge = Charge(charge_database) start = time() charge.run(device, data) end = time() # used for tracking time of this substep print('Charge: %s min' % ((end-start) / 60)) data['time'] -= data['time'][-1] # discharge at constant power discharge_power = ptree.get_double('discharge_power') final_voltage = ptree.get_double('final_voltage') time_step = ptree.get_double('time_step') discharge_database = PropertyTree() discharge_database.put_string('discharge_mode', 'constant_power') discharge_database.put_double('discharge_power', discharge_power) discharge_database.put_string('discharge_stop_at_1', 'voltage_less_than') discharge_database.put_double('discharge_voltage_limit', final_voltage) discharge_database.put_double('discharge_rest_time', 10 * time_step) discharge_database.put_double('time_step', time_step) discharge = Discharge(discharge_database) start = time() discharge.run(device, data) end = time() # used for tracking time of this substep print('Discharge: %s min' % ((end-start) / 60)) return data
def test_voltage_limit(self): ptree = PropertyTree() ptree.put_double('voltage_limit', 1.7) # upper limit ptree.put_string('end_criterion', 'voltage_greater_than') voltage_limit = EndCriterion.factory(ptree) voltage_limit.reset(5.0, device) device.evolve_one_time_step_constant_voltage(0.2, 1.3) self.assertFalse(voltage_limit.check(0.0, device)) self.assertFalse(voltage_limit.check(60.0, device)) device.evolve_one_time_step_constant_voltage(0.2, 1.7) self.assertTrue(voltage_limit.check(45.0, device)) device.evolve_one_time_step_constant_voltage(0.2, 2.1) self.assertTrue(voltage_limit.check(45.0, device)) # lower limit ptree.put_string('end_criterion', 'voltage_less_than') voltage_limit = EndCriterion.factory(ptree) voltage_limit.reset(0.0, device) device.evolve_one_time_step_constant_voltage(0.2, 1.3) self.assertTrue(voltage_limit.check(0.0, device)) device.evolve_one_time_step_constant_voltage(0.2, 1.7) self.assertTrue(voltage_limit.check(45.0, device)) device.evolve_one_time_step_constant_voltage(0.2, 2.1) self.assertFalse(voltage_limit.check(45.0, device))
def test_fourier_analysis(self): ptree = PropertyTree() ptree.put_int('steps_per_cycle', 3) ptree.put_int('cycles', 1) ptree.put_int('ignore_cycles', 0) ptree.put_string('harmonics', '1') # uninitialized data data = {} self.assertRaises(KeyError, fourier_analysis, data, ptree) # empty data data = initialize_data() self.assertRaises(IndexError, fourier_analysis, data, ptree) # bad data data['time'] = array([1, 2, 3], dtype=float) data['current'] = array([4, 5, 6], dtype=float) data['voltage'] = array([7, 8], dtype=float) self.assertRaises(AssertionError, fourier_analysis, data, ptree) # poor data (size not a power of 2) data['voltage'] = array([7, 8, 9], dtype=float) with catch_warnings(): simplefilter("error") self.assertRaises(RuntimeWarning, fourier_analysis, data, ptree) # data unchanged after analyze dummy = array([1, 2, 3, 4, 5, 6, 7, 8], dtype=float) data['time'] = dummy data['current'] = dummy data['voltage'] = dummy # ptree needs to be updated self.assertRaises(AssertionError, fourier_analysis, data, ptree) ptree.put_int('steps_per_cycle', 4) ptree.put_int('cycles', 2) ptree.put_int('ignore_cycles', 0) fourier_analysis(data, ptree) self.assertTrue(all(equal(data['time'], dummy))) self.assertTrue(all(equal(data['current'], dummy))) self.assertTrue(all(equal(data['voltage'], dummy)))
def test_get_array(self): ptree = PropertyTree() # array of double ptree.put_string('array_double', '3.14,1.41') array_double = ptree.get_array_double('array_double') self.assertEqual(array_double, [3.14, 1.41]) # ... string ptree.put_string('array_int', '1,2,3') array_int = ptree.get_array_int('array_int') self.assertEqual(array_int, [1, 2, 3]) # ... int ptree.put_string('array_string', 'uno,dos,tres,cuatro') array_string = ptree.get_array_string('array_string') self.assertEqual(array_string, ['uno', 'dos', 'tres', 'cuatro']) # ... bool ptree.put_string('array_bool', 'true,FALSE,False') array_bool = ptree.get_array_bool('array_bool') self.assertEqual(array_bool, [True, False, False])
def test_time_steps(self): ptree = PropertyTree() ptree.put_int('stages', 2) ptree.put_int('cycles', 1) ptree.put_double('time_step', 1.0) ptree.put_string('stage_0.mode', 'hold') ptree.put_string('stage_0.end_criterion', 'time') ptree.put_double('stage_0.duration', 2.0) ptree.put_string('stage_1.mode', 'rest') ptree.put_string('stage_1.end_criterion', 'time') ptree.put_double('stage_1.duration', 1.0) ptree.put_double('stage_1.time_step', 0.1) multi = MultiStage(ptree) data = initialize_data() steps = multi.run(device, data) self.assertEqual(steps, 12) self.assertEqual(steps, len(data['time'])) self.assertAlmostEqual(data['time'][5], 2.4) self.assertAlmostEqual(data['voltage'][0], data['voltage'][1]) self.assertAlmostEqual(data['current'][3], 0.0)
def setup_expertiment(): eis_database = PropertyTree() eis_database.put_double('frequency_upper_limit', 1e+4) eis_database.put_double('frequency_lower_limit', 1e-6) eis_database.put_int('steps_per_decade', 3) eis_database.put_int('steps_per_cycle', 1024) eis_database.put_int('cycles', 2) eis_database.put_int('ignore_cycles', 1) eis_database.put_double('dc_voltage', 0) eis_database.put_string('harmonics', ' 3') eis_database.put_string('amplitudes', ' 5e-3') eis_database.put_string('phases', '0') return eis_database
def test_charge_constant_voltage(self): ptree = PropertyTree() ptree.put_string('charge_mode', 'constant_voltage') ptree.put_double('charge_voltage', 1.4) ptree.put_string('charge_stop_at_1', 'current_less_than') ptree.put_double('charge_current_limit', 1e-6) ptree.put_string('charge_stop_at_2', 'time') ptree.put_double('charge_max_duration', 60) ptree.put_double('time_step', 0.2) charge = Charge(ptree) data = initialize_data() charge.run(device, data) self.assertTrue(data['time'][-1] >= 60 or abs(data['current'][-1]) <= 1e-6) self.assertAlmostEqual(data['voltage'][-1], 1.4)
def test_rest(self): ptree = PropertyTree() ptree.put_string('mode', 'rest') evolve_one_time_step = TimeEvolution.factory(ptree) evolve_one_time_step(device, 0.1) self.assertEqual(device.get_current(), 0.0)
def test_compound_criterion(self): ptree = PropertyTree() ptree.put_string('end_criterion', 'compound') ptree.put_string('criterion_0.end_criterion', 'time') ptree.put_double('criterion_0.duration', 5.0) ptree.put_string('criterion_1.end_criterion', 'voltage_greater_than') ptree.put_double('criterion_1.voltage_limit', 2.0) # no default value for now self.assertRaises(KeyError, EndCriterion.factory, ptree) ptree.put_string('logical_operator', 'bad_operator') self.assertRaises(RuntimeError, EndCriterion.factory, ptree) ptree.put_string('logical_operator', 'or') compound_criterion = EndCriterion.factory(ptree) compound_criterion.reset(0.0, device) device.evolve_one_time_step_constant_voltage(0.1, 1.0) self.assertFalse(compound_criterion.check(3.0, device)) self.assertTrue(compound_criterion.check(5.0, device)) device.evolve_one_time_step_constant_voltage(0.1, 2.0) self.assertTrue(compound_criterion.check(3.0, device)) self.assertTrue(compound_criterion.check(5.0, device)) ptree.put_string('logical_operator', 'and') compound_criterion = EndCriterion.factory(ptree) compound_criterion.reset(0.0, device) device.evolve_one_time_step_constant_voltage(0.1, 1.0) self.assertFalse(compound_criterion.check(3.0, device)) self.assertFalse(compound_criterion.check(5.0, device)) device.evolve_one_time_step_constant_voltage(0.1, 2.0) self.assertFalse(compound_criterion.check(3.0, device)) self.assertTrue(compound_criterion.check(5.0, device)) ptree.put_string('logical_operator', 'xor') compound_criterion = EndCriterion.factory(ptree) compound_criterion.reset(0.0, device) device.evolve_one_time_step_constant_voltage(0.1, 1.0) self.assertFalse(compound_criterion.check(3.0, device)) self.assertTrue(compound_criterion.check(5.0, device)) device.evolve_one_time_step_constant_voltage(0.1, 2.0) self.assertTrue(compound_criterion.check(3.0, device)) self.assertFalse(compound_criterion.check(5.0, device))
def test_always_statisfied(self): ptree = PropertyTree() ptree.put_string('end_criterion', 'skip') always_statisfied = EndCriterion.factory(ptree) always_statisfied.reset(0.0, device) self.assertTrue(always_statisfied.check(NaN, device))
def test_invalid_end_criterion(self): ptree = PropertyTree() ptree.put_string('end_criterion', 'bad_name') self.assertRaises(RuntimeError, EndCriterion.factory, ptree)
def test_invalid_time_evolution(self): ptree = PropertyTree() ptree.put_string('mode', 'unexpected') self.assertRaises(RuntimeError, TimeEvolution.factory, ptree)