From 5945d78ba5f7283bed7ef6fea7c3f9606e3e119f Mon Sep 17 00:00:00 2001
From: Markus Quaritsch <markus.quaritsch@tugraz.at>
Date: Tue, 31 May 2022 17:11:51 +0200
Subject: [PATCH] adding pto component for E2 vehicles: powertrain builder,
 data adapter, new component

---
 VECTO/GUI/VehicleForm.vb                      |   5 +
 VECTO/GUI/XMLImportJobDialog.vb               |  42 +++----
 .../EngineeringDataAdapter.cs                 |  14 +++
 .../EngineeringModeVectoRunDataFactory.cs     |   8 +-
 .../Models/Declaration/AuxDemandEntry.cs      |   2 +
 .../Models/Declaration/PTOTransmission.cs     |   5 +-
 .../Simulation/Impl/PowertrainBuilder.cs      |  48 +++++++-
 .../Models/SimulationComponent/Impl/PEVPTO.cs | 116 ++++++++++++++++++
 8 files changed, 212 insertions(+), 28 deletions(-)
 create mode 100644 VectoCore/VectoCore/Models/SimulationComponent/Impl/PEVPTO.cs

diff --git a/VECTO/GUI/VehicleForm.vb b/VECTO/GUI/VehicleForm.vb
index 0874ea8567..e22fc89127 100644
--- a/VECTO/GUI/VehicleForm.vb
+++ b/VECTO/GUI/VehicleForm.vb
@@ -738,6 +738,11 @@ Public Class VehicleForm
 			veh.VehicleTankSystem = CType(If(cbTankSystem.SelectedIndex > 0, cbTankSystem.SelectedValue, Nothing), TankSystem?)
 		End If
 
+		if (VehicleType = VectoSimulationJobType.BatteryElectricVehicle) Then
+		    veh.PtoType = CType(cbPTOType.SelectedValue, String)
+		    veh.PtoLossMap.Init(GetPath(file), tbPTOLossMap.Text)
+		End If
+
 		If (VehicleType = VectoSimulationJobType.ParallelHybridVehicle OrElse VehicleType = VectoSimulationJobType.BatteryElectricVehicle OrElse VehicleType = VectoSimulationJobType.SerialHybridVehicle) Then
 			For Each reess As ListViewItem In lvREESSPacks.Items
 				veh.ReessPacks.Add(Tuple.Create(reess.SubItems(REESPackTbl.ReessFile).Text, reess.SubItems(REESPackTbl.Count).Text.ToInt(), reess.SubItems(REESPackTbl.StringId).Text.ToInt()))
diff --git a/VECTO/GUI/XMLImportJobDialog.vb b/VECTO/GUI/XMLImportJobDialog.vb
index d462a579bd..9afc8e727a 100644
--- a/VECTO/GUI/XMLImportJobDialog.vb
+++ b/VECTO/GUI/XMLImportJobDialog.vb
@@ -1,27 +1,27 @@
-Imports System.IO
-
-Public Class XMLImportJobDialog
-	Private Sub btnBrowseJob_Click(sender As Object, e As EventArgs) Handles btnBrowseJob.Click
-		Dim dialog As OpenFileDialog = New OpenFileDialog()
-		If (dialog.ShowDialog() = DialogResult.OK) Then
-			tbJobFile.Text = Path.GetFullPath(dialog.FileName)
-		End If
-	End Sub
-
-	Private Sub btnBrowseOutput_Click(sender As Object, e As EventArgs) Handles btnBrowseOutput.Click
+Imports System.IO
+
+Public Class XMLImportJobDialog
+	Private Sub btnBrowseJob_Click(sender As Object, e As EventArgs) Handles btnBrowseJob.Click
+		Dim dialog As OpenFileDialog = New OpenFileDialog()
+		If (dialog.ShowDialog() = DialogResult.OK) Then
+			tbJobFile.Text = Path.GetFullPath(dialog.FileName)
+		End If
+	End Sub
+
+	Private Sub btnBrowseOutput_Click(sender As Object, e As EventArgs) Handles btnBrowseOutput.Click
 		If Not FolderFileBrowser.OpenDialog("") Then
 			Exit Sub
-		End If
+		End If
 
 		Dim filePath As String = FolderFileBrowser.Files(0)
 		tbDestination.Text = Path.GetFullPath(filePath)
