epinterface 1.2.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {epinterface-1.2.0 → epinterface-1.3.0}/.cursor/Dockerfile.agent +3 -1
- epinterface-1.3.0/.cursor/rules/uv-dependencies.mdc +44 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/.github/workflows/main.yml +2 -2
- {epinterface-1.2.0 → epinterface-1.3.0}/PKG-INFO +1 -1
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/geometry.py +120 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/builder.py +82 -8
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/components/composer.py +3 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/pyproject.toml +1 -1
- epinterface-1.3.0/tests/test_construct_zone_def.py +68 -0
- epinterface-1.3.0/tests/test_neighbor_shading_geometry.py +151 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/uv.lock +1 -1
- {epinterface-1.2.0 → epinterface-1.3.0}/.cursor/environment.json +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/.dockerignore +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/.editorconfig +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/.github/actions/setup-python-env/action.yml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/.github/workflows/on-release-main.yml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/.github/workflows/validate-codecov-config.yml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/.gitignore +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/.pre-commit-config.yaml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/.prettierignore +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/.python-version +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/.vscode/settings.json +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/CONTRIBUTING.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/LICENSE +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/Makefile +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/README.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/benchmarking/benchmark.ipynb +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/benchmarking/benchmark.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/codecov.yaml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/docs/cli.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/docs/index.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/docs/modules/actions.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/docs/modules/builder.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/docs/modules/climate-studio.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/docs/modules/geometry.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/docs/modules/interface.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/docs/modules/sbem/components.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/docs/modules/sbem/composer.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/docs/modules/sbem/index.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/docs/modules/weather.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/__init__.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/actions.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/analysis/__init__.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/analysis/energy_and_peak.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/analysis/overheating.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/builder.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/cli.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/climate_studio/__init__.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/climate_studio/builder.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/climate_studio/interface.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/constants/__init__.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/data/Minimal.idf +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/data/USA_MA_Boston-Logan.Intl.AP.725090_TMYx.2009-2023.ddy +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/data/USA_MA_Boston-Logan.Intl.AP.725090_TMYx.2009-2023.epw +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/data/USA_MA_Boston-Logan.Intl.AP.725090_TMYx.2009-2023.zip +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/data/__init__.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/data/res_schedules.parquet +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/ddy_injector_bayes.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/ddy_interface_bayes.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/interface.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/py.typed +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/__init__.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/annotations.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/common.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/components/__init__.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/components/envelope.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/components/materials.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/components/operations.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/components/schedules.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/components/space_use.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/components/systems.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/components/zones.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/exceptions.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/fields/__init__.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/fields/spec.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/flat_model.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/interface.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/model_graph_structure.md +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/client.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/migrations/20250309133033_create_initial_schema/migration.sql +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/migrations/20250310162045_switch_to_using_one_week_per_month/migration.sql +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/migrations/20250317202733_change_names_ventilation/migration.sql +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/migrations/20250325185158_add_mutually_exclusive_ventilation_techtypes/migration.sql +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/migrations/20250326141941_reduce_naming_complexity_ventilation/migration.sql +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/migrations/20250331141910_add_support_for_attic_and_basement_constructions/migration.sql +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/migrations/20250919152559_decouple_basement_infiltration/migration.sql +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/migrations/migration_lock.toml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/partial_types.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/schema.prisma +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/seed_fns.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/utils.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/settings.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/epinterface/weather.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/mkdocs.yml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/notebooks/everett_lib.json +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/notebooks/everett_lib_v2.ipynb +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/notebooks/everett_retrofit_actions.ipynb +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/notebooks/everett_retrofits.yaml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/notebooks/idf-demo.ipynb +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/notebooks/idf-demo.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/notebooks/view-geo.ipynb +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/scripts/gridsim.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/conftest.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/20251001_Template_MAWebApp.xlsx +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/USA_MA_Chicopee-Westover.Metro.AP.744910_TMYx.2009-2023.zip +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/component-map-ma.yml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/components-ma.db +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/old/0318_Template_MAWebApp.xlsx +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/old/Template_MAWebApp.xlsx +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/old/component-map-ma.yml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/old/components-ma-old.db +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/old/components-ma.db +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/old/components-ma.db Kopie +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/old/components-ma.db Kopie 2 +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/old/components-ma.db Kopie 3 +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/old/semantic-fields-ma.yml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/semantic-fields-ma.yml +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/test_lib.json +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/data/tester_lib.xlsx +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_actions.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_analysis/__init__.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_analysis/test_overheating.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_components/test_builder.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_components/test_component_lib.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_components/test_envelope_comps.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_components/test_operations.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_components/test_space_use_comps.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_composer.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_db.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_example.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_geometry.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tests/test_weather.py +0 -0
- {epinterface-1.2.0 → epinterface-1.3.0}/tox.ini +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
ARG EP_VERSION=24.2.0
|
|
2
2
|
ARG PYTHON_VERSION=3.12
|
|
3
|
+
ARG UV_VERSION=0.9.30
|
|
3
4
|
FROM nrel/energyplus:${EP_VERSION} AS energyplus
|
|
4
5
|
|
|
5
6
|
# Remove unnecessary datasets to reduce image size
|
|
@@ -13,8 +14,9 @@ RUN mkdir -p /energyplus-local && \
|
|
|
13
14
|
# ep is installed by the nrel image
|
|
14
15
|
cp -r * /energyplus-local/
|
|
15
16
|
|
|
17
|
+
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv
|
|
16
18
|
FROM python:${PYTHON_VERSION}-slim-bookworm AS main
|
|
17
|
-
COPY --from=
|
|
19
|
+
COPY --from=uv /uv /uvx /bin/
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
# Install dependencies for running energy models
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: How to add packages with uv and when to use optional-dependencies vs dependency-groups
|
|
3
|
+
globs: pyproject.toml
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Adding Dependencies with uv
|
|
8
|
+
|
|
9
|
+
Use `uv add` to add packages. Where they go depends on who needs them.
|
|
10
|
+
|
|
11
|
+
## `uv add` usage
|
|
12
|
+
|
|
13
|
+
- **Core runtime dependency** (always required by the library):
|
|
14
|
+
```bash
|
|
15
|
+
uv add <package>
|
|
16
|
+
```
|
|
17
|
+
Adds to `[project] dependencies` in `pyproject.toml`.
|
|
18
|
+
|
|
19
|
+
- **Optional extra** (for downstream installers):
|
|
20
|
+
```bash
|
|
21
|
+
uv add --optional <extra-name> <package>
|
|
22
|
+
```
|
|
23
|
+
Adds to `[project.optional-dependencies]` under the given extra (e.g. `uv add --optional plot pandas-plot`).
|
|
24
|
+
|
|
25
|
+
- **Local-only group** (dev/docs/tooling, not published as installable extras):
|
|
26
|
+
```bash
|
|
27
|
+
uv add --group <group-name> <package>
|
|
28
|
+
```
|
|
29
|
+
Adds to `[dependency-groups]` (e.g. `uv add --group dev pytest`).
|
|
30
|
+
|
|
31
|
+
## When to use which
|
|
32
|
+
|
|
33
|
+
| Place | Who gets it | Use for |
|
|
34
|
+
|-------|-------------|--------|
|
|
35
|
+
| **`project.dependencies`** | Everyone who installs `epinterface` | Required runtime deps only. |
|
|
36
|
+
| **`project.optional-dependencies`** | Only people who install e.g. `uv add epinterface[extra-name]` or `pip install epinterface[extra-name]` | Optional features other projects may want when using epinterface as a library. |
|
|
37
|
+
| **`dependency-groups`** | Only this repo (local dev, CI, docs) | Testing, linting, docs, pre-commit, etc. Not exposed as pip/uv extras. |
|
|
38
|
+
|
|
39
|
+
**Rule of thumb:** If another repo would ever do `pip install epinterface[something]` to get optional functionality, put that in **optional-dependencies**. If it’s only for developing or building this repo, put it in **dependency-groups**.
|
|
40
|
+
|
|
41
|
+
## Examples (this project)
|
|
42
|
+
|
|
43
|
+
- **optional-dependencies:** e.g. a `viz` extra for plotting only used by some downstream users → `[project.optional-dependencies] viz = ["matplotlib", ...]`.
|
|
44
|
+
- **dependency-groups:** `dev` (pytest, pyright, pre-commit), `docs` (mkdocs, mkdocstrings) — local tooling only, not installable as `epinterface[dev]`.
|
|
@@ -34,8 +34,8 @@ jobs:
|
|
|
34
34
|
# EPINTERFACE_ENERGYPLUS_VERSION: ${{ matrix.energyplus-version }}
|
|
35
35
|
strategy:
|
|
36
36
|
matrix:
|
|
37
|
-
python-version: ["3.
|
|
38
|
-
energyplus-version: [22.2.0,
|
|
37
|
+
python-version: ["3.12"]
|
|
38
|
+
energyplus-version: [22.2.0, 24.2.0, 25.2.0] # later, add 9.4.0
|
|
39
39
|
energyplus-platform: [Linux-Ubuntu22.04]
|
|
40
40
|
include:
|
|
41
41
|
- runs-on: ubuntu-latest
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: epinterface
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: This is a repository for dynamically generating energy models within Python, relying on Archetypal and Eppy for most of its functionality.
|
|
5
5
|
Project-URL: Homepage, https://github.com/szvsw/epinterface
|
|
6
6
|
Project-URL: Documentation, https://szvsw.github.io/epinterface
|
|
@@ -7,6 +7,7 @@ import numpy as np
|
|
|
7
7
|
from archetypal.idfclass import IDF
|
|
8
8
|
from geomeppy.geom.polygons import Polygon3D
|
|
9
9
|
from geomeppy.geom.vectors import Vector2D
|
|
10
|
+
from numpy.typing import ArrayLike
|
|
10
11
|
from pydantic import BaseModel, Field
|
|
11
12
|
from shapely import LineString, Polygon, from_wkt
|
|
12
13
|
from shapely.affinity import translate
|
|
@@ -192,6 +193,125 @@ def compute_shading_mask(
|
|
|
192
193
|
return shading_mask
|
|
193
194
|
|
|
194
195
|
|
|
196
|
+
def shading_fence_closed_ring(
|
|
197
|
+
elevations: ArrayLike,
|
|
198
|
+
d: float,
|
|
199
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, float]:
|
|
200
|
+
"""Construct N vertical 'shading fence' rectangles whose bases are tangent segments forming a closed regular polygon around a circle of radius d.
|
|
201
|
+
|
|
202
|
+
Inputs:
|
|
203
|
+
elevations: (N,) elevation angles theta_k [radians]
|
|
204
|
+
d: radius to tangency points (base midpoints)
|
|
205
|
+
|
|
206
|
+
Outputs:
|
|
207
|
+
azimuths: (N,) inferred azimuths alpha_k = 2πk/N
|
|
208
|
+
p0: (N, 2) base endpoint A (x,y)
|
|
209
|
+
p1: (N, 2) base endpoint B (x,y)
|
|
210
|
+
h: (N,) heights h_k = d * tan(theta_k)
|
|
211
|
+
w: scalar side length / segment width = 2 d tan(π/N)
|
|
212
|
+
|
|
213
|
+
Notes:
|
|
214
|
+
- With this construction, the segments intersect/meet: p1[k] == p0[k+1] (cyclic),
|
|
215
|
+
up to floating point tolerance.
|
|
216
|
+
- N must be >= 3 for a closed polygon.
|
|
217
|
+
"""
|
|
218
|
+
theta = np.asarray(elevations, dtype=np.float64)
|
|
219
|
+
if theta.ndim != 1:
|
|
220
|
+
msg = f"elevations must be 1D, got shape {theta.shape}"
|
|
221
|
+
raise ValueError(msg)
|
|
222
|
+
|
|
223
|
+
N = theta.shape[0]
|
|
224
|
+
if N < 3:
|
|
225
|
+
msg = "Need at least 3 elevations (N >= 3) to form a closed ring."
|
|
226
|
+
raise ValueError(msg)
|
|
227
|
+
|
|
228
|
+
# Inferred equally spaced azimuths
|
|
229
|
+
k = np.arange(N, dtype=np.float64)
|
|
230
|
+
azimuths = 2.0 * np.pi * k / N
|
|
231
|
+
|
|
232
|
+
ca, sa = np.cos(azimuths), np.sin(azimuths)
|
|
233
|
+
|
|
234
|
+
# Tangency points (base midpoints) on circle radius d
|
|
235
|
+
px = d * ca
|
|
236
|
+
py = d * sa
|
|
237
|
+
|
|
238
|
+
# Unit tangent direction (perpendicular to radius)
|
|
239
|
+
tx = -sa
|
|
240
|
+
ty = ca
|
|
241
|
+
|
|
242
|
+
# Choose width so adjacent tangent segments meet (circumscribed regular N-gon)
|
|
243
|
+
half_w = d * np.tan(np.pi / N)
|
|
244
|
+
w = 2.0 * half_w
|
|
245
|
+
|
|
246
|
+
# Endpoints in xy: p ± half_w * t
|
|
247
|
+
p0 = np.stack([px - half_w * tx, py - half_w * ty], axis=-1)
|
|
248
|
+
p1 = np.stack([px + half_w * tx, py + half_w * ty], axis=-1)
|
|
249
|
+
|
|
250
|
+
# Heights from elevation angles
|
|
251
|
+
h = d * np.tan(theta)
|
|
252
|
+
|
|
253
|
+
return azimuths, p0, p1, h, w
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def prepare_neighbor_shading_for_idf(
|
|
257
|
+
building: Polygon | str,
|
|
258
|
+
neighbors: Sequence[Polygon | str | None],
|
|
259
|
+
neighbor_heights: Sequence[float | int | None],
|
|
260
|
+
*,
|
|
261
|
+
azimuthal_angle: float = 2 * np.pi / 48,
|
|
262
|
+
fence_radius: float = 100,
|
|
263
|
+
outward_offset: float = 2,
|
|
264
|
+
f2f_height: float,
|
|
265
|
+
) -> tuple[list[Polygon], list[int]]:
|
|
266
|
+
"""Prepare neighbor polygons and floor counts for match_idf_to_building_and_neighbors.
|
|
267
|
+
|
|
268
|
+
Computes a shading mask from the building and neighbors, converts it to shading fence
|
|
269
|
+
quadrilaterals, and returns polygons plus floor counts suitable for IDF shading blocks.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
building: The building polygon (or WKT string).
|
|
273
|
+
neighbors: Neighbor polygons (or WKT strings).
|
|
274
|
+
neighbor_heights: Heights of each neighbor [m].
|
|
275
|
+
azimuthal_angle: Angular spacing for shading rays (default 2π/48).
|
|
276
|
+
fence_radius: Radius for shading fence tangency points.
|
|
277
|
+
outward_offset: Distance to extend fence quadrilaterals outward.
|
|
278
|
+
f2f_height: Floor-to-floor height for converting heights to floor counts.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
mask_polys: List of Shapely Polygons (quadrilaterals) for each shading fence.
|
|
282
|
+
neighbor_floors: List of floor counts (int) for each fence.
|
|
283
|
+
"""
|
|
284
|
+
shading_mask = compute_shading_mask(
|
|
285
|
+
building, neighbors, neighbor_heights, azimuthal_angle
|
|
286
|
+
)
|
|
287
|
+
az, p0, p1, h, _w = shading_fence_closed_ring(
|
|
288
|
+
elevations=shading_mask, d=fence_radius
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
building_geom = building if isinstance(building, Polygon) else from_wkt(building)
|
|
292
|
+
centroid = building_geom.centroid
|
|
293
|
+
cx, cy = centroid.x, centroid.y
|
|
294
|
+
|
|
295
|
+
angles = 2 * np.pi * np.arange(len(az)) / len(az)
|
|
296
|
+
outward = np.stack([np.cos(angles), np.sin(angles)], axis=-1)
|
|
297
|
+
p2 = p1 + outward_offset * outward
|
|
298
|
+
p3 = p0 + outward_offset * outward
|
|
299
|
+
|
|
300
|
+
mask_polys = [
|
|
301
|
+
Polygon([
|
|
302
|
+
(p0[i, 0] + cx, p0[i, 1] + cy),
|
|
303
|
+
(p1[i, 0] + cx, p1[i, 1] + cy),
|
|
304
|
+
(p2[i, 0] + cx, p2[i, 1] + cy),
|
|
305
|
+
(p3[i, 0] + cx, p3[i, 1] + cy),
|
|
306
|
+
(p0[i, 0] + cx, p0[i, 1] + cy),
|
|
307
|
+
])
|
|
308
|
+
for i in range(len(az))
|
|
309
|
+
]
|
|
310
|
+
neighbor_floors = [int(h[i] // f2f_height) for i in range(len(h))]
|
|
311
|
+
|
|
312
|
+
return mask_polys, neighbor_floors
|
|
313
|
+
|
|
314
|
+
|
|
195
315
|
ZoningType = Literal["core/perim", "by_storey"]
|
|
196
316
|
|
|
197
317
|
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
"""A module for building the energy model using the SBEM template library approach."""
|
|
2
2
|
|
|
3
3
|
import gc
|
|
4
|
+
import io
|
|
4
5
|
import logging
|
|
6
|
+
import os
|
|
5
7
|
import shutil
|
|
8
|
+
import sys
|
|
6
9
|
import tempfile
|
|
7
10
|
from collections.abc import Callable
|
|
8
11
|
from dataclasses import dataclass
|
|
12
|
+
from datetime import datetime
|
|
9
13
|
from pathlib import Path
|
|
10
14
|
from typing import Literal, cast, get_args
|
|
11
15
|
from uuid import uuid4
|
|
12
16
|
|
|
13
17
|
import numpy as np
|
|
14
18
|
import pandas as pd
|
|
19
|
+
import yaml
|
|
15
20
|
from archetypal.idfclass import IDF
|
|
16
21
|
from archetypal.idfclass.sql import Sql
|
|
17
22
|
from ladybug.epw import EPW
|
|
@@ -38,6 +43,10 @@ from epinterface.interface import (
|
|
|
38
43
|
add_default_schedules,
|
|
39
44
|
add_default_sim_controls,
|
|
40
45
|
)
|
|
46
|
+
from epinterface.sbem.components.composer import (
|
|
47
|
+
construct_composer_model,
|
|
48
|
+
construct_graph,
|
|
49
|
+
)
|
|
41
50
|
from epinterface.sbem.components.envelope import (
|
|
42
51
|
ConstructionAssemblyComponent,
|
|
43
52
|
EnvelopeAssemblyComponent,
|
|
@@ -46,6 +55,7 @@ from epinterface.sbem.components.envelope import (
|
|
|
46
55
|
from epinterface.sbem.components.systems import DHWFuelType, FuelType
|
|
47
56
|
from epinterface.sbem.components.zones import ZoneComponent
|
|
48
57
|
from epinterface.sbem.exceptions import NotImplementedParameter
|
|
58
|
+
from epinterface.sbem.prisma.client import PrismaSettings
|
|
49
59
|
from epinterface.settings import energyplus_settings
|
|
50
60
|
from epinterface.weather import BaseWeather
|
|
51
61
|
|
|
@@ -1206,6 +1216,78 @@ class Model(BaseWeather, validate_assignment=True):
|
|
|
1206
1216
|
return zone_weights, zone_names
|
|
1207
1217
|
|
|
1208
1218
|
|
|
1219
|
+
def construct_zone_def(
|
|
1220
|
+
component_map_path: Path,
|
|
1221
|
+
db_path: Path,
|
|
1222
|
+
semantic_field_context: dict[str, float | str | int],
|
|
1223
|
+
) -> ZoneComponent:
|
|
1224
|
+
"""Construct the zone definition from a component map and database.
|
|
1225
|
+
|
|
1226
|
+
Args:
|
|
1227
|
+
component_map_path: Path to the component map YAML file.
|
|
1228
|
+
db_path: Path to the component database file.
|
|
1229
|
+
semantic_field_context: The semantic field values used to compile the zone definition.
|
|
1230
|
+
|
|
1231
|
+
Returns:
|
|
1232
|
+
zone_def: The zone definition for the simulation.
|
|
1233
|
+
"""
|
|
1234
|
+
g = construct_graph(ZoneComponent)
|
|
1235
|
+
SelectorModel = construct_composer_model(
|
|
1236
|
+
g,
|
|
1237
|
+
ZoneComponent,
|
|
1238
|
+
use_children=False,
|
|
1239
|
+
)
|
|
1240
|
+
|
|
1241
|
+
with open(component_map_path) as f:
|
|
1242
|
+
component_map_yaml = yaml.safe_load(f)
|
|
1243
|
+
selector = SelectorModel.model_validate(component_map_yaml)
|
|
1244
|
+
|
|
1245
|
+
if db_path.exists():
|
|
1246
|
+
mtime = os.path.getmtime(db_path)
|
|
1247
|
+
mtime_str = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
|
|
1248
|
+
logger.info(
|
|
1249
|
+
f"Loading database: {db_path} "
|
|
1250
|
+
f"(modified: {mtime_str}, size: {db_path.stat().st_size} bytes)"
|
|
1251
|
+
)
|
|
1252
|
+
else:
|
|
1253
|
+
logger.error(f"Database file not found: {db_path}")
|
|
1254
|
+
|
|
1255
|
+
settings = PrismaSettings.New(
|
|
1256
|
+
database_path=db_path, if_exists="ignore", auto_register=False
|
|
1257
|
+
)
|
|
1258
|
+
db = settings.db
|
|
1259
|
+
|
|
1260
|
+
def _stdout_has_fileno() -> bool:
|
|
1261
|
+
"""True if sys.stdout has a real OS file descriptor (required by Prisma on Windows in Jupyter)."""
|
|
1262
|
+
try:
|
|
1263
|
+
sys.stdout.fileno()
|
|
1264
|
+
except (AttributeError, io.UnsupportedOperation, OSError, ValueError):
|
|
1265
|
+
return False
|
|
1266
|
+
else:
|
|
1267
|
+
return True
|
|
1268
|
+
|
|
1269
|
+
if not _stdout_has_fileno():
|
|
1270
|
+
with open(os.devnull, "w") as _devnull:
|
|
1271
|
+
_old_stdout, _old_stderr = sys.stdout, sys.stderr
|
|
1272
|
+
sys.stdout = sys.stderr = _devnull
|
|
1273
|
+
try:
|
|
1274
|
+
with db:
|
|
1275
|
+
zone = cast(
|
|
1276
|
+
ZoneComponent,
|
|
1277
|
+
selector.get_component(context=semantic_field_context, db=db),
|
|
1278
|
+
)
|
|
1279
|
+
finally:
|
|
1280
|
+
sys.stdout, sys.stderr = _old_stdout, _old_stderr
|
|
1281
|
+
_devnull.close()
|
|
1282
|
+
else:
|
|
1283
|
+
with db:
|
|
1284
|
+
zone = cast(
|
|
1285
|
+
ZoneComponent,
|
|
1286
|
+
selector.get_component(context=semantic_field_context, db=db),
|
|
1287
|
+
)
|
|
1288
|
+
return zone
|
|
1289
|
+
|
|
1290
|
+
|
|
1209
1291
|
@dataclass
|
|
1210
1292
|
class ModelRunResults:
|
|
1211
1293
|
"""The results of a model run."""
|
|
@@ -1219,14 +1301,6 @@ class ModelRunResults:
|
|
|
1219
1301
|
|
|
1220
1302
|
|
|
1221
1303
|
if __name__ == "__main__":
|
|
1222
|
-
import yaml
|
|
1223
|
-
|
|
1224
|
-
from epinterface.sbem.components.composer import (
|
|
1225
|
-
construct_composer_model,
|
|
1226
|
-
construct_graph,
|
|
1227
|
-
)
|
|
1228
|
-
from epinterface.sbem.prisma.client import PrismaSettings
|
|
1229
|
-
|
|
1230
1304
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
1231
1305
|
# database_path = Path("/Users/daryaguettler/globi/data/Brazil/components-lib.db")
|
|
1232
1306
|
# component_map_path = Path(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""A module for automatically fetching and composing SBEM objects."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import uuid
|
|
4
5
|
from typing import Any, ClassVar, Literal, TypeVar, get_origin
|
|
5
6
|
|
|
@@ -9,6 +10,8 @@ from pydantic import BaseModel, Field, create_model
|
|
|
9
10
|
|
|
10
11
|
from epinterface.sbem.common import NamedObject
|
|
11
12
|
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
12
15
|
"""Tree construction:
|
|
13
16
|
|
|
14
17
|
Every node in the tree may or may not have a selector action specified.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "epinterface"
|
|
3
|
-
version = "v1.
|
|
3
|
+
version = "v1.3.0"
|
|
4
4
|
description = "This is a repository for dynamically generating energy models within Python, relying on Archetypal and Eppy for most of its functionality."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Tests for construct_zone_def."""
|
|
2
|
+
|
|
3
|
+
import tempfile
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from epinterface.sbem.builder import construct_zone_def
|
|
9
|
+
from epinterface.sbem.components.zones import ZoneComponent
|
|
10
|
+
from epinterface.sbem.prisma.client import PrismaSettings
|
|
11
|
+
from epinterface.sbem.prisma.seed_fns import (
|
|
12
|
+
create_dhw_systems,
|
|
13
|
+
create_envelope,
|
|
14
|
+
create_hvac_systems,
|
|
15
|
+
create_operations,
|
|
16
|
+
create_schedules,
|
|
17
|
+
create_space_use_children,
|
|
18
|
+
create_zone,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture(scope="module")
|
|
23
|
+
def preseeded_db_path_and_component_map():
|
|
24
|
+
"""Create a preseeded database and minimal component map for construct_zone_def tests."""
|
|
25
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
26
|
+
db_path = Path(temp_dir) / "test.db"
|
|
27
|
+
component_map_path = Path(temp_dir) / "component-map.yaml"
|
|
28
|
+
|
|
29
|
+
settings = PrismaSettings.New(
|
|
30
|
+
database_path=db_path, if_exists="raise", auto_register=False
|
|
31
|
+
)
|
|
32
|
+
with settings.db:
|
|
33
|
+
create_schedules(settings.db)
|
|
34
|
+
last_space_use_name = create_space_use_children(settings.db)
|
|
35
|
+
last_hvac_name = create_hvac_systems(settings.db)
|
|
36
|
+
last_dhw_name = create_dhw_systems(settings.db)
|
|
37
|
+
_last_ops_name = create_operations(
|
|
38
|
+
settings.db, last_space_use_name, last_hvac_name, last_dhw_name
|
|
39
|
+
)
|
|
40
|
+
create_envelope(settings.db)
|
|
41
|
+
create_zone(settings.db)
|
|
42
|
+
|
|
43
|
+
component_map_path.write_text(
|
|
44
|
+
"selector:\n source_fields: [basic]\n",
|
|
45
|
+
encoding="utf-8",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
yield db_path, component_map_path
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_construct_zone_def_returns_zone_component(
|
|
52
|
+
preseeded_db_path_and_component_map: tuple[Path, Path],
|
|
53
|
+
):
|
|
54
|
+
"""Test that construct_zone_def returns a valid ZoneComponent from the preseeded db."""
|
|
55
|
+
db_path, component_map_path = preseeded_db_path_and_component_map
|
|
56
|
+
|
|
57
|
+
zone = construct_zone_def(
|
|
58
|
+
component_map_path=component_map_path,
|
|
59
|
+
db_path=db_path,
|
|
60
|
+
semantic_field_context={"basic": "default_zone"},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
assert isinstance(zone, ZoneComponent)
|
|
64
|
+
assert zone.Name == "default_zone"
|
|
65
|
+
assert zone.Envelope is not None
|
|
66
|
+
assert zone.Operations is not None
|
|
67
|
+
assert zone.Envelope.Name == "default_env"
|
|
68
|
+
assert zone.Operations.Name == "default_ops"
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Tests for neighbor shading geometry (shading_fence_closed_ring, prepare_neighbor_shading_for_idf)."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
from shapely import Polygon
|
|
6
|
+
|
|
7
|
+
from epinterface.geometry import (
|
|
8
|
+
compute_shading_mask,
|
|
9
|
+
prepare_neighbor_shading_for_idf,
|
|
10
|
+
shading_fence_closed_ring,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_shading_fence_closed_ring():
|
|
15
|
+
"""Test shading_fence_closed_ring output shapes and cyclic closure."""
|
|
16
|
+
elevations = np.array([0.1, 0.2, 0.3])
|
|
17
|
+
d = 100.0
|
|
18
|
+
|
|
19
|
+
az, p0, p1, h, w = shading_fence_closed_ring(elevations=elevations, d=d)
|
|
20
|
+
|
|
21
|
+
assert az.shape == (3,)
|
|
22
|
+
assert p0.shape == (3, 2)
|
|
23
|
+
assert p1.shape == (3, 2)
|
|
24
|
+
assert h.shape == (3,)
|
|
25
|
+
assert isinstance(w, float)
|
|
26
|
+
|
|
27
|
+
# Cyclic closure: p1[k] == p0[(k+1) % N] (within floating point tolerance)
|
|
28
|
+
for k in range(3):
|
|
29
|
+
np.testing.assert_allclose(p1[k], p0[(k + 1) % 3], atol=1e-12)
|
|
30
|
+
|
|
31
|
+
# Heights: h[k] == d * tan(elevations[k])
|
|
32
|
+
expected_h = d * np.tan(elevations)
|
|
33
|
+
np.testing.assert_allclose(h, expected_h, rtol=1e-10)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_shading_fence_closed_ring_raises_for_n_less_than_3():
|
|
37
|
+
"""Test that shading_fence_closed_ring raises ValueError for N < 3."""
|
|
38
|
+
elevations = np.array([0.1, 0.2])
|
|
39
|
+
|
|
40
|
+
with pytest.raises(ValueError, match="at least 3 elevations"):
|
|
41
|
+
shading_fence_closed_ring(elevations=elevations, d=100)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_prepare_neighbor_shading_for_idf_no_neighbors():
|
|
45
|
+
"""Test prepare_neighbor_shading_for_idf with no neighbors (default 48 azimuths)."""
|
|
46
|
+
building = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
|
|
47
|
+
|
|
48
|
+
mask_polys, neighbor_floors = prepare_neighbor_shading_for_idf(
|
|
49
|
+
building=building,
|
|
50
|
+
neighbors=[],
|
|
51
|
+
neighbor_heights=[],
|
|
52
|
+
f2f_height=3.0,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
assert len(mask_polys) == 48
|
|
56
|
+
assert len(neighbor_floors) == 48
|
|
57
|
+
|
|
58
|
+
for poly in mask_polys:
|
|
59
|
+
assert poly.is_valid
|
|
60
|
+
assert not poly.is_empty
|
|
61
|
+
assert len(poly.exterior.coords) >= 4 # closed ring has repeated first point
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_prepare_neighbor_shading_for_idf_with_neighbors():
|
|
65
|
+
"""Test prepare_neighbor_shading_for_idf with one neighbor."""
|
|
66
|
+
building = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
|
|
67
|
+
neighbor = Polygon([(20, 5), (25, 5), (25, 15), (20, 15)])
|
|
68
|
+
neighbor_height = 10.0
|
|
69
|
+
|
|
70
|
+
mask_polys, neighbor_floors = prepare_neighbor_shading_for_idf(
|
|
71
|
+
building=building,
|
|
72
|
+
neighbors=[neighbor],
|
|
73
|
+
neighbor_heights=[neighbor_height],
|
|
74
|
+
f2f_height=3.0,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
assert len(mask_polys) == 48
|
|
78
|
+
assert len(neighbor_floors) == 48
|
|
79
|
+
|
|
80
|
+
for poly in mask_polys:
|
|
81
|
+
assert poly.is_valid
|
|
82
|
+
assert not poly.is_empty
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_shading_mask_from_real_geometry_matches_shading_mask_from_fake_ring():
|
|
86
|
+
"""Shading mask from real neighbor geometry equals shading mask from the fake ring.
|
|
87
|
+
|
|
88
|
+
Manually construct a main building with neighbors scattered around it. Compute the
|
|
89
|
+
shading mask from these real polygons. Then build the fake ring (mask_polys) from
|
|
90
|
+
that shading mask and compute the shading mask again from the fake ring. The two
|
|
91
|
+
masks should match, since the fake ring is designed to reproduce the same elevation
|
|
92
|
+
angles when rays are cast from the building centroid.
|
|
93
|
+
"""
|
|
94
|
+
azimuthal_angle = 2 * np.pi / 48
|
|
95
|
+
fence_radius = 100.0
|
|
96
|
+
|
|
97
|
+
# Main building: 10x10 square centered at (5, 5) - centroid at (5, 5)
|
|
98
|
+
building = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
|
|
99
|
+
|
|
100
|
+
# Neighbors: rectangles to the east, north, and northwest with different heights.
|
|
101
|
+
# Building centroid is (5, 5). Rays will intersect these at various distances.
|
|
102
|
+
neighbor_east = Polygon([(15, 2), (25, 2), (25, 8), (15, 8)]) # east, height 12m
|
|
103
|
+
neighbor_north = Polygon([(2, 15), (8, 15), (8, 22), (2, 22)]) # north, height 9m
|
|
104
|
+
neighbor_nw = Polygon([
|
|
105
|
+
(-5, 8),
|
|
106
|
+
(-2, 8),
|
|
107
|
+
(-2, 12),
|
|
108
|
+
(-5, 12),
|
|
109
|
+
]) # northwest, height 6m
|
|
110
|
+
|
|
111
|
+
neighbors = [neighbor_east, neighbor_north, neighbor_nw]
|
|
112
|
+
neighbor_heights = [12.0, 9.0, 6.0]
|
|
113
|
+
|
|
114
|
+
# Shading mask from real geometry
|
|
115
|
+
mask_from_real = compute_shading_mask(
|
|
116
|
+
building=building,
|
|
117
|
+
neighbors=neighbors,
|
|
118
|
+
neighbor_heights=neighbor_heights,
|
|
119
|
+
azimuthal_angle=azimuthal_angle,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Build the fake ring from this mask
|
|
123
|
+
mask_polys, _neighbor_floors = prepare_neighbor_shading_for_idf(
|
|
124
|
+
building=building,
|
|
125
|
+
neighbors=neighbors,
|
|
126
|
+
neighbor_heights=neighbor_heights,
|
|
127
|
+
azimuthal_angle=azimuthal_angle,
|
|
128
|
+
fence_radius=fence_radius,
|
|
129
|
+
f2f_height=3.0,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Heights of the fake ring fences: h = d * tan(elevation) for each azimuth.
|
|
133
|
+
# We need these exact heights (not the floor-discretized ones) for an exact match.
|
|
134
|
+
_, _, _, fence_heights, _ = shading_fence_closed_ring(
|
|
135
|
+
elevations=mask_from_real, d=fence_radius
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Shading mask from the fake ring (using exact fence heights)
|
|
139
|
+
mask_from_fake_ring = compute_shading_mask(
|
|
140
|
+
building=building,
|
|
141
|
+
neighbors=mask_polys,
|
|
142
|
+
neighbor_heights=fence_heights.tolist(),
|
|
143
|
+
azimuthal_angle=azimuthal_angle,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
np.testing.assert_allclose(
|
|
147
|
+
mask_from_real,
|
|
148
|
+
mask_from_fake_ring,
|
|
149
|
+
atol=1e-10,
|
|
150
|
+
err_msg="Shading mask from real geometry should match shading mask from fake ring",
|
|
151
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{epinterface-1.2.0 → epinterface-1.3.0}/epinterface/sbem/prisma/migrations/migration_lock.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|