# -*- 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 service battery (low voltage).
Sub-Modules:
.. currentmodule:: co2mpas.core.model.physical.electrics.batteries.service
.. autosummary::
:nosignatures:
:toctree: service/
status
"""
import functools
import numpy as np
import schedula as sh
from co2mpas.defaults import dfl
import co2mpas.utils as co2_utl
from .status import dsp as _status
dsp = sh.BlueDispatcher(
name='Service Battery',
description='Models the service battery (e.g., low voltage).'
)
[docs]@sh.add_function(dsp, outputs=['service_battery_currents'])
def calculate_service_battery_currents(
service_battery_electric_powers, service_battery_nominal_voltage):
"""
Calculate the service battery current vector [A].
:param service_battery_electric_powers:
Service battery electric power [kW].
:type service_battery_electric_powers: numpy.array
:param service_battery_nominal_voltage:
Service battery nominal voltage [V].
:type service_battery_nominal_voltage: float
:return:
Service battery current vector [A].
:rtype: numpy.array
"""
c = 1000.0 / service_battery_nominal_voltage
return service_battery_electric_powers * c
[docs]@sh.add_function(dsp, outputs=['service_battery_currents'], weight=sh.inf(1, 0))
def calculate_service_battery_currents_v1(
service_battery_capacity, times, service_battery_state_of_charges):
"""
Calculate the service battery current vector [A].
:param service_battery_capacity:
Service battery capacity [Ah].
:type service_battery_capacity: float
:param times:
Time vector [s].
:type times: numpy.array
:param service_battery_state_of_charges:
State of charge of the service battery [%].
:type service_battery_state_of_charges: numpy.array
:return:
Service battery current vector [A].
:rtype: numpy.array
"""
from scipy.interpolate import UnivariateSpline as Spline
soc = service_battery_state_of_charges
ib = Spline(times, soc, w=np.tile(10, times.shape[0])).derivative()(times)
return ib * (service_battery_capacity * 36.0)
[docs]@sh.add_function(dsp, outputs=['service_battery_capacity'])
def identify_service_battery_capacity(
times, service_battery_currents, service_battery_state_of_charges):
"""
Identify service battery capacity [Ah].
:param times:
Time vector [s].
:type times: numpy.array
:param service_battery_currents:
Service battery current vector [A].
:type service_battery_currents: numpy.array
:param service_battery_state_of_charges:
State of charge of the service battery [%].
:type service_battery_state_of_charges: numpy.array
:return:
Service battery capacity [Ah].
:rtype: float
"""
soc = service_battery_state_of_charges
ib = calculate_service_battery_currents_v1(1, times, soc)
b = (ib < -dfl.EPS) | (ib > dfl.EPS)
return co2_utl.reject_outliers(service_battery_currents[b] / ib[b])[0]
[docs]@sh.add_function(dsp, outputs=['service_battery_electric_powers'])
def calculate_service_battery_electric_powers(
service_battery_currents, service_battery_nominal_voltage):
"""
Calculate the service battery electric power [kW].
:param service_battery_currents:
Service battery current vector [A].
:type service_battery_currents: numpy.array
:param service_battery_nominal_voltage:
Service battery nominal voltage [V].
:type service_battery_nominal_voltage: float
:return:
Service battery electric power [kW].
:rtype: numpy.array
"""
return service_battery_currents * (service_battery_nominal_voltage / 1000.0)
[docs]@sh.add_function(dsp, outputs=['service_battery_electric_powers_supply'])
def calculate_service_battery_electric_powers_supply(
alternator_electric_powers, dcdc_converter_electric_powers,
starter_electric_powers):
"""
Calculate the service battery electric power supply [kW].
:param alternator_electric_powers:
Alternator electric power [kW].
:type alternator_electric_powers: numpy.array
:param dcdc_converter_electric_powers:
DC/DC converter electric power [kW].
:type dcdc_converter_electric_powers: numpy.array
:param starter_electric_powers:
Starter electric power [kW].
:type starter_electric_powers: numpy.array
:return:
Service battery electric power supply [kW].
:rtype: numpy.array
"""
p = alternator_electric_powers + dcdc_converter_electric_powers
p += starter_electric_powers
return p
[docs]@sh.add_function(dsp, outputs=['service_battery_electric_powers'], weight=1)
def calculate_service_battery_electric_powers_v1(
service_battery_loads, service_battery_electric_powers_supply):
"""
Calculate the service battery electric power [kW].
:param service_battery_loads:
Service battery load vector [kW].
:type service_battery_loads: numpy.array
:param service_battery_electric_powers_supply:
Service battery electric power supply [kW].
:type service_battery_electric_powers_supply: numpy.array
:return:
Service battery electric power [kW].
:rtype: numpy.array
"""
return service_battery_loads - service_battery_electric_powers_supply
[docs]@sh.add_function(dsp, outputs=['initial_service_battery_state_of_charge'])
def identify_initial_service_battery_state_of_charge(
service_battery_state_of_charges):
"""
Identify the initial state of charge of service battery [%].
:param service_battery_state_of_charges:
State of charge of the service battery [%].
:type service_battery_state_of_charges: numpy.array
:return:
Initial state of charge of the service battery [%].
:rtype: float
"""
return service_battery_state_of_charges[0]
[docs]@sh.add_function(
dsp, outputs=['initial_service_battery_state_of_charge'], weight=10
)
def default_initial_service_battery_state_of_charge(cycle_type):
"""
Return the default initial state of charge of service battery [%].
:param cycle_type:
Cycle type (WLTP or NEDC).
:type cycle_type: str
:return:
Initial state of charge of the service battery [%].
:rtype: float
"""
d = dfl.functions.default_initial_service_battery_state_of_charge
return d.initial_state_of_charge[cycle_type]
[docs]@sh.add_function(dsp, outputs=['service_battery_state_of_charges'])
def calculate_service_battery_state_of_charges(
service_battery_capacity, initial_service_battery_state_of_charge,
times, service_battery_currents):
"""
Calculates the state of charge of the service battery [%].
:param service_battery_capacity:
Service battery capacity [Ah].
:type service_battery_capacity: float
:param initial_service_battery_state_of_charge:
Initial state of charge of the service battery [%].
:type initial_service_battery_state_of_charge: float
:param times:
Time vector [s].
:type times: numpy.array
:param service_battery_currents:
Service battery current vector [A].
:type service_battery_currents: numpy.array
:return:
State of charge of the service battery [%].
:rtype: numpy.array
"""
soc = np.empty_like(times, float)
soc[0] = initial_service_battery_state_of_charge
bc = (service_battery_currents[:-1] + service_battery_currents[1:])
bc *= np.diff(times)
bc /= 2.0 * service_battery_capacity * 36.0
for i, v in enumerate(bc, 1):
soc[i] = max(0.0, min(soc[i - 1] + v, 100.0))
return soc
[docs]@sh.add_function(dsp, outputs=['service_battery_loads'])
def calculate_service_battery_loads(
service_battery_electric_powers,
service_battery_electric_powers_supply):
"""
Calculates service battery load vector [kW].
:param service_battery_electric_powers:
Service battery electric power [kW].
:type service_battery_electric_powers: numpy.array
:param service_battery_electric_powers_supply:
Service battery electric power supply [kW].
:type service_battery_electric_powers_supply: numpy.array
:return:
Service battery load vector [kW].
:rtype: numpy.array
"""
p = service_battery_electric_powers + service_battery_electric_powers_supply
return p
[docs]@sh.add_function(dsp, outputs=['service_battery_loads'])
def calculate_service_battery_loads_v1(on_engine, service_battery_load):
"""
Calculates service battery load vector [kW].
:param on_engine:
If the engine is on [-].
:type on_engine: numpy.array
:param service_battery_load:
Service electric load (engine off and on) [kW].
:type service_battery_load: float, float
:return:
Service battery load vector [kW].
:rtype: numpy.array
"""
return np.where(on_engine, *service_battery_load[::-1])
[docs]@sh.add_function(dsp, outputs=['service_battery_load'])
def identify_service_battery_load(
service_battery_loads, engine_powers_out, on_engine):
"""
Identifies service electric load (engine off and on) [kW].
:param service_battery_loads:
Service battery load vector [kW].
:type service_battery_loads: numpy.array
:param engine_powers_out:
Engine power vector [kW].
:type engine_powers_out: numpy.array
:param on_engine:
If the engine is on [-].
:type on_engine: numpy.array
:return:
Service electric load (engine off and on) [kW].
:rtype: float, float
"""
rjo, mae = co2_utl.reject_outliers, co2_utl.mae
p, b = service_battery_loads, engine_powers_out >= -dfl.EPS
on = min(0.0, co2_utl.reject_outliers(p[on_engine & b], med=np.mean)[0])
off, b_off = on, b & ~on_engine & (p < 0)
if b_off.any():
off = rjo(p[b_off], med=np.mean)[0]
if on > off:
p = p[b]
if mae(p, on) > mae(p, off):
on = off
else:
off = on
return off, on
[docs]@sh.add_function(dsp, outputs=['service_battery_delta_state_of_charge'])
def calculate_service_battery_delta_state_of_charge(
service_battery_state_of_charges):
"""
Calculates the overall delta state of charge of the service battery [%].
:param service_battery_state_of_charges:
State of charge of the service battery [%].
:type service_battery_state_of_charges: numpy.array
:return:
Overall delta state of charge of the service battery [%].
:rtype: float
"""
soc = service_battery_state_of_charges
return soc[-1] - soc[0]
dsp.add_dispatcher(
dsp_id='status_model',
dsp=_status,
inputs=(
'service_battery_start_window_width', 'service_battery_nominal_voltage',
'service_battery_state_of_charge_balance', 'on_engine', 'accelerations',
'alternator_electric_powers', 'dcdc_converter_electric_powers', 'times',
'service_battery_initialization_time', 'service_battery_status_model',
'service_battery_electric_powers_supply_threshold', 'motive_powers',
'service_battery_state_of_charges', 'engine_starts', 'is_hybrid',
'service_battery_state_of_charge_balance_window',
'service_battery_capacity',
),
outputs=(
'service_battery_initialization_time', 'service_battery_status_model',
'service_battery_electric_powers_supply_threshold',
'service_battery_state_of_charge_balance_window',
'service_battery_state_of_charge_balance',
'service_battery_charging_statuses',
)
)
# noinspection PyMissingOrEmptyDocstring
[docs]class ServiceBatteryModel:
[docs] def __init__(self, service_battery_status_model, dcdc_current_model,
alternator_current_model, has_energy_recuperation,
service_battery_initialization_time, service_battery_load,
initial_service_battery_state_of_charge,
service_battery_nominal_voltage, service_battery_capacity):
self.status = functools.partial(
service_battery_status_model.predict, has_energy_recuperation,
service_battery_initialization_time
)
self.nominal_voltage = service_battery_nominal_voltage
self.dcdc = dcdc_current_model
self.alternator = alternator_current_model
self.current_load = np.divide(
service_battery_load, service_battery_nominal_voltage / 1e3
)
self._d_soc = service_battery_capacity * 36.0 * 2
self.init_soc = initial_service_battery_state_of_charge
self.reset()
# noinspection PyAttributeOutsideInit
[docs] def reset(self):
self._prev_status = 0
self._prev_time = 0
self._prev_current = 0
self._prev_soc = self.init_soc
def __call__(self, time, motive_power, acceleration, on_engine,
starter_current=0, prev_soc=None, prev_status=None,
update=True):
if prev_status is None:
prev_status = self._prev_status
if prev_soc is None:
prev_soc = self._prev_soc
alt_c = dcdc_c = .0
status_ = self.status(time, prev_status, prev_soc, motive_power)
if status_:
if status_ == 1:
dcdc_c = self.dcdc(time, prev_soc, status_)
if on_engine:
alt_c = self.alternator(
time, prev_soc, status_, motive_power, acceleration
)
c = self.current_load[int(on_engine)] - alt_c - dcdc_c - starter_current
dsoc = (c + self._prev_current) * (time - self._prev_time) / self._d_soc
soc = max(0.0, min(prev_soc + dsoc, 100.0))
if update:
self._prev_status, self._prev_soc = status_, soc
self._prev_time, self._prev_current = time, c
return soc, status_, dcdc_c, alt_c
dsp.add_data('has_energy_recuperation', dfl.values.has_energy_recuperation)
[docs]@sh.add_function(dsp, outputs=['service_battery_model'])
def define_service_battery_model(
service_battery_status_model, dcdc_current_model,
alternator_current_model, has_energy_recuperation,
service_battery_initialization_time, service_battery_load,
initial_service_battery_state_of_charge,
service_battery_nominal_voltage, service_battery_capacity):
"""
Define a service battery model.
:param service_battery_status_model:
A function that predicts the service battery charging status.
:type service_battery_status_model: BatteryStatusModel
:param dcdc_current_model:
DC/DC converter current model.
:type dcdc_current_model: callable
:param alternator_current_model:
Alternator current model.
:type alternator_current_model: callable
:param has_energy_recuperation:
Is the vehicle equipped with any brake energy recuperation technology?
:type has_energy_recuperation: bool
:param service_battery_initialization_time:
Service battery initialization time delta [s].
:type service_battery_initialization_time: float
:param service_battery_load:
Service electric load (engine off and on) [kW].
:type service_battery_load: float, float
:param initial_service_battery_state_of_charge:
Initial state of charge of the service battery [%].
:type initial_service_battery_state_of_charge: float
:param service_battery_nominal_voltage:
Service battery nominal voltage [V].
:type service_battery_nominal_voltage: float
:param service_battery_capacity:
Service battery capacity [Ah].
:type service_battery_capacity: float
:return:
Service battery model.
:rtype: ServiceBatteryModel
"""
return ServiceBatteryModel(
service_battery_status_model, dcdc_current_model,
alternator_current_model, has_energy_recuperation,
service_battery_initialization_time, service_battery_load,
initial_service_battery_state_of_charge,
service_battery_nominal_voltage, service_battery_capacity
)