From 80b42b80ffd795468347632cd5d1b07432d41bb4 Mon Sep 17 00:00:00 2001
From: Markus Quaritsch <markus.quaritsch@tugraz.at>
Date: Tue, 13 Jul 2021 12:56:04 +0200
Subject: [PATCH] saving and loading battery system works, write modal results
 for batterysystem (total/average of all batteries / battery strings)

---
 VECTO/GUI/VehicleForm.vb                      |  2 +-
 VECTO/Input Files/Vehicle.vb                  |  7 ++-
 .../InputData/FileIO/JSON/JSONVehicleData.cs  | 17 ++++--
 .../Data/Battery/BatteryData.cs               |  9 ++-
 .../SimulationComponent/Impl/BatterySystem.cs | 58 +++++++++++++++++--
 .../Strategies/HybridStrategy.cs              | 22 +++----
 .../OutputData/FileIO/JSONFileWriter.cs       |  4 +-
 .../OutputData/ModalDataContainer.cs          | 33 ++++++++---
 8 files changed, 116 insertions(+), 36 deletions(-)

diff --git a/VECTO/GUI/VehicleForm.vb b/VECTO/GUI/VehicleForm.vb
index 031978f731..17faa44e86 100644
--- a/VECTO/GUI/VehicleForm.vb
+++ b/VECTO/GUI/VehicleForm.vb
@@ -481,7 +481,7 @@ Public Class VehicleForm
 			If(angledrive.LossMap Is Nothing, "", GetRelativePath(angledrive.LossMap.Source, basePath))
 
 		If (vehicle.VehicleType = VectoSimulationJobType.BatteryElectricVehicle OrElse vehicle.VehicleType = VectoSimulationJobType.ParallelHybridVehicle) Then
-		    lvREESSPacks.Clear()
+		    lvREESSPacks.Items.Clear()
 		    For Each entry As IElectricStorageEngineeringInputData In vehicle.Components.ElectricStorage.ElectricStorageElements.OrderBy(function(x) x.StringId)
 		        lvREESSPacks.Items.Add(CreateREESSPackListViewItem(GetRelativePath(entry.REESSPack.DataSource.SourceFile, basePath), entry.Count, entry.StringId))
 		    Next
diff --git a/VECTO/Input Files/Vehicle.vb b/VECTO/Input Files/Vehicle.vb
index 75d79ec6d4..4bbc2859e6 100644
--- a/VECTO/Input Files/Vehicle.vb	
+++ b/VECTO/Input Files/Vehicle.vb	
@@ -106,7 +106,8 @@ Public Class Vehicle
 
 		Axles = New List(Of AxleInputData)
 		torqueLimitsList = New List(Of ITorqueLimitInputData)
-		PtoLossMap = New SubPath()
+		ReessPacks = new List(Of Tuple(Of String,Integer,Integer))
+	    PtoLossMap = New SubPath()
 		PtoCycleStandstill = New SubPath()
         PtoCycleDriving = new SubPath()
 		ElectricMotorFile = New SubPath()
@@ -928,12 +929,12 @@ Public Class ElectricStorageSystemWrapper
 
     Public ReadOnly Property ElectricStorageElements As IList(Of IElectricStorageDeclarationInputData) Implements IElectricStorageSystemDeclarationInputData.ElectricStorageElements
 	get
-			_vehicle.ReessPacks.Select(Function(x) new ElectricStorageWrapper(x, _vehicle.FilePath)).toList()
+			return _vehicle.ReessPacks.Select(Function(x) new ElectricStorageWrapper(x, GetPath(_vehicle.FilePath))).Cast(of IElectricStorageDeclarationInputData) .toList()
 	End Get
     End Property
     Public ReadOnly Property IElectricStorageSystemEngineeringInputData_ElectricStorageElements As IList(Of IElectricStorageEngineeringInputData) Implements IElectricStorageSystemEngineeringInputData.ElectricStorageElements
 	get
-
+	    return _vehicle.ReessPacks.Select(Function(x) new ElectricStorageWrapper(x, GetPath(_vehicle.FilePath))).cast(of IElectricStorageEngineeringInputData).toList()
 	End Get
     End Property
 End Class
