Skip to content

Commit

Permalink
irradiation vs irradiance
Browse files Browse the repository at this point in the history
Use solar irradiance when discussing the rate of energy at a specific instant.
Use solar irradiation when discussing the cumulative energy over time.
  • Loading branch information
dhimmel committed Jan 5, 2025
1 parent 96fc5b4 commit 24d5fc6
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 50 deletions.
12 changes: 6 additions & 6 deletions openskistats/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
subplot_orientations,
)
from openskistats.sunlight import (
add_solar_irradiance_columns,
add_solar_irradiation_columns,
)
from openskistats.utils import (
get_data_directory,
Expand Down Expand Up @@ -71,12 +71,12 @@ def get_lifts_parquet_path(testing: bool = False) -> Path:
}

_aggregate_run_segment_sunlight_exprs = {
"solar_irradiance_season": pl_weighted_mean(
value_col="solar_irradiance_season",
"solar_irradiation_season": pl_weighted_mean(
value_col="solar_irradiation_season",
weight_col="distance_vertical_drop",
),
"solar_irradiance_solstice": pl_weighted_mean(
value_col="solar_irradiance_solstice",
"solar_irradiation_solstice": pl_weighted_mean(
value_col="solar_irradiation_solstice",
weight_col="distance_vertical_drop",
),
}
Expand Down Expand Up @@ -135,7 +135,7 @@ def process_and_export_runs() -> None:
.filter(pl.col("index").is_not_null())
.pipe(add_spatial_metric_columns, partition_by="run_id")
.collect()
.pipe(add_solar_irradiance_columns)
.pipe(add_solar_irradiation_columns)
.lazy()
.select(
"run_id",
Expand Down
6 changes: 3 additions & 3 deletions openskistats/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def get_ski_area_frontend_table(story: bool = False) -> pl.DataFrame:
"min_elevation",
"max_elevation",
"vertical_drop",
"solar_irradiance_season",
"solar_irradiation_season",
"bearing_mean",
"bearing_alignment",
"poleward_affinity",
Expand Down Expand Up @@ -420,7 +420,7 @@ def _ski_area_metric_style(ci: reactable.CellInfo) -> dict[str, Any] | None:
footer=reactable.JS("footerMaxMeters"),
),
reactable.Column(
id="solar_irradiance_season",
id="solar_irradiation_season",
name="Sunlight",
show=not story,
format=reactable.ColFormat(
Expand Down Expand Up @@ -535,7 +535,7 @@ def _ski_area_metric_style(ci: reactable.CellInfo) -> dict[str, Any] | None:
"min_elevation",
"max_elevation",
"vertical_drop",
"solar_irradiance_season",
"solar_irradiation_season",
],
),
reactable.ColGroup(
Expand Down
30 changes: 15 additions & 15 deletions openskistats/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ class SkiRunUsage(StrEnum):
snow_park = "snow_park"


_solar_irradiance_description = (
"This value measures solar irradiance by treating each run segment as a plane according to its latitude, longitude, elevation, bearing, and slope. "
_solar_irradiation_description = (
"This value measures solar irradiation by treating each run segment as a plane according to its latitude, longitude, elevation, bearing, and slope. "
"Solar irradiance is computed using clear sky estimates for diffuse normal irradiance, global horizontal irradiance, and direct horizontal irradiance according to the Ineichen and Perez model with Linke turbidity.",
)

Expand Down Expand Up @@ -124,27 +124,27 @@ class RunSegmentModel(Model): # type: ignore [misc]
description="Slope of the segment from the previous coordinate to the current coordinate in degrees."
),
]
solar_irradiance_season: Annotated[
solar_irradiation_season: Annotated[
float | None,
Field(
ge=0,
lt=15, # practical limit
description=f"Average daily solar irradiance received by the segment over the course of a typical 115 ski season in kilowatt-hours per square meter (kW/m²/day). {_solar_irradiance_description}",
description=f"Average daily solar irradiation received by the segment over the course of a typical 115 ski season in kilowatt-hours per square meter (kW/m²/day). {_solar_irradiation_description}",
),
]
solar_irradiance_solstice: Annotated[
solar_irradiation_solstice: Annotated[
float | None,
Field(
ge=0,
lt=15, # practical limit
description="Solar irradiance received by the segment on the winter solstice in kilowatt-hours per square meter (kW/m²/day).",
description="Solar irradiation received by the segment on the winter solstice in kilowatt-hours per square meter (kW/m²/day).",
),
]
solar_irradiance_cache_version: Annotated[
solar_cache_version: Annotated[
int | None,
Field(
description="Version of the solar irradiance calculation and parameters used to compute the solar irradiance values. "
"This version can be incremented in the source code to invalidate cached solar irradiance values.",
description="Version of the solar irradiation calculation and parameters used to compute the solar irradiation values. "
"This version can be incremented in the source code to invalidate cached solar irradiation values.",
),
]

Expand Down Expand Up @@ -431,20 +431,20 @@ class SkiAreaModel(Model): # type: ignore [misc]
description="Peak elevation of the ski area in meters computed as the highest elevation along all runs.",
),
]
solar_irradiance_season: Annotated[
solar_irradiation_season: Annotated[
float | None,
Field(
description="Average daily solar irradiance received by run segments over the course of a typical 120 ski season in kilowatt-hours per square meter (kW/m²/day). "
description="Average daily solar irradiation received by run segments over the course of a typical 120 ski season in kilowatt-hours per square meter (kW/m²/day). "
"The average is weighted by the vertical drop of each segment. "
f"{_solar_irradiance_description}",
f"{_solar_irradiation_description}",
),
]
solar_irradiance_solstice: Annotated[
solar_irradiation_solstice: Annotated[
float | None,
Field(
description="Average daily solar irradiance received by run segments on the winter solstice in kilowatt-hours per square meter (kW/m²/day). "
description="Average daily solar irradiation received by run segments on the winter solstice in kilowatt-hours per square meter (kW/m²/day). "
"The average is weighted by the vertical drop of each segment. "
f"{_solar_irradiance_description}",
f"{_solar_irradiation_description}",
),
]
for field_name in BearingStatsModel.model_fields:
Expand Down
52 changes: 26 additions & 26 deletions openskistats/sunlight.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
running_in_test,
)

SOLAR_IRRADIANCE_CACHE_VERSION = 1
SOLAR_CACHE_VERSION = 1
"""
Increment this version number to invalidate the solar irradiance cache.
"""
Expand Down Expand Up @@ -78,12 +78,14 @@ def compute_solar_irradiance(
def collapse_solar_irradiance(
irrad_df: pl.DataFrame,
) -> dict[str, float]:
def get_mean_irradiance(df: pl.DataFrame) -> float:
"""Aggregate solar irradiance to solar irradiation."""

def get_irradiation(df: pl.DataFrame) -> float:
return 24 * float(df["poa_global"].mean()) / 1_000

return {
"solar_irradiance_season": get_mean_irradiance(irrad_df),
"solar_irradiance_solstice": get_mean_irradiance(
"solar_irradiation_season": get_irradiation(irrad_df),
"solar_irradiation_solstice": get_irradiation(
irrad_df.filter(pl.col("is_solstice"))
),
}
Expand Down Expand Up @@ -200,7 +202,7 @@ def write_dartmouth_skiway_solar_irradiance() -> pl.DataFrame:
return skiway_df


def add_solar_irradiance_columns(
def add_solar_irradiation_columns(
run_segments: pl.DataFrame,
skip_cache: bool = False,
max_items: int | None = int(
Expand All @@ -210,13 +212,13 @@ def add_solar_irradiance_columns(
"""
Adds three columns to a run coordinate/segment DataFrame:
- solar_irradiance_cache_version
- solar_irradiance_season
- solar_irradiance_solstice
- solar_cache_version
- solar_irradiation_season
- solar_irradiation_solstice
Unless clear_cache is True, a lookup of prior results is attempted because the computation is quite slow.
"""
segments_cached = load_solar_irradiance_cache_pl(skip_cache=skip_cache)
segments_cached = load_solar_irradiation_cache_pl(skip_cache=skip_cache)
n_segments = run_segments["segment_hash"].drop_nulls().n_unique()
segments_to_compute = (
run_segments
Expand All @@ -234,32 +236,32 @@ def add_solar_irradiance_columns(
.to_dicts()
)
logging.info(
f"Solar irradiance requested for {n_segments:,} segments, {len(segments_to_compute):,} segments not in cache."
f"Solar irradiation requested for {n_segments:,} segments, {len(segments_to_compute):,} segments not in cache."
)
if max_items is not None:
segments_to_compute = segments_to_compute[:max_items]
logging.info(
f"Computing solar irradiance for {len(segments_to_compute):,} segments after limiting to {max_items=}."
f"Computing solar irradiation for {len(segments_to_compute):,} segments after limiting to {max_items=}."
)

def _process_segment(segment: dict[str, Any]) -> dict[str, float]:
segment_hash = segment.pop("segment_hash")
result = compute_solar_irradiance(**segment).pipe(collapse_solar_irradiance)
assert isinstance(result, dict)
result["segment_hash"] = segment_hash
result["solar_irradiance_cache_version"] = SOLAR_IRRADIANCE_CACHE_VERSION
result["solar_cache_version"] = SOLAR_CACHE_VERSION
return result

start_time = perf_counter()
with ThreadPoolExecutor() as executor:
results = list(executor.map(_process_segment, segments_to_compute))
total_time = perf_counter() - start_time
logging.info(
f"Computed solar irradiance for {len(segments_to_compute):,} segments in {total_time / 60:.1f} minutes: "
f"Computed solar irradiation for {len(segments_to_compute):,} segments in {total_time / 60:.1f} minutes: "
f"{total_time / len(segments_to_compute):.4f} seconds per segment."
)
segments_computed = pl.DataFrame(
data=results, schema=_get_solar_irradiance_cache_schema()
data=results, schema=_get_solar_irradiation_cache_schema()
)
return run_segments.join(
pl.concat([segments_cached, segments_computed]),
Expand All @@ -268,12 +270,12 @@ def _process_segment(segment: dict[str, Any]) -> dict[str, float]:
)


def _get_solar_irradiance_cache_schema() -> dict[str, pl.DataType]:
def _get_solar_irradiation_cache_schema() -> dict[str, pl.DataType]:
return {
"segment_hash": pl.UInt64,
"solar_irradiance_cache_version": pl.UInt8,
"solar_irradiance_season": pl.Float32,
"solar_irradiance_solstice": pl.Float32,
"solar_cache_version": pl.UInt8,
"solar_irradiation_season": pl.Float32,
"solar_irradiation_solstice": pl.Float32,
}


Expand All @@ -290,27 +292,25 @@ def _get_runs_cache_path(skip_cache: bool = False) -> str | None | Path:
return local_path


def load_solar_irradiance_cache_pl(skip_cache: bool = False) -> pl.DataFrame:
def load_solar_irradiation_cache_pl(skip_cache: bool = False) -> pl.DataFrame:
path = _get_runs_cache_path(skip_cache=skip_cache)
if not path:
return pl.DataFrame(
data=[],
schema=_get_solar_irradiance_cache_schema(),
schema=_get_solar_irradiation_cache_schema(),
)
return (
pl.scan_parquet(source=path)
.select("run_id", "run_coordinates_clean")
.explode("run_coordinates_clean")
.unnest("run_coordinates_clean")
.filter(pl.col("segment_hash").is_not_null())
.filter(
pl.col("solar_irradiance_cache_version") == SOLAR_IRRADIANCE_CACHE_VERSION
)
.filter(pl.col("solar_cache_version") == SOLAR_CACHE_VERSION)
.select(
"segment_hash",
"solar_irradiance_cache_version",
"solar_irradiance_season",
"solar_irradiance_solstice",
"solar_cache_version",
"solar_irradiation_season",
"solar_irradiation_solstice",
)
.unique(subset=["segment_hash"])
.collect()
Expand Down

0 comments on commit 24d5fc6

Please sign in to comment.