#####################################################################
# DOC #
#####################################################################
"""
@author: F. Ramognino <federico.ramognino@polimi.it>
Last update: 12/06/2023
"""
#####################################################################
# IMPORT #
#####################################################################
from collections.abc import Iterable
import numpy as np
import math
from libICEpost.src.base.BaseClass import BaseClass
#############################################################################
# MAIN CLASSES #
#############################################################################
[docs]
class EngineTime(BaseClass):
"""
Base class for handling engine timings during cycle.
Attibutes:
- IVC (float): Inlet valve closing [CA]
- EVO (float): Inlet valve closing [CA]
- n (float): Rotational speed [rpm]
- omega (float): Rotational speed [rad/s]
- time (float): The current time instant [CA]
- deltaT (float): Current time-step [CA]
- oldTime (float): The old time instant [CA]
- startTime (float): The start-time for post-processing [CA]
- endTime (float): The end-time for post-processing [CA]
"""
time:float
"""The current time instant"""
deltaT:float
"""Current time-step"""
oldTime:float
"""The old time instant"""
startTime:float
"""The start time"""
endTime:float
"""The end time"""
#########################################################################
#Constructor:
def __init__(self,speed, *, IVC:float, EVO:float, startTime:float=None, endTime:float=None):
"""
Construct from keyword arguments.
Args:
IVC (float): Inlet valve closing [CA]
EVO (float): Inlet valve closing [CA]
speed (float): Rotational speed [rpm]
startTime (float, optional): The start-time for post-processing [CA]. If None, set to IVC. Defaults to None.
endTime (float, optional): The end-time for post-processing [CA]. If None, set to EVO. Defaults to None.
"""
#Argument checking:
self.checkType(IVC, float, "IVC")
self.checkType(EVO, float, "EVO")
self.checkType(speed, float, "speed")
if not startTime is None:
self.checkType(startTime, float, "startTime")
else:
startTime = IVC
if not endTime is None:
self.checkType(endTime, float, "endTime")
else:
endTime = EVO
self.n = speed
self.omega = speed / 60.0 * 2.0 * math.pi
self.IVC = IVC
self.EVO = EVO
self.startTime = startTime
self.endTime = endTime
self.time = None
self.oldTime = None
######################################
#NOTE: overwrite in child class if necessary
@property
def timings(self) -> dict[str,float]:
"""
A dictionary with the relevant timings (IVC, EVO, etc...)
Returns:
dict[str,float]
"""
return {"IVC":self.IVC, "EVO":self.EVO}
#########################################################################
[docs]
@classmethod
def fromDictionary(cls, dictionary:dict):
"""
Construct from dicionary
"""
return cls(**dictionary)
#########################################################################
@property
def dCAdt(self) -> float:
"""
conversion ratio from CA to s
"""
return (self.n * 6.0)
#########################################################################
#Dunder methods
[docs]
def __str__(self):
STR = "{:15s} {:15s}".format("TypeName", self.TypeName)
STR += "\n{:15s} {:15.3f} {:15s}".format("n", self.n,"[rpm]")
STR += "\n{:15s} {:15.3f} {:15s}".format("omega", self.omega,"[rad/s]")
STR += "\n{:15s} {:15.3f} {:15s}".format("startTime", self.startTime,"[CAD]")
STR += "\n{:15s} {:15.3f} {:15s}".format("endTime", self.endTime,"[CAD]")
STR += "\n{:15s} {:15.3f} {:15s}".format("IVC", self.IVC,"[CAD]")
STR += "\n{:15s} {:15.3f} {:15s}".format("EVO", self.EVO,"[CAD]")
return STR
###################################
#Call method used for iteration over time series:
[docs]
def __call__(self, timeList:Iterable[float]):
"""
Iteration over time steries, from startTime to endTime.
Args:
timeList (Iterable[float]): list of times
Yields:
float: current time
"""
#Update start-time to be consistent with the avaliable data:
self.updateStartTime(timeList)
for CA in timeList:
if (CA > self.startTime) and (CA <= self.endTime):
self.time = CA
self.deltaT = self.time - self.oldTime
yield CA
self.oldTime = CA
#########################################################################
#CA to Time:
[docs]
def CA2Time(self,CA:float|Iterable[float]) -> float|np.ndarray:
"""
Converts CA to time [s]
Args:
CA (float | Iterable[float]): Time in CA
Returns:
float|np.ndarray: Time in seconds
"""
self.checkType(CA, (float, Iterable), "CA")
if isinstance(CA, Iterable) and not isinstance(CA, np.ndarray):
self.checkArray(CA, float, "CA")
return np.array(CA)/self.dCAdt
else:
return CA/self.dCAdt
###################################
#Time to CA:
[docs]
def Time2CA(self,t:float|Iterable[float]) -> float|np.ndarray:
"""
Converts time [s] to CA
Args:
t (float | Iterable[float]): Time in seconds
Returns:
float|np.ndarray: time in CA
"""
self.checkType(t, (float, Iterable), "t")
if isinstance(t, Iterable) and not isinstance(t, np.ndarray):
self.checkArray(t, float, "t")
return np.array(t)*self.dCAdt
else:
return t*self.dCAdt
###################################
[docs]
def isCombustion(self,CA:float|Iterable[float]=None) -> bool|np.ndarray:
"""
Check if combustion has started.
Args:
CA (float | Iterable[float] | None): Crank angle to check. If None, checks for self.time
Returns:
bool|np.ndarray: If combustion started
"""
if not CA is None:
self.checkType(CA, (float, Iterable), "CA")
if isinstance(CA, Iterable):
self.checkArray(CA, float, "CA")
else:
CA = self.time
if not self.startOfCombustion() is None:
out = (CA > self.startOfCombustion())
return np.array(out) if isinstance(CA, Iterable) else out
else:
return False
###################################
[docs]
def startOfCombustion(self) -> float|None:
"""
Instant of start of combustion (overwritten in derived class depending on combustion mode). By default, returns None (motoring condition).
"""
return None
###################################
[docs]
def isClosedValves(self,CA:float|Iterable[float]=None) -> bool|np.ndarray:
"""
Check if at closed valves (after IVC and before EVO)
Args:
CA (float | Iterable[float] | None): Cranc angle to check. If None, checks for self.time
Returns:
bool|np.ndarray: If at closed valves
"""
if not CA is None:
self.checkType(CA, (float, Iterable), "CA")
if isinstance(CA, Iterable):
self.checkArray(CA, float, "CA")
else:
CA = self.time
if isinstance(CA, Iterable):
out = (np.array(CA >= self.IVC) & np.array(CA <= self.EVO))
else:
out = ((CA >= self.IVC) and (CA <= self.EVO))
return out
###################################
[docs]
def updateStartTime(self, timeList:Iterable[float]) -> None:
"""
Update the start-time to be consistent with the avaliable data
Args:
timeList (Iterable[float]): The avaliable time series
"""
self.checkType(timeList, Iterable, "timeList")
timeList = np.array(timeList)
self.startTime = timeList[timeList >= self.startTime][0]
self.time = self.startTime
self.oldTime = self.startTime
#############################################################################
EngineTime.createRuntimeSelectionTable()