diff --git a/VectoCore/VectoCore/InputData/FileIO/JSON/JSONVehicleData.cs b/VectoCore/VectoCore/InputData/FileIO/JSON/JSONVehicleData.cs
index c27faf5e8b..e99c766fe2 100644
--- a/VectoCore/VectoCore/InputData/FileIO/JSON/JSONVehicleData.cs
+++ b/VectoCore/VectoCore/InputData/FileIO/JSON/JSONVehicleData.cs
@@ -125,11 +125,20 @@ namespace TUGraz.VectoCore.InputData.FileIO.JSON
 		protected virtual JSONElectricStorageSystemEngineeringInputData ReadBatteries()
 		{
 			var entries = new List<IElectricStorageEngineeringInputData>();
-			foreach (var entry in Body["Batteries"]) {
+			if (Body["Batteries"] != null) {
+				foreach (var entry in Body["Batteries"]) {
+					entries.Add(new JSONElectricStorageEngineeringInputData() {
+						Count = entry.GetEx<int>("NumPacks"),
+						StringId = entry.GetEx<int>("StreamId"),
+						REESSPack = JSONInputDataFactory.ReadREESSData(
+							Path.Combine(BasePath, entry.GetEx<string>("BatteryFile")), false)
+					});
+				}
+			} else {
 				entries.Add(new JSONElectricStorageEngineeringInputData() {
-					Count = entry.GetEx<int>("NumPacks"),
-					StringId = entry.GetEx<int>("StreamId"),
-					REESSPack = JSONInputDataFactory.ReadREESSData(Path.Combine(BasePath, entry["Battery"].GetEx<string>("BatteryFile")), false)
+					Count = Body["Battery"].GetEx<int>("NumPacks"),
+					StringId = 0,
+					REESSPack = JSONInputDataFactory.ReadREESSData(Path.Combine(BasePath, Body["Battery"].GetEx<string>("BatteryFile")), false)
 				});
 			}
 
diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Data/Battery/BatteryData.cs b/VectoCore/VectoCore/Models/SimulationComponent/Data/Battery/BatteryData.cs
index a52cae4734..52ba4b9dbf 100644
--- a/VectoCore/VectoCore/Models/SimulationComponent/Data/Battery/BatteryData.cs
+++ b/VectoCore/VectoCore/Models/SimulationComponent/Data/Battery/BatteryData.cs
@@ -17,8 +17,13 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Data.Battery {
 		public List<Tuple<int, BatteryData>> Batteries { get; internal set; }
 
 		public double InitialSoC { get; internal set; }
-		public AmpereSecond Capacity {
-			get { throw new NotImplementedException();}
+		public AmpereSecond Capacity
+		{
+			get
+			{
+				return Batteries.Select(x => x.Item1).Distinct().OrderBy(x => x).Aggregate(0.SI<AmpereSecond>(),
+					(current, s) => current + Batteries.Where(x => x.Item1 == s).Min(x => x.Item2.Capacity));
+			}
 		}
 	}
 
diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Impl/BatterySystem.cs b/VectoCore/VectoCore/Models/SimulationComponent/Impl/BatterySystem.cs
index baacffa2c2..f915d7754a 100644
--- a/VectoCore/VectoCore/Models/SimulationComponent/Impl/BatterySystem.cs
+++ b/VectoCore/VectoCore/Models/SimulationComponent/Impl/BatterySystem.cs
@@ -8,12 +8,13 @@ using TUGraz.VectoCommon.Utils;
 using TUGraz.VectoCore.Configuration;
 using TUGraz.VectoCore.Models.Connector.Ports.Impl;
 using TUGraz.VectoCore.Models.Simulation;
+using TUGraz.VectoCore.Models.Simulation.Data;
 using TUGraz.VectoCore.Models.SimulationComponent.Data.Battery;
 using TUGraz.VectoCore.OutputData;
 
 namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
 {
-	public class BatterySystem : VectoSimulationComponent, IElectricEnergyStorage, IElectricEnergyStoragePort
+	public class BatterySystem : StatefulVectoSimulationComponent<BatterySystem.State>, IElectricEnergyStorage, IElectricEnergyStoragePort
 	{
 		public class BatteryString
 		{
@@ -42,6 +43,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
 			public AmpereSecond Capacity => _capacity ?? (_capacity = _batteries.Min(x => x.Capacity));
 
 			public double SoC => _batteries.Min(x => x.StateOfCharge * x.Capacity) / Capacity;
+			public WattSecond StoredEnergy => _batteries.Min(x => x.StateOfCharge * x.Capacity) * OpenCircuitVoltage;
 
 			public Watt MaxDischargePower(Second dt)
 			{
@@ -79,12 +81,18 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
 					current = SelectSolution(solutions, powerDemand.Value(), dt);
 				}
 
+				if (!dryRun) {
+					Current = current;
+				}
+
 				return _batteries.Select(x => {
 					var demand = (x.InternalVoltage + x.InternalResistance * current) * current;
 					return x.Request(absTime, dt, demand, dryRun);
 				}).ToList();
 			}
 
+			public Ampere Current { get; set; }
+
 			private Ampere SelectSolution(double[] solutions, double sign, Second dt)
 			{
 				var maxCurrent = Math.Sign(sign) < 0
@@ -114,19 +122,34 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
 
 		public override void CommitSimulationStep(Second time, Second simulationInterval, IModalDataContainer container)
 		{
+			base.CommitSimulationStep(time, simulationInterval, container);
 			foreach (var battery in Batteries) {
 				foreach (var b in battery.Value.Batteries) {
 					b.CommitSimulationStep(time, simulationInterval, container);
 				}
 			}
-			base.CommitSimulationStep(time, simulationInterval, container);
+			
 		}
 
 		protected override void DoWriteModalResults(Second time, Second simulationInterval, IModalDataContainer container)
 		{
-			
+			var cellVoltage = InternalVoltage;
+			container[ModalResultField.U0_reess] = cellVoltage;
+			container[ModalResultField.U_reess_terminal] =
+				cellVoltage +
+				CurrentState.TotalCurrent *
+				InternalResistance; // adding both terms because pos. current charges the battery!
+			container[ModalResultField.I_reess] = CurrentState.TotalCurrent;
+			container[ModalResultField.REESSStateOfCharge] = CurrentState.StateOfCharge.SI();
+			container[ModalResultField.P_reess_terminal] = CurrentState.PowerDemand;
+			container[ModalResultField.P_reess_int] = cellVoltage * CurrentState.TotalCurrent;
+			container[ModalResultField.P_reess_loss] = CurrentState.BatteryLoss;
+			container[ModalResultField.P_reess_charge_max] = CurrentState.MaxChargePower;
+			container[ModalResultField.P_reess_discharge_max] = CurrentState.MaxDischargePower;
 		}
 
+		public Ohm InternalResistance => (1 / Batteries.Sum(bs => 1 / bs.Value.InternalResistance.Value())).SI<Ohm>();
+		
 		protected override void DoCommitSimulationStep(Second time, Second simulationInterval)
 		{
 			
@@ -150,7 +173,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
 		public AmpereSecond TotalCapacity =>
 			_totalCapacity ?? (_totalCapacity = Batteries.Values.Sum(bs => bs.Capacity));
 
-		public WattSecond StoredEnergy { get; }
+		public WattSecond StoredEnergy => Batteries.Values.Sum(bs => bs.StoredEnergy);
 		
 		public Watt MaxChargePower(Second dt)
 		{
@@ -208,6 +231,10 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
 						distributedPower += power;
 					}
 				}
+
+				if (limitBB.Count > 0) {
+					Log.Debug($"BB ${string.Join(", ", limitBB.Keys)} are at max - recalculating power distribution");
+				}
 			} while (!distributedPower.IsEqual(powerDemand));
 
 			powerDemands = powerDemands.Concat(limitBB).ToDictionary(x => x.Key, x=> x.Value);
@@ -229,6 +256,15 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
 				};
 			}
 
+			var current = Batteries.Values.Sum(x => x.Current);
+			
+			CurrentState.StateOfCharge = StateOfCharge;
+			CurrentState.PowerDemand = powerDemand;
+			CurrentState.MaxChargePower = maxChargePower;
+			CurrentState.MaxDischargePower = maxDischargePower;
+			CurrentState.TotalCurrent = current;
+			CurrentState.BatteryLoss = current * InternalResistance * current;
+
 			if (responses.All(bb => bb.Value.All(b => b is RESSResponseSuccess))) {
 				return new RESSResponseSuccess(this) {
 					AbsTime = absTime,
@@ -277,5 +313,19 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
 		}
 
 		#endregion
+
+		public class State
+		{
+			public double StateOfCharge;
+
+			public Second SimulationInterval;
+
+			public Watt PowerDemand;
+
+			public Ampere TotalCurrent;
+			public Watt MaxChargePower;
+			public Watt MaxDischargePower;
+			public Watt BatteryLoss;
+		}
 	}
 }
\ No newline at end of file
diff --git a/VectoCore/VectoCore/Models/SimulationComponent/Strategies/HybridStrategy.cs b/VectoCore/VectoCore/Models/SimulationComponent/Strategies/HybridStrategy.cs
index 523aeb4367..5dd8b70961 100644
--- a/VectoCore/VectoCore/Models/SimulationComponent/Strategies/HybridStrategy.cs
+++ b/VectoCore/VectoCore/Models/SimulationComponent/Strategies/HybridStrategy.cs
@@ -557,17 +557,17 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Strategies
 				shiftStrategyParameters.AllowedGearRangeFC = shiftStrategyParameters.AllowedGearRangeFC.LimitTo(1, 2);
 			}
 
-			// TODO: MQ 20210712 how to handle with batterysystem
-			//var auxEnergyReserve = ModelData.ElectricAuxDemand * StrategyParameters.AuxReserveTime;
-			//BatteryDischargeEnergyThreshold = 0.SI<WattSecond>();
-			//if (auxEnergyReserve > 0) {
-			//	var minSoc = Math.Max(ModelData.BatteryData?.MinSOC ?? ModelData.SuperCapData.MinVoltage / ModelData.SuperCapData.MaxVoltage,
-			//		StrategyParameters.MinSoC);
-			//	BatteryDischargeEnergyThreshold =
-			//		ModelData.BatteryData.Capacity * minSoc * ModelData.BatteryData.SOCMap.Lookup(minSoc) +
-			//		auxEnergyReserve;
-			//}
-			AllowEmergencyShift = false;
+            // TODO: MQ 20210712 how to handle with batterysystem
+            //var auxEnergyReserve = ModelData.ElectricAuxDemand * StrategyParameters.AuxReserveTime;
+            BatteryDischargeEnergyThreshold = 0.SI<WattSecond>();
+            //if (auxEnergyReserve > 0) {
+            //	var minSoc = Math.Max(ModelData.BatteryData?.MinSOC ?? ModelData.SuperCapData.MinVoltage / ModelData.SuperCapData.MaxVoltage,
+            //		StrategyParameters.MinSoC);
+            //	BatteryDischargeEnergyThreshold =
+            //		ModelData.BatteryData.Capacity * minSoc * ModelData.BatteryData.SOCMap.Lookup(minSoc) +
+            //		auxEnergyReserve;
+            //}
+            AllowEmergencyShift = false;
 		}
 
 		public virtual IHybridController Controller { protected get; set; }
