Source code for hyperloop.Python.pod.drivetrain.battery

import numpy as np
import scipy.interpolate, scipy.integrate
from openmdao.api import Component, Problem, Group
import os


[docs]class Battery(Component): """The `Battery` class represents a battery component in an OpenMDAO model. A `Battery` models battery performance by finding a general battery voltage performance curve ([1]_, [2]_) based on standard cell properties and can be used to determine the number of cells and cell configuration needed to meet specification. Params ------ des_time : float time until design power point (h) time_of_flight : float total mission time (h) battery_cross_section_area : float cross_sectional area of battery used to compute length (cm^2) des_power : float design power (W) des_current : float design current (A) q_l : float discharge limit (unitless) e_full : float fully charged voltage (V) e_nom : float voltage at end of nominal voltage (V) e_exp : float voltage at end of exponential zone (V) q_n : float single cell capacity (A*h) t_exp : float time to reach exponential zone (h) t_nom : float time to reach nominal zone (h) r : float resistance of individual battery cell (Ohms) cell_mass : float mass of a single cell (g) cell_height : float height of a single cylindrical cell (mm) cell_diameter : float diamter of a single cylindrical cell (mm) Outputs ------- n_cells : float total number battery cells (unitless) battery_length : float length of battery (cm) output_voltage : float output voltage of battery configuration (V) battery_mass : float total mass of cells in battery configuration (kg) battery_volume : float total volume of cells in battery configuration (cm^3) battery_cost : float total cost of battery cells in (USD) Notes ----- Battery cost based on purchase of 1000 bateries from ecia ([3]_) price listing References ---------- .. [1] Gladin, Ali, Collins, "Conceptual Modeling of Electric and Hybrid-Electric Propulsion for UAS Applications" Georgia Tech, 2015 .. [2] D. N. Mavris, "Subsonic Ultra Green Aircraft Research - Phase II," NASA Langley Research Center, 2014 .. [3] eciaauthorized.com/search/HHR650D """ # TODO rematch battery performance data to 18650 or similar Li-Ion battery instead of # outdated Ni-Mh battery # TODO account for additional battery containment hardware # TODO fix voltage to certain range? def __init__(self): """Initializes a `Battery` object Sets up the given Params/Outputs of the OpenMDAO `Battery` component, initializes their shape, and sets them to their default values. """ super(Battery, self).__init__() # setup mission characteristics self.add_param('des_time', val=1.0, desc='time until design power point', units='h') self.add_param('time_of_flight', val=1.0, desc='total mission time', units='h') self.add_param('des_power', val=7.0, desc='design power', units='W') self.add_param('des_current', val=1.0, desc='design current', units='A') self.add_param('q_l', val=0.1, desc='discharge limit', units='unitless') # setup battery characteristics self.add_param('e_full', val=1.4, desc='fully charged voltage', units='V') self.add_param('e_nom', val=1.2, desc='voltage at end of nominal zone', units='V') self.add_param('e_exp', val=1.27, desc='voltage at end of exponential zone', units='V') self.add_param('q_n', val=3.5, desc='Single cell capacity', units='A*h') self.add_param('t_exp', val=1.0, desc='time to reach exponential zone', units='h') self.add_param('t_nom', val=4.3, desc='time to reach nominal zone', units='h') self.add_param('r', val=0.0046, desc='battery resistance', units='Ohms') self.add_param('battery_cross_section_area', 15000.0, desc='cross_sectional area of battery used to compute length', units='cm**2') self.add_param('cell_mass', val=170.0, desc='mass of a single cell', units='g') self.add_param('cell_height', val=61.0, desc='height a single cylindrical cell', units='mm') self.add_param('cell_diameter', val=33.0, desc='diamter of a single cylindrical cell', units='mm') # setup outputs self.add_output('n_cells', val=1.0, desc='total number of battery cells', units='unitless') self.add_output('output_voltage', val=1000.0, desc='output voltage of battery configuration', units='V') self.add_output('battery_mass', val=1.0, desc='total mass of cells in battery configuration', units='kg') self.add_output('battery_volume', val=1.0, desc='total volume of cells in battery configuration', units='cm**3') self.add_output('battery_cost', val=1.0, desc='total materials cost of battery configuration', units='$') self.add_output('battery_length', val=1.0, desc='length of battery', units='cm')
[docs] def solve_nonlinear(self, params, unknowns, resids): """Runs the `Battery` component and sets its respective outputs to their calculated results Args ---------- params : `VecWrapper` `VecWrapper` containing parameters unknowns : `VecWrapper` `VecWrapper` containing outputs and states resids : `VecWrapper` `VecWrapper` containing residuals """ #print(params['des_power']) #print(params['des_current']) # check representation invariant self._check_rep(params, unknowns, resids) # FIXME using ceiling for cell calculations, despite advice against # need to change to proper way of constraining to integer values as # battery falls apart for n_paralell < 0 (i.e. if n_paralell = 0.01 and # not 1.0, then we get absurd output voltage levels cap_discharge = self._calculate_total_discharge( params['time_of_flight'], params['des_current']) # print((params['q_n'] * (1 - params['q_l']))) # print cap_discharge n_parallel = cap_discharge / (params['q_n'] * (1 - params['q_l'])) # print('usaable cap %f ' % (params['q_n'] * (1 - params['q_l']))) # print('cap dis %f' % cap_discharge) # print(n_parallel) single_bat_current = params['des_current'] / n_parallel # print('single bat cur %f' % single_bat_current) # single_bat_current = 7 # single_bat_current = 7 # n_parallel = -params['des_current'] / single_bat_current # single_bat_current = 13 single_bat_discharge = self._calculate_total_discharge( params['des_time'], single_bat_current) # # calculate general battery performance curve paramaters # # TODO calculate performance curve parameters from Panasonic 18650 # # battery, currently using parameters from SUGAR paper # # # voltage drop over exponential zone # # a = params['params['e_full']'] - params['e_exp'] # a = 0.144 # # discharge of single cell from full to end of exponential zone # q_exp = self._calculate_total_discharge( # params['t_exp'], params['des_current']) / n_parallel # # # time constant of the exponential zone # # b = 3 / q_exp # b = 2.3077 # discharge over the nominal zone # q_nom = self._calculate_total_discharge( # params['t_nom'], params['des_current']) / n_parallel # # polarization voltage # # k = (params['params['e_full']'] - params['e_nom'] + a * (np.exp(-b * q_nom) - 1)) * (params['q_n'] - q_nom) # k = 0.01875 # # # no load constant voltage of battery # # k = polarization voltage, params['r'] = resistance, # # e_0 = params['params['e_full']'] + k + params['r'] * single_bat_current - a # e_0 = 1.2848 # # # general voltage performance curve # v_batt = e_0 - k * (params['q_n'] / ( # params['q_n'] - single_bat_discharge)) + a * np.exp( # -b * single_bat_discharge) - params['r'] * single_bat_current # single battery power at design power point dir = os.path.dirname(__file__) filename = os.path.join(dir, '18650.csv') data = np.loadtxt(filename, dtype='float', delimiter=',').transpose() func = scipy.interpolate.UnivariateSpline(data[0], data[1]) # print single_bat_discharge v_batt = func(single_bat_discharge * 1000) p_bat = v_batt * single_bat_current # print(v_batt) # print('p_bat %f' % p_bat) energy_cap = scipy.integrate.quad(func, 0, single_bat_discharge * 1000)[0] / 1000 #print('energy cap %f' % energy_cap) # total number of battery cells n_cells = params['des_power'] / p_bat #print('n_cells %f' % n_cells) n_cells = np.ceil(n_cells) n_parallel = np.ceil(n_parallel) n_series = np.ceil(n_cells / n_parallel) # assert n_cells >= n_parallel unknowns['n_cells'] = n_cells # calculate volume of cells accounting for hexagonal packing efficiency of 0.9069 and convert from mm^3 to cm^3 # unknowns['battery_volume'] =(n_cells * ( # params['cell_height'] * np.pi * np.power(params['cell_diameter'] / # 2, 2)) / 0.9069 / 1000) / 3.7 # calculate mass of cells and convert to kg # including rough approx of li-ion density # TODO dev real model # unknowns['battery_mass'] = params['cell_mass'] * n_cells / 1000 / 5 unknowns['battery_mass'] = energy_cap * n_cells / 265 unknowns['battery_volume'] = energy_cap * n_cells / 730 * 1000 / 0.9069 # calculate output voltage of battery in the nominal zone unknowns['output_voltage'] = n_series * params['e_nom'] unknowns['battery_cost'] = n_cells * 12.95 unknowns['battery_length'] = unknowns['battery_volume'] / params['battery_cross_section_area'] # check representation invariant self._check_rep(params, unknowns, resids)
def _calculate_total_discharge(self, time, current): """Calculates the total discharge over a given load profile Integrates the load profile from t = 0 to t = time Args ---------- time : float the upper bound of the integration current : float the constant load profile Returns ------- float the total discharge over the load profile """ return time * current def _check_rep(self, params, unknowns, resids): """Checks that the representation invariant of the `Battery` class holds Args ---------- params : `VecWrapper` `VecWrapper` containing parameters unknowns : `VecWrapper` `VecWrapper` containing outputs and states resids : `VecWrapper` `VecWrapper` containing residuals """ # assert params['q_l'] > 0 # assert params['des_time'] > 0 # assert params['time_of_flight'] > 0 # assert params['des_power'] > 0 # assert params['des_current'] > 0 # assert params['e_full'] > 0 # assert params['q_n'] > 0 # assert params['e_exp'] > 0 # assert params['e_nom'] > 0 # assert params['t_nom'] > 0 # assert params['t_exp'] > 0 # assert params['r'] > 0 # assert unknowns['n_cells'] > 0
if __name__ == '__main__': # set up problem root = Group() p = Problem(root) p.root.add('comp', Battery()) p.setup() p.root.list_connections() p.run() # print following properties print('Ncells(cells) : %f' % p['comp.n_cells']) print('mass: %f' % p['comp.battery_mass']) print('volume: %f' % p['comp.battery_volume']) print('voltage: %f' % p['comp.output_voltage']) print('cost : %f' % p['comp.battery_cost']) print(p['comp.battery_length'])