def test_no_name(self): ptree = PropertyTree() ptree.put_double('initial_voltage', 0) ptree.put_double('final_voltage', 0) ptree.put_double('scan_limit_1', 1) ptree.put_double('scan_limit_2', 0) ptree.put_double('step_size', 0.1) ptree.put_double('scan_rate', 1) ptree.put_int('cycles', 1) cv = CyclicVoltammetry(ptree) try: cv.run(device) except: self.fail('calling run without data should not raise') data = initialize_data() cv.run(device, data) voltage = array([0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1., 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.], dtype=float) time = linspace(0, 2, 21) try: testing.assert_array_almost_equal(data['voltage'], voltage) testing.assert_array_almost_equal(data['time'], time) except AssertionError as e: print(e) self.fail()
def testSeriesRC(self): # make series RC equivalent circuit 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) # 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'] R_computed = real(Z_computed) X_computed = imag(Z_computed) M_computed = 20*log10(absolute(Z_computed)) P_computed = angle(Z_computed)*180/pi # compute the exact solution Z_exact = R+1/(1j*C*2*pi*f) R_exact = real(Z_exact) X_exact = imag(Z_exact) 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_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 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 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_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_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 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_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_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_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_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_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_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_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 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_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_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_accuracy_pycap_simulation(self): # # tests the accuracy of a pycap simulation against a # straight run dualfoil sim with different timesteps # df1 = Dualfoil(path=path) # manual runs df2 = Dualfoil(path=path) # pycap simulation im = df_manip.InputManager(path=path) # testing a charge-to-hold-const-voltage # manual # use InputManager to set the input file c = -10.0 # constant charge current # charge for 5 minutes straight im.add_new_leg(c, 5, 1) df1.run() df1.outbot.update_output() v = 4.539 # expected voltage after 5 minutes # hold constant voltage for 3 minutes straight im.add_new_leg(v, 3.0, 0) df1.run() df1.outbot.update_output() # pycap simulation # build a ptree input ptree = PropertyTree() ptree.put_double('time_step', 30.0) # 30 second time step ptree.put_string('charge_mode', 'constant_current') ptree.put_double('charge_current', 10.0) ptree.put_string('charge_stop_at_1', 'voltage_greater_than') ptree.put_double('charge_voltage_limit', v) ptree.put_bool('charge_voltage_finish', True) # hold end voltage after either 3 minutes have passed # OR current falls under 1 ampere ptree.put_double('charge_voltage_finish_max_time', 180.0) ptree.put_double('charge_voltage_finish_current_limit', 1.0) const_current_const_voltage = Charge(ptree) const_current_const_voltage.run(df2) o1 = df1.outbot.output # contains sim1 output o2 = df2.outbot.output # contains sim2 output # affirm we make it this far and have usable data self.assertTrue(len(o1['time']) > 0) self.assertTrue(len(o2['time']) > 0) # lengths of data should be different self.assertFalse(len(o1['time']) == len(o2['time'])) # TEST LOGIC: # -Merge the two outputs into one, sorted by # increasing time stamps. # -Compare the consistency of the two simulations # by checking for smooth changes within the curves # of the combined output lists o1['time'].extend(o2['time']) time = ar(o1['time']) # nparray o1['voltage'].extend(o2['voltage']) voltage = ar(o1['voltage']) # nparray o1['current'].extend(o2['current']) current = ar(o1['current']) # np array # create a dictionary with the combined output lists output = {'time': time, 'voltage': voltage, 'current': current } # sort based on time, keeping the three types aligned key = argsort(output['time']) # for using the key to sort the list tmp = {'time': [], 'voltage': [], 'current': []} for i in key: tmp['time'].append(output['time'][i]) tmp['voltage'].append(output['voltage'][i]) tmp['current'].append(output['current'][i]) # reassign ordered set to `output` as nparrays output['time'] = ar(tmp['time']) output['voltage'] = ar(tmp['voltage']) output['current'] = ar(tmp['current']) # BELOW: first 20 seconds are identical time stamps; # skip these to avoid errors from incorrect sorting # REASON FOR ERROR: Dualfoil only prints time data as # precice as minutes to three decimal places. So when # the following is generated.... # Manual Run | Pycap Simulation # (min) (V) (amp) | (min) (V) (amp) # .001 4.52345 10.0 | .001 4.52345 10.0 # .001 4.52349 10.0 | .001 4.52349 10.0 # ... ... # ...python's `sorted()` function has no way of # distinguishing entries; it instead returns this: # [ # (.001, 4.52345, 10.0), # (.001, 4.52349, 10.0), <- these two should # (.001, 4.52345, 10.0), <- be switched # (.001, 4.52349, 10.0) # ] # SOLUTION: consistency test affirms that the exact same # time step will produce same current and voltage, so # skip ahead to first instance where time stamps will # be out of alignment i = 0 while output['time'][i] <= 0.4: # 24 seconds i = i + 1 index_limit = len(output['time'])-1 # go through and affirm smoothness of curve while i < index_limit: # Check if time values are the same to 3 decimal places. # If so, current and voltage are not guarunteed # to also be exactly the same, but should be close if output['time'][i] == output['time'][i-1]: # affirm that current is virtually the same self.assertAlmostEqual(output['current'][i], output['current'][i-1]) # BELOW: delta is eased slightly # REASON: `sorted()` can't tell which entry came # first from same time-stamp if from different # simulations; allow for this with error error = 3e-5 self.assertAlmostEqual(output['voltage'][i], output['voltage'][i-1], delta=error) else: # Time values are different # Check to affirm that the variable NOT being held # constant is steadily increasing / decreasing # First part happens in first 4 minutes if output['time'][i] <= 5.0: # part 1, const currrent # current should be equal self.assertEqual(output['current'][i], output['current'][i-1]) # voltage should not have decreased self.assertTrue(output['voltage'][i], output['voltage'][i-1]) else: # part 2, const voltage # current should be getting less positive self.assertTrue(output['current'][i] <= output['current'][i-1], msg=(output['current'][i-2:i+10], output['time'][i-2:i+10])) # voltage should decrease, then stay at 4.54 if output['voltage'][i-1] == 4.54: self.assertEqual(output['voltage'][i], output['voltage'][i-1]) else: self.assertTrue(output['voltage'][i] <= output['voltage'][i-1]) # update index i = i + 1
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 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_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))
parser.add_option('--param_'+str(p),type=float) # parse the command line arguments (options,args)=parser.parse_args() # make device database device_database=PropertyTree() device_database.parse_xml('super_capacitor.xml') # adjust the parameters in the database options_dict=vars(options) for var in options_dict: path=uq_database.get_string('uq.'+var+'.name') # next line is there to ensure that path already exists old_value=device_database.get_double(path) new_value=options_dict[var] print var,path,new_value device_database.put_double(path,new_value) # build the energy storage device device=EnergyStorageDevice(device_database.get_child('device')) # parse the electrochmical impedance spectroscopy database eis_database=PropertyTree() eis_database.parse_xml('eis.xml') # measure the impedance impedance_spectrum_data=measure_impedance_spectrum(device,eis_database.get_child('eis')) # extract the results frequency=impedance_spectrum_data['frequency'] complex_impedance=impedance_spectrum_data['impedance'] resistance=real(complex_impedance) reactance=imag(complex_impedance) modulus=absolute(complex_impedance) argument=angle(complex_impedance,deg=True)
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_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_accuracy_pycap_simulation(self): # # tests the accuracy of a pycap simulation against a # straight run dualfoil sim with different timesteps # df1 = Dualfoil(path=path) # manual runs df2 = Dualfoil(path=path) # pycap simulation im = df_manip.InputManager(path=path) # testing a charge-to-hold-const-voltage # manual # use InputManager to set the input file c = -10.0 # constant charge current # charge for 5 minutes straight im.add_new_leg(c, 5, 1) df1.run() df1.outbot.update_output() v = 4.539 # expected voltage after 5 minutes # hold constant voltage for 3 minutes straight im.add_new_leg(v, 3.0, 0) df1.run() df1.outbot.update_output() # pycap simulation # build a ptree input ptree = PropertyTree() ptree.put_double('time_step', 30.0) # 30 second time step ptree.put_string('charge_mode', 'constant_current') ptree.put_double('charge_current', 10.0) ptree.put_string('charge_stop_at_1', 'voltage_greater_than') ptree.put_double('charge_voltage_limit', v) ptree.put_bool('charge_voltage_finish', True) # hold end voltage after either 3 minutes have passed # OR current falls under 1 ampere ptree.put_double('charge_voltage_finish_max_time', 180.0) ptree.put_double('charge_voltage_finish_current_limit', 1.0) const_current_const_voltage = Charge(ptree) const_current_const_voltage.run(df2) o1 = df1.outbot.output # contains sim1 output o2 = df2.outbot.output # contains sim2 output # affirm we make it this far and have usable data self.assertTrue(len(o1['time']) > 0) self.assertTrue(len(o2['time']) > 0) # lengths of data should be different self.assertFalse(len(o1['time']) == len(o2['time'])) # TEST LOGIC: # -Merge the two outputs into one, sorted by # increasing time stamps. # -Compare the consistency of the two simulations # by checking for smooth changes within the curves # of the combined output lists o1['time'].extend(o2['time']) time = ar(o1['time']) # nparray o1['voltage'].extend(o2['voltage']) voltage = ar(o1['voltage']) # nparray o1['current'].extend(o2['current']) current = ar(o1['current']) # np array # create a dictionary with the combined output lists output = {'time': time, 'voltage': voltage, 'current': current} # sort based on time, keeping the three types aligned key = argsort(output['time']) # for using the key to sort the list tmp = {'time': [], 'voltage': [], 'current': []} for i in key: tmp['time'].append(output['time'][i]) tmp['voltage'].append(output['voltage'][i]) tmp['current'].append(output['current'][i]) # reassign ordered set to `output` as nparrays output['time'] = ar(tmp['time']) output['voltage'] = ar(tmp['voltage']) output['current'] = ar(tmp['current']) # BELOW: first 20 seconds are identical time stamps; # skip these to avoid errors from incorrect sorting # REASON FOR ERROR: Dualfoil only prints time data as # precice as minutes to three decimal places. So when # the following is generated.... # Manual Run | Pycap Simulation # (min) (V) (amp) | (min) (V) (amp) # .001 4.52345 10.0 | .001 4.52345 10.0 # .001 4.52349 10.0 | .001 4.52349 10.0 # ... ... # ...python's `sorted()` function has no way of # distinguishing entries; it instead returns this: # [ # (.001, 4.52345, 10.0), # (.001, 4.52349, 10.0), <- these two should # (.001, 4.52345, 10.0), <- be switched # (.001, 4.52349, 10.0) # ] # SOLUTION: consistency test affirms that the exact same # time step will produce same current and voltage, so # skip ahead to first instance where time stamps will # be out of alignment i = 0 while output['time'][i] <= 0.4: # 24 seconds i = i + 1 index_limit = len(output['time']) - 1 # go through and affirm smoothness of curve while i < index_limit: # Check if time values are the same to 3 decimal places. # If so, current and voltage are not guarunteed # to also be exactly the same, but should be close if output['time'][i] == output['time'][i - 1]: # affirm that current is virtually the same self.assertAlmostEqual(output['current'][i], output['current'][i - 1]) # BELOW: delta is eased slightly # REASON: `sorted()` can't tell which entry came # first from same time-stamp if from different # simulations; allow for this with error error = 3e-5 self.assertAlmostEqual(output['voltage'][i], output['voltage'][i - 1], delta=error) else: # Time values are different # Check to affirm that the variable NOT being held # constant is steadily increasing / decreasing # First part happens in first 4 minutes if output['time'][i] <= 5.0: # part 1, const currrent # current should be equal self.assertEqual(output['current'][i], output['current'][i - 1]) # voltage should not have decreased self.assertTrue(output['voltage'][i], output['voltage'][i - 1]) else: # part 2, const voltage # current should be getting less positive self.assertTrue( output['current'][i] <= output['current'][i - 1], msg=(output['current'][i - 2:i + 10], output['time'][i - 2:i + 10])) # voltage should decrease, then stay at 4.54 if output['voltage'][i - 1] == 4.54: self.assertEqual(output['voltage'][i], output['voltage'][i - 1]) else: self.assertTrue( output['voltage'][i] <= output['voltage'][i - 1]) # update index i = i + 1
parser.add_option('--param_' + str(p), type=float) # parse the command line arguments (options, args) = parser.parse_args() # make device database device_database = PropertyTree() device_database.parse_xml('super_capacitor.xml') # adjust the parameters in the database options_dict = vars(options) for var in options_dict: path = uq_database.get_string('uq.' + var + '.name') # next line is there to ensure that path already exists old_value = device_database.get_double(path) new_value = options_dict[var] print var, path, new_value device_database.put_double(path, new_value) # build the energy storage device device = EnergyStorageDevice(device_database.get_child('device')) # parse the electrochmical impedance spectroscopy database eis_database = PropertyTree() eis_database.parse_xml('eis.xml') # measure the impedance impedance_spectrum_data = measure_impedance_spectrum( device, eis_database.get_child('eis')) # extract the results frequency = impedance_spectrum_data['frequency'] complex_impedance = impedance_spectrum_data['impedance'] resistance = real(complex_impedance) reactance = imag(complex_impedance) modulus = absolute(complex_impedance)