def test_export_eclab_ascii_format(self): # define dummy experiment # it is quicker than building an actual EIS experiment class DummyExperiment(Experiment): def __new__(cls, *args, **kwargs): return object.__new__(DummyExperiment) def __init__(self, ptree): Experiment.__init__(self) dummy = DummyExperiment(PropertyTree()) # produce dummy data for the experiment # here just a circle on the complex plane n = 10 f = ones(n, dtype=float) Z = ones(n, dtype=complex) for i in range(n): f[i] = 10**(i / (n - 1)) Z[i] = cos(2 * pi * i / (n - 1)) + 1j * sin(2 * pi * i / (n - 1)) dummy._data['frequency'] = f dummy._data['impedance'] = Z # need a supercapacitor here to make sure method inspect() is kept in # sync with the EC-Lab headers ptree = PropertyTree() ptree.parse_info('super_capacitor.info') super_capacitor = EnergyStorageDevice(ptree) dummy._extra_data = super_capacitor.inspect() # export the data to ECLab format eclab = ECLabAsciiFile('untitled.mpt') eclab.update(dummy) # check that all lines end up with Windows-style line break '/r/n' # file need to be open in byte mode or the line ending will be # converted to '\n'... # also check that the number of lines in the headers has been computed # correctly and that the last one contains the column headers with open('untitled.mpt', mode='rb') as fin: lines = fin.readlines() for line in lines: self.assertNotEqual(line.find(b'\r\n'), -1) self.assertNotEqual(line.find(b'\r\n'), len(line) - 4) header_lines = int(lines[1].split( b':')[1].lstrip(b'').rstrip(b'\r\n')) self.assertEqual( header_lines, len(eclab._unformated_headers) ) self.assertEqual(lines[header_lines - 1].find(b'freq/Hz'), 0) # check Nyquist plot does not throw nyquist = NyquistPlot('nyquist.png') nyquist.update(dummy)
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_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_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'])
# # This file is subject to the Modified BSD License and may not be distributed # without copyright and license information. Please refer to the file LICENSE # for the text and further information on this license. from pycap import PropertyTree, EnergyStorageDevice from pycap import Charge from pycap import initialize_data from mpi4py import MPI import unittest comm = MPI.COMM_WORLD filename = 'series_rc.info' ptree = PropertyTree() ptree.parse_info(filename) device = EnergyStorageDevice(ptree, comm) class capChargeTestCase(unittest.TestCase): 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)
(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) magnitude = 20 * log10(absolute(complex_impedance))