Code development platform for open source projects from the European Union institutions :large_blue_circle: EU Login authentication by SMS has been phased out. To see alternatives please check here

Skip to content
Snippets Groups Projects
Commit 063e16aa authored by Markus Quaritsch's avatar Markus Quaritsch
Browse files

Merge branch 'develop' into feature/VECTO-388-add-shift-losses-for-at-power-shifts

Conflicts:
	VectoCore/VectoCore/Models/Simulation/Data/ModalResult.cs
	VectoCore/VectoCore/Models/SimulationComponent/IShiftStrategy.cs
	VectoCore/VectoCore/Models/SimulationComponent/Impl/AMTShiftStrategy.cs
	VectoCore/VectoCore/Models/SimulationComponent/Impl/ATGearbox.cs
	VectoCore/VectoCore/Models/SimulationComponent/Impl/ATShiftStrategy.cs
	VectoCore/VectoCore/Models/SimulationComponent/Impl/CycleGearbox.cs
	VectoCore/VectoCore/Models/SimulationComponent/Impl/ShiftStrategy.cs
parents 87281892 be33aee4
Branches
Tags
No related merge requests found
Showing
with 607 additions and 481 deletions
......@@ -619,6 +619,12 @@ namespace TUGraz.VectoCommon.Utils
[DebuggerHidden]
private PerSecond(double val) : base(val, new Unit[0], DenominatorDefault) {}
[DebuggerHidden]
public static PerSquareSecond operator /(PerSecond perSecond, Second second)
{
return SIBase<PerSquareSecond>.Create(perSecond.Val / second.Value());
}
public double AsRPM
{
get { return Val * 60 / (2 * Math.PI); }
......
......@@ -178,7 +178,9 @@ namespace TUGraz.VectoCore.InputData.Reader.DataObjectAdapter
// powersplit transmission: torque converter already contains ratio and losses
gearData.TorqueConverterRatio = 1;
gearData.TorqueConverterGearLossMap = TransmissionLossMapReader.Create(1, 1, string.Format("TCGear {0}", i + 1));
gearData.TorqueConverterShiftPolygon = gearbox.TorqueConverter.ShiftPolygon == null ? null : ShiftPolygonReader.Create(gearbox.TorqueConverter.ShiftPolygon);
gearData.TorqueConverterShiftPolygon = gearbox.TorqueConverter.ShiftPolygon == null
? null
: ShiftPolygonReader.Create(gearbox.TorqueConverter.ShiftPolygon);
}
}
if (gearbox.Type == GearboxType.ATSerial) {
......@@ -186,14 +188,18 @@ namespace TUGraz.VectoCore.InputData.Reader.DataObjectAdapter
// torqueconverter is active in first gear - duplicate ratio and lossmap for torque converter mode
gearData.TorqueConverterRatio = gearData.Ratio;
gearData.TorqueConverterGearLossMap = gearData.LossMap;
gearData.TorqueConverterShiftPolygon = gearbox.TorqueConverter.ShiftPolygon == null ? null : ShiftPolygonReader.Create(gearbox.TorqueConverter.ShiftPolygon);
gearData.TorqueConverterShiftPolygon = gearbox.TorqueConverter.ShiftPolygon == null
? null
: ShiftPolygonReader.Create(gearbox.TorqueConverter.ShiftPolygon);
}
if (i == 1 && gearDifferenceRatio >= DeclarationData.Gearbox.TorqueConverterSecondGearThreshold) {
// ratio between first and second gear is above threshold, torqueconverter is active in second gear as well
// -> duplicate ratio and lossmap for torque converter mode, remove locked transmission for previous gear
gearData.TorqueConverterRatio = gearData.Ratio;
gearData.TorqueConverterGearLossMap = gearData.LossMap;
gearData.TorqueConverterShiftPolygon = gearbox.TorqueConverter.ShiftPolygon == null ? null : ShiftPolygonReader.Create(gearbox.TorqueConverter.ShiftPolygon);
gearData.TorqueConverterShiftPolygon = gearbox.TorqueConverter.ShiftPolygon == null
? null
: ShiftPolygonReader.Create(gearbox.TorqueConverter.ShiftPolygon);
// NOTE: the lower gear in 'gears' dictionary has index i !!
gears[i].Ratio = double.NaN;
gears[i].LossMap = null;
......@@ -331,8 +337,9 @@ namespace TUGraz.VectoCore.InputData.Reader.DataObjectAdapter
TransmissionType = pto.PTOTransmissionType,
LossMap = PTOIdleLossMapReader.Create(pto.PTOLossMap),
};
if (pto.PTOCycle != null)
if (pto.PTOCycle != null) {
ptoData.PTOCycle = DrivingCycleDataReader.ReadFromDataTable(pto.PTOCycle, CycleType.PTO, "PTO", false);
}
return ptoData;
}
......
......@@ -112,327 +112,4 @@ namespace TUGraz.VectoCore.Models.Simulation.Data
VectoCSVFile.Write(fileName, this);
}
}
/// <summary>
/// Enum with field definitions of the Modal Results File (.vmod).
/// </summary>
public enum ModalResultField
{
/// <summary>
/// Time step [s].
/// Midpoint of the simulated interval.
/// </summary>
[ModalResultField(typeof(SI), caption: "time [s]")] time,
/// <summary>
/// Simulation interval around the current time step. [s]
/// </summary>
[ModalResultField(typeof(SI), "simulation_interval", "dt [s]")] simulationInterval,
/// <summary>
/// Engine speed [1/min].
/// </summary>
[ModalResultField(typeof(SI), caption: "n_eng_avg [1/min]", outputFactor: 60 / (2 * Math.PI))] n_eng_avg,
/// <summary>
/// [Nm] Engine torque.
/// </summary>
[ModalResultField(typeof(SI), caption: "T_eng_fcmap [Nm]")] T_eng_fcmap,
/// <summary>
/// [Nm] Full load torque
/// </summary>
[ModalResultField(typeof(SI), caption: "Tq_full [Nm]")] Tq_full,
/// <summary>
/// [Nm] Motoring torque
/// </summary>
[ModalResultField(typeof(SI), caption: "Tq_drag [Nm]")] Tq_drag,
/// <summary>
/// [kW] Engine power.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_eng_out [kW]", outputFactor: 1e-3)] P_eng_out,
/// <summary>
/// [kW] Engine full load power.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_eng_full [kW]", outputFactor: 1e-3)] P_eng_full,
/// <summary>
/// [kW] Engine drag power.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_eng_drag [kW]", outputFactor: 1e-3)] P_eng_drag,
/// <summary>
/// [kW] Engine power at clutch (equals Pe minus loss due to rotational inertia Pa Eng).
/// </summary>
[ModalResultField(typeof(SI), caption: "P_clutch_out [kW]", outputFactor: 1e-3)] P_clutch_out,
/// <summary>
/// [kW] Rotational acceleration power: Engine.
/// </summary>
[ModalResultField(typeof(SI), name: "P_eng_inertia", caption: "P_eng_inertia [kW]", outputFactor: 1e-3)] P_eng_inertia,
/// <summary>
/// [kW] Total auxiliary power demand .
/// </summary>
[ModalResultField(typeof(SI), caption: "P_aux [kW]", outputFactor: 1e-3)] P_aux,
/// <summary>
/// [g/h] Fuel consumption from FC map..
/// </summary>
[ModalResultField(typeof(SI), name: "FC-Map", caption: "FC-Map [g/h]", outputFactor: 3600 * 1000)] FCMap,
/// <summary>
/// [g/h] Fuel consumption after Auxiliary-Start/Stop Correction. (Based on FC.)
/// </summary>
[ModalResultField(typeof(SI), name: "FC-AUXc", caption: "FC-AUXc [g/h]", outputFactor: 3600 * 1000)] FCAUXc,
/// <summary>
/// [g/h] Fuel consumption after WHTC Correction. (Based on FC-AUXc.)
/// </summary>
[ModalResultField(typeof(SI), name: "FC-WHTCc", caption: "FC-WHTCc [g/h]", outputFactor: 3600 * 1000)] FCWHTCc,
/// <summary>
/// [g/h] Fuel consumption after smart auxiliary correction.
/// </summary>
[ModalResultField(typeof(SI), name: "FC-AAUX", caption: "FC-AAUX [g/h]", outputFactor: 3600 * 1000)] FCAAUX,
/// <summary>
/// [g/h] Fuel consumption after WHTC Correction. (Based on FC-AUXc.)
/// </summary>
[ModalResultField(typeof(SI), name: "FC-Final", caption: "FC-Final [g/h]", outputFactor: 3600 * 1000)] FCFinal,
/// <summary>
/// [km] Travelled distance.
/// </summary>
[ModalResultField(typeof(SI), caption: "dist [m]")] dist,
/// <summary>
/// [km/h] Actual vehicle speed.
/// </summary>
[ModalResultField(typeof(SI), caption: "v_act [km/h]", outputFactor: 3.6)] v_act,
/// <summary>
/// [km/h] Target vehicle speed.
/// </summary>
[ModalResultField(typeof(SI), caption: "v_targ [km/h]", outputFactor: 3.6)] v_targ,
/// <summary>
/// [m/s2] Vehicle acceleration.
/// </summary>
[ModalResultField(typeof(SI), caption: "acc [m/s^2]")] acc,
/// <summary>
/// [%] Road gradient.
/// </summary>
[ModalResultField(typeof(SI), caption: "grad [%]")] grad,
/// <summary>
/// [-] GearData. "0" = clutch opened / neutral. "0.5" = lock-up clutch is open (AT with torque converter only, see
/// Gearbox)
/// </summary>
[ModalResultField(typeof(uint), caption: "Gear [-]")] Gear,
/// <summary>
/// [kW] Gearbox losses.
/// </summary>
[ModalResultField(typeof(SI), name: "P_gbx_loss", caption: "P_gbx_loss [kW]", outputFactor: 1e-3)] P_gbx_loss,
[ModalResultField(typeof(SI), name: "P_gbx_shift_loss", caption: "P_gbx_shift_loss [kW]", outputFactor: 1e-3)] P_gbx_shift_loss,
/// <summary>
/// [kW] Losses in differential / axle transmission.
/// </summary>
[ModalResultField(typeof(SI), name: "Ploss Diff", caption: "P_axle_loss [kW]", outputFactor: 1e-3)] P_axle_loss,
/// <summary>
/// [kW] Losses in angle transmission.
/// </summary>
[ModalResultField(typeof(SI), name: "Ploss Angle", caption: "P_angle_loss [kW]", outputFactor: 1e-3)] P_angle_loss,
/// <summary>
/// [kW] Retarder losses.
/// </summary>
[ModalResultField(typeof(SI), name: "P_ret_loss", caption: "P_ret_loss [kW]", outputFactor: 1e-3)] P_ret_loss,
/// <summary>
/// [kW] Rotational acceleration power: Gearbox.
/// </summary>
[ModalResultField(typeof(SI), name: "P_gbx_inertia", caption: "P_gbx_inertia [kW]", outputFactor: 1e-3)] P_gbx_inertia,
/// <summary>
/// [kW] Vehicle acceleration power.
/// </summary>
[ModalResultField(typeof(SI), name: "P_veh_inertia", caption: "P_veh_inertia [kW]", outputFactor: 1e-3)] P_veh_inertia,
/// <summary>
/// [kW] Rolling resistance power demand.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_roll [kW]", outputFactor: 1e-3)] P_roll,
/// <summary>
/// [kW] Air resistance power demand.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_air [kW]", outputFactor: 1e-3)] P_air,
/// <summary>
/// [kW] Power demand due to road gradient.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_slope [kW]", outputFactor: 1e-3)] P_slope,
/// <summary>
/// [kW] Total power demand at wheel = sum of rolling, air, acceleration and road gradient resistance.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_wheel_in [kW]", outputFactor: 1e-3)] P_wheel_in,
/// <summary>
/// [kW] Brake power. Drag power is included in Pe.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_brake_loss [kW]", outputFactor: 1e-3)] P_brake_loss,
[ModalResultField(typeof(SI), caption: "P_wheel_inertia [kW]", outputFactor: 1e-3)] P_wheel_inertia,
[ModalResultField(typeof(SI), caption: "P_brake_in [kW]", outputFactor: 1e-3)] P_brake_in,
[ModalResultField(typeof(SI), caption: "P_axle_in [kW]", outputFactor: 1e-3)] P_axle_in,
[ModalResultField(typeof(SI), caption: "P_angle_in [kW]", outputFactor: 1e-3)] P_angle_in,
[ModalResultField(typeof(SI), caption: "P_ret_in [kW]", outputFactor: 1e-3)] P_retarder_in,
[ModalResultField(typeof(SI), caption: "P_gbx_in [kW]", outputFactor: 1e-3)] P_gbx_in,
[ModalResultField(typeof(SI), caption: "P_clutch_loss [kW]", outputFactor: 1e-3)] P_clutch_loss,
[ModalResultField(typeof(SI), caption: "P_trac [kW]", outputFactor: 1e-3)] P_trac,
[ModalResultField(typeof(SI), caption: "P_eng_fcmap [kW]", outputFactor: 1e-3)] P_eng_fcmap,
/// <summary>
/// [kW] Power demand of Auxiliary with ID xxx. See also Aux Dialog and Driving Cycle.
/// </summary>
[ModalResultField(typeof(SI), outputFactor: 1e-3)] P_aux_,
/// <summary>
/// [-] true/false indicate whether torque converter is locked or not (only applicable for gears with TC)
/// </summary>
[ModalResultField(typeof(int), caption: "TC locked")] TC_Locked,
/// <summary>
/// [-] Torque converter speed ratio
/// </summary>
[ModalResultField(typeof(double), name: "TCnu")] TorqueConverterSpeedRatio,
/// <summary>
/// [-] Torque converter torque ratio
/// </summary>
[ModalResultField(typeof(double), name: "TCmu")] TorqueConverterTorqueRatio,
[ModalResultField(typeof(SI), "P_TC_out [kW]", outputFactor: 1e-3)] P_TC_out,
/// <summary>
/// [kW] Power loss at the torque converter.
/// </summary>
[ModalResultField(typeof(SI), "P_TC_loss [kW]", outputFactor: 1e-3)] P_TC_loss,
/// <summary>
/// [Nm] Torque converter output torque
/// </summary>
[ModalResultField(typeof(SI), "T_TC_out")] TC_TorqueOut,
/// <summary>
/// [1/min] Torque converter output speed
/// </summary>
[ModalResultField(typeof(SI), "n_TC_out", outputFactor: 60 / (2 * Math.PI))] TC_angularSpeedOut,
/// <summary>
/// [Nm] Torque converter output torque
/// </summary>
[ModalResultField(typeof(SI), "T_TC_in")] TC_TorqueIn,
/// <summary>
/// [1/min] Torque converter output speed
/// </summary>
[ModalResultField(typeof(SI), "n_TC_in", outputFactor: 60 / (2 * Math.PI))] TC_angularSpeedIn,
/// <summary>
/// [m] Altitude
/// </summary>
[ModalResultField(typeof(SI))] altitude,
[ModalResultField(typeof(SI), name: "ds [m]")] simulationDistance,
[ModalResultField(typeof(double), caption: "AA_NonSmartAlternatorsEfficiency [%]")] AA_NonSmartAlternatorsEfficiency,
[ModalResultField(typeof(SI), caption: "AA_SmartIdleCurrent_Amps [A]")] AA_SmartIdleCurrent_Amps,
[ModalResultField(typeof(double), caption: "AA_SmartIdleAlternatorsEfficiency [%]")] AA_SmartIdleAlternatorsEfficiency,
[ModalResultField(typeof(SI), caption: "AA_SmartTractionCurrent_Amps [A]")] AA_SmartTractionCurrent_Amps,
[ModalResultField(typeof(double), caption: "AA_SmartTractionAlternatorEfficiency [%]")] AA_SmartTractionAlternatorEfficiency,
[ModalResultField(typeof(SI), caption: "AA_SmartOverrunCurrent_Amps [A]")] AA_SmartOverrunCurrent_Amps,
[ModalResultField(typeof(double), caption: "AA_SmartOverrunAlternatorEfficiency [%]")] AA_SmartOverrunAlternatorEfficiency,
[ModalResultField(typeof(SI), caption: "AA_CompressorFlowRate_LitrePerSec [Ni L/s]")] AA_CompressorFlowRate_LitrePerSec,
[ModalResultField(typeof(int), caption: "AA_OverrunFlag [bool]")] AA_OverrunFlag,
[ModalResultField(typeof(int), caption: "AA_EngineIdleFlag [bool]")] AA_EngineIdleFlag,
[ModalResultField(typeof(int), caption: "AA_CompressorFlag [bool]")] AA_CompressorFlag,
[ModalResultField(typeof(SI), caption: "AA_TotalCycleFC_Grams [g]", outputFactor: 1000)] AA_TotalCycleFC_Grams,
[ModalResultField(typeof(SI), caption: "AA_TotalCycleFC_Litres [l]")] AA_TotalCycleFC_Litres,
[ModalResultField(typeof(SI), caption: "AA_AveragePowerDemandCrankHVACMechanicals [W]")] AA_AveragePowerDemandCrankHVACMechanicals,
[ModalResultField(typeof(SI), caption: "AA_AveragePowerDemandCrankHVACElectricals [W]")] AA_AveragePowerDemandCrankHVACElectricals,
[ModalResultField(typeof(SI), caption: "AA_AveragePowerDemandCrankElectrics [W]")] AA_AveragePowerDemandCrankElectrics,
[ModalResultField(typeof(SI), caption: "AA_AveragePowerDemandCrankPneumatics [W]")] AA_AveragePowerDemandCrankPneumatics,
[ModalResultField(typeof(SI), caption: "AA_TotalCycleFuelConsumptionCompressorOff [g]", outputFactor: 1000)] AA_TotalCycleFuelConsumptionCompressorOff,
[ModalResultField(typeof(SI), caption: "AA_TotalCycleFuelConsumptionCompressorOn [g]", outputFactor: 1000)] AA_TotalCycleFuelConsumptionCompressorOn,
}
[AttributeUsage(AttributeTargets.Field)]
public class ModalResultFieldAttribute : Attribute
{
internal ModalResultFieldAttribute(Type dataType, string name = null, string caption = null, uint decimals = 4,
double outputFactor = 1, bool showUnit = false)
{
DataType = dataType;
Name = name;
Caption = caption;
Decimals = decimals;
OutputFactor = outputFactor;
ShowUnit = showUnit;
}
public bool ShowUnit { get; private set; }
public double OutputFactor { get; private set; }
public uint Decimals { get; private set; }
public Type DataType { get; private set; }
public string Name { get; private set; }
public string Caption { get; private set; }
}
public static class ModalResultFieldExtensionMethods
{
public static string GetName(this ModalResultField field)
{
return GetAttribute(field).Name ?? field.ToString();
}
public static string GetCaption(this ModalResultField field)
{
return GetAttribute(field).Caption ?? GetAttribute(field).Name ?? field.ToString();
}
public static string GetShortCaption(this ModalResultField field)
{
var caption = GetCaption(field);
return Regex.Replace(caption, @"\[.*?\]|\<|\>", "").Trim();
}
public static ModalResultFieldAttribute GetAttribute(this ModalResultField field)
{
return (ModalResultFieldAttribute)Attribute.GetCustomAttribute(ForValue(field), typeof(ModalResultFieldAttribute));
}
private static MemberInfo ForValue(ModalResultField field)
{
return typeof(ModalResultField).GetField(Enum.GetName(typeof(ModalResultField), field));
}
}
}
\ No newline at end of file
using System;
using System.Reflection;
using System.Text.RegularExpressions;
using TUGraz.VectoCommon.Utils;
namespace TUGraz.VectoCore.Models.Simulation.Data
{
/// <summary>
/// Enum with field definitions of the Modal Results File (.vmod).
/// </summary>
public enum ModalResultField
{
/// <summary>
/// Time step [s].
/// Midpoint of the simulated interval.
/// </summary>
[ModalResultField(typeof(SI), caption: "time [s]")] time,
/// <summary>
/// Simulation interval around the current time step. [s]
/// </summary>
[ModalResultField(typeof(SI), "simulation_interval", "dt [s]")] simulationInterval,
/// <summary>
/// Engine speed [1/min].
/// </summary>
[ModalResultField(typeof(SI), caption: "n_eng_avg [1/min]", outputFactor: 60 / (2 * Math.PI))] n_eng_avg,
/// <summary>
/// [Nm] Engine torque.
/// </summary>
[ModalResultField(typeof(SI), caption: "T_eng_fcmap [Nm]")] T_eng_fcmap,
/// <summary>
/// [Nm] Full load torque
/// </summary>
[ModalResultField(typeof(SI), caption: "Tq_full [Nm]")] Tq_full,
/// <summary>
/// [Nm] Motoring torque
/// </summary>
[ModalResultField(typeof(SI), caption: "Tq_drag [Nm]")] Tq_drag,
/// <summary>
/// [kW] Engine power.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_eng_out [kW]", outputFactor: 1e-3)] P_eng_out,
/// <summary>
/// [kW] Engine full load power.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_eng_full [kW]", outputFactor: 1e-3)] P_eng_full,
/// <summary>
/// [kW] Engine drag power.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_eng_drag [kW]", outputFactor: 1e-3)] P_eng_drag,
/// <summary>
/// [kW] Engine power at clutch (equals Pe minus loss due to rotational inertia Pa Eng).
/// </summary>
[ModalResultField(typeof(SI), caption: "P_clutch_out [kW]", outputFactor: 1e-3)] P_clutch_out,
/// <summary>
/// [kW] Rotational acceleration power: Engine.
/// </summary>
[ModalResultField(typeof(SI), name: "P_eng_inertia", caption: "P_eng_inertia [kW]", outputFactor: 1e-3)] P_eng_inertia,
/// <summary>
/// [kW] Total auxiliary power demand .
/// </summary>
[ModalResultField(typeof(SI), caption: "P_aux [kW]", outputFactor: 1e-3)] P_aux,
/// <summary>
/// [g/h] Fuel consumption from FC map..
/// </summary>
[ModalResultField(typeof(SI), name: "FC-Map", caption: "FC-Map [g/h]", outputFactor: 3600 * 1000)] FCMap,
/// <summary>
/// [g/h] Fuel consumption after Auxiliary-Start/Stop Correction. (Based on FC.)
/// </summary>
[ModalResultField(typeof(SI), name: "FC-AUXc", caption: "FC-AUXc [g/h]", outputFactor: 3600 * 1000)] FCAUXc,
/// <summary>
/// [g/h] Fuel consumption after WHTC Correction. (Based on FC-AUXc.)
/// </summary>
[ModalResultField(typeof(SI), name: "FC-WHTCc", caption: "FC-WHTCc [g/h]", outputFactor: 3600 * 1000)] FCWHTCc,
/// <summary>
/// [g/h] Fuel consumption after smart auxiliary correction.
/// </summary>
[ModalResultField(typeof(SI), name: "FC-AAUX", caption: "FC-AAUX [g/h]", outputFactor: 3600 * 1000)] FCAAUX,
/// <summary>
/// [g/h] Fuel consumption after WHTC Correction. (Based on FC-AUXc.)
/// </summary>
[ModalResultField(typeof(SI), name: "FC-Final", caption: "FC-Final [g/h]", outputFactor: 3600 * 1000)] FCFinal,
/// <summary>
/// [km] Travelled distance.
/// </summary>
[ModalResultField(typeof(SI), caption: "dist [m]")] dist,
/// <summary>
/// [km/h] Actual vehicle speed.
/// </summary>
[ModalResultField(typeof(SI), caption: "v_act [km/h]", outputFactor: 3.6)] v_act,
/// <summary>
/// [km/h] Target vehicle speed.
/// </summary>
[ModalResultField(typeof(SI), caption: "v_targ [km/h]", outputFactor: 3.6)] v_targ,
/// <summary>
/// [m/s2] Vehicle acceleration.
/// </summary>
[ModalResultField(typeof(SI), caption: "acc [m/s^2]")] acc,
/// <summary>
/// [%] Road gradient.
/// </summary>
[ModalResultField(typeof(SI), caption: "grad [%]")] grad,
/// <summary>
/// [-] GearData. "0" = clutch opened / neutral. "0.5" = lock-up clutch is open (AT with torque converter only, see
/// Gearbox)
/// </summary>
[ModalResultField(typeof(uint), caption: "Gear [-]")] Gear,
[ModalResultField(typeof(SI), caption: "n_gbx_out_avg [1/min]", outputFactor: 60 / (2 * Math.PI))] n_gbx_out_avg,
[ModalResultField(typeof(SI), caption: "T_gbx_out [Nm]")] T_gbx_out,
/// <summary>
/// [kW] Gearbox losses.
/// </summary>
[ModalResultField(typeof(SI), name: "P_gbx_loss", caption: "P_gbx_loss [kW]", outputFactor: 1e-3)] P_gbx_loss,
[ModalResultField(typeof(SI), name: "P_gbx_shift_loss", caption: "P_gbx_shift_loss [kW]", outputFactor: 1e-3)] P_gbx_shift_loss,
/// <summary>
/// [kW] Losses in differential / axle transmission.
/// </summary>
[ModalResultField(typeof(SI), name: "Ploss Diff", caption: "P_axle_loss [kW]", outputFactor: 1e-3)] P_axle_loss,
/// <summary>
/// [kW] Losses in angle transmission.
/// </summary>
[ModalResultField(typeof(SI), name: "Ploss Angle", caption: "P_angle_loss [kW]", outputFactor: 1e-3)] P_angle_loss,
/// <summary>
/// [kW] Retarder losses.
/// </summary>
[ModalResultField(typeof(SI), name: "P_ret_loss", caption: "P_ret_loss [kW]", outputFactor: 1e-3)] P_ret_loss,
/// <summary>
/// [kW] Rotational acceleration power: Gearbox.
/// </summary>
[ModalResultField(typeof(SI), name: "P_gbx_inertia", caption: "P_gbx_inertia [kW]", outputFactor: 1e-3)] P_gbx_inertia,
/// <summary>
/// [kW] Vehicle acceleration power.
/// </summary>
[ModalResultField(typeof(SI), name: "P_veh_inertia", caption: "P_veh_inertia [kW]", outputFactor: 1e-3)] P_veh_inertia,
/// <summary>
/// [kW] Rolling resistance power demand.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_roll [kW]", outputFactor: 1e-3)] P_roll,
/// <summary>
/// [kW] Air resistance power demand.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_air [kW]", outputFactor: 1e-3)] P_air,
/// <summary>
/// [kW] Power demand due to road gradient.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_slope [kW]", outputFactor: 1e-3)] P_slope,
/// <summary>
/// [kW] Total power demand at wheel = sum of rolling, air, acceleration and road gradient resistance.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_wheel_in [kW]", outputFactor: 1e-3)] P_wheel_in,
/// <summary>
/// [kW] Brake power. Drag power is included in Pe.
/// </summary>
[ModalResultField(typeof(SI), caption: "P_brake_loss [kW]", outputFactor: 1e-3)] P_brake_loss,
[ModalResultField(typeof(SI), caption: "P_wheel_inertia [kW]", outputFactor: 1e-3)] P_wheel_inertia,
[ModalResultField(typeof(SI), caption: "P_brake_in [kW]", outputFactor: 1e-3)] P_brake_in,
[ModalResultField(typeof(SI), caption: "P_axle_in [kW]", outputFactor: 1e-3)] P_axle_in,
[ModalResultField(typeof(SI), caption: "P_angle_in [kW]", outputFactor: 1e-3)] P_angle_in,
[ModalResultField(typeof(SI), caption: "P_ret_in [kW]", outputFactor: 1e-3)] P_retarder_in,
[ModalResultField(typeof(SI), caption: "P_gbx_in [kW]", outputFactor: 1e-3)] P_gbx_in,
[ModalResultField(typeof(SI), caption: "P_clutch_loss [kW]", outputFactor: 1e-3)] P_clutch_loss,
[ModalResultField(typeof(SI), caption: "P_trac [kW]", outputFactor: 1e-3)] P_trac,
[ModalResultField(typeof(SI), caption: "P_eng_fcmap [kW]", outputFactor: 1e-3)] P_eng_fcmap,
[ModalResultField(typeof(SI), caption: "P_eng_full_stat [kW]", outputFactor: 1e-3)] P_eng_full_stat,
/// <summary>
/// [kW] Power demand of Auxiliary with ID xxx. See also Aux Dialog and Driving Cycle.
/// </summary>
[ModalResultField(typeof(SI), outputFactor: 1e-3)] P_aux_,
/// <summary>
/// [-] true/false indicate whether torque converter is locked or not (only applicable for gears with TC)
/// </summary>
[ModalResultField(typeof(int), caption: "TC locked")] TC_Locked,
/// <summary>
/// [-] Torque converter speed ratio
/// </summary>
[ModalResultField(typeof(double), name: "TCnu")] TorqueConverterSpeedRatio,
/// <summary>
/// [-] Torque converter torque ratio
/// </summary>
[ModalResultField(typeof(double), name: "TCmu")] TorqueConverterTorqueRatio,
[ModalResultField(typeof(SI), "P_TC_out [kW]", outputFactor: 1e-3)] P_TC_out,
/// <summary>
/// [kW] Power loss at the torque converter.
/// </summary>
[ModalResultField(typeof(SI), "P_TC_loss [kW]", outputFactor: 1e-3)] P_TC_loss,
/// <summary>
/// [Nm] Torque converter output torque
/// </summary>
[ModalResultField(typeof(SI), "T_TC_out")] TC_TorqueOut,
/// <summary>
/// [1/min] Torque converter output speed
/// </summary>
[ModalResultField(typeof(SI), "n_TC_out", outputFactor: 60 / (2 * Math.PI))] TC_angularSpeedOut,
/// <summary>
/// [Nm] Torque converter output torque
/// </summary>
[ModalResultField(typeof(SI), "T_TC_in")] TC_TorqueIn,
/// <summary>
/// [1/min] Torque converter output speed
/// </summary>
[ModalResultField(typeof(SI), "n_TC_in", outputFactor: 60 / (2 * Math.PI))] TC_angularSpeedIn,
/// <summary>
/// [m] Altitude
/// </summary>
[ModalResultField(typeof(SI))] altitude,
[ModalResultField(typeof(SI), name: "ds [m]")] simulationDistance,
[ModalResultField(typeof(double), caption: "AA_NonSmartAlternatorsEfficiency [%]")] AA_NonSmartAlternatorsEfficiency,
[ModalResultField(typeof(SI), caption: "AA_SmartIdleCurrent_Amps [A]")] AA_SmartIdleCurrent_Amps,
[ModalResultField(typeof(double), caption: "AA_SmartIdleAlternatorsEfficiency [%]")] AA_SmartIdleAlternatorsEfficiency,
[ModalResultField(typeof(SI), caption: "AA_SmartTractionCurrent_Amps [A]")] AA_SmartTractionCurrent_Amps,
[ModalResultField(typeof(double), caption: "AA_SmartTractionAlternatorEfficiency [%]")] AA_SmartTractionAlternatorEfficiency,
[ModalResultField(typeof(SI), caption: "AA_SmartOverrunCurrent_Amps [A]")] AA_SmartOverrunCurrent_Amps,
[ModalResultField(typeof(double), caption: "AA_SmartOverrunAlternatorEfficiency [%]")] AA_SmartOverrunAlternatorEfficiency,
[ModalResultField(typeof(SI), caption: "AA_CompressorFlowRate_LitrePerSec [Ni L/s]")] AA_CompressorFlowRate_LitrePerSec,
[ModalResultField(typeof(int), caption: "AA_OverrunFlag [bool]")] AA_OverrunFlag,
[ModalResultField(typeof(int), caption: "AA_EngineIdleFlag [bool]")] AA_EngineIdleFlag,
[ModalResultField(typeof(int), caption: "AA_CompressorFlag [bool]")] AA_CompressorFlag,
[ModalResultField(typeof(SI), caption: "AA_TotalCycleFC_Grams [g]", outputFactor: 1000)] AA_TotalCycleFC_Grams,
[ModalResultField(typeof(SI), caption: "AA_TotalCycleFC_Litres [l]")] AA_TotalCycleFC_Litres,
[ModalResultField(typeof(SI), caption: "AA_AveragePowerDemandCrankHVACMechanicals [W]")] AA_AveragePowerDemandCrankHVACMechanicals,
[ModalResultField(typeof(SI), caption: "AA_AveragePowerDemandCrankHVACElectricals [W]")] AA_AveragePowerDemandCrankHVACElectricals,
[ModalResultField(typeof(SI), caption: "AA_AveragePowerDemandCrankElectrics [W]")] AA_AveragePowerDemandCrankElectrics,
[ModalResultField(typeof(SI), caption: "AA_AveragePowerDemandCrankPneumatics [W]")] AA_AveragePowerDemandCrankPneumatics,
[ModalResultField(typeof(SI), caption: "AA_TotalCycleFuelConsumptionCompressorOff [g]", outputFactor: 1000)] AA_TotalCycleFuelConsumptionCompressorOff,
[ModalResultField(typeof(SI), caption: "AA_TotalCycleFuelConsumptionCompressorOn [g]", outputFactor: 1000)] AA_TotalCycleFuelConsumptionCompressorOn,
}
[AttributeUsage(AttributeTargets.Field)]
public class ModalResultFieldAttribute : Attribute
{
internal ModalResultFieldAttribute(Type dataType, string name = null, string caption = null, uint decimals = 4,
double outputFactor = 1, bool showUnit = false)
{
DataType = dataType;
Name = name;
Caption = caption;
Decimals = decimals;
OutputFactor = outputFactor;
ShowUnit = showUnit;
}
public bool ShowUnit { get; private set; }
public double OutputFactor { get; private set; }
public uint Decimals { get; private set; }
public Type DataType { get; private set; }
public string Name { get; private set; }
public string Caption { get; private set; }
}
public static class ModalResultFieldExtensionMethods
{
public static string GetName(this ModalResultField field)
{
return GetAttribute(field).Name ?? field.ToString();
}
public static string GetCaption(this ModalResultField field)
{
return GetAttribute(field).Caption ?? GetAttribute(field).Name ?? field.ToString();
}
public static string GetShortCaption(this ModalResultField field)
{
var caption = GetCaption(field);
return Regex.Replace(caption, @"\[.*?\]|\<|\>", "").Trim();
}
public static Type GetDataType(this ModalResultField field)
{
return GetAttribute(field).DataType;
}
public static ModalResultFieldAttribute GetAttribute(this ModalResultField field)
{
return (ModalResultFieldAttribute)Attribute.GetCustomAttribute(ForValue(field), typeof(ModalResultFieldAttribute));
}
private static MemberInfo ForValue(ModalResultField field)
{
return typeof(ModalResultField).GetField(Enum.GetName(typeof(ModalResultField), field));
}
}
}
\ No newline at end of file
......@@ -31,6 +31,7 @@
using TUGraz.VectoCommon.Models;
using TUGraz.VectoCommon.Utils;
using TUGraz.VectoCore.Models.SimulationComponent;
using TUGraz.VectoCore.Models.SimulationComponent.Data.Gearbox;
namespace TUGraz.VectoCore.Models.Simulation.DataBus
......@@ -59,5 +60,12 @@ namespace TUGraz.VectoCore.Models.Simulation.DataBus
Second LastShift { get; }
GearData GetGearData(uint gear);
/// <summary>
/// Returns the next gear during shifting operations
/// </summary>
GearInfo NextGear { get; }
Second TractionInterruption { get; }
}
}
\ No newline at end of file
......@@ -185,6 +185,7 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
public string RunSuffix;
}
[DebuggerDisplay("{Run.RunIdentifier}: {Run.RunName}, {Run.CycleName}")]
internal class RunEntry : LoggingObject
{
public IVectoRun Run;
......
......@@ -221,12 +221,14 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
private static IIdleController GetIdleController(PTOData pto, ICombustionEngine engine)
{
if (pto == null) {
return engine.IdleController;
} else {
var controller = engine.IdleController;
if (pto != null) {
var ptoController = new PTOCycleController(pto.PTOCycle);
return new IdleControllerSwitcher(engine.IdleController, ptoController);
controller = new IdleControllerSwitcher(engine.IdleController, ptoController);
}
return controller;
}
internal static IAuxInProvider CreateAdvancedAuxiliaries(VectoRunData data, IVehicleContainer container)
......
......@@ -44,6 +44,7 @@ using TUGraz.VectoCore.InputData;
using TUGraz.VectoCore.InputData.Reader.Impl;
using TUGraz.VectoCore.Models.SimulationComponent.Data;
using TUGraz.VectoCore.OutputData;
using TUGraz.VectoCore.OutputData.ModFilter;
using TUGraz.VectoCore.OutputData.PDF;
namespace TUGraz.VectoCore.Models.Simulation.Impl
......@@ -102,6 +103,7 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
public bool WriteModalResults { get; set; }
public bool ModalResults1Hz { get; set; }
public bool ActualModalData { get; set; }
/// <summary>
/// Creates powertrain and initializes it with the component's data.
......@@ -111,9 +113,14 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
{
var i = 0;
var modDataFilter = ModalResults1Hz
? new IModalDataFilter[] { new ModalDataContainer.ModalData1HzFilter() }
? new IModalDataFilter[] { new ModalData1HzFilter() }
: null;
if (ActualModalData) {
modDataFilter = new[] { new ActualModalDataFilter(), };
}
var warning1Hz = false;
foreach (var data in DataReader.NextRun()) {
......@@ -167,5 +174,7 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
yield return run;
}
}
}
}
\ No newline at end of file
......@@ -125,25 +125,34 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
Log.Error(vse);
Container.RunStatus = Status.Aborted;
Container.FinishSimulation();
throw new VectoSimulationException("{6} - absTime: {0}, distance: {1}, dt: {2}, v: {3}, Gear: {4} | {5}", vse,
throw new VectoSimulationException("{6} ({7} {8}) - absTime: {0}, distance: {1}, dt: {2}, v: {3}, Gear: {4} | {5}",
vse,
AbsTime, Container.Distance, dt, Container.VehicleSpeed, TryCatch(() => Container.Gear),
vse.Message, RunIdentifier);
vse.Message, RunIdentifier, CycleName, RunSuffix);
} catch (VectoException ve) {
Log.Error("SIMULATION RUN ABORTED! ========================");
Log.Error(ve);
Container.RunStatus = Status.Aborted;
try {
Container.FinishSimulation();
throw new VectoSimulationException("{6} - absTime: {0}, distance: {1}, dt: {2}, v: {3}, Gear: {4} | {5}", ve,
} catch (Exception ve2) {
ve = new VectoException("Multiple Exceptions occured.",
new AggregateException(ve, new VectoException("Exception during finishing Simulation.", ve2)));
}
throw new VectoSimulationException("{6} ({7} {8}) - absTime: {0}, distance: {1}, dt: {2}, v: {3}, Gear: {4} | {5}",
ve,
AbsTime, Container.Distance, dt, Container.VehicleSpeed, TryCatch(() => Container.Gear), ve.Message,
RunIdentifier);
RunIdentifier, CycleName, RunSuffix);
} catch (Exception e) {
Log.Error("SIMULATION RUN ABORTED! ========================");
Log.Error(e);
Container.RunStatus = Status.Aborted;
Container.FinishSimulation();
throw new VectoSimulationException("{6} - absTime: {0}, distance: {1}, dt: {2}, v: {3}, Gear: {4} | {5}", e, AbsTime,
throw new VectoSimulationException("{6} ({7} {8}) - absTime: {0}, distance: {1}, dt: {2}, v: {3}, Gear: {4} | {5}",
e, AbsTime,
Container.Distance, dt, Container.VehicleSpeed, TryCatch(() => Container.Gear), e.Message,
RunIdentifier);
RunIdentifier, CycleName, RunSuffix);
}
Container.RunStatus = Status.Success;
Container.FinishSimulation();
......
......@@ -138,6 +138,16 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
return Gearbox.GetGearData(gear);
}
public GearInfo NextGear
{
get { return Gearbox.NextGear; }
}
public Second TractionInterruption
{
get { return Gearbox.TractionInterruption; }
}
#endregion
#region IEngineCockpit
......
......@@ -172,26 +172,24 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Engine
return retVal;
}
private List<PerSecond> FindEngineSpeedForPower(FullLoadCurveEntry p1, FullLoadCurveEntry p2, Watt power)
private IEnumerable<PerSecond> FindEngineSpeedForPower(FullLoadCurveEntry p1, FullLoadCurveEntry p2, Watt power)
{
var k = (p2.TorqueFullLoad - p1.TorqueFullLoad) / (p2.EngineSpeed - p1.EngineSpeed);
var d = p2.TorqueFullLoad - k * p2.EngineSpeed;
var retVal = new List<PerSecond>();
if (k.IsEqual(0, 0.0001)) {
// constant torque, solve linear equation
// constant torque: solve linear equation
// power = M * n_eng_avg
retVal.Add(power / d);
} else {
// non-constant torque, solve quadratic equation for engine speed (n_eng_avg)
return (power / d).ToEnumerable();
}
// non-constant torque: solve quadratic equation for engine speed (n_eng_avg)
// power = M(n_eng_avg) * n_eng_avg = (k * n_eng_avg + d) * n_eng_avg = k * n_eng_avg^2 + d * n_eng_avg
retVal = VectoMath.QuadraticEquationSolver(k.Value(), d.Value(), -power.Value()).SI<PerSecond>().ToList();
if (retVal.Count == 0) {
var retVal = VectoMath.QuadraticEquationSolver(k.Value(), d.Value(), -power.Value());
if (retVal.Length == 0) {
Log.Info("No real solution found for requested power demand: P: {0}, p1: {1}, p2: {2}", power, p1, p2);
}
}
retVal = retVal.Where(x => x >= p1.EngineSpeed && x <= p2.EngineSpeed).ToList();
return retVal;
return retVal.Where(x => x >= p1.EngineSpeed && x <= p2.EngineSpeed).Select(x => x.SI<PerSecond>());
}
protected internal Watt ComputeArea(PerSecond lowEngineSpeed, PerSecond highEngineSpeed)
......
......@@ -98,10 +98,10 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data
[Required, SIRange(0, double.MaxValue)]
public MeterPerSquareSecond UpshiftMinAcceleration { get; internal set; }
[Required, SIRange(0.5, 1)]
[SIRange(0.5, 1)]
public Second PowershiftShiftTime { get; internal set; }
[Required, Range(0, 1)]
[Range(0, 1)]
public double PowershiftInertiaFactor { get; internal set; }
// ReSharper disable once UnusedMember.Global -- used via Validation
......@@ -116,8 +116,12 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data
Math.Round(Constants.SimulationSettings.RequiredTorqueConverterSpeedRatio / gearboxData.Gears[1].Ratio *
gearboxData.Gears[1].TorqueConverterRatio, 4);
result.AddRange(gearboxData.TorqueConverterData.Validate(mode, gearboxData.Type));
result.AddRange(gearboxData.PowershiftShiftTime.Validate(mode, gearboxData.Type));
result.AddRange(gearboxData.PowershiftInertiaFactor.Validate(mode, gearboxData.Type));
//result.AddRange(gearboxData.PowershiftShiftTime.Validate(mode, gearboxData.Type));
//result.AddRange(gearboxData.PowershiftInertiaFactor.Validate(mode, gearboxData.Type));
validationContext.MemberName = "PowershiftInertiaFactor";
Validator.TryValidateProperty(gearboxData.PowershiftInertiaFactor, validationContext, result);
validationContext.MemberName = "PowershiftShiftTime";
Validator.TryValidateProperty(gearboxData.PowershiftShiftTime, validationContext, result);
}
if (result.Any()) {
......
......@@ -29,6 +29,7 @@
* Martin Rexeis, rexeis@ivt.tugraz.at, IVT, Graz University of Technology
*/
using TUGraz.VectoCommon.Utils;
using TUGraz.VectoCore.Models.Connector.Ports;
namespace TUGraz.VectoCore.Models.SimulationComponent
......@@ -36,7 +37,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent
public interface IIdleController : ITnOutPort
{
ITnOutPort RequestPort { set; }
void Reset();
}
}
\ No newline at end of file
......@@ -42,29 +42,19 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
/// </summary>
public class AMTShiftStrategy : ShiftStrategy
{
/// <summary>
/// The previous gear before the disengagement. Used for GetGear() when skipGears is false.
/// </summary>
protected uint PreviousGear;
protected uint _nextGear { get; set; }
public AMTShiftStrategy(GearboxData data, IDataBus dataBus) : base(data, dataBus)
{
PreviousGear = 1;
EarlyShiftUp = true;
SkipGears = true;
}
private bool SpeedTooLowForEngine(uint gear, PerSecond outAngularSpeed)
{
return (outAngularSpeed * Data.Gears[gear].Ratio).IsSmaller(DataBus.EngineIdleSpeed);
}
// original vecto2.2: (inAngularSpeed - IdleSpeed) / (RatedSpeed - IdleSpeed) >= 1.2
// = inAngularSpeed - IdleSpeed >= 1.2*(RatedSpeed - IdleSpeed)
// = inAngularSpeed >= 1.2*RatedSpeed - 0.2*IdleSpeed
private bool SpeedTooHighForEngine(uint gear, PerSecond outAngularSpeed)
{
return
......@@ -88,10 +78,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
return _nextGear;
}
public override void Disengage(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outEngineSpeed)
{
PreviousGear = Gearbox.Gear;
}
public override void Disengage(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outEngineSpeed) {}
public override uint InitGear(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity)
{
......@@ -106,7 +93,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
var response = _gearbox.Initialize(gear, outTorque, outAngularVelocity);
var fullLoadPower = response.EnginePowerRequest - response.DeltaFullLoad;
var fullLoadPower = response.DynamicFullLoadPower; //EnginePowerRequest - response.DeltaFullLoad;
var reserve = 1 - response.EnginePowerRequest / fullLoadPower;
var inTorque = response.ClutchPowerRequest / inAngularSpeed;
......
......@@ -92,6 +92,12 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
TorqueConverter.NextComponent = other;
}
public override GearInfo NextGear
{
get { return _strategy.NextGear; }
}
public override bool ClutchClosed(Second absTime)
{
return true;
......@@ -367,6 +373,10 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
container[ModalResultField.P_gbx_shift_loss] = CurrentState.PowershiftLosses == null
? 0.SI<Watt>()
: CurrentState.PowershiftLosses * avgInAngularSpeed;
container[ModalResultField.n_gbx_out_avg] = (PreviousState.OutAngularVelocity +
CurrentState.OutAngularVelocity) / 2.0;
container[ModalResultField.T_gbx_out] = CurrentState.OutTorque;
}
protected override void DoCommitSimulationStep()
......
......@@ -186,12 +186,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
return;
}
// C -> 0
//if (!_gearbox.TorqueConverterLocked && gear == 1) {
// _nextGear.SetState(absTime, true, 1, false);
// return;
//}
// L -> 0 -- not allowed!!
throw new VectoSimulationException(
"ShiftStrategy wanted to shift down but current gear is locked (L) and has no torque converter (C) and disenganging directly from (L) is not allowed.");
......@@ -326,7 +320,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
Data.Gears[gear].TorqueConverterShiftPolygon.IsAboveUpshiftCurve(inTorque, inEngineSpeed);
}
protected class NextGearState
public class NextGearState
{
public Second AbsTime;
public bool Disengaged;
......
......@@ -111,6 +111,13 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
return ModelData.Gears[gear];
}
public abstract GearInfo NextGear { get; }
public Second TractionInterruption
{
get { return ModelData.TractionInterruption; }
}
#endregion
public abstract bool ClutchClosed(Second absTime);
......
......@@ -61,6 +61,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
// mAAUX_Global.advancedAuxModel.Signals.WHTC = Declaration.WHTCcorrFactor
CurrentState = new BusAuxState();
PreviousState = new BusAuxState();
PreviousState.AngularSpeed = engineIdleSpeed;
AdditionalAux = additionalAux;
......
......@@ -48,7 +48,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
private readonly PerSecond _idleSpeed;
private readonly PerSecond _ratedSpeed;
private const double ClutchEff = 1;
private ClutchState _clutchState = ClutchState.ClutchSlipping;
public IIdleController IdleController
{
......@@ -63,8 +62,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
private readonly SI _clutchSpeedSlippingFactor;
private IIdleController _idleController;
protected Clutch(IVehicleContainer container) : base(container) {}
public Clutch(IVehicleContainer container, CombustionEngineData engineData) : base(container)
{
_idleSpeed = engineData.IdleSpeed;
......@@ -73,22 +70,11 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
(_idleSpeed + Constants.SimulationSettings.ClutchClosingSpeedNorm * (_ratedSpeed - _idleSpeed));
}
public ClutchState State()
{
return _clutchState;
}
//public ITnOutPort IdleControlPort
//{
// get { return NextComponent; }
//}
public virtual IResponse Initialize(NewtonMeter outTorque, PerSecond outAngularVelocity)
public IResponse Initialize(NewtonMeter outTorque, PerSecond outAngularVelocity)
{
NewtonMeter torqueIn;
PerSecond engineSpeedIn;
if (DataBus.DriverBehavior == DrivingBehavior.Halted /*DataBus.VehicleStopped*/) {
_clutchState = ClutchState.ClutchOpened;
engineSpeedIn = _idleSpeed;
torqueIn = 0.SI<NewtonMeter>();
} else {
......@@ -101,12 +87,11 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
return retVal;
}
public virtual IResponse Request(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity,
public IResponse Request(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity,
bool dryRun = false)
{
if (outAngularVelocity == null) {
Log.Debug("Invoking IdleController...");
var retval = IdleController.Request(absTime, dt, outTorque, null, dryRun);
retval.ClutchPowerRequest = 0.SI<Watt>();
CurrentState.SetState(0.SI<NewtonMeter>(), retval.EngineSpeed, outTorque, retval.EngineSpeed);
......@@ -122,7 +107,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
NewtonMeter torqueIn;
PerSecond angularVelocityIn;
if (DataBus.DriverBehavior == DrivingBehavior.Halted /*DataBus.VehicleStopped*/) {
_clutchState = ClutchState.ClutchOpened;
angularVelocityIn = _idleSpeed;
torqueIn = 0.SI<NewtonMeter>();
} else {
......@@ -130,10 +114,12 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
}
Log.Debug("to Engine: torque: {0}, angularVelocity: {1}, power {2}", torqueIn, angularVelocityIn,
Formulas.TorqueToPower(torqueIn, angularVelocityIn));
CurrentState.SetState(torqueIn, angularVelocityIn, outTorque, outAngularVelocity);
var retVal = NextComponent.Request(absTime, dt, torqueIn, angularVelocityIn, dryRun);
var retVal = NextComponent.Request(absTime, dt, torqueIn, angularVelocityIn, dryRun);
if (!dryRun) {
CurrentState.SetState(torqueIn, angularVelocityIn, outTorque, outAngularVelocity);
}
retVal.ClutchPowerRequest = outTorque *
((PreviousState.OutAngularVelocity ?? 0.SI<PerSecond>()) + CurrentState.OutAngularVelocity) / 2.0;
return retVal;
......@@ -147,7 +133,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
var engineSpeedNorm = (angularVelocity - _idleSpeed) / (_ratedSpeed - _idleSpeed);
if (engineSpeedNorm < Constants.SimulationSettings.ClutchClosingSpeedNorm) {
_clutchState = ClutchState.ClutchSlipping;
// MQ: 27.5.2016: when angularVelocity is 0 (at the end of the simulation interval) don't use the
// angularVelocity but average angular velocity
// Reason: if angularVelocity = 0 also the power (torque * angularVelocity) is 0 and then
......@@ -160,8 +145,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
angularVelocityIn = _clutchSpeedSlippingFactor * engineSpeed + _idleSpeed;
torqueIn = torque * effectiveAngularVelocity / ClutchEff / angularVelocityIn;
} else {
_clutchState = ClutchState.ClutchClosed;
}
}
......@@ -176,7 +159,6 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
container[ModalResultField.P_clutch_out] = CurrentState.OutTorque * avgOutAngularVelocity;
container[ModalResultField.P_clutch_loss] = CurrentState.InTorque * avgInAngularVelocity -
CurrentState.OutTorque * avgOutAngularVelocity;
//(CurrentState.InTorque - CurrentState.OutTorque) * avgInAngularVelocity;
}
}
}
......
......@@ -38,6 +38,7 @@ using TUGraz.VectoCore.Models.Connector.Ports;
using TUGraz.VectoCore.Models.Connector.Ports.Impl;
using TUGraz.VectoCore.Models.Simulation;
using TUGraz.VectoCore.Models.Simulation.Data;
using TUGraz.VectoCore.Models.Simulation.DataBus;
using TUGraz.VectoCore.Models.SimulationComponent.Data;
using TUGraz.VectoCore.OutputData;
using TUGraz.VectoCore.Utils;
......@@ -74,14 +75,14 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
protected IAuxPort EngineAux;
public CombustionEngine(IVehicleContainer cockpit, CombustionEngineData modelData, bool pt1Disabled = false)
: base(cockpit)
public CombustionEngine(IVehicleContainer container, CombustionEngineData modelData, bool pt1Disabled = false)
: base(container)
{
PT1Disabled = pt1Disabled;
ModelData = modelData;
PreviousState.OperationMode = EngineOperationMode.Idle;
//PreviousState.EnginePower = 0.SI<Watt>();
PreviousState.OperationMode = EngineOperationMode.Undef;
PreviousState.EnginePower = 0.SI<Watt>();
PreviousState.EngineSpeed = ModelData.IdleSpeed;
PreviousState.dt = 1.SI<Second>();
......@@ -134,7 +135,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
public IIdleController IdleController
{
get { return EngineIdleController ?? (EngineIdleController = new CombustionEngineIdleController(this)); }
get { return EngineIdleController ?? (EngineIdleController = new CombustionEngineIdleController(this, DataBus)); }
}
protected CombustionEngineIdleController EngineIdleController { get; set; }
......@@ -178,37 +179,35 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
CurrentState.OperationMode = EngineOperationMode.Stopped;
}
CurrentState.dt = dt;
CurrentState.EngineSpeed = angularVelocity;
var avgEngineSpeed = (PreviousState.EngineSpeed + CurrentState.EngineSpeed) / 2.0;
CurrentState.EngineTorqueOut = torqueOut;
CurrentState.FullDragTorque = ModelData.FullLoadCurve.DragLoadStationaryTorque(avgEngineSpeed);
var avgEngineSpeed = (PreviousState.EngineSpeed + angularVelocity) / 2.0;
var fullDragTorque = ModelData.FullLoadCurve.DragLoadStationaryTorque(avgEngineSpeed);
var dynamicFullLoadPower = ComputeFullLoadPower(avgEngineSpeed, dt);
CurrentState.DynamicFullLoadTorque = dynamicFullLoadPower / avgEngineSpeed;
CurrentState.InertiaTorqueLoss =
var dynamicFullLoadTorque = dynamicFullLoadPower / avgEngineSpeed;
var inertiaTorqueLoss =
Formulas.InertiaPower(angularVelocity, PreviousState.EngineSpeed, ModelData.Inertia, dt) /
avgEngineSpeed;
var auxTorqueDemand = EngineAux == null
? 0.SI<NewtonMeter>()
: EngineAux.TorqueDemand(absTime, dt, CurrentState.EngineTorqueOut,
CurrentState.EngineTorqueOut + CurrentState.InertiaTorqueLoss, angularVelocity, dryRun);
: EngineAux.TorqueDemand(absTime, dt, torqueOut,
torqueOut + inertiaTorqueLoss, angularVelocity, dryRun);
// compute the torque the engine has to provide. powertrain + aux + its own inertia
var totalTorqueDemand = torqueOut + auxTorqueDemand + CurrentState.InertiaTorqueLoss;
var totalTorqueDemand = torqueOut + auxTorqueDemand + inertiaTorqueLoss;
Log.Debug("EngineInertiaTorque: {0}", CurrentState.InertiaTorqueLoss);
Log.Debug("Drag Curve: torque: {0}, power: {1}", CurrentState.FullDragTorque,
CurrentState.FullDragTorque * avgEngineSpeed);
Log.Debug("EngineInertiaTorque: {0}", inertiaTorqueLoss);
Log.Debug("Drag Curve: torque: {0}, power: {1}", fullDragTorque, fullDragTorque * avgEngineSpeed);
Log.Debug("Dynamic FullLoad: torque: {0}, power: {1}", CurrentState.DynamicFullLoadTorque, dynamicFullLoadPower);
Log.Debug("Dynamic FullLoad: torque: {0}, power: {1}", dynamicFullLoadTorque, dynamicFullLoadPower);
ValidatePowerDemand(totalTorqueDemand); // requires CurrentState.FullDragTorque and DynamicfullLoad to be set!
// get max. torque as limited by gearbox. gearbox only limits torqueOut!
var gearboxFullLoad = DataBus.GearMaxTorque;
var deltaFull = ComputeDelta(torqueOut, totalTorqueDemand, CurrentState.DynamicFullLoadTorque, gearboxFullLoad, true);
var deltaDrag = ComputeDelta(torqueOut, totalTorqueDemand, CurrentState.FullDragTorque,
var deltaFull = ComputeDelta(torqueOut, totalTorqueDemand, dynamicFullLoadTorque, gearboxFullLoad, true);
var deltaDrag = ComputeDelta(torqueOut, totalTorqueDemand, fullDragTorque,
gearboxFullLoad != null ? -gearboxFullLoad : null, false);
if (dryRun) {
......@@ -217,12 +216,18 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
DeltaDragLoad = deltaDrag * avgEngineSpeed,
EnginePowerRequest = torqueOut * avgEngineSpeed,
DynamicFullLoadPower = dynamicFullLoadPower,
DragPower = CurrentState.FullDragTorque * avgEngineSpeed,
DragPower = fullDragTorque * avgEngineSpeed,
AuxiliariesPowerDemand = auxTorqueDemand * avgEngineSpeed,
EngineSpeed = angularVelocity,
Source = this,
};
}
CurrentState.dt = dt;
CurrentState.EngineSpeed = angularVelocity;
CurrentState.EngineTorqueOut = torqueOut;
CurrentState.FullDragTorque = fullDragTorque;
CurrentState.DynamicFullLoadTorque = dynamicFullLoadTorque;
CurrentState.InertiaTorqueLoss = inertiaTorqueLoss;
if (
(deltaFull * avgEngineSpeed).IsGreater(0.SI<Watt>(), Constants.SimulationSettings.LineSearchTolerance) &&
......@@ -327,13 +332,13 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
protected virtual void ValidatePowerDemand(NewtonMeter torqueDemand)
{
if (CurrentState.FullDragTorque >= 0 && torqueDemand < 0) {
throw new VectoSimulationException("P_engine_drag > 0! Tq_drag: {1}, Tq_eng: {2}, n_eng_avg: {0} [1/min] ",
CurrentState.EngineSpeed.AsRPM, CurrentState.FullDragTorque, CurrentState.EngineTorque);
throw new VectoSimulationException("P_engine_drag > 0! Tq_drag: {0}, Tq_eng: {1}, n_eng_avg: {2} [1/min] ",
CurrentState.FullDragTorque, torqueDemand, CurrentState.EngineSpeed.AsRPM);
}
if (CurrentState.DynamicFullLoadTorque <= 0 && torqueDemand > 0) {
throw new VectoSimulationException("P_engine_full < 0! Tq_drag: {1}, Tq_eng: {2}, n_eng_avg: {0} [1/min] ",
CurrentState.EngineSpeed.AsRPM, CurrentState.FullDragTorque, CurrentState.EngineTorque);
throw new VectoSimulationException("P_engine_full < 0! Tq_full: {0}, Tq_eng: {1}, n_eng_avg: {2} [1/min] ",
CurrentState.DynamicFullLoadTorque, torqueDemand, CurrentState.EngineSpeed.AsRPM);
}
}
......@@ -353,6 +358,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
container[ModalResultField.T_eng_fcmap] = CurrentState.EngineTorque;
container[ModalResultField.P_eng_full] = CurrentState.DynamicFullLoadTorque * avgEngineSpeed;
container[ModalResultField.P_eng_full_stat] = CurrentState.StationaryFullLoadTorque * avgEngineSpeed;
container[ModalResultField.P_eng_drag] = CurrentState.FullDragTorque * avgEngineSpeed;
container[ModalResultField.Tq_full] = CurrentState.DynamicFullLoadTorque;
container[ModalResultField.Tq_drag] = CurrentState.FullDragTorque;
......@@ -436,12 +442,16 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
var tStarPrev = pt1 * Math.Log(1.0 / (1 - powerRatio), Math.E).SI<Second>();
var tStar = tStarPrev + PreviousState.dt;
dynFullPowerCalculated = stationaryFullLoadPower * (1 - Math.Exp((-tStar / pt1).Value()));
dynFullPowerCalculated = VectoMath.Max(PreviousState.EnginePower, dynFullPowerCalculated);
}
// new check in vecto 3.x (according to Martin Rexeis)
if (dynFullPowerCalculated < StationaryIdleFullLoadPower) {
dynFullPowerCalculated = StationaryIdleFullLoadPower;
}
if (dynFullPowerCalculated > stationaryFullLoadPower) {
dynFullPowerCalculated = stationaryFullLoadPower;
}
return dynFullPowerCalculated;
}
......@@ -478,28 +488,46 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
protected class CombustionEngineIdleController : LoggingObject, IIdleController
{
protected readonly double PeDropSlope = -5;
protected readonly double PeDropOffset = 1.0;
private readonly double PeDropSlope = -5;
private readonly double PeDropOffset = 1.0;
protected readonly CombustionEngine Engine;
private readonly CombustionEngine _engine;
private readonly IDataBus _dataBus;
protected Second IdleStart;
protected Watt LastEnginePower;
private Second _idleStart;
private Watt _lastEnginePower;
public CombustionEngineIdleController(CombustionEngine combustionEngine)
public ITnOutPort RequestPort { private get; set; }
public CombustionEngineIdleController(CombustionEngine combustionEngine, IDataBus dataBus)
{
Engine = combustionEngine;
_engine = combustionEngine;
_dataBus = dataBus;
}
public ITnOutPort RequestPort { private get; set; }
public IResponse Initialize(NewtonMeter outTorque, PerSecond outAngularVelocity)
{
return new ResponseSuccess { Source = this };
}
public void Reset()
{
IdleStart = null;
_idleStart = null;
}
public IResponse Request(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity,
bool dryRun = false)
{
if (!_dataBus.VehicleStopped && _dataBus.Gear != _dataBus.NextGear.Gear && _dataBus.Gear != 0 &&
_dataBus.NextGear.Gear != 0) {
return RequestDoubleClutch(absTime, dt, outTorque, outAngularVelocity, dryRun);
} else {
return RequestIdling(absTime, dt, outTorque, outAngularVelocity, dryRun);
}
}
private IResponse RequestDoubleClutch(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity,
bool dryRun = false)
{
if (outAngularVelocity != null) {
throw new VectoException("IdleController can only handle idle requests, i.e. angularVelocity == null!");
......@@ -507,58 +535,102 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
if (!outTorque.IsEqual(0)) {
throw new VectoException("Torque has to be 0 for idle requests!");
}
if (IdleStart == null) {
IdleStart = absTime;
LastEnginePower = Engine.PreviousState.EnginePower;
var targetVelocity = _engine.PreviousState.EngineSpeed / _dataBus.GetGearData(_dataBus.Gear).Ratio *
_dataBus.GetGearData(_dataBus.NextGear.Gear).Ratio;
var velocitySlope = (targetVelocity - _engine.PreviousState.EngineSpeed) / _dataBus.TractionInterruption;
var nextAngularSpeed = (velocitySlope * dt + _engine.PreviousState.EngineSpeed);
if (nextAngularSpeed < _engine.ModelData.IdleSpeed) {
nextAngularSpeed = _engine.ModelData.IdleSpeed;
}
var retVal = RequestPort.Request(absTime, dt, 0.SI<NewtonMeter>(), nextAngularSpeed);
retVal.Switch().
Case<ResponseSuccess>().
Case<ResponseUnderload>(r => {
var angularSpeed = SearchAlgorithm.Search(nextAngularSpeed, r.Delta,
Constants.SimulationSettings.EngineIdlingSearchInterval,
getYValue: result => ((ResponseDryRun)result).DeltaDragLoad,
evaluateFunction: n => RequestPort.Request(absTime, dt, 0.SI<NewtonMeter>(), n, true),
criterion: result => ((ResponseDryRun)result).DeltaDragLoad.Value());
Log.Debug("Found operating point for idling. absTime: {0}, dt: {1}, torque: {2}, angularSpeed: {3}", absTime, dt,
0.SI<NewtonMeter>(), angularSpeed);
if (angularSpeed < _engine.ModelData.IdleSpeed) {
angularSpeed = _engine.ModelData.IdleSpeed;
}
retVal = RequestPort.Request(absTime, dt, 0.SI<NewtonMeter>(), angularSpeed);
}).
Case<ResponseOverload>(r => {
var angularSpeed = SearchAlgorithm.Search(nextAngularSpeed, r.Delta,
-Constants.SimulationSettings.EngineIdlingSearchInterval,
getYValue: result => ((ResponseDryRun)result).DeltaFullLoad,
evaluateFunction: n => RequestPort.Request(absTime, dt, 0.SI<NewtonMeter>(), n, true),
criterion: result => ((ResponseDryRun)result).DeltaFullLoad.Value());
Log.Debug("Found operating point for idling. absTime: {0}, dt: {1}, torque: {2}, angularSpeed: {3}", absTime, dt,
0.SI<NewtonMeter>(), angularSpeed);
angularSpeed = angularSpeed.LimitTo(_engine.ModelData.IdleSpeed, _engine.EngineRatedSpeed);
retVal = RequestPort.Request(absTime, dt, 0.SI<NewtonMeter>(), angularSpeed);
}).
Default(r => { throw new UnexpectedResponseException("searching Idling point", r); });
return retVal;
}
private IResponse RequestIdling(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity,
bool dryRun = false)
{
if (outAngularVelocity != null) {
throw new VectoException("IdleController can only handle idle requests, i.e. angularVelocity == null!");
}
if (!outTorque.IsEqual(0)) {
throw new VectoException("Torque has to be 0 for idle requests!");
}
if (_idleStart == null) {
_idleStart = absTime;
_lastEnginePower = _engine.PreviousState.EnginePower;
}
IResponse retVal;
var idleTime = absTime - IdleStart + dt;
var prevEngineSpeed = Engine.PreviousState.EngineSpeed;
var dragLoad = Engine.ModelData.FullLoadCurve.DragLoadStationaryPower(prevEngineSpeed);
var idleTime = absTime - _idleStart + dt;
var prevEngineSpeed = _engine.PreviousState.EngineSpeed;
var dragLoad = _engine.ModelData.FullLoadCurve.DragLoadStationaryPower(prevEngineSpeed);
var nextEnginePower = (LastEnginePower - dragLoad) * VectoMath.Max(idleTime.Value() * PeDropSlope + PeDropOffset, 0) +
dragLoad;
var nextEnginePower = (_lastEnginePower - dragLoad) *
VectoMath.Max(idleTime.Value() * PeDropSlope + PeDropOffset, 0) + dragLoad;
var auxDemandResponse = RequestPort.Request(absTime, dt, outTorque, prevEngineSpeed, true);
var auxDemandResponse = RequestPort.Request(absTime, dt, 0.SI<NewtonMeter>(), prevEngineSpeed, true);
var deltaEnginePower = nextEnginePower - (auxDemandResponse.AuxiliariesPowerDemand ?? 0.SI<Watt>());
var deltaTorque = deltaEnginePower / prevEngineSpeed;
var deltaAngularSpeed = deltaTorque / Engine.ModelData.Inertia * dt;
var deltaAngularSpeed = deltaTorque / _engine.ModelData.Inertia * dt;
var nextAngularSpeed = prevEngineSpeed;
if (deltaAngularSpeed > 0) {
retVal = RequestPort.Request(absTime, dt, outTorque, nextAngularSpeed);
retVal = RequestPort.Request(absTime, dt, 0.SI<NewtonMeter>(), nextAngularSpeed);
return retVal;
}
nextAngularSpeed = prevEngineSpeed + deltaAngularSpeed;
if (nextAngularSpeed < Engine.ModelData.IdleSpeed) {
nextAngularSpeed = Engine.ModelData.IdleSpeed;
}
nextAngularSpeed = (prevEngineSpeed + deltaAngularSpeed)
.LimitTo(_engine.ModelData.IdleSpeed, _engine.EngineRatedSpeed);
retVal = RequestPort.Request(absTime, dt, outTorque, nextAngularSpeed);
retVal = RequestPort.Request(absTime, dt, 0.SI<NewtonMeter>(), nextAngularSpeed);
retVal.Switch().
Case<ResponseSuccess>().
Case<ResponseUnderload>(r => {
var angularSpeed = SearchAlgorithm.Search(nextAngularSpeed, r.Delta,
Constants.SimulationSettings.EngineIdlingSearchInterval,
getYValue: result => ((ResponseDryRun)result).DeltaDragLoad,
evaluateFunction: n => RequestPort.Request(absTime, dt, outTorque, n, true),
evaluateFunction: n => RequestPort.Request(absTime, dt, 0.SI<NewtonMeter>(), n, true),
criterion: result => ((ResponseDryRun)result).DeltaDragLoad.Value());
Log.Debug("Found operating point for idling. absTime: {0}, dt: {1}, torque: {2}, angularSpeed: {3}", absTime, dt,
outTorque, angularSpeed);
retVal = RequestPort.Request(absTime, dt, outTorque, angularSpeed);
0.SI<NewtonMeter>(), angularSpeed);
retVal = RequestPort.Request(absTime, dt, 0.SI<NewtonMeter>(), angularSpeed);
}).
Default(r => { throw new UnexpectedResponseException("searching Idling point", r); });
return retVal;
}
public IResponse Initialize(NewtonMeter outTorque, PerSecond outAngularVelocity)
{
return new ResponseSuccess() { Source = this };
}
}
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment