# -*- 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 mechanic of the wheels.
"""
import math
import regex
import schedula as sh
from co2mpas.defaults import dfl
dsp = sh.BlueDispatcher(
name='Wheel model', description='It models the wheel dynamics.'
)
[docs]def calculate_wheel_power(velocities, accelerations, road_loads, vehicle_mass):
"""
Calculates the wheel power [kW].
:param velocities:
Velocity [km/h].
:type velocities: numpy.array | float
:param accelerations:
Acceleration [m/s2].
:type accelerations: numpy.array | float
:param road_loads:
Cycle road loads [N, N/(km/h), N/(km/h)^2].
:type road_loads: list, tuple
:param vehicle_mass:
Vehicle mass [kg].
:type vehicle_mass: float
:return:
Power at wheels [kW].
:rtype: numpy.array | float
"""
f0, f1, f2 = road_loads
quadratic_term = f0 + (f1 + f2 * velocities) * velocities
vel = velocities / 3600
return (quadratic_term + 1.03 * vehicle_mass * accelerations) * vel
# noinspection PyIncorrectDocstring,SpellCheckingInspection
[docs]@sh.add_function(dsp, outputs=['wheel_torques'])
def calculate_wheel_torques(wheel_powers, wheel_speeds, coef=30000 / math.pi):
"""
Calculates torque at the wheels [N*m].
:param wheel_powers:
Power at the wheels [kW].
:type wheel_powers: numpy.array | float
:param wheel_speeds:
Rotating speed of the wheel [RPM].
:type wheel_speeds: numpy.array | float
:return:
Torque at the wheels [N*m].
:rtype: numpy.array | float
"""
import numpy as np
with np.errstate(divide='ignore', invalid='ignore'):
torques = np.divide(wheel_powers, wheel_speeds) * coef
return np.where(wheel_speeds, torques, 0)
[docs]@sh.add_function(dsp, outputs=['wheel_powers'])
def calculate_wheel_powers(wheel_torques, wheel_speeds):
"""
Calculates power at the wheels [kW].
:param wheel_torques:
Torque at the wheel [N*m].
:type wheel_torques: numpy.array | float
:param wheel_speeds:
Rotating speed of the wheel [RPM].
:type wheel_speeds: numpy.array | float
:return:
Power at the wheels [kW].
:rtype: numpy.array | float
"""
return wheel_torques * wheel_speeds * (math.pi / 30000.0)
def _compile_speed_function(r_dynamic):
c = 30.0 / (3.6 * math.pi * r_dynamic)
def _wheel_speed(velocities):
return velocities * c
return _wheel_speed
[docs]@sh.add_function(dsp, outputs=['wheel_speeds'])
def calculate_wheel_speeds(velocities, r_dynamic):
"""
Calculates rotating speed of the wheels [RPM].
:param velocities:
Vehicle velocity [km/h].
:type velocities: numpy.array | float
:param r_dynamic:
Dynamic radius of the wheels [m].
:type r_dynamic: float
:return:
Rotating speed of the wheel [RPM].
:rtype: numpy.array | float
"""
return _compile_speed_function(r_dynamic)(velocities)
[docs]@sh.add_function(dsp, outputs=['r_dynamic'])
def identify_r_dynamic(
velocity_speed_ratios, gear_box_ratios, final_drive_ratios):
"""
Identifies the dynamic radius of the wheels [m].
:param velocity_speed_ratios:
Constant velocity speed ratios of the gear box [km/(h*RPM)].
:type velocity_speed_ratios: dict[int | float]
:param gear_box_ratios:
Gear box ratios [-].
:type gear_box_ratios: dict[int, float | int]
:param final_drive_ratios:
Final drive ratios [-].
:type final_drive_ratios: dict[int, float | int]
:return:
Dynamic radius of the wheels [m].
:rtype: float
"""
from co2mpas.utils import reject_outliers
from .gear_box.mechanical import calculate_speed_velocity_ratios
svr = calculate_speed_velocity_ratios(
gear_box_ratios, final_drive_ratios, 1
)
r = [svr[k] * vs for k, vs in velocity_speed_ratios.items() if k]
r_dynamic = reject_outliers(r)[0]
return r_dynamic
dsp.add_data('stop_velocity', dfl.values.stop_velocity)
[docs]@sh.add_function(dsp, outputs=['r_dynamic'], weight=10)
def identify_r_dynamic_v1(
velocities, gears, gear_box_speeds_in, gear_box_ratios,
final_drive_ratios, stop_velocity):
"""
Identifies the dynamic radius of the wheels [m].
:param velocities:
Vehicle velocity [km/h].
:type velocities: numpy.array
:param gears:
Gear vector [-].
:type gears: numpy.array
:param gear_box_speeds_in:
Gear box speed [RPM].
:type gear_box_speeds_in: numpy.array
:param gear_box_ratios:
Gear box ratios [-].
:type gear_box_ratios: dict[int, float | int]
:param final_drive_ratios:
Final drive ratios [-].
:type final_drive_ratios: dict[int, float | int]
:param stop_velocity:
Maximum velocity to consider the vehicle stopped [km/h].
:type stop_velocity: float
:return:
Dynamic radius of the wheels [m].
:rtype: float
"""
import numpy as np
from co2mpas.utils import reject_outliers
from .gear_box import mechanical as gb_mec
svr = gb_mec.calculate_speed_velocity_ratios(
gear_box_ratios, final_drive_ratios, 1.0
)
vsr = gb_mec.calculate_velocity_speed_ratios(svr)
speed_x_r_dyn_ratios = gb_mec.calculate_gear_box_speeds_in(
gears, velocities, vsr, stop_velocity
)
with np.errstate(divide='ignore', invalid='ignore'):
r_dynamic = speed_x_r_dyn_ratios / gear_box_speeds_in
r_dynamic = r_dynamic[np.isfinite(r_dynamic)]
r_dynamic = reject_outliers(r_dynamic)[0]
return r_dynamic
[docs]@sh.add_function(dsp, outputs=['r_dynamic'], weight=11)
def identify_r_dynamic_v2(
velocities, gears, engine_speeds_out, gear_box_ratios,
final_drive_ratios, stop_velocity, on_engine):
"""
Identifies the dynamic radius of the wheels [m].
:param velocities:
Vehicle velocity [km/h].
:type velocities: numpy.array
:param gears:
Gear vector [-].
:type gears: numpy.array
:param engine_speeds_out:
Engine speed [RPM].
:type engine_speeds_out: numpy.array
:param gear_box_ratios:
Gear box ratios [-].
:type gear_box_ratios: dict[int, float | int]
:param final_drive_ratios:
Final drive ratios [-].
:type final_drive_ratios: dict[int, float | int]
:param stop_velocity:
Maximum velocity to consider the vehicle stopped [km/h].
:type stop_velocity: float
:param on_engine:
If the engine is on [-].
:type on_engine: numpy.array
:return:
Dynamic radius of the wheels [m].
:rtype: float
"""
return identify_r_dynamic_v1(
velocities[on_engine], gears[on_engine], engine_speeds_out[on_engine],
gear_box_ratios, final_drive_ratios, stop_velocity
)
dsp.add_data('plateau_acceleration', dfl.values.plateau_acceleration)
dsp.add_data('change_gear_window_width', dfl.values.change_gear_window_width)
[docs]@sh.add_function(dsp, outputs=['r_dynamic'], weight=12)
def identify_r_dynamic_v3(
times, velocities, accelerations, r_wheels, gear_box_speeds_in,
gear_box_ratios, final_drive_ratios, idle_engine_speed, stop_velocity,
plateau_acceleration, change_gear_window_width):
"""
Identifies the dynamic radius of the wheels [m].
:param times:
Time vector [s].
:type times: numpy.array
:param velocities:
Vehicle velocity [km/h].
:type velocities: numpy.array
:param accelerations:
Vehicle acceleration [m/s2].
:type accelerations: numpy.array
:param r_wheels:
Radius of the wheels [m].
:type r_wheels: float
:param gear_box_speeds_in:
Gear box speed [RPM].
:type gear_box_speeds_in: numpy.array
:param gear_box_ratios:
Gear box ratios [-].
:type gear_box_ratios: dict[int, float | int]
:param final_drive_ratios:
Final drive ratios [-].
:type final_drive_ratios: dict[int, float | int]
:param idle_engine_speed:
Engine speed idle median and std [RPM].
:type idle_engine_speed: (float, float)
:param stop_velocity:
Maximum velocity to consider the vehicle stopped [km/h].
:type stop_velocity: float
:param plateau_acceleration:
Maximum acceleration to be at constant velocity [m/s2].
:type plateau_acceleration: float
:param change_gear_window_width:
Time window used to apply gear change filters [s].
:type change_gear_window_width: float
:return:
Dynamic radius of the wheels [m].
:rtype: float
"""
from .gear_box import mechanical as gb_mec
svr = gb_mec.calculate_speed_velocity_ratios(
gear_box_ratios, final_drive_ratios, r_wheels
)
gears = gb_mec.identify_gears(
times, velocities, accelerations, gear_box_speeds_in,
gb_mec.calculate_velocity_speed_ratios(svr), stop_velocity,
plateau_acceleration, change_gear_window_width, idle_engine_speed
)
r_dynamic = identify_r_dynamic_v1(
velocities, gears, gear_box_speeds_in, gear_box_ratios,
final_drive_ratios, stop_velocity
)
return r_dynamic
[docs]@sh.add_function(dsp, outputs=['r_dynamic'], weight=13)
def identify_r_dynamic_v4(
times, velocities, accelerations, r_wheels, engine_speeds_out,
gear_box_ratios, final_drive_ratios, idle_engine_speed, stop_velocity,
plateau_acceleration, change_gear_window_width, on_engine):
"""
Identifies the dynamic radius of the wheels [m].
:param times:
Time vector [s].
:type times: numpy.array
:param velocities:
Vehicle velocity [km/h].
:type velocities: numpy.array
:param accelerations:
Vehicle acceleration [m/s2].
:type accelerations: numpy.array
:param r_wheels:
Radius of the wheels [m].
:type r_wheels: float
:param engine_speeds_out:
Engine speed [RPM].
:type engine_speeds_out: numpy.array
:param gear_box_ratios:
Gear box ratios [-].
:type gear_box_ratios: dict[int, float | int]
:param final_drive_ratios:
Final drive ratios [-].
:type final_drive_ratios: dict[int, float | int]
:param idle_engine_speed:
Engine speed idle median and std [RPM].
:type idle_engine_speed: (float, float)
:param stop_velocity:
Maximum velocity to consider the vehicle stopped [km/h].
:type stop_velocity: float
:param plateau_acceleration:
Maximum acceleration to be at constant velocity [m/s2].
:type plateau_acceleration: float
:param change_gear_window_width:
Time window used to apply gear change filters [s].
:type change_gear_window_width: float
:param on_engine:
If the engine is on [-].
:type on_engine: numpy.array
:return:
Dynamic radius of the wheels [m].
:rtype: float
"""
return identify_r_dynamic_v3(
times[on_engine], velocities[on_engine], accelerations[on_engine],
r_wheels, engine_speeds_out[on_engine], gear_box_ratios,
final_drive_ratios, idle_engine_speed, stop_velocity,
plateau_acceleration, change_gear_window_width
)
_re_tyre_code_iso = regex.compile(
r"""
^(?P<use>([a-z]){1,2})?\s*
(?P<nominal_section_width>(\d){3})\s*
\/\s*
(?P<aspect_ratio>(\d){2,3})?
((\s*(?P<carcass>[a-z])\s*)|\s+)
(?P<rim_diameter>(\d){1,2}(\.(\d){1,2})?)
(\s+(?P<use>C))?
(\s+(?P<load_index>(\d){2,3}(/(\d){2,3})?)\s*
(?P<speed_rating>(\([a-z]\)|[a-z]\d?)))?\s*
(\s*((?P<load_range>[a-z])(^| )))?
(\s+(?P<additional_marks>.*))?$
""", regex.IGNORECASE | regex.X | regex.DOTALL)
_re_tyre_code_numeric = regex.compile(
r"""
^((?P<diameter>(\d){2})\s*x\s*)?
(?P<nominal_section_width>(\d){1,2}(\.(\d){1,2})?)\s*
((\s*(?P<carcass>([a-z]|-))\s*)|\s+)
(?P<rim_diameter>(\d){2}(\.(\d){1,2})?)\s*
(?P<use>(LT|C))\s*
((?P<load_index>(\d){2,3}(/(\d){2,3})?)\s*
(?P<speed_rating>(\([a-z]\)|[a-z]\d?)))?\s*
(\s*((?P<load_range>[a-z])(^| )))?
(\s+(?P<additional_marks>.*))?$
""", regex.IGNORECASE | regex.X | regex.DOTALL)
_re_tyre_code_pax = regex.compile(
r"""
^(?P<use>([a-z]){1,2})?\s*
(?P<nominal_section_width>(\d){3})\s*-\s*
(?P<diameter>(\d){2,3})
((\s*(?P<carcass>[a-z])\s*)|\s+)
(?P<rim_diameter>(\d){2,3})
((\s*(?P<load_range>[a-z])?\s*)|\s+)
(\s*(?P<load_index>(\d){2,3})\s*
(?P<speed_rating>[a-z]))?\s*
(\s*(?P<additional_marks>.*))?
""", regex.IGNORECASE | regex.X | regex.DOTALL)
def _format_tyre_dimensions(tyre_dimensions):
import schema
frt = schema.Schema({
schema.Optional('additional_marks'): schema.Use(str),
schema.Optional('aspect_ratio'): schema.Use(float),
schema.Optional('carcass'): schema.Use(str),
'rim_diameter': schema.Use(float),
schema.Optional('diameter'): schema.Use(float),
schema.Optional('load_index'): schema.Use(str),
schema.Optional('load_range'): schema.Use(str),
'nominal_section_width': schema.Use(float),
schema.Optional('speed_rating'): schema.Use(str),
schema.Optional('use'): schema.Use(str),
schema.Optional('code'): schema.Use(str),
})
m = {k: v for k, v in tyre_dimensions.items() if v is not None}
return frt.validate(m)
[docs]@sh.add_function(dsp, outputs=['tyre_dimensions'])
def calculate_tyre_dimensions(tyre_code):
"""
Calculates the tyre dimensions from the tyre code.
:param tyre_code:
Tyre code (e.g.,P225/70R14).
:type tyre_code: str
:return:
Tyre dimensions.
:rtype: dict
"""
import schema
it = [
('iso', _re_tyre_code_iso),
('numeric', _re_tyre_code_numeric),
('pax', _re_tyre_code_pax)
]
for c, _r in it:
try:
m = _r.match(tyre_code).groupdict()
m['code'] = c
if c == 'numeric' and 'aspect_ratio' not in m:
b = m['nominal_section_width'].split('.')[-1][-1] == '5'
m['aspect_ratio'] = '82' if b else '92'
return _format_tyre_dimensions(m)
except (AttributeError, schema.SchemaError):
pass
raise ValueError('Invalid tyre code: %s', tyre_code)
# noinspection PyUnusedLocal
def _format_tyre_code(
nominal_section_width, rim_diameter, aspect_ratio=0, use='', carcass='',
load_index='', speed_rating='', additional_marks='', load_range='',
code='iso', diameter=None, **kw):
if code == 'iso':
parts = (
'%s%d/%d%s%d' % (use, nominal_section_width, aspect_ratio,
carcass or ' ', rim_diameter),
)
elif code == 'numeric':
diameter = '%.2fx' % diameter if diameter is not None else ''
parts = (
'%s%.2f%s%.2f %s' % (diameter, nominal_section_width,
carcass or ' ', rim_diameter, use),
)
else:
parts = (
'%d-%d%s%d%s' % (nominal_section_width, diameter, carcass or ' ',
rim_diameter, load_range),
)
parts += (
'%s%s' % (load_index, speed_rating),
load_range,
additional_marks
)
return ' '.join(p for p in parts if p)
[docs]@sh.add_function(dsp, outputs=['tyre_code'])
def define_tyre_code(tyre_dimensions):
"""
Returns the tyre code from the tyre dimensions.
:param tyre_dimensions:
Tyre dimensions.
.. note:: The fields are : use, nominal_section_width, aspect_ratio,
carcass, diameter, load_index, speed_rating, and additional_marks.
:type tyre_dimensions: dict
:return:
Tyre code (e.g.,P225/70R14).
:rtype: str
"""
return _format_tyre_code(**tyre_dimensions)
[docs]@sh.add_function(dsp, outputs=['tyre_code'], weight=5)
def default_tyre_code(r_dynamic):
"""
Return one of the most popular tyre code according to the r dynamic.
:param r_dynamic:
Dynamic radius of the wheels [m].
:type r_dynamic: float
:return:
Tyre code (e.g.,P225/70R14).
:rtype: str
"""
pop = (
'165/65R13', '155/70R13', '165/70R13', '165/60R14', '185/60R14',
'155/65R14', '165/65R14', '175/65R14', '185/65R14', '165/70R14',
'175/70R14', '195/50R15', '185/55R15', '195/55R15', '185/60R15',
'195/60R15', '205/60R15', '175/65R15', '185/65R15', '195/65R15',
'195/70R15', '195/45R16', '205/45R16', '205/50R16', '195/55R16',
'205/55R16', '215/55R16', '205/60R16', '215/60R16', '215/65R16',
'205/40R17', '245/40R17', '205/45R17', '215/45R17', '225/45R17',
'235/45R17', '205/50R17', '215/50R17', '225/50R17', '215/55R17',
'225/55R17', '215/60R17', '225/65R17', '235/65R17', '225/40R18',
'235/40R18', '245/40R18', '225/45R18', '235/60R18', '255/35R19'
)
def _key_func(c):
r = calculate_r_wheels(calculate_tyre_dimensions(c))
return r <= r_dynamic, (r - r_dynamic) ** 2
return min(pop, key=_key_func)
[docs]@sh.add_function(dsp, outputs=['r_wheels'])
def calculate_r_wheels(tyre_dimensions):
"""
Calculates the radius of the wheels [m] from the tyre dimensions.
:param tyre_dimensions:
Tyre dimensions.
.. note:: The fields are : use, nominal_section_width, aspect_ratio,
carcass, diameter, load_index, speed_rating, and additional_marks.
:type tyre_dimensions: dict
:return:
Radius of the wheels [m].
:rtype: float
"""
if 'diameter' in tyre_dimensions:
if tyre_dimensions['code'] == 'pax':
return tyre_dimensions['diameter'] / 2000 # Diameter is in mm.
return tyre_dimensions['diameter'] * 0.0254 # Diameter is in inches.
a = tyre_dimensions['aspect_ratio'] / 100 # Aspect ratio is Height/Width.
w = tyre_dimensions['nominal_section_width']
if tyre_dimensions.get('code', 'iso') == 'iso':
w /= 1000 # Width is in mm.
else:
w *= 0.0254 # Width is in inches.
dr = tyre_dimensions['rim_diameter'] * 0.0254 # Rim is in inches.
return a * w + dr / 2
dsp.add_data(
data_id='tyre_dynamic_rolling_coefficient', initial_dist=50,
default_value=dfl.values.tyre_dynamic_rolling_coefficient,
)
[docs]@sh.add_function(dsp, outputs=['r_dynamic'])
def calculate_r_dynamic(r_wheels, tyre_dynamic_rolling_coefficient):
"""
Calculates the dynamic radius of the wheels [m].
:param r_wheels:
Radius of the wheels [m].
:type r_wheels: float
:param tyre_dynamic_rolling_coefficient:
Dynamic rolling coefficient [-].
:type tyre_dynamic_rolling_coefficient: float
:return:
Dynamic radius of the wheels [m].
:rtype: float
"""
return tyre_dynamic_rolling_coefficient * r_wheels
[docs]@sh.add_function(dsp, outputs=['tyre_dynamic_rolling_coefficient'])
def identify_tyre_dynamic_rolling_coefficient(r_wheels, r_dynamic):
"""
Identifies the dynamic rolling coefficient [-].
:param r_wheels:
Radius of the wheels [m].
:type r_wheels: float
:param r_dynamic:
Dynamic radius of the wheels [m].
:type r_dynamic: float
:return:
Dynamic rolling coefficient [-].
:rtype: float
"""
return r_dynamic / r_wheels
dsp.add_function(
function=sh.bypass, inputs=['motive_powers'], outputs=['wheel_powers']
)