-	End Sub
-
-	Private Sub btnClose_Click(sender As Object, e As EventArgs) Handles btnClose.Click
-		Close()
-	End Sub
-
-	Private Sub btnImport_Click(sender As Object, e As EventArgs) Handles btnImport.Click
-		' TODO!
-	End Sub
+	End Sub
+
+	Private Sub btnClose_Click(sender As Object, e As EventArgs) Handles btnClose.Click
+		Close()
+	End Sub
+
+	Private Sub btnImport_Click(sender As Object, e As EventArgs) Handles btnImport.Click
+		' TODO!
+	End Sub
 End Class
\ No newline at end of file
diff --git a/VectoCore/VectoCore/InputData/Reader/DataObjectAdapter/EngineeringDataAdapter.cs b/VectoCore/VectoCore/InputData/Reader/DataObjectAdapter/EngineeringDataAdapter.cs
index 5da50a0bf3..344bbdeb65 100644
--- a/VectoCore/VectoCore/InputData/Reader/DataObjectAdapter/EngineeringDataAdapter.cs
+++ b/VectoCore/VectoCore/InputData/Reader/DataObjectAdapter/EngineeringDataAdapter.cs
@@ -1280,6 +1280,20 @@ namespace TUGraz.VectoCore.InputData.Reader.DataObjectAdapter
 			return retVal;
 		}
 
+		public PTOData CreateBatteryElectricPTOTransmissionData(IPTOTransmissionInputData pto)
+		{
+			if (pto.PTOTransmissionType != "None") {
+				var ptoData = new PTOData() {
+					TransmissionType = pto.PTOTransmissionType,
+					LossMap = pto.PTOLossMap == null
+						? PTOIdleLossMapReader.GetZeroLossMap()
+						: PTOIdleLossMapReader.Create(pto.PTOLossMap)
+				};
+				return ptoData;
+			}
+
+			return null;
+		}
 	}
 
 	public class IEPCGearboxInputData : IGearboxDeclarationInputData
diff --git a/VectoCore/VectoCore/InputData/Reader/Impl/EngineeringModeVectoRunDataFactory.cs b/VectoCore/VectoCore/InputData/Reader/Impl/EngineeringModeVectoRunDataFactory.cs
index 572099be39..e3ac366d41 100644
--- a/VectoCore/VectoCore/InputData/Reader/Impl/EngineeringModeVectoRunDataFactory.cs
+++ b/VectoCore/VectoCore/InputData/Reader/Impl/EngineeringModeVectoRunDataFactory.cs
@@ -311,9 +311,9 @@ namespace TUGraz.VectoCore.InputData.Reader.Impl
 
 				var crossWindRequired = vehicle.Components.AirdragInputData.CrossWindCorrectionMode ==
 										CrossWindCorrectionMode.VAirBetaLookupTable;
-				//var ptoTransmissionData = dao.CreatePTOTransmissionData(vehicle.Components.PTOTransmissionInputData);
+                var ptoTransmissionData = dao.CreateBatteryElectricPTOTransmissionData(vehicle.Components.PTOTransmissionInputData);
 
-				var drivingCycle = GetDrivingCycle(cycle, crossWindRequired);
+                var drivingCycle = GetDrivingCycle(cycle, crossWindRequired);
 
 				var vehicleData = dao.CreateVehicleData(vehicle);
 				yield return new VectoRunData
@@ -329,8 +329,8 @@ namespace TUGraz.VectoCore.InputData.Reader.Impl
 					Aux = dao.CreateAuxiliaryData(vehicle.Components.AuxiliaryInputData),
 					BusAuxiliaries = dao.CreateBusAuxiliariesData(vehicle.Components.AuxiliaryInputData, vehicleData, VectoSimulationJobType.BatteryElectricVehicle),
 					Retarder = dao.CreateRetarderData(vehicle.Components.RetarderInputData, powertrainPosition),
-					//PTO = ptoTransmissionData,
-					Cycle = new DrivingCycleProxy(drivingCycle, cycle.Name),
+                    PTO = ptoTransmissionData,
+                    Cycle = new DrivingCycleProxy(drivingCycle, cycle.Name),
 					ExecutionMode = ExecutionMode.Engineering,
 					ElectricMachinesData = electricMachinesData,
 					//HybridStrategyParameters = dao.CreateHybridStrategyParameters(InputDataProvider.JobInputData.HybridStrategyParameters),
