From 47d835d336433c50428d82664307e2d17670d39f Mon Sep 17 00:00:00 2001 From: Douwe Ravers Date: Tue, 14 Apr 2026 22:20:01 +0200 Subject: [PATCH] Added quarter based simulation with efficiency and capacity parameters. --- Models/BatteryDayResult.cs | 14 ----- Models/EnergyData.cs | 9 +++ Models/SimulatedBatteryEnergyData.cs | 19 ++++++ Pages/Home.razor | 93 ++++++++++++++++++---------- Services/BatterySimulator.cs | 75 ++++++++++++++-------- Services/FluviusDataHandler.cs | 17 ++--- 6 files changed, 147 insertions(+), 80 deletions(-) delete mode 100644 Models/BatteryDayResult.cs create mode 100644 Models/SimulatedBatteryEnergyData.cs diff --git a/Models/BatteryDayResult.cs b/Models/BatteryDayResult.cs deleted file mode 100644 index d2c033f..0000000 --- a/Models/BatteryDayResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace BattSim.Models -{ - public class BatteryDayResult - { - public DateOnly Date { get; set; } - public double ChargedEnergy { get; set; } - public double UsedEnergy { get; set; } - public double RemainingEnergy { get; set; } - public double ReducedConsumption { get; set; } - public double ReducedProduction { get; set; } - } -} \ No newline at end of file diff --git a/Models/EnergyData.cs b/Models/EnergyData.cs index 18e93fb..fc2c2f0 100644 --- a/Models/EnergyData.cs +++ b/Models/EnergyData.cs @@ -8,5 +8,14 @@ namespace BattSim.Models public bool DayTariff { get; set; } public double Consumption { get; set; } public double Production { get; set; } + + public EnergyData(){} + public EnergyData(EnergyData other) + { + Time = other.Time; + DayTariff = other.DayTariff; + Consumption = other.Consumption; + Production = other.Production; + } } } \ No newline at end of file diff --git a/Models/SimulatedBatteryEnergyData.cs b/Models/SimulatedBatteryEnergyData.cs new file mode 100644 index 0000000..6d3c83b --- /dev/null +++ b/Models/SimulatedBatteryEnergyData.cs @@ -0,0 +1,19 @@ +using System; + +namespace BattSim.Models +{ + public class SimulatedBatteryEnergyData : EnergyData + { + public double BatteryCharge { get; set; } + public double SimulatedConsumption { get; set; } + public double SimulatedProduction { get; set; } + + public SimulatedBatteryEnergyData(EnergyData energyData) : base(energyData) { } + public SimulatedBatteryEnergyData(SimulatedBatteryEnergyData simulatedBatteryEnergyData) : base(simulatedBatteryEnergyData) + { + BatteryCharge = simulatedBatteryEnergyData.BatteryCharge; + SimulatedConsumption = simulatedBatteryEnergyData.SimulatedConsumption; + SimulatedProduction = simulatedBatteryEnergyData.SimulatedProduction; + } + } +} \ No newline at end of file diff --git a/Pages/Home.razor b/Pages/Home.razor index 98f9260..08610d4 100644 --- a/Pages/Home.razor +++ b/Pages/Home.razor @@ -4,32 +4,29 @@ @using BattSim.Models @using BattSim.Services -BattSim +Energie Simulator -

BattSim

+

Energie Simulator

-

Input Data

+

Je Fluvius Energie Data

-

Upload your Fluvius quarterly csv file here. The longer the timeframe the longer it takes to process.

+

Ga naar de website van Fluvius en download je historische elektriciteitsverbruiksgegevens met kwartier nauwkeurigheid. Voor een optimale simulatie is het raadzaam om een volledig jaar te downloaden. Dit geeft je inzicht in seizoensgebonden patronen en helpt om realistische uitkomsten te genereren.

