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

Test simulations too

parent 304acfb6
......@@ -11,3 +11,5 @@ wheels/
# Legacy reference code (not part of the library)
old/
.cache
......@@ -221,6 +221,7 @@ def run_workflow_with_citygml(
replaces: dict | None = None,
description: str | None = None,
destination: str | None = None,
project_path: Path | None = None,
) -> SimStadtResults:
"""Copy a workflow template, inject parameters, run SimStadt, and return parsed results.
......@@ -230,6 +231,9 @@ def run_workflow_with_citygml(
replaces: Optional dict of XML-fragment regex replacements for params.xml.
description: Human-readable label for the result object.
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):
template_path = get_template_path() / template
......@@ -240,9 +244,21 @@ def run_workflow_with_citygml(
if destination is None:
destination = random_id() + "_" + template_name.rsplit("_", 1)[-1]
project_path = citygml_path.parent
if project_path is None:
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)
output_files = run_workflow(workflow_path, [citygml_path.name])
output_files = run_workflow(workflow_path, [citygml_ref])
if description is None:
description = f"{template_name} for {citygml_path.name}"
......
import os
from pathlib import Path
import pytest
......@@ -31,10 +30,3 @@ def simstadt_folder():
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
or SIMSTADTPY_TEST_PROJECT is not set."""
"""Live integration tests for SimStadt workflows. Skipped if SimStadt is not installed."""
from pathlib import Path
import pytest
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]:
......@@ -12,162 +20,47 @@ def to_dict(values: list[KPI]) -> dict[str, KPI]:
@pytest.mark.live
def test_pv(live_project_path):
citygml = live_project_path / "Grombühl_mini.gml"
meteonorm = "Wuerzburg-hour.csv"
min_roof_insolation = 800
########################
# 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",
def test_heat_demand(simstadt_folder):
results = run_workflow_with_citygml(
TEST_TEMPLATES / "01_HeatDemand",
TEST_GML,
project_path=TEST_PROJECT,
description="Heat demand – MiniBuchwald",
)
kpis = to_dict(results.kpis)
assert "Heated area" in kpis
assert "Yearly Heating demand" in kpis
assert "Yearly Cooling demand" in kpis
assert kpis["Year of construction"].value > 1900
assert kpis["Yearly Heating demand"].value > 0
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
assert tree_kpis["Yearly Cooling demand"].value < shadow_kpis["Yearly Cooling demand"].value * 0.95
########################
# Refurbishment #
########################
refurb_results = heatdemand_simulation(
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,
@pytest.mark.live
def test_pv(simstadt_folder):
results = run_workflow_with_citygml(
TEST_TEMPLATES / "03_PV",
TEST_GML,
project_path=TEST_PROJECT,
description="PV – MiniBuchwald",
)
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 refurb_kpis["Mean Uvalue"].value < tree_kpis["Mean Uvalue"].value * 0.50
assert "PV potential yield" in kpis
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
def test_greenwater(live_project_path):
citygml_with_trees = live_project_path / "Grombühl_mini_with_trees.gml"
meteonorm = "Wuerzburg-hour.csv"
results = greenwater_simulation(citygml_with_trees, meteonorm, irrigation_ratio=1.0)
def test_load_profile(simstadt_folder):
results = run_workflow_with_citygml(
TEST_TEMPLATES / "02_LoadProfile",
TEST_GML,
project_path=TEST_PROJECT,
description="Load profile – MiniBuchwald",
)
kpis = to_dict(results.kpis)
assert "Rain" in kpis
assert "Evaporative cooling" in kpis
assert "Irrigation" in kpis
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)
assert "Number of buildings" in kpis
assert "Total load [MWh]" in kpis or any("load" in k.lower() for k in kpis)
assert kpis["Number of buildings"].value > 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