diff --git a/VectoCore/VectoCore/Models/Declaration/AuxDemandEntry.cs b/VectoCore/VectoCore/Models/Declaration/AuxDemandEntry.cs
index ea94166fb0..b9561d719e 100644
--- a/VectoCore/VectoCore/Models/Declaration/AuxDemandEntry.cs
+++ b/VectoCore/VectoCore/Models/Declaration/AuxDemandEntry.cs
@@ -36,5 +36,7 @@ namespace TUGraz.VectoCore.Models.Declaration
 	public struct AuxDemandEntry
 	{
 		public Watt PowerDemand;
+
+		public NewtonMeter TorqueLoss;
 	}
 }
\ No newline at end of file
diff --git a/VectoCore/VectoCore/Models/Declaration/PTOTransmission.cs b/VectoCore/VectoCore/Models/Declaration/PTOTransmission.cs
index 61cb57c6f3..5d0eadeb40 100644
--- a/VectoCore/VectoCore/Models/Declaration/PTOTransmission.cs
+++ b/VectoCore/VectoCore/Models/Declaration/PTOTransmission.cs
@@ -48,7 +48,10 @@ namespace TUGraz.VectoCore.Models.Declaration
 		{
 			Data = table.Rows.Cast<DataRow>().ToDictionary(
 				r => r.Field<string>("technology"),
-				r => new AuxDemandEntry { PowerDemand = r.ParseDouble("powerloss").SI<Watt>() });
+				r => new AuxDemandEntry {
+					PowerDemand = r.ParseDouble("powerloss").SI<Watt>(),
+					TorqueLoss = r.ParseDouble("torqueloss").SI<NewtonMeter>()
+				});
 		}
 
 		public string[] GetTechnologies()
diff --git a/VectoCore/VectoCore/Models/Simulation/Impl/PowertrainBuilder.cs b/VectoCore/VectoCore/Models/Simulation/Impl/PowertrainBuilder.cs
index 8bf4c9418e..81ffafc6af 100644
--- a/VectoCore/VectoCore/Models/Simulation/Impl/PowertrainBuilder.cs
+++ b/VectoCore/VectoCore/Models/Simulation/Impl/PowertrainBuilder.cs
@@ -78,7 +78,7 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
 						case VectoSimulationJobType.ConventionalVehicle: return BuildFullPowertrainConventional(data);
 						case VectoSimulationJobType.ParallelHybridVehicle: return BuildFullPowertrainParallelHybrid(data);
 						case VectoSimulationJobType.SerialHybridVehicle: return BuildFullPowertrainSerialHybrid(data);
-						case VectoSimulationJobType.BatteryElectricVehicle: return BuildBatteryElectricPowertrain(data);
+						case VectoSimulationJobType.BatteryElectricVehicle: return BuildFulPowertrainBatteryElectric(data);
 						case VectoSimulationJobType.EngineOnlySimulation: return BuildEngineOnly(data);
 						case VectoSimulationJobType.IEPC_E: return BuildFullPowertrainIEPCE(data);
 						case VectoSimulationJobType.IEPC_S: return BuildFullPowertrainIEPCSerial(data);
@@ -662,7 +662,7 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
 		///       â””Engine E2
 		/// </code>
 		/// </summary>
