Commit ea249156 authored by Eric Duminil's avatar Eric Duminil
Browse files

Test simulations too

parent 304acfb6
...@@ -11,3 +11,5 @@ wheels/ ...@@ -11,3 +11,5 @@ wheels/
# Legacy reference code (not part of the library) # Legacy reference code (not part of the library)
old/ old/
.cache
...@@ -221,6 +221,7 @@ def run_workflow_with_citygml( ...@@ -221,6 +221,7 @@ def run_workflow_with_citygml(
replaces: dict | None = None, replaces: dict | None = None,
description: str | None = None, description: str | None = None,
destination: str | None = None, destination: str | None = None,
project_path: Path | None = None,
) -> SimStadtResults: ) -> SimStadtResults:
"""Copy a workflow template, inject parameters, run SimStadt, and return parsed results. """Copy a workflow template, inject parameters, run SimStadt, and return parsed results.
...@@ -230,6 +231,9 @@ def run_workflow_with_citygml( ...@@ -230,6 +231,9 @@ def run_workflow_with_citygml(
replaces: Optional dict of XML-fragment regex replacements for params.xml. replaces: Optional dict of XML-fragment regex replacements for params.xml.
description: Human-readable label for the result object. description: Human-readable label for the result object.
destination: Output workflow folder name; defaults to a timestamped random id. destination: Output workflow folder name; defaults to a timestamped random id.
project_path: Directory where the workflow folder is created. Defaults to
citygml_path.parent. When set to a different directory, citygml_path is
passed as an absolute path so SimStadt can locate it.
""" """
if isinstance(template, str): if isinstance(template, str):
template_path = get_template_path() / template template_path = get_template_path() / template
...@@ -240,9 +244,21 @@ def run_workflow_with_citygml( ...@@ -240,9 +244,21 @@ def run_workflow_with_citygml(
if destination is None: if destination is None:
destination = random_id() + "_" + template_name.rsplit("_", 1)[-1] destination = random_id() + "_" + template_name.rsplit("_", 1)[-1]
if project_path is None:
project_path = citygml_path.parent project_path = citygml_path.parent
citygml_ref = citygml_path.name
else:
repo_path = project_path.parent
try:
citygml_ref = str(citygml_path.relative_to(repo_path))
except ValueError as e:
raise ValueError(
f"CityGML file {citygml_path} is not under the repository root {repo_path}. "
"Place the GML file inside the repository directory or omit project_path."
) from e
workflow_path = copy_workflow_from_template(template_path, project_path, destination, replaces) workflow_path = copy_workflow_from_template(template_path, project_path, destination, replaces)
output_files = run_workflow(workflow_path, [citygml_path.name]) output_files = run_workflow(workflow_path, [citygml_ref])
if description is None: if description is None:
description = f"{template_name} for {citygml_path.name}" description = f"{template_name} for {citygml_path.name}"
......
import os
from pathlib import Path from pathlib import Path
import pytest import pytest
...@@ -31,10 +30,3 @@ def simstadt_folder(): ...@@ -31,10 +30,3 @@ def simstadt_folder():
pytest.skip(str(e)) pytest.skip(str(e))
@pytest.fixture(scope="session")
def live_project_path(simstadt_folder):
"""Skip the test if SIMSTADTPY_TEST_PROJECT is not set."""
project = os.environ.get("SIMSTADTPY_TEST_PROJECT")
if not project:
pytest.skip("SIMSTADTPY_TEST_PROJECT env var not set")
return Path(project)
"""Live integration tests for SimStadt workflows. Skipped if SimStadt is not installed """Live integration tests for SimStadt workflows. Skipped if SimStadt is not installed."""
or SIMSTADTPY_TEST_PROJECT is not set."""
from pathlib import Path
import pytest import pytest
from simstadtpy.results import KPI from simstadtpy.results import KPI
from simstadtpy.workflows import greenwater_simulation, heatdemand_simulation, photovoltaic_simulation from simstadtpy.runner import run_workflow_with_citygml
TEST_DATA = Path(__file__).parent / "data"
TEST_TEMPLATES = TEST_DATA / "Templates"
# SimStadt resolves GML paths relative to the repository root (project_path.parent).
# With project in TestRepo/Temp.proj, repo root = TestRepo/ → GML must live there.
TEST_GML = TEST_DATA / "TestRepo" / "MiniBuchwald.gml"
TEST_PROJECT = TEST_DATA / "TestRepo" / "Temp.proj"
def to_dict(values: list[KPI]) -> dict[str, KPI]: def to_dict(values: list[KPI]) -> dict[str, KPI]:
...@@ -12,162 +20,47 @@ def to_dict(values: list[KPI]) -> dict[str, KPI]: ...@@ -12,162 +20,47 @@ def to_dict(values: list[KPI]) -> dict[str, KPI]:
@pytest.mark.live @pytest.mark.live
def test_pv(live_project_path): def test_heat_demand(simstadt_folder):
citygml = live_project_path / "Grombühl_mini.gml" results = run_workflow_with_citygml(
meteonorm = "Wuerzburg-hour.csv" TEST_TEMPLATES / "01_HeatDemand",
min_roof_insolation = 800 TEST_GML,
project_path=TEST_PROJECT,
######################## description="Heat demand – MiniBuchwald",
# No shadow, no tree #
########################
results = photovoltaic_simulation(
citygml,
meteonorm_filename=meteonorm,
with_shadows=False,
min_roof_insolation=min_roof_insolation,
)
pv_kpis = to_dict(results.kpis)
assert "PV potential yield" in pv_kpis
assert "Roof area for PV" in pv_kpis
assert "PV specific yield" in pv_kpis
assert abs(pv_kpis["PV specific yield"].value - 1_100) <= 200
########################
# No shadow, more roofs #
########################
more_pv_kpis = to_dict(
photovoltaic_simulation(
citygml,
meteonorm_filename=meteonorm,
pv_share_flat=80,
pv_share_tilted=80,
with_shadows=False,
min_roof_insolation=min_roof_insolation,
).kpis
)
assert more_pv_kpis["PV potential nominal power"].value > pv_kpis["PV potential nominal power"].value * 1.5
assert more_pv_kpis["PV potential yield"].value > pv_kpis["PV potential yield"].value * 1.5
assert abs(more_pv_kpis["Irradiance in module plane"].value - pv_kpis["Irradiance in module plane"].value) <= 1
assert abs(more_pv_kpis["Roof area for PV"].value - pv_kpis["Roof area for PV"].value) <= 100
########################
# Shadow, no tree #
########################
shadow_results = photovoltaic_simulation(
citygml,
meteonorm_filename=meteonorm,
with_shadows=True,
min_roof_insolation=min_roof_insolation,
)
shadow_pv_kpis = to_dict(shadow_results.kpis)
assert pv_kpis["PV potential nominal power"].value == pytest.approx(
shadow_pv_kpis["PV potential nominal power"].value
)
assert shadow_pv_kpis["PV potential yield"].value < pv_kpis["PV potential yield"].value
assert shadow_pv_kpis["Irradiance in module plane"].value < pv_kpis["Irradiance in module plane"].value
########################
# Shadow, trees #
########################
tree_results = photovoltaic_simulation(
live_project_path / "Grombühl_mini_with_comically_large_trees.gml",
meteonorm_filename=meteonorm,
with_shadows=True,
min_roof_insolation=min_roof_insolation,
)
tree_pv_kpis = to_dict(tree_results.kpis)
assert tree_pv_kpis["PV potential nominal power"].value < shadow_pv_kpis["PV potential nominal power"].value * 0.8
assert tree_pv_kpis["PV potential yield"].value < shadow_pv_kpis["PV potential yield"].value * 0.8
assert tree_pv_kpis["Irradiance in module plane"].value < shadow_pv_kpis["Irradiance in module plane"].value
@pytest.mark.live
def test_heat_demand(live_project_path):
citygml = live_project_path / "Grombühl_mini.gml"
meteonorm = live_project_path / "Wuerzburg-hour.csv"
########################
# No shadow, no tree #
########################
results = heatdemand_simulation(
citygml,
meteonorm_filename=str(meteonorm),
with_shadows=False,
calculation_mode="HEATING_AND_COOLING",
) )
kpis = to_dict(results.kpis) kpis = to_dict(results.kpis)
assert "Heated area" in kpis assert "Heated area" in kpis
assert "Yearly Heating demand" in kpis assert "Yearly Heating demand" in kpis
assert "Yearly Cooling demand" in kpis assert kpis["Yearly Heating demand"].value > 0
assert kpis["Year of construction"].value > 1900 assert kpis["Heated area"].value > 0
########################
# Shadow, no tree #
########################
shadow_results = heatdemand_simulation(
citygml,
meteonorm_filename=str(meteonorm),
with_shadows=True,
calculation_mode="HEATING_AND_COOLING",
)
shadow_kpis = to_dict(shadow_results.kpis)
assert shadow_kpis["Yearly Heating demand"].value > kpis["Yearly Heating demand"].value * 1.02
assert shadow_kpis["Yearly Cooling demand"].value < kpis["Yearly Cooling demand"].value * 0.95
########################
# Shadow, trees #
########################
tree_results = heatdemand_simulation(
live_project_path / "Grombühl_mini_with_trees.gml",
meteonorm_filename=str(meteonorm),
with_shadows=True,
calculation_mode="HEATING_AND_COOLING",
)
tree_kpis = to_dict(tree_results.kpis)
assert tree_kpis["Yearly Heating demand"].value > shadow_kpis["Yearly Heating demand"].value * 1.01 @pytest.mark.live
assert tree_kpis["Yearly Cooling demand"].value < shadow_kpis["Yearly Cooling demand"].value * 0.95 def test_pv(simstadt_folder):
results = run_workflow_with_citygml(
######################## TEST_TEMPLATES / "03_PV",
# Refurbishment # TEST_GML,
######################## project_path=TEST_PROJECT,
refurb_results = heatdemand_simulation( description="PV – MiniBuchwald",
live_project_path / "Grombühl_mini_with_trees.gml",
meteonorm_filename=str(meteonorm),
with_shadows=True,
calculation_mode="HEATING_AND_COOLING",
refurbishment_ratio=1.0,
) )
refurb_kpis = to_dict(refurb_results.kpis) kpis = to_dict(results.kpis)
assert refurb_kpis["Yearly Heating demand"].value < tree_kpis["Yearly Heating demand"].value * 0.50 assert "PV potential yield" in kpis
assert refurb_kpis["Mean Uvalue"].value < tree_kpis["Mean Uvalue"].value * 0.50 assert "Roof area for PV" in kpis
assert kpis["PV potential yield"].value > 0
assert kpis["Roof area for PV"].value > 0
@pytest.mark.live @pytest.mark.live
def test_greenwater(live_project_path): def test_load_profile(simstadt_folder):
citygml_with_trees = live_project_path / "Grombühl_mini_with_trees.gml" results = run_workflow_with_citygml(
meteonorm = "Wuerzburg-hour.csv" TEST_TEMPLATES / "02_LoadProfile",
TEST_GML,
results = greenwater_simulation(citygml_with_trees, meteonorm, irrigation_ratio=1.0) project_path=TEST_PROJECT,
description="Load profile – MiniBuchwald",
)
kpis = to_dict(results.kpis) kpis = to_dict(results.kpis)
assert "Rain" in kpis assert "Number of buildings" in kpis
assert "Evaporative cooling" in kpis assert "Total load [MWh]" in kpis or any("load" in k.lower() for k in kpis)
assert "Irrigation" in kpis assert kpis["Number of buildings"].value > 0
assert kpis["Rain"].rounded_value == pytest.approx(458)
more_results = greenwater_simulation(citygml_with_trees, meteonorm, irrigation_ratio=0.0)
more_kpis = to_dict(more_results.kpis)
assert "Rain" in more_kpis
assert "Evaporative cooling" in more_kpis
assert "Irrigation" in more_kpis
assert kpis["Irrigation"].value > 500
assert more_kpis["Irrigation"].rounded_value == pytest.approx(0)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment