Skip to content

Commit

Permalink
update sunlight percent to be more accurate / useful irradiance
Browse files Browse the repository at this point in the history
  • Loading branch information
toddmedema committed Apr 13, 2024
1 parent 7cf8cbf commit eb7f318
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 23 deletions.
1 change: 1 addition & 0 deletions src/Constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const LOCATIONS = {
},
} as { [id: string]: LocationType };
export const OUTSKIRTS_WIND_MULTIPLIER = 2; // https://github.com/toddmedema/electrify/issues/96
export const EQUATOR_RADIANCE = 1000; // at sea level, equator, clear day, noon https://en.wikipedia.org/wiki/Solar_irradiance

export const TICK_MS = {
PAUSED: 250,
Expand Down
41 changes: 29 additions & 12 deletions src/data/Weather.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DAYS_PER_MONTH, DAYS_PER_YEAR } from "../Constants";
import { DAYS_PER_MONTH, DAYS_PER_YEAR, EQUATOR_RADIANCE } from "../Constants";
import { DateType, RawWeatherType } from "../Types";
import { getRandomRange } from "../helpers/Math";
import { getSunriseSunset } from "../helpers/DateTime";
Expand Down Expand Up @@ -79,31 +79,48 @@ export function getWeather(date: DateType): RawWeatherType {
};
}

// 0-1, percent of sun's energy hitting a unit of land relative to max at equator (~1360 w/m2)
// Is later multiplied by cloudiness
// TODO should this just take in cloudiness? The game knows future weather...
// TODO change to watts per sq meter or some fixed value, and verify that it's returning reasonably accurate values per location and season
// (hoping that day length alone is a sufficient proxy / ideally don't need to make it any more complex)
// https://earthobservatory.nasa.gov/features/EnergyBalance/page2.php
// indicates a roughly linear correlation that each degree off from 0*N/S = 0.7% less sunlight
export function getRawSunlightPercent(
// TODO fix the pointiness, esp in shorter winter months - Maybe by factoring in day lenght to determine the shape of the curve?
// Day length / minutes from dark used as proxy for season / max sun height
// Rough approximation of solar output: https://www.wolframalpha.com/input/?i=plot+1%2F%281+%2B+e+%5E+%28-0.015+*+%28x+-+260%29%29%29+from+0+to+420
// Potential more complex model for solar panels: https://pro.arcgis.com/en/pro-app/3.1/tool-reference/spatial-analyst/how-solar-radiation-is-calculated.htm
/**
* Calculates the raw solar irradiance in watts per square meter (W/m2) for a given date and location, not accounting for weather
* It first calculates the base irradiance based on the latitude, with a reduction factor for higher latitudes.
* It then gets the sunrise and sunset times for the given date and location.
* If the current time is between sunrise and sunset, it calculates the minutes from darkness (either sunrise or sunset, whichever is closer).
* It then calculates the irradiance based on a mathematical model that approximates the solar output as a bell curve.
* This model takes into account the time of day and the length of the day to approximate the height of the sun and the season.
* If the current time is outside of sunrise and sunset, it returns 0, indicating no solar irradiance.
*
* @param {DateType} date - The date and time to calculate the irradiance for.
* @param {number} lat - The latitude of the location to calculate the irradiance for.
* @param {number} long - The longitude of the location to calculate the irradiance for.
* @param {number} cloudCoverPercent - The percentage of cloud cover, from 0 to 100.
* @returns {number} - The calculated raw solar irradiance in W/m2.
*/
export function getRawSolarIrradianceWM2(
date: DateType,
lat: number,
long: number
long: number,
cloudCoverPercent: number
) {
let irradiance = EQUATOR_RADIANCE * (1 - 0.007 * Math.abs(lat)); // w/m2
irradiance *= 1 - cloudCoverPercent / 100;
const { sunrise, sunset } = getSunriseSunset(date, lat, long);
if (date.minuteOfDay >= sunrise && date.minuteOfDay <= sunset) {
const minutesFromDark = Math.min(
date.minuteOfDay - sunrise,
sunset - date.minuteOfDay
);
// TODO fix the pointiness, esp in shorter winter months
// Maybe by factoring in day lenght to determine the shape of the curve?

// Day length / minutes from dark used as proxy for season / max sun height
// Rough approximation of solar output: https://www.wolframalpha.com/input/?i=plot+1%2F%281+%2B+e+%5E+%28-0.015+*+%28x+-+260%29%29%29+from+0+to+420
// Solar panels generally follow a Bell curve
// Potential more complex model for solar panels: https://pro.arcgis.com/en/pro-app/3.1/tool-reference/spatial-analyst/how-solar-radiation-is-calculated.htm
return 1 / (1 + Math.pow(Math.E, -0.015 * (minutesFromDark - 260)));
return (
irradiance / (1 + Math.pow(Math.E, -0.015 * (minutesFromDark - 260)))
);
}
return 0;
}
Expand Down
14 changes: 9 additions & 5 deletions src/helpers/Energy.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OUTSKIRTS_WIND_MULTIPLIER } from "../Constants";
import { EQUATOR_RADIANCE, OUTSKIRTS_WIND_MULTIPLIER } from "../Constants";

