# -*- coding: utf-8 -*-
#
# Copyright 2015-2019 European Commission (JRC);
# Licensed under the EUPL (the 'Licence');
# You may not use this work except in compliance with the Licence.
# You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
"""
Functions and `dsp` model to model the drive battery (high voltage).
"""
import numpy as np
import schedula as sh
from co2mpas.defaults import dfl
import co2mpas.utils as co2_utl
dsp = sh.BlueDispatcher(
name='Drive Battery',
description='Models the drive battery (e.g., high voltage).'
)
[docs]@sh.add_function(dsp, outputs=['drive_battery_voltages'])
def calculate_drive_battery_voltages(
drive_battery_electric_powers, drive_battery_currents):
"""
Calculate the drive battery voltage [V].
:param drive_battery_electric_powers:
Drive battery electric power [kW].
:type drive_battery_electric_powers: numpy.array
:param drive_battery_currents:
Drive battery current vector [A].
:type drive_battery_currents: numpy.array
:return:
Drive battery voltage [V].
:rtype: numpy.array
"""
from .service import calculate_service_battery_currents as func
return func(drive_battery_electric_powers, drive_battery_currents)
[docs]@sh.add_function(dsp, outputs=['drive_battery_currents'])
def calculate_drive_battery_currents(
drive_battery_electric_powers, drive_battery_voltages):
"""
Calculate the drive battery current vector [A].
:param drive_battery_electric_powers:
Drive battery electric power [kW].
:type drive_battery_electric_powers: numpy.array
:param drive_battery_voltages:
Drive battery voltage [V].
:type drive_battery_voltages: numpy.array
:return:
Drive battery current vector [A].
:rtype: numpy.array
"""
from .service import calculate_service_battery_currents as func
return func(drive_battery_electric_powers, drive_battery_voltages)
[docs]@sh.add_function(dsp, outputs=['drive_battery_currents'], weight=1)
def calculate_drive_battery_currents_v1(
drive_battery_capacity, times, drive_battery_state_of_charges):
"""
Calculate the drive battery current vector [A].
:param drive_battery_capacity:
Drive battery capacity [Ah].
:type drive_battery_capacity: float
:param times:
Time vector [s].
:type times: numpy.array
:param drive_battery_state_of_charges:
State of charge of the drive battery [%].
:type drive_battery_state_of_charges: numpy.array
:return:
Drive battery current vector [A].
:rtype: numpy.array
"""
from .service import calculate_service_battery_currents_v1 as func
return func(drive_battery_capacity, times, drive_battery_state_of_charges)
[docs]@sh.add_function(dsp, outputs=['drive_battery_capacity'])
def identify_drive_battery_capacity(
times, drive_battery_currents, drive_battery_state_of_charges):
"""
Identify drive battery capacity [Ah].
:param times:
Time vector [s].
:type times: numpy.array
:param drive_battery_currents:
Drive battery current vector [A].
:type drive_battery_currents: numpy.array
:param drive_battery_state_of_charges:
State of charge of the drive battery [%].
:type drive_battery_state_of_charges: numpy.array
:return:
Drive battery capacity [Ah].
:rtype: float
"""
from .service import identify_service_battery_capacity as func
return func(times, drive_battery_currents, drive_battery_state_of_charges)
[docs]@sh.add_function(dsp, outputs=['drive_battery_electric_powers'])
def calculate_drive_battery_electric_powers(
drive_battery_currents, drive_battery_voltages):
"""
Calculate the drive battery electric power [kW].
:param drive_battery_currents:
Drive battery current vector [A].
:type drive_battery_currents: numpy.array
:param drive_battery_voltages:
Drive battery voltage [V].
:type drive_battery_voltages: float
:return:
Drive battery electric power [kW].
:rtype: numpy.array
"""
from .service import calculate_service_battery_electric_powers as func
return func(drive_battery_currents, drive_battery_voltages)
[docs]@sh.add_function(dsp, outputs=['drive_battery_electric_powers'], weight=1)
def calculate_drive_battery_electric_powers_v1(
drive_battery_loads, motors_electric_powers,
dcdc_converter_electric_powers_demand):
"""
Calculate the drive battery electric power [kW].
:param drive_battery_loads:
Drive battery load vector [kW].
:type drive_battery_loads: numpy.array
:param motors_electric_powers:
Cumulative motors electric power [kW].
:type motors_electric_powers: numpy.array
:param dcdc_converter_electric_powers_demand:
DC/DC converter electric power demand [kW].
:type dcdc_converter_electric_powers_demand: numpy.array
:return:
Drive battery electric power [kW].
:rtype: numpy.array
"""
p = drive_battery_loads - motors_electric_powers
p += dcdc_converter_electric_powers_demand
return p
[docs]@sh.add_function(dsp, outputs=['initial_drive_battery_state_of_charge'])
def identify_initial_drive_battery_state_of_charge(
drive_battery_state_of_charges):
"""
Identify the initial state of charge of drive battery [%].
:param drive_battery_state_of_charges:
State of charge of the drive battery [%].
:type drive_battery_state_of_charges: numpy.array
:return:
Initial state of charge of the drive battery [%].
:rtype: float
"""
return drive_battery_state_of_charges[0]
[docs]@sh.add_function(
dsp, outputs=['initial_drive_battery_state_of_charge'], weight=10
)
def default_initial_drive_battery_state_of_charge(
electrical_hybridization_degree):
"""
Return the default initial state of charge of drive battery [%].
:param electrical_hybridization_degree:
Electrical hybridization degree (i.e., mild, full, plugin, electric).
:type electrical_hybridization_degree: str
:return:
Initial state of charge of the drive battery [%].
:rtype: float
"""
d = dfl.functions.default_initial_drive_battery_state_of_charge
return d.initial_state_of_charge[electrical_hybridization_degree]
[docs]@sh.add_function(dsp, outputs=['drive_battery_state_of_charges'])
def calculate_drive_battery_state_of_charges(
drive_battery_capacity, initial_drive_battery_state_of_charge,
times, drive_battery_currents):
"""
Calculates the state of charge of the drive battery [%].
:param drive_battery_capacity:
Drive battery capacity [Ah].
:type drive_battery_capacity: float
:param initial_drive_battery_state_of_charge:
Initial state of charge of the drive battery [%].
:type initial_drive_battery_state_of_charge: float
:param times:
Time vector [s].
:type times: numpy.array
:param drive_battery_currents:
Drive battery current vector [A].
:type drive_battery_currents: numpy.array
:return:
State of charge of the drive battery [%].
:rtype: numpy.array
"""
from .service import calculate_service_battery_state_of_charges as func
return func(
drive_battery_capacity, initial_drive_battery_state_of_charge, times,
drive_battery_currents
)
[docs]@sh.add_function(dsp, outputs=['drive_battery_loads'])
def calculate_drive_battery_loads(
drive_battery_electric_powers, motors_electric_powers,
dcdc_converter_electric_powers_demand):
"""
Calculates drive battery load vector [kW].
:param drive_battery_electric_powers:
Drive battery electric power [kW].
:type drive_battery_electric_powers: numpy.array
:param motors_electric_powers:
Cumulative motors electric power [kW].
:type motors_electric_powers: numpy.array
:param dcdc_converter_electric_powers_demand:
DC/DC converter electric power demand [kW].
:type dcdc_converter_electric_powers_demand: numpy.array
:return:
Drive battery load vector [kW].
:rtype: numpy.array
"""
p = drive_battery_electric_powers + motors_electric_powers
p -= dcdc_converter_electric_powers_demand
return p
dsp.add_data('drive_battery_load', 0, sh.inf(11, 0))
[docs]@sh.add_function(dsp, outputs=['drive_battery_loads'])
def calculate_drive_battery_loads_v1(times, drive_battery_load):
"""
Calculates drive battery load vector [kW].
:param times:
Time vector [s].
:type times: numpy.array
:param drive_battery_load:
Drive electric load [kW].
:type drive_battery_load: float
:return:
Drive battery load vector [kW].
:rtype: numpy.array
"""
return np.tile(drive_battery_load, times.shape)
[docs]@sh.add_function(dsp, outputs=['drive_battery_load'])
def identify_drive_battery_load(drive_battery_loads):
"""
Identifies drive electric load [kW].
:param drive_battery_loads:
Drive battery load vector [kW].
:type drive_battery_loads: numpy.array
:return:
Drive electric load [kW].
:rtype: float
"""
return co2_utl.reject_outliers(drive_battery_loads)[0]
[docs]@sh.add_function(dsp, outputs=['drive_battery_delta_state_of_charge'])
def calculate_drive_battery_delta_state_of_charge(
drive_battery_state_of_charges):
"""
Calculates the overall delta state of charge of the drive battery [%].
:param drive_battery_state_of_charges:
State of charge of the drive battery [%].
:type drive_battery_state_of_charges: numpy.array
:return:
Overall delta state of charge of the drive battery [%].
:rtype: float
"""
from .service import calculate_service_battery_delta_state_of_charge as func
return func(drive_battery_state_of_charges)
# noinspection PyMissingOrEmptyDocstring,PyProtectedMember
[docs]class DriveBatteryModel:
[docs] def __init__(self, service_battery_model, drive_battery_load,
initial_drive_battery_state_of_charge, drive_battery_capacity,
dcdc_converter_efficiency=.95, r0=None, ocv=None,
n_parallel_cells=1, n_series_cells=1):
self.r0, self.ocv = r0, ocv
self.np, self.ns = n_parallel_cells, n_series_cells
r0 is not None and ocv is not None and self.compile()
self.service = service_battery_model
self.dcdc_efficiency = dcdc_converter_efficiency
self._d_soc = drive_battery_capacity * 36.0 * 2
self.init_soc = initial_drive_battery_state_of_charge
self.drive_battery_load = drive_battery_load
self.reset()
self._dcdc_p = self.service.nominal_voltage / 1e3
# noinspection PyAttributeOutsideInit
[docs] def reset(self):
self.service.reset()
self._prev_current = 0
self._prev_soc = self.init_soc
# noinspection PyAttributeOutsideInit
[docs] def compile(self):
n = self.np / self.r0
self.c_b, self.c_ac4 = -self.ocv * n / 2, 1e3 / self.ns * n
self.c_b2 = self.c_b ** 2
self.v_b = self.ocv * self.ns / 2
self.v_ac4 = self.ns / n * 1e3
self.v_b2 = self.v_b ** 2
self._ocv = self.ocv * self.ns / 1e3
self._r0 = self.ns / n / 1e3
[docs] def fit(self, currents, voltages):
from sklearn.linear_model import RANSACRegressor
m = RANSACRegressor(random_state=0)
m.fit(currents[:, None], voltages)
r0, ocv = float(m.estimator_.coef_), float(m.estimator_.intercept_)
self.r0 = r0 * self.np / self.ns
self.ocv = ocv / self.ns
self.compile()
return self
[docs] def currents(self, powers):
cur = np.sqrt(np.maximum(self.c_b2 + self.c_ac4 * powers, 0)) + self.c_b
return cur
[docs] def powers(self, currents):
return (self._ocv + currents * self._r0) * currents
[docs] def voltages(self, powers):
vol = self.v_b + np.sqrt(np.maximum(self.v_b2 + self.v_ac4 * powers, 0))
return vol
def __call__(self, current, time, motive_power, acceleration, on_engine,
starter_current=0, prev_soc=None, update=True,
service_kw=None):
dt = time - self.service._prev_time
from ..motors.p4 import calculate_motor_p4_powers_v1 as f
dcdc_p = f(self.service(
time, motive_power, acceleration, on_engine, starter_current,
update=update, **(service_kw or {})
)[2] * self._dcdc_p, self.dcdc_efficiency) + self.drive_battery_load
if prev_soc is None:
prev_soc = self._prev_soc
current = current + self.currents(dcdc_p)
dsoc = (current + self._prev_current) * dt / self._d_soc
soc = max(0.0, min(prev_soc + dsoc, 100.0))
if update:
self._prev_soc, self._prev_current = soc, current
return soc
[docs]@sh.add_function(dsp, outputs=['drive_battery_model'])
def calibrate_drive_battery_model(
service_battery_model, initial_drive_battery_state_of_charge,
drive_battery_capacity, drive_battery_n_parallel_cells,
drive_battery_n_series_cells, drive_battery_currents,
dcdc_converter_efficiency, drive_battery_load, drive_battery_voltages):
"""
Calibrate the drive battery current model.
:param service_battery_model:
Service battery model.
:type service_battery_model: ServiceBatteryModel
:param initial_drive_battery_state_of_charge:
Initial state of charge of the drive battery [%].
:type initial_drive_battery_state_of_charge: float
:param drive_battery_capacity:
Drive battery capacity [Ah].
:type drive_battery_capacity: float
:param drive_battery_n_parallel_cells:
Number of battery cells in parallel [-].
:type drive_battery_n_parallel_cells: int
:param drive_battery_n_series_cells:
Number of battery cells in series [-].
:type drive_battery_n_series_cells: int
:param drive_battery_currents:
Drive battery current vector [A].
:type drive_battery_currents: numpy.array
:param dcdc_converter_efficiency:
DC/DC converter efficiency [-].
:type dcdc_converter_efficiency: float
:param drive_battery_load:
Drive electric load [kW].
:type drive_battery_load: float
:param drive_battery_voltages:
Drive battery voltage [V].
:type drive_battery_voltages: numpy.array
:return:
Drive battery current model.
:rtype: DriveBatteryModel
"""
return DriveBatteryModel(
service_battery_model, drive_battery_load,
initial_drive_battery_state_of_charge, drive_battery_capacity,
dcdc_converter_efficiency,
n_parallel_cells=drive_battery_n_parallel_cells,
n_series_cells=drive_battery_n_series_cells
).fit(drive_battery_currents, drive_battery_voltages)
[docs]@sh.add_function(dsp, outputs=['drive_battery_r0', 'drive_battery_ocv'])
def get_drive_battery_r0_and_ocv(drive_battery_model):
"""
Returns drive battery resistance and open circuit voltage [ohm, V].
:param drive_battery_model:
Drive battery current model.
:type drive_battery_model: DriveBatteryModel
:return:
Drive battery resistance and open circuit voltage [ohm, V].
:rtype: float, float
"""
return drive_battery_model.r0, drive_battery_model.ocv
dsp.add_data('drive_battery_n_cells', 1)
[docs]@sh.add_function(dsp, outputs=['drive_battery_nominal_voltage'])
def identify_drive_battery_nominal_voltage(drive_battery_voltages):
"""
Identify the drive battery nominal voltage [V].
:param drive_battery_voltages:
Drive battery voltage [V].
:type drive_battery_voltages: numpy.array
:return:
Drive battery nominal voltage [V].
:rtype: float
"""
return np.median(drive_battery_voltages[drive_battery_voltages > dfl.EPS])
[docs]@sh.add_function(dsp, outputs=['drive_battery_voltages'], weight=sh.inf(15, 0))
def define_drive_battery_voltages(times, drive_battery_nominal_voltage):
"""
Defines drive battery voltage vector [V].
:param times:
Time vector [s].
:type times: numpy.array
:param drive_battery_nominal_voltage:
Drive battery nominal voltage [V].
:type drive_battery_nominal_voltage: float
:return:
Drive battery voltage [V].
:rtype: numpy.array
"""
return np.tile(drive_battery_nominal_voltage, times.size)
[docs]@sh.add_function(dsp, outputs=['drive_battery_n_parallel_cells'])
def calculate_drive_battery_n_parallel_cells(
drive_battery_n_cells, drive_battery_n_series_cells):
"""
Calculate the number of battery cells in parallel [-].
:param drive_battery_n_cells:
Number of battery cells [-].
:type drive_battery_n_cells: int
:param drive_battery_n_series_cells:
Number of battery cells in series [-].
:type drive_battery_n_series_cells: int
:return:
Number of battery cells in parallel [-].
:rtype: int
"""
return drive_battery_n_cells / drive_battery_n_series_cells
dsp.add_data('drive_battery_technology', dfl.values.drive_battery_technology)
[docs]@sh.add_function(dsp, outputs=['drive_battery_n_parallel_cells'], weight=5)
def calculate_drive_battery_n_parallel_cells_v1(
drive_battery_technology, drive_battery_nominal_voltage,
drive_battery_n_cells):
"""
Calculate the number of battery cells in parallel [-].
:param drive_battery_technology:
Drive battery technology type (e.g., NiMH, Li-NCA (Li-Ni-Co-Al), etc.).
:type drive_battery_technology: str
:param drive_battery_n_cells:
Number of battery cells [-].
:type drive_battery_n_cells: int
:param drive_battery_nominal_voltage:
Drive battery nominal voltage [V].
:type drive_battery_nominal_voltage: float
:return:
Number of battery cells in parallel [-].
:rtype: int
"""
v = dfl.functions.calculate_drive_battery_n_parallel_cells_v1.reference_volt
v = v.get(drive_battery_technology, v['unknown'])
n = np.ceil(v / drive_battery_nominal_voltage * drive_battery_n_cells)
n = int(min(n, drive_battery_n_cells))
while n < drive_battery_n_cells and drive_battery_n_cells % n:
n += 1
return n
[docs]@sh.add_function(dsp, outputs=['drive_battery_n_series_cells'])
def calculate_drive_battery_n_series_cells(
drive_battery_n_cells, drive_battery_n_parallel_cells):
"""
Calculate the number of battery cells in parallel [-].
:param drive_battery_n_cells:
Number of battery cells [-].
:type drive_battery_n_cells: int
:param drive_battery_n_parallel_cells:
Number of battery cells in parallel [-].
:type drive_battery_n_parallel_cells: int
:return:
Number of battery cells in series [-].
:rtype: int
"""
return drive_battery_n_cells / drive_battery_n_parallel_cells
[docs]@sh.add_function(dsp, outputs=['drive_battery_n_cells'])
def calculate_drive_battery_n_cells(
drive_battery_n_parallel_cells, drive_battery_n_series_cells):
"""
Calculate the number of battery cells [-].
:param drive_battery_n_parallel_cells:
Number of battery cells in parallel [-].
:type drive_battery_n_parallel_cells: int
:param drive_battery_n_series_cells:
Number of battery cells in series [-].
:type drive_battery_n_series_cells: int
:return:
Number of battery cells [-].
:rtype: int
"""
return drive_battery_n_parallel_cells * drive_battery_n_series_cells
[docs]@sh.add_function(dsp, outputs=['drive_battery_model'])
def define_drive_battery_model(
service_battery_model, drive_battery_load, dcdc_converter_efficiency,
initial_drive_battery_state_of_charge, drive_battery_capacity,
drive_battery_r0, drive_battery_ocv, drive_battery_n_parallel_cells,
drive_battery_n_series_cells):
"""
Define the drive battery current model.
:param service_battery_model:
Service battery model.
:type service_battery_model: ServiceBatteryModel
:param dcdc_converter_efficiency:
DC/DC converter efficiency [-].
:type dcdc_converter_efficiency: float
:param drive_battery_load:
Drive electric load [kW].
:type drive_battery_load: float
:param initial_drive_battery_state_of_charge:
Initial state of charge of the drive battery [%].
:type initial_drive_battery_state_of_charge: float
:param drive_battery_capacity:
Drive battery capacity [Ah].
:type drive_battery_capacity: float
:param drive_battery_r0:
Drive battery resistance [ohm].
:type drive_battery_r0: float
:param drive_battery_ocv:
Drive battery open circuit voltage [V].
:type drive_battery_ocv: float
:param drive_battery_n_parallel_cells:
Number of battery cells in parallel [-].
:type drive_battery_n_parallel_cells: int
:param drive_battery_n_series_cells:
Number of battery cells in series [-].
:type drive_battery_n_series_cells: int
:return:
Drive battery current model.
:rtype: DriveBatteryModel
"""
return DriveBatteryModel(
service_battery_model, drive_battery_load,
initial_drive_battery_state_of_charge, drive_battery_capacity,
dcdc_converter_efficiency, drive_battery_r0, drive_battery_ocv,
drive_battery_n_parallel_cells, drive_battery_n_series_cells
)
[docs]@sh.add_function(dsp, outputs=['drive_battery_voltages'])
def calculate_drive_battery_voltages_v1(
drive_battery_electric_powers, drive_battery_model):
"""
Calculate the drive battery current vector [A].
:param drive_battery_electric_powers:
Drive battery electric power [kW].
:type drive_battery_electric_powers: numpy.array
:param drive_battery_model:
Drive battery current model.
:type drive_battery_model: DriveBatteryModel
:return:
Drive battery current vector [A].
:rtype: numpy.array
"""
return drive_battery_model.voltages(drive_battery_electric_powers)
[docs]@sh.add_function(dsp, outputs=['motors_electric_powers'])
def calculate_motors_electric_powers(
motor_p0_electric_powers, motor_p1_electric_powers,
motor_p2_planetary_electric_powers, motor_p2_electric_powers,
motor_p3_front_electric_powers, motor_p3_rear_electric_powers,
motor_p4_front_electric_powers, motor_p4_rear_electric_powers):
"""
Calculate motors electric power [kW].
:param motor_p0_electric_powers:
Electric power of motor P0 [kW].
:type motor_p0_electric_powers: numpy.array | float
:param motor_p1_electric_powers:
Electric power of motor P1 [kW].
:type motor_p1_electric_powers: numpy.array | float
:param motor_p2_planetary_electric_powers:
Electric power of planetary motor P2 [kW].
:type motor_p2_planetary_electric_powers: numpy.array | float
:param motor_p2_electric_powers:
Electric power of motor P2 [kW].
:type motor_p2_electric_powers: numpy.array | float
:param motor_p3_front_electric_powers:
Electric power of motor P3 front [kW].
:type motor_p3_front_electric_powers: numpy.array | float
:param motor_p3_rear_electric_powers:
Electric power of motor P3 rear [kW].
:type motor_p3_rear_electric_powers: numpy.array | float
:param motor_p4_front_electric_powers:
Electric power of motor P4 front [kW].
:type motor_p4_front_electric_powers: numpy.array | float
:param motor_p4_rear_electric_powers:
Electric power of motor P4 rear [kW].
:type motor_p4_rear_electric_powers: numpy.array | float
:return:
Cumulative motors electric power [kW].
:rtype: numpy.array | float
"""
p = motor_p0_electric_powers + motor_p1_electric_powers
p += motor_p2_planetary_electric_powers
p += motor_p2_electric_powers
p += motor_p3_front_electric_powers
p += motor_p3_rear_electric_powers
p += motor_p4_front_electric_powers
p += motor_p4_rear_electric_powers
return p
[docs]@sh.add_function(dsp, outputs=['motors_electric_powers'])
def calculate_motors_electric_powers_v1(
drive_battery_electric_powers, dcdc_converter_electric_powers_demand,
drive_battery_loads):
"""
Calculates drive battery load vector [kW].
:param drive_battery_electric_powers:
Drive battery electric power [kW].
:type drive_battery_electric_powers: numpy.array
:param drive_battery_loads:
Drive battery load vector [kW].
:type drive_battery_loads: numpy.array
:param dcdc_converter_electric_powers_demand:
DC/DC converter electric power demand [kW].
:type dcdc_converter_electric_powers_demand: numpy.array
:return:
Cumulative motors electric power [kW].
:rtype: numpy.array
"""
p = drive_battery_loads - drive_battery_electric_powers
p += dcdc_converter_electric_powers_demand
return p