- @if (_isLoadingFile) - { -

Loading...

- } + @if (_isLoadingFile) {

Data aan het inladen...

} else { - @if (FluviusDataRaw.Count != 0) + @if (FluviusDataRaw.Length != 0) { -

@(FluviusDataRaw.Count) entries read.

+

@(FluviusDataRaw.Length) kwartieren ingeladen.

} - @if (FluviusDataDaily.Count != 0) + @if (FluviusDataDaily.Length != 0) { - + - + @@ -46,8 +43,8 @@
@code{ - Dictionary FluviusDataRaw = []; - Dictionary FluviusDataDaily = []; + EnergyData[] FluviusDataRaw = []; + EnergyData[] FluviusDataDaily = []; bool _isLoadingFile = false; private async Task OnFileUploaded(InputFileChangeEventArgs e) @@ -79,41 +76,75 @@ private void OnSeriesClick(){} private string FormatObject(object value) { if(value is double d) return $"{d:0.##} kWh"; - if(value is DateTime date) return date.ToString("dd/MM/yyyy"); + if(value is DateTime time) return time.ToString("dd/MM/yyyy"); + if(value is DateOnly date) return date.ToString("dd/MM/yyyy"); return string.Empty; } } -

Simulate Battery

+

Batterij Simulatie

-

Generate adjusted energy data simulating the effect of a battery with properties as configured here:

+

+ Met je Fluvius-kwartierdata simuleren we een batterij die elk kwartier de energie die normaal op het elektriciteitsnet wordt teruggeleverd, opvangt en de stroom die normaal uit het net wordt gehaald vervangt—mits de batterij nog voldoende capaciteit heeft. + Op deze manier kun je verschillende batterijcapaciteiten testen om te zien welke installatie het beste past bij jouw verbruiksprofiel en besparingsdoelen. Daarnaast kun je onderzoeken hoe verschillende batterijtypes, met uiteenlopende efficiëntieniveaus, de uitkomsten beïnvloeden. Onder efficiëntie verstaan we hier het aandeel van de opgeslagen energie dat uiteindelijk ook daadwerkelijk weer kan worden gebruikt. +

  • -

    Capacity: kWh

    +

    Batterijcapaciteit: kWh

  • -

    Discharge Rate: kW

    -
  • -
  • -

    Charge Rate: kW

    -
  • -
  • -

    Round-trip Efficiency: %

    +

    Round-trip Efficiëntie: %

+ @if (_isSimulating) {

Simulating...

} + else + { + @if (SimulationDataDaily.Length != 0) + { + + + + + + + + + + + + + + + + + + + + + + + + + + } + }
@code { double BatteryCapacity = 7.5; - double DischargeRate = 3; - double ChargeRate = 3; double Efficiency = 90; - BatteryDayResult[] SimulationData = []; + bool _isSimulating = false; - private async Task SimulateBattery(){ + SimulatedBatteryEnergyData[] SimulationData = []; + SimulatedBatteryEnergyData[] SimulationDataDaily = []; + + private void SimulateBattery(){ Console.WriteLine("Simulating..."); - // SimulationData = BatterySimulator.SimulateBattery(EnergyData, BatteryCapacity).ToArray(); + _isSimulating = true; + SimulationData = BatterySimulator.SimulateBattery(FluviusDataRaw, BatteryCapacity, Efficiency/100); + SimulationDataDaily = BatterySimulator.GenerateDailyData(SimulationData); + _isSimulating = false; Console.WriteLine("Done simulating!"); } } diff --git a/Services/BatterySimulator.cs b/Services/BatterySimulator.cs index 7bc1811..55e71a9 100644 --- a/Services/BatterySimulator.cs +++ b/Services/BatterySimulator.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Collections.Generic; using BattSim.Models; @@ -5,39 +6,59 @@ namespace BattSim.Services { public static class BatterySimulator { - public static List SimulateBattery(EnergyData[] data, double batteryCapacity) + public static SimulatedBatteryEnergyData[] SimulateBattery(EnergyData[] energyData, double batteryCapacity, double efficiency) { - var results = new List(); - double remainingEnergy = 0; + var results = new List(); + double batteryCharge = 0; - foreach (var day in data) + foreach (var e in energyData) { - // // Charge battery from production - // var chargedEnergy = System.Math.Min(day.TotalProduction, batteryCapacity); - // var excessProduction = day.TotalProduction - chargedEnergy; + var simulatedBatteryEnergyData = new SimulatedBatteryEnergyData(e); + // Simulate charging the battery based on production + double excessProduction = batteryCharge + e.Production - batteryCapacity; + batteryCharge = double.Min(batteryCharge + e.Production, batteryCapacity); + simulatedBatteryEnergyData.SimulatedProduction = double.Max(excessProduction, 0); - // // Use battery for consumption - // var usedEnergy = System.Math.Min(chargedEnergy + remainingEnergy, day.TotalConsumption); - // var remainingAfterUse = chargedEnergy + remainingEnergy - usedEnergy; - - // // Calculate reduced values - // var reducedConsumption = System.Math.Min(usedEnergy, day.TotalConsumption); - // var reducedProduction = day.TotalProduction - chargedEnergy; - - // results.Add(new BatteryDayResult - // { - // Date = day.Date, - // ChargedEnergy = chargedEnergy, - // UsedEnergy = usedEnergy, - // RemainingEnergy = remainingAfterUse, - // ReducedConsumption = reducedConsumption, - // ReducedProduction = reducedProduction - // }); - - // remainingEnergy = remainingAfterUse; + // Simulate discharging the battery based on consumption + double availableEnergy = batteryCharge * efficiency; + double deficit = e.Consumption - availableEnergy; + double energyDrawn = Math.Min(e.Consumption, availableEnergy); + batteryCharge = batteryCharge - (energyDrawn / efficiency); // Adjust for loss + simulatedBatteryEnergyData.SimulatedConsumption = Math.Max(deficit, 0); + + // Register the current charge + simulatedBatteryEnergyData.BatteryCharge = batteryCharge; + results.Add(simulatedBatteryEnergyData); } - return results; + return results.ToArray(); + } + + public static SimulatedBatteryEnergyData[] GenerateDailyData(SimulatedBatteryEnergyData[] simulationData) + { + var simulationDataCopy = (SimulatedBatteryEnergyData[]) simulationData.Clone(); + var dailySimulationData = new ConcurrentDictionary(); + Parallel.ForEach(simulationDataCopy, simulationPoint => + { + var date = DateOnly.FromDateTime(simulationPoint.Time); + + // Use AddOrUpdate to avoid double lookup + dailySimulationData.AddOrUpdate( + date, + new SimulatedBatteryEnergyData(simulationPoint), // If key doesn't exist, add this value + (_, existing) => + { + // If key exists, aggregate the values + existing.Consumption += simulationPoint.Consumption; + existing.Production += simulationPoint.Production; + existing.SimulatedConsumption += simulationPoint.SimulatedConsumption; + existing.SimulatedProduction += simulationPoint.SimulatedProduction; + existing.BatteryCharge = double.Max(existing.BatteryCharge, simulationPoint.BatteryCharge); + return existing; + } + ); + }); + return dailySimulationData.Values.ToArray(); } } } \ No newline at end of file diff --git a/Services/FluviusDataHandler.cs b/Services/FluviusDataHandler.cs index ebdff32..4e9ad61 100644 --- a/Services/FluviusDataHandler.cs +++ b/Services/FluviusDataHandler.cs @@ -13,7 +13,7 @@ namespace BattSim.Services { public static class FluviusDataHandler { - public static async Task> LoadAndProcessFile(IBrowserFile file) + public static async Task LoadAndProcessFile(IBrowserFile file) { var lines = await ReadCsvFile(file); var energyData = new ConcurrentDictionary(); // Thread-safe collection @@ -48,21 +48,22 @@ namespace BattSim.Services Console.WriteLine($"Error parsing line: {line}. Skipping to next line. Exception: {e}"); } }); - return energyData.ToDictionary(); + var results = energyData.Values.ToArray(); + results.Sort((a,b)=>a.Time.CompareTo(b.Time)); + return results; } - public static Dictionary GenerateDailyData(Dictionary energyData) + public static EnergyData[] GenerateDailyData(EnergyData[] energyData) { var dailyEnergyData = new ConcurrentDictionary(); - Parallel.ForEach(energyData, entry => + Parallel.ForEach(energyData, energy => { - var date = DateOnly.FromDateTime(entry.Key); - var energy = entry.Value; + var date = DateOnly.FromDateTime(energy.Time); // Use AddOrUpdate to avoid double lookup dailyEnergyData.AddOrUpdate( date, - energy, // If key doesn't exist, add this value + new EnergyData(energy), // If key doesn't exist, add this value (_, existing) => { // If key exists, aggregate the values @@ -72,7 +73,7 @@ namespace BattSim.Services } ); }); - return dailyEnergyData.ToDictionary(); + return dailyEnergyData.Values.ToArray(); } private static async Task> ReadCsvFile(IBrowserFile file)