diff --git a/VectoCore/VectoCore/OutputData/FileIO/JSONFileWriter.cs b/VectoCore/VectoCore/OutputData/FileIO/JSONFileWriter.cs
index eb8f9ee587..0eb4d17f6f 100644
--- a/VectoCore/VectoCore/OutputData/FileIO/JSONFileWriter.cs
+++ b/VectoCore/VectoCore/OutputData/FileIO/JSONFileWriter.cs
@@ -520,7 +520,7 @@ public class JSONFileWriter : IOutputFileWriter
 		body.Add("InitialSoC", vehicle.InitialSOC * 100);
 		body.Add("PowertrainConfiguration", "ParallelHybrid");
 		body.Add("ElectricMotors", electricMotorsOut);
-		body.Add("Battery", battery);
+		body.Add("Batteries", battery);
 
 		WriteFile(header, body, filename);
 	}
@@ -551,7 +551,7 @@ public class JSONFileWriter : IOutputFileWriter
 		body.Add("InitialSoC", vehicle.InitialSOC * 100);
 		body.Add("PowertrainConfiguration", "BatteryElectric");
 		body.Add("ElectricMotors", electricMotorsOut);
-		body.Add("Battery", battery);
+		body.Add("Batteries", battery);
 
 		//body.Add("IdlingSpeed", vehicle.EngineIdleSpeed.AsRPM);
 		//body.Add("Retarder", retarderOut);
