@ -9,6 +9,7 @@ namespace Simulator {
batteryCapacity : number ;
batteryCapacity : number ;
batteryEfficiency : number = 0.9 ;
batteryEfficiency : number = 0.9 ;
gridTransformerEfficiency : number = 0.85 ;
gridTransformerEfficiency : number = 0.85 ;
rechargeMargin : number = 0.15 ; // We recharge the battery before an outing if it would otherwise go below this charge ratio
solarPanelEfficiency : number = 0.15 ;
solarPanelEfficiency : number = 0.15 ;
solarPanelArea : number = 1.0 ; // in square meters
solarPanelArea : number = 1.0 ; // in square meters
@ -117,6 +118,8 @@ namespace Simulator {
uphillSpeed : number ;
uphillSpeed : number ;
downhillSpeed : number ;
downhillSpeed : number ;
averageSpeed : number ;
averageSpeed : number ;
outOfBatteryDistance : number ; // distance, in km, that the driver had to pedal without assistance, because the battery was empty
}
}
export function simulate ( vehicle : Vehicle , solarIrradiance : number [ ] , planning : OutingPlanning ) : SimulationResult {
export function simulate ( vehicle : Vehicle , solarIrradiance : number [ ] , planning : OutingPlanning ) : SimulationResult {
@ -136,7 +139,9 @@ namespace Simulator {
flatTerrainSpeed : 0 ,
flatTerrainSpeed : 0 ,
uphillSpeed : 0 ,
uphillSpeed : 0 ,
downhillSpeed : 0 ,
downhillSpeed : 0 ,
averageSpeed : 0
averageSpeed : 0 ,
outOfBatteryDistance : 0
} ;
} ;
let remainingBatteryCharge = vehicle . batteryCapacity ;
let remainingBatteryCharge = vehicle . batteryCapacity ;
@ -144,7 +149,7 @@ namespace Simulator {
let outing : Outing = { distance : 0 , ascendingElevation : 0 } ;
let outing : Outing = { distance : 0 , ascendingElevation : 0 } ;
let consumption : ConsumptionData = { motorEnergy : 0 , humanEnergy : 0 , averageSpeed : 0 } ;
let consumption : ConsumptionData = { motorEnergy : 0 , humanEnergy : 0 , averageSpeed : 0 } ;
let resetConsumption = function ( outConsumption : ConsumptionData ) {
let resetConsumption = function ( outConsumption : ConsumptionData ) {
c onsumption. motorEnergy = 0 ; c onsumption. humanEnergy = 0 ; c onsumption. averageSpeed = 0 ;
outC onsumption. motorEnergy = 0 ; outC onsumption. humanEnergy = 0 ; outC onsumption. averageSpeed = 0 ;
} ;
} ;
let flatTerrainRatio = MathUtils . clamp ( planning . flatTerrainRatio , 0.0 , 1.0 ) ;
let flatTerrainRatio = MathUtils . clamp ( planning . flatTerrainRatio , 0.0 , 1.0 ) ;
@ -188,27 +193,44 @@ namespace Simulator {
result . totalProducedSolarEnergy += production ;
result . totalProducedSolarEnergy += production ;
let solarCharge = production * vehicle . batteryEfficiency ;
let solarCharge = production * vehicle . batteryEfficiency ;
// TODO: we should keep a margin because real users will recharge before they reach the bare minimum required for an outing
let remainingBatteryChargeBeforeOuting = remainingBatteryCharge ;
remainingBatteryCharge += solarCharge - consumption . motorEnergy ;
remainingBatteryCharge += solarCharge - consumption . motorEnergy ;
let fullGridRecharge = false ;
let gridRechargeFrom = - 1 ;
let lowBatteryThreshold = vehicle . rechargeMargin * vehicle . batteryCapacity ;
if ( remainingBatteryCharge > vehicle . batteryCapacity ) {
if ( remainingBatteryCharge > vehicle . batteryCapacity ) {
solarCharge -= remainingBatteryCharge - vehicle . batteryCapacity ;
solarCharge -= remainingBatteryCharge - vehicle . batteryCapacity ;
remainingBatteryCharge = vehicle . batteryCapacity ;
remainingBatteryCharge = vehicle . batteryCapacity ;
}
}
else if ( remainingBatteryCharge <= 0 || ( day == 364 && hour == 23 ) ) {
else if ( remainingBatteryCharge <= lowBatteryThreshold || ( day == 364 && hour == 23 ) ) {
// TODO: detect if battery capacity is too low for a single outing, abort simulation and display an explanation for the user
gridRechargeFrom = remainingBatteryChargeBeforeOuting ;
fullGridRecharge = remainingBatteryCharge <= 0 ;
let rechargeEnergy = vehicle . batteryCapacity - remainingBatteryCharge ;
let rechargeEnergy = vehicle . batteryCapacity - remainingBatteryChargeBeforeOuting ;
remainingBatteryCharge += rechargeEnergy ;
remainingBatteryCharge += rechargeEnergy ;
result . cumulatedGridRechargeEnergy += rechargeEnergy ;
result . cumulatedGridRechargeEnergy += rechargeEnergy ;
result . gridChargeCount += 1 ;
result . gridChargeCount += 1 ;
if ( remainingBatteryCharge < 0 )
{
// battery was exhausted during outing
let missingEnergy = - remainingBatteryCharge ;
result . outOfBatteryDistance += outing . distance * missingEnergy / consumption . motorEnergy ;
consumption . motorEnergy -= missingEnergy ;
consumption . humanEnergy += missingEnergy ;
// charge battery again after outing
let secondRechargeEnergy = vehicle . batteryCapacity ;
remainingBatteryCharge = vehicle . batteryCapacity ;
result . cumulatedGridRechargeEnergy += secondRechargeEnergy ;
result . gridChargeCount += 1 ;
gridRechargeFrom = 0 ;
}
}
}
result . cumulatedMotorConsumption += consumption . motorEnergy ;
result . cumulatedMotorConsumption += consumption . motorEnergy ;
result . cumulatedSolarRechargeEnergy += solarCharge ;
result . cumulatedSolarRechargeEnergy += solarCharge ;
result . batteryLevel [ hourIdx ] = fullGridRecharge ? 0 : remainingBatteryCharge ;
result . batteryLevel [ hourIdx ] = gridRechargeFrom >= 0 ? gridRechargeFrom : remainingBatteryCharge ;
}
}
}
}