export function getWindOutputFactor(windKph: number) {
// Wind gradient, assuming 10m weather station, 100m wind turbine, neutral air above human habitation - https://en.wikipedia.org/wiki/Wind_gradient
Expand All @@ -15,12 +15,16 @@ export function getWindOutputFactor(windKph: number) {
return windOutputFactor;
}

// Sunlight percent is a proxy for time of year and lat/long
// Since solar panel nameplate wattages are usually rated at peak output at equator noon, we use that as baseline
// Solar panels slightly less efficient in warm weather, declining about 1% efficiency per 1C starting at 10C
// TODO what about rain and snow, esp panels covered in snow?
// TODO what about rain and snow, esp panels covered in snow? We should update irradianceWM2 based on weather when it's originally calculated...
// but that still means we'd need to track some additional historic value of "even though it's not currently snowing, they're still covered in snow"
export function getSolarOutputFactor(
sunlightPercent: number,
irradianceWM2: number,
temepratureC: number
) {
return sunlightPercent * Math.max(1, 1 - (temepratureC - 10) / 100);
return (
(irradianceWM2 * Math.max(1, 1 - (temepratureC - 10) / 100)) /
EQUATOR_RADIANCE
);
}
13 changes: 7 additions & 6 deletions src/reducers/Game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import { arrayMove } from "../helpers/Math";
import { getWindOutputFactor, getSolarOutputFactor } from "../helpers/Energy";
import { getFuelPricesPerMBTU } from "../data/FuelPrices";
import { getRawSunlightPercent, getWeather } from "../data/Weather";
import { getRawSolarIrradianceWM2, getWeather } from "../data/Weather";
import { dialogOpen, dialogClose, snackbarOpen } from "./UI";
import {
DIFFICULTIES,
Expand Down Expand Up @@ -530,9 +530,12 @@ function reforecastWeatherAndPrices(state: GameType): TickPresentFutureType[] {
return {
...t,
...fuelPrices,
sunlight:
getRawSunlightPercent(date, state.location.lat, state.location.long) *
(weather.CLOUD_PCT / 100),
sunlight: getRawSolarIrradianceWM2(
date,
state.location.lat,
state.location.long,
weather.CLOUD_PCT
),
windKph: OUTSKIRTS_WIND_MULTIPLIER * weather.WIND_KPH,
temperatureC: weather.TEMP_C,
};
Expand Down Expand Up @@ -583,8 +586,6 @@ function updateSupplyFacilitiesFinances(
now.temperatureC
);

console.log(now.minute, now.sunlight, solarOutputFactor);

// Pre-check how much extra supply we'll need to charge batteries
let indexOfLastUnchargedBattery = -1;
let totalChargeNeeded = 0;
Expand Down

0 comments on commit eb7f318

Please sign in to comment.