diff --git a/VectoCore/VectoCore/OutputData/ModalDataContainer.cs b/VectoCore/VectoCore/OutputData/ModalDataContainer.cs
index 456c4a4381..45c2cd88bb 100644
--- a/VectoCore/VectoCore/OutputData/ModalDataContainer.cs
+++ b/VectoCore/VectoCore/OutputData/ModalDataContainer.cs
@@ -93,7 +93,7 @@ namespace TUGraz.VectoCore.OutputData
 			ModalResultField.EM_Off_,
 		};
 
-		private readonly ModalResultField[] _batteryColumns = new[] {
+		private readonly ModalResultField[] _batterySignals = new[] {
 			ModalResultField.U0_reess,
 			ModalResultField.U_reess_terminal,
 			ModalResultField.I_reess,
@@ -105,6 +105,9 @@ namespace TUGraz.VectoCore.OutputData
 			ModalResultField.P_reess_discharge_max
 		};
 
+		protected Dictionary<int, Dictionary<ModalResultField, DataColumn>> BatteryColumns =
+			new Dictionary<int, Dictionary<ModalResultField, DataColumn>>();
+
 		public static readonly IList<ModalResultField> FuelConsumptionSignals = new[] {
 			ModalResultField.FCMap, ModalResultField.FCNCVc, ModalResultField.FCWHTCc, // ModalResultField.FCAAUX,
 			ModalResultField.FCICEStopStart,  ModalResultField.FCFinal
@@ -883,7 +886,7 @@ namespace TUGraz.VectoCore.OutputData
 
 		public object this[ModalResultField key, int? idx] {
 			get {
-				if (!_batteryColumns.Contains(key)) {
+				if (!_batterySignals.Contains(key)) {
 					throw new VectoException("ModalResult with index is only supported for REESS fields");
 				}
 
@@ -891,14 +894,26 @@ namespace TUGraz.VectoCore.OutputData
 			}
 			set
 			{
-				if (!_batteryColumns.Contains(key)) {
-					throw new VectoException("ModalResult with index is only supported for REESS fields");
-				}
-
-				if (idx.HasValue) {
-					CurrentRow[$"{key.GetCaption()}_{idx.Value}"] = value;
-				} else {
+				if (idx == null) {
 					CurrentRow[key.GetName()] = value;
+				} else {
+					if (!_batterySignals.Contains(key)) {
+						throw new VectoException("ModalResult with index is only supported for REESS fields");
+					}
+					if (!BatteryColumns.ContainsKey(idx.Value)) {
+						BatteryColumns[idx.Value] = new Dictionary<ModalResultField, DataColumn>();
+					}
+
+					var entry = BatteryColumns[idx.Value];
+					if (!entry.ContainsKey(key)) {
+						var col = Data.Columns.Add($"{key.GetName()}_{idx.Value}", typeof(SI));
+						col.ExtendedProperties[ModalResults.ExtendedPropertyNames.Decimals] = key.GetAttribute().Decimals;
+						col.ExtendedProperties[ModalResults.ExtendedPropertyNames.OutputFactor] = key.GetAttribute().OutputFactor;
+						col.ExtendedProperties[ModalResults.ExtendedPropertyNames.ShowUnit] = key.GetAttribute().ShowUnit;
+						entry[key] = col;
+					}
+
+					CurrentRow[entry[key]] = value;
 				}
 			}
 		}
-- 
GitLab