Added quarter based simulation with efficiency and capacity parameters.
This commit is contained in:
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,5 +8,14 @@ namespace BattSim.Models
|
|||||||
public bool DayTariff { get; set; }
|
public bool DayTariff { get; set; }
|
||||||
public double Consumption { get; set; }
|
public double Consumption { get; set; }
|
||||||
public double Production { 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
19
Models/SimulatedBatteryEnergyData.cs
Normal file
19
Models/SimulatedBatteryEnergyData.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,32 +4,29 @@
|
|||||||
@using BattSim.Models
|
@using BattSim.Models
|
||||||
@using BattSim.Services
|
@using BattSim.Services
|
||||||
|
|
||||||
<PageTitle>BattSim</PageTitle>
|
<PageTitle>Energie Simulator</PageTitle>
|
||||||
|
|
||||||
<h1>BattSim</h1>
|
<h1>Energie Simulator</h1>
|
||||||
|
|
||||||
<h2>Input Data</h2>
|
<h2>Je Fluvius Energie Data</h2>
|
||||||
<div>
|
<div>
|
||||||
<p>Upload your Fluvius quarterly csv file here. The longer the timeframe the longer it takes to process.</p>
|
<p>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.</p>
|
||||||
<InputFile OnChange="OnFileUploaded" accept=".csv"/>
|
<InputFile OnChange="OnFileUploaded" accept=".csv"/>
|
||||||
@if (_isLoadingFile)
|
@if (_isLoadingFile) { <p>Data aan het inladen...</p> }
|
||||||
{
|
|
||||||
<p>Loading...</p>
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (FluviusDataRaw.Count != 0)
|
@if (FluviusDataRaw.Length != 0)
|
||||||
{
|
{
|
||||||
<p>@(FluviusDataRaw.Count) entries read.</p>
|
<p>@(FluviusDataRaw.Length) kwartieren ingeladen.</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (FluviusDataDaily.Count != 0)
|
@if (FluviusDataDaily.Length != 0)
|
||||||
{
|
{
|
||||||
<RadzenChart>
|
<RadzenChart>
|
||||||
<RadzenAreaSeries Smooth=true Data="@FluviusDataDaily.Values" CategoryProperty="Time" Title="Consumption" ValueProperty="Consumption">
|
<RadzenAreaSeries Smooth=true Data="@FluviusDataDaily" CategoryProperty="Time" Title="Consumption" ValueProperty="Consumption">
|
||||||
<RadzenChartTooltipOptions Visible="true"/>
|
<RadzenChartTooltipOptions Visible="true"/>
|
||||||
</RadzenAreaSeries>
|
</RadzenAreaSeries>
|
||||||
<RadzenAreaSeries Smooth=true Data="@FluviusDataDaily.Values" CategoryProperty="Time" Title="Production" ValueProperty="Production">
|
<RadzenAreaSeries Smooth=true Data="@FluviusDataDaily" CategoryProperty="Time" Title="Production" ValueProperty="Production">
|
||||||
<RadzenChartTooltipOptions Visible="true"/>
|
<RadzenChartTooltipOptions Visible="true"/>
|
||||||
</RadzenAreaSeries>
|
</RadzenAreaSeries>
|
||||||
<RadzenCategoryAxis Formatter="@FormatObject" Padding="20" LabelAutoRotation="-45">
|
<RadzenCategoryAxis Formatter="@FormatObject" Padding="20" LabelAutoRotation="-45">
|
||||||
@@ -46,8 +43,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code{
|
@code{
|
||||||
Dictionary<DateTime, EnergyData> FluviusDataRaw = [];
|
EnergyData[] FluviusDataRaw = [];
|
||||||
Dictionary<DateOnly, EnergyData> FluviusDataDaily = [];
|
EnergyData[] FluviusDataDaily = [];
|
||||||
bool _isLoadingFile = false;
|
bool _isLoadingFile = false;
|
||||||
|
|
||||||
private async Task OnFileUploaded(InputFileChangeEventArgs e)
|
private async Task OnFileUploaded(InputFileChangeEventArgs e)
|
||||||
@@ -79,41 +76,75 @@
|
|||||||
private void OnSeriesClick(){}
|
private void OnSeriesClick(){}
|
||||||
private string FormatObject(object value) {
|
private string FormatObject(object value) {
|
||||||
if(value is double d) return $"{d:0.##} kWh";
|
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;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<h2>Simulate Battery</h2>
|
<h2>Batterij Simulatie</h2>
|
||||||
<div>
|
<div>
|
||||||
<p>Generate adjusted energy data simulating the effect of a battery with properties as configured here:</p>
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<p>Capacity: <InputNumber @bind-value="BatteryCapacity"/> kWh</p>
|
<p>Batterijcapaciteit: <InputNumber @bind-value="BatteryCapacity"/> kWh</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>Discharge Rate: <InputNumber @bind-value="DischargeRate"/> kW</p>
|
<p>Round-trip Efficiëntie: <InputNumber @bind-value="Efficiency"/> %</p>
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Charge Rate: <InputNumber @bind-value="ChargeRate"/> kW</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<p>Round-trip Efficiency: <InputNumber @bind-value="Efficiency"/> %</p>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<Button @onclick="SimulateBattery">Simulate</Button>
|
<Button @onclick="SimulateBattery">Simulate</Button>
|
||||||
|
@if (_isSimulating) { <p>Simulating...</p> }
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (SimulationDataDaily.Length != 0)
|
||||||
|
{
|
||||||
|
<RadzenChart>
|
||||||
|
<RadzenAreaSeries Smooth=true Data="@SimulationDataDaily" CategoryProperty="Time" Title="Consumption" ValueProperty="Consumption">
|
||||||
|
<RadzenChartTooltipOptions Visible="true"/>
|
||||||
|
</RadzenAreaSeries>
|
||||||
|
<RadzenAreaSeries Smooth=true Data="@SimulationDataDaily" CategoryProperty="Time" Title="Production" ValueProperty="Production">
|
||||||
|
<RadzenChartTooltipOptions Visible="true"/>
|
||||||
|
</RadzenAreaSeries>
|
||||||
|
<RadzenAreaSeries Smooth=true Data="@SimulationDataDaily" CategoryProperty="Time" Title="SimulatedConsumption" ValueProperty="SimulatedConsumption">
|
||||||
|
<RadzenChartTooltipOptions Visible="true"/>
|
||||||
|
</RadzenAreaSeries>
|
||||||
|
<RadzenAreaSeries Smooth=true Data="@SimulationDataDaily" CategoryProperty="Time" Title="SimulatedProduction" ValueProperty="SimulatedProduction">
|
||||||
|
<RadzenChartTooltipOptions Visible="true"/>
|
||||||
|
</RadzenAreaSeries>
|
||||||
|
<RadzenAreaSeries Smooth=true Data="@SimulationDataDaily" CategoryProperty="Time" Title="BatteryCharge" ValueProperty="BatteryCharge">
|
||||||
|
<RadzenChartTooltipOptions Visible="true"/>
|
||||||
|
</RadzenAreaSeries>
|
||||||
|
<RadzenCategoryAxis Formatter="@FormatObject" Padding="20" LabelAutoRotation="-45">
|
||||||
|
<RadzenGridLines Visible="true"/>
|
||||||
|
<RadzenAxisTitle Text="Time"/>
|
||||||
|
</RadzenCategoryAxis>
|
||||||
|
<RadzenValueAxis Formatter="@FormatObject">
|
||||||
|
<RadzenGridLines Visible="true"/>
|
||||||
|
<RadzenAxisTitle Text="Energy"/>
|
||||||
|
</RadzenValueAxis>
|
||||||
|
</RadzenChart>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
double BatteryCapacity = 7.5;
|
double BatteryCapacity = 7.5;
|
||||||
double DischargeRate = 3;
|
|
||||||
double ChargeRate = 3;
|
|
||||||
double Efficiency = 90;
|
double Efficiency = 90;
|
||||||
BatteryDayResult[] SimulationData = [];
|
bool _isSimulating = false;
|
||||||
|
|
||||||
private async Task SimulateBattery(){
|
SimulatedBatteryEnergyData[] SimulationData = [];
|
||||||
|
SimulatedBatteryEnergyData[] SimulationDataDaily = [];
|
||||||
|
|
||||||
|
private void SimulateBattery(){
|
||||||
Console.WriteLine("Simulating...");
|
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!");
|
Console.WriteLine("Done simulating!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BattSim.Models;
|
using BattSim.Models;
|
||||||
|
|
||||||
@@ -5,39 +6,59 @@ namespace BattSim.Services
|
|||||||
{
|
{
|
||||||
public static class BatterySimulator
|
public static class BatterySimulator
|
||||||
{
|
{
|
||||||
public static List<BatteryDayResult> SimulateBattery(EnergyData[] data, double batteryCapacity)
|
public static SimulatedBatteryEnergyData[] SimulateBattery(EnergyData[] energyData, double batteryCapacity, double efficiency)
|
||||||
{
|
{
|
||||||
var results = new List<BatteryDayResult>();
|
var results = new List<SimulatedBatteryEnergyData>();
|
||||||
double remainingEnergy = 0;
|
double batteryCharge = 0;
|
||||||
|
|
||||||
foreach (var day in data)
|
foreach (var e in energyData)
|
||||||
{
|
{
|
||||||
// // Charge battery from production
|
var simulatedBatteryEnergyData = new SimulatedBatteryEnergyData(e);
|
||||||
// var chargedEnergy = System.Math.Min(day.TotalProduction, batteryCapacity);
|
// Simulate charging the battery based on production
|
||||||
// var excessProduction = day.TotalProduction - chargedEnergy;
|
double excessProduction = batteryCharge + e.Production - batteryCapacity;
|
||||||
|
batteryCharge = double.Min(batteryCharge + e.Production, batteryCapacity);
|
||||||
|
simulatedBatteryEnergyData.SimulatedProduction = double.Max(excessProduction, 0);
|
||||||
|
|
||||||
// // Use battery for consumption
|
// Simulate discharging the battery based on consumption
|
||||||
// var usedEnergy = System.Math.Min(chargedEnergy + remainingEnergy, day.TotalConsumption);
|
double availableEnergy = batteryCharge * efficiency;
|
||||||
// var remainingAfterUse = chargedEnergy + remainingEnergy - usedEnergy;
|
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);
|
||||||
|
|
||||||
// // Calculate reduced values
|
// Register the current charge
|
||||||
// var reducedConsumption = System.Math.Min(usedEnergy, day.TotalConsumption);
|
simulatedBatteryEnergyData.BatteryCharge = batteryCharge;
|
||||||
// var reducedProduction = day.TotalProduction - chargedEnergy;
|
results.Add(simulatedBatteryEnergyData);
|
||||||
|
|
||||||
// results.Add(new BatteryDayResult
|
|
||||||
// {
|
|
||||||
// Date = day.Date,
|
|
||||||
// ChargedEnergy = chargedEnergy,
|
|
||||||
// UsedEnergy = usedEnergy,
|
|
||||||
// RemainingEnergy = remainingAfterUse,
|
|
||||||
// ReducedConsumption = reducedConsumption,
|
|
||||||
// ReducedProduction = reducedProduction
|
|
||||||
// });
|
|
||||||
|
|
||||||
// remainingEnergy = remainingAfterUse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SimulatedBatteryEnergyData[] GenerateDailyData(SimulatedBatteryEnergyData[] simulationData)
|
||||||
|
{
|
||||||
|
var simulationDataCopy = (SimulatedBatteryEnergyData[]) simulationData.Clone();
|
||||||
|
var dailySimulationData = new ConcurrentDictionary<DateOnly, SimulatedBatteryEnergyData>();
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ namespace BattSim.Services
|
|||||||
{
|
{
|
||||||
public static class FluviusDataHandler
|
public static class FluviusDataHandler
|
||||||
{
|
{
|
||||||
public static async Task<Dictionary<DateTime, EnergyData>> LoadAndProcessFile(IBrowserFile file)
|
public static async Task<EnergyData[]> LoadAndProcessFile(IBrowserFile file)
|
||||||
{
|
{
|
||||||
var lines = await ReadCsvFile(file);
|
var lines = await ReadCsvFile(file);
|
||||||
var energyData = new ConcurrentDictionary<DateTime,EnergyData>(); // Thread-safe collection
|
var energyData = new ConcurrentDictionary<DateTime,EnergyData>(); // Thread-safe collection
|
||||||
@@ -48,21 +48,22 @@ namespace BattSim.Services
|
|||||||
Console.WriteLine($"Error parsing line: {line}. Skipping to next line. Exception: {e}");
|
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<DateOnly, EnergyData> GenerateDailyData(Dictionary<DateTime, EnergyData> energyData)
|
public static EnergyData[] GenerateDailyData(EnergyData[] energyData)
|
||||||
{
|
{
|
||||||
var dailyEnergyData = new ConcurrentDictionary<DateOnly, EnergyData>();
|
var dailyEnergyData = new ConcurrentDictionary<DateOnly, EnergyData>();
|
||||||
Parallel.ForEach(energyData, entry =>
|
Parallel.ForEach(energyData, energy =>
|
||||||
{
|
{
|
||||||
var date = DateOnly.FromDateTime(entry.Key);
|
var date = DateOnly.FromDateTime(energy.Time);
|
||||||
var energy = entry.Value;
|
|
||||||
|
|
||||||
// Use AddOrUpdate to avoid double lookup
|
// Use AddOrUpdate to avoid double lookup
|
||||||
dailyEnergyData.AddOrUpdate(
|
dailyEnergyData.AddOrUpdate(
|
||||||
date,
|
date,
|
||||||
energy, // If key doesn't exist, add this value
|
new EnergyData(energy), // If key doesn't exist, add this value
|
||||||
(_, existing) =>
|
(_, existing) =>
|
||||||
{
|
{
|
||||||
// If key exists, aggregate the values
|
// If key exists, aggregate the values
|
||||||
@@ -72,7 +73,7 @@ namespace BattSim.Services
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return dailyEnergyData.ToDictionary();
|
return dailyEnergyData.Values.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<List<string>> ReadCsvFile(IBrowserFile file)
|
private static async Task<List<string>> ReadCsvFile(IBrowserFile file)
|
||||||
|
|||||||
Reference in New Issue
Block a user