-		private IVehicleContainer BuildBatteryElectricPowertrain(VectoRunData data)
+		private IVehicleContainer BuildFulPowertrainBatteryElectric(VectoRunData data)
 		{
 			if (data.Cycle.CycleType != CycleType.DistanceBased) {
 				throw new VectoException("CycleType must be DistanceBased");
@@ -725,6 +725,7 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
 						.AddComponent(GetRetarder(RetarderType.TransmissionOutputRetarder, data.Retarder, container))
 						.AddComponent(gearbox)
 						.AddComponent(GetRetarder(RetarderType.TransmissionInputRetarder, data.Retarder, container))
+						.AddComponent(data.PTO != null ? GetPEVPTO(container, data): null)
 						.AddComponent(em);
 
 					new ATClutchInfo(container);
@@ -754,6 +755,49 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
 			return container;
 		}
 
+		private IPowerTrainComponent GetPEVPTO(VehicleContainer container, VectoRunData data)
+		{
+			if (data.PTO == null) {
+				return null;
+			}
+			var pto = new PEVPTO(container);
+
+			RoadSweeperAuxiliary rdSwpAux = null;
+			PTODriveAuxiliary ptoDrive = null;
+			if (data.ExecutionMode == ExecutionMode.Engineering && data.Cycle.Entries.Any(x => x.PTOActive == PTOActivity.PTOActivityRoadSweeping)) {
+				if (data.DriverData.PTODriveMinSpeed == null) {
+					throw new VectoSimulationException("PTO activity 'road sweeping' requested, but no min. engine speed or gear provided");
+				}
+				rdSwpAux = new RoadSweeperAuxiliary(container);
+				pto.Add(Constants.Auxiliaries.IDs.PTORoadsweeping, (nEng, absTime, dt, dryRun) => rdSwpAux.PowerDemand(nEng, absTime, dt, dryRun) / nEng);
+				container.ModalData?.AddAuxiliary(Constants.Auxiliaries.IDs.PTORoadsweeping, Constants.Auxiliaries.PowerPrefix + Constants.Auxiliaries.IDs.PTORoadsweeping);
+			}
+
+			if (data.ExecutionMode == ExecutionMode.Engineering &&
+				data.Cycle.Entries.Any(x => x.PTOActive == PTOActivity.PTOActivityWhileDrive)) {
+				if (data.PTOCycleWhileDrive == null) {
+					throw new VectoException("PTO activation while drive requested in cycle but no PTO cycle provided");
+				}
+
+				ptoDrive = new PTODriveAuxiliary(container, data.PTOCycleWhileDrive);
+				pto.Add(Constants.Auxiliaries.IDs.PTODuringDrive, (nEng, absTime, dt, dryRun) => ptoDrive.PowerDemand(nEng, absTime, dt, dryRun) / nEng);
+				container.ModalData?.AddAuxiliary(Constants.Auxiliaries.IDs.PTODuringDrive, Constants.Auxiliaries.PowerPrefix + Constants.Auxiliaries.IDs.PTODuringDrive);
+			}
+			if (data.PTO != null) {
+				pto.AddConstant(Constants.Auxiliaries.IDs.PTOTransmission,
+								DeclarationData.PTOTransmission.Lookup(data.PTO.TransmissionType).TorqueLoss);
+				container.ModalData?.AddAuxiliary(Constants.Auxiliaries.IDs.PTOTransmission,
+												Constants.Auxiliaries.PowerPrefix + Constants.Auxiliaries.IDs.PTOTransmission);
+
+				pto.Add(Constants.Auxiliaries.IDs.PTOConsumer,
+						(n, absTime, dt, dryRun) => container.DrivingCycleInfo.PTOActive || (rdSwpAux?.Active(absTime) ?? false) || (ptoDrive?.Active(absTime) ?? false) ? null : data.PTO.LossMap.GetTorqueLoss(n));
+				container.ModalData?.AddAuxiliary(Constants.Auxiliaries.IDs.PTOConsumer,
+												Constants.Auxiliaries.PowerPrefix + Constants.Auxiliaries.IDs.PTOConsumer);
+			}
+
+			return pto;
+		}
+
 		private static Retarder GetRetarder(RetarderType type, RetarderData data, IVehicleContainer container) =>
 			type == data.Type ? new Retarder(container, data.LossMap, data.Ratio) : null;
 
diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Impl/PEVPTO.cs b/VectoCore/VectoCore/Models/SimulationComponent/Impl/PEVPTO.cs
new file mode 100644
index 0000000000..6fcfa4e524
--- /dev/null
+++ b/VectoCore/VectoCore/Models/SimulationComponent/Impl/PEVPTO.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using TUGraz.VectoCommon.Models;
+using TUGraz.VectoCommon.Utils;
+using TUGraz.VectoCore.Configuration;
+using TUGraz.VectoCore.Models.Connector.Ports;
+using TUGraz.VectoCore.Models.Declaration;
+using TUGraz.VectoCore.Models.Simulation;
+using TUGraz.VectoCore.Models.SimulationComponent.Data;
+using TUGraz.VectoCore.OutputData;
+
+namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
+{
+	public class PEVPTO : StatefulProviderComponent<PEVPTO.State, ITnOutPort, ITnInPort, ITnOutPort>,
+		IPowerTrainComponent, ITnInPort, ITnOutPort
+	{
+		public class State
+		{
+			public Dictionary<string, NewtonMeter> PowerDemands { get; set; }
+			public PerSecond OutAngularVelocity { get; set; }
+		}
+
+		protected readonly Dictionary<string, Func<PerSecond, Second, Second, bool, NewtonMeter>> Auxiliaries =
+			new Dictionary<string, Func<PerSecond, Second, Second, bool, NewtonMeter>>();
+
+		public PEVPTO(IVehicleContainer container) : base(container)
+		{
+			
+		}
+
+		public void AddConstant(string auxId, NewtonMeter torqueLoss)
+		{
+			Add(auxId, (nEng, absTime, dt, dryRun) => torqueLoss);
+		}
+
+		public void Add(string auxId, Func<PerSecond, Second, Second, bool, NewtonMeter> torqueLossFunction)
+		{
+			Auxiliaries[auxId] = torqueLossFunction;
+		}
+
+		#region Implementation of ITnOutPort
+
+		public IResponse Initialize(NewtonMeter outTorque, PerSecond outAngularVelocity)
+		{
+			PreviousState.OutAngularVelocity = outAngularVelocity;
+			var torqueLoss = outAngularVelocity.IsEqual(0)
+				? 0.SI<NewtonMeter>()
+				: ComputeTorqueLoss(0.SI<Second>(), Constants.SimulationSettings.TargetTimeInterval, outAngularVelocity,
+					false);
+
+			return NextComponent.Initialize(outTorque + torqueLoss, outAngularVelocity);
+		}
+
+		public IResponse Request(Second absTime, Second dt, NewtonMeter outTorque, PerSecond outAngularVelocity, bool dryRun)
+		{
+			var avgAngularSpeed = PreviousState.OutAngularVelocity != null
+				? (outAngularVelocity + PreviousState.OutAngularVelocity) / 2.0
+				: outAngularVelocity;
+
+			var torqueLoss = 0.SI<NewtonMeter>();
+			if (!DataBus.GearboxInfo.GearEngaged(absTime)) {
+				var powerDemands = new Dictionary<string, NewtonMeter>(Auxiliaries.Count);
+				foreach (var aux in Auxiliaries) {
+					powerDemands[aux.Key] = 0.SI<NewtonMeter>();
+				}
+				if (!dryRun) {
+					CurrentState.PowerDemands = powerDemands;
+				}
+			} else {
+				if (outAngularVelocity != null && !avgAngularSpeed.IsEqual(0)) {
+					torqueLoss = ComputeTorqueLoss(absTime, dt, avgAngularSpeed, dryRun);
+				}
+			}
+
+			if (!dryRun) {
+				CurrentState.OutAngularVelocity = outAngularVelocity;
+			}
+			
+			return NextComponent.Request(absTime, dt, outTorque + torqueLoss, outAngularVelocity, dryRun);
+		}
+
+
+		#endregion
+
+		private NewtonMeter ComputeTorqueLoss(Second absTime, Second dt, PerSecond avgAngularSpeed, bool dryRun)
+		{
+			var powerDemands = new Dictionary<string, NewtonMeter>(Auxiliaries.Count);
+			foreach (var item in Auxiliaries) {
+				var value = item.Value(avgAngularSpeed, absTime, dt, dryRun);
+				if (value != null) {
+					powerDemands[item.Key] = value;
+				}
+			}
+			if (!dryRun) {
+				CurrentState.PowerDemands = powerDemands;
+			}
+
+			return powerDemands.Sum(kv => kv.Value) ?? 0.SI<NewtonMeter>();
+		}
+
+
+		#region Overrides of VectoSimulationComponent
+
+		protected override void DoWriteModalResults(Second time, Second simulationInterval, IModalDataContainer container)
+		{
+			var avgAngularSpeed = PreviousState.OutAngularVelocity != null
+				? (CurrentState.OutAngularVelocity + PreviousState.OutAngularVelocity) / 2.0
+				: CurrentState.OutAngularVelocity;
+			foreach (var kv in CurrentState.PowerDemands) {
+				container[kv.Key] = kv.Value * avgAngularSpeed;
+			}
+		}
+
+		#endregion
+	}
+}
\ No newline at end of file
-- 
GitLab