pyvcell 0.0.1__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.
pyvcell-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024, Jim Schaff
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pyvcell-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.1
2
+ Name: pyvcell
3
+ Version: 0.0.1
4
+ Summary: This is the python wrapper for vcell modeling and simulation
5
+ Home-page: https://github.com/virtualcell/pyvcell
6
+ Author: Jim Schaff
7
+ Author-email: fschaff@uchc.edu
8
+ Requires-Python: >=3.10,<4.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: h5py (>=3.11.0,<4.0.0)
14
+ Requires-Dist: numexpr (>=2.10.0,<3.0.0)
15
+ Requires-Dist: numpy (>=1.26.4,<2.0.0)
16
+ Requires-Dist: orjson (>=3.10.3,<4.0.0)
17
+ Requires-Dist: pyvcell-fvsolver (>=0.0.4,<0.0.5)
18
+ Requires-Dist: typer (>=0.12.3,<0.13.0)
19
+ Requires-Dist: vtk (>=9.3.1,<10.0.0)
20
+ Requires-Dist: zarr (>=2.17.2,<3.0.0)
21
+ Project-URL: Documentation, https://virtualcell.github.io/pyvcell/
22
+ Project-URL: Repository, https://github.com/virtualcell/pyvcell
23
+ Description-Content-Type: text/markdown
24
+
25
+ # pyvcell
26
+
27
+ [![Release](https://img.shields.io/github/v/release/virtualcell/pyvcell)](https://img.shields.io/github/v/release/virtualcell/pyvcell)
28
+ [![Build status](https://img.shields.io/github/actions/workflow/status/virtualcell/pyvcell/main.yml?branch=main)](https://github.com/virtualcell/pyvcell/actions/workflows/main.yml?query=branch%3Amain)
29
+ [![codecov](https://codecov.io/gh/virtualcell/pyvcell/branch/main/graph/badge.svg)](https://codecov.io/gh/virtualcell/pyvcell)
30
+ [![Commit activity](https://img.shields.io/github/commit-activity/m/virtualcell/pyvcell)](https://img.shields.io/github/commit-activity/m/virtualcell/pyvcell)
31
+ [![License](https://img.shields.io/github/license/virtualcell/pyvcell)](https://img.shields.io/github/license/virtualcell/pyvcell)
32
+
33
+ This is the python wrapper for vcell modeling and simulation
34
+
35
+ - **Github repository**: <https://github.com/virtualcell/pyvcell/>
36
+ - **Documentation** <https://virtualcell.github.io/pyvcell/>
37
+
38
+ ## Getting started with your project
39
+
40
+ First, create a repository on GitHub with the same name as this project, and then run the following commands:
41
+
42
+ ```bash
43
+ git init -b main
44
+ git add .
45
+ git commit -m "init commit"
46
+ git remote add origin git@github.com:virtualcell/pyvcell.git
47
+ git push -u origin main
48
+ ```
49
+
50
+ Finally, install the environment and the pre-commit hooks with
51
+
52
+ ```bash
53
+ make install
54
+ ```
55
+
56
+ You are now ready to start development on your project!
57
+ The CI/CD pipeline will be triggered when you open a pull request, merge to main, or when you create a new release.
58
+
59
+ To finalize the set-up for publishing to PyPi or Artifactory, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/publishing/#set-up-for-pypi).
60
+ For activating the automatic documentation with MkDocs, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/mkdocs/#enabling-the-documentation-on-github).
61
+ To enable the code coverage reports, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/codecov/).
62
+
63
+ ## Releasing a new version
64
+
65
+ - Create an API Token on [Pypi](https://pypi.org/).
66
+ - Add the API Token to your projects secrets with the name `PYPI_TOKEN` by visiting [this page](https://github.com/virtualcell/pyvcell/settings/secrets/actions/new).
67
+ - Create a [new release](https://github.com/virtualcell/pyvcell/releases/new) on Github.
68
+ - Create a new tag in the form `*.*.*`.
69
+
70
+ For more details, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/cicd/#how-to-trigger-a-release).
71
+
72
+ ---
73
+
74
+ Repository initiated with [fpgmaas/cookiecutter-poetry](https://github.com/fpgmaas/cookiecutter-poetry).
75
+
@@ -0,0 +1,50 @@
1
+ # pyvcell
2
+
3
+ [![Release](https://img.shields.io/github/v/release/virtualcell/pyvcell)](https://img.shields.io/github/v/release/virtualcell/pyvcell)
4
+ [![Build status](https://img.shields.io/github/actions/workflow/status/virtualcell/pyvcell/main.yml?branch=main)](https://github.com/virtualcell/pyvcell/actions/workflows/main.yml?query=branch%3Amain)
5
+ [![codecov](https://codecov.io/gh/virtualcell/pyvcell/branch/main/graph/badge.svg)](https://codecov.io/gh/virtualcell/pyvcell)
6
+ [![Commit activity](https://img.shields.io/github/commit-activity/m/virtualcell/pyvcell)](https://img.shields.io/github/commit-activity/m/virtualcell/pyvcell)
7
+ [![License](https://img.shields.io/github/license/virtualcell/pyvcell)](https://img.shields.io/github/license/virtualcell/pyvcell)
8
+
9
+ This is the python wrapper for vcell modeling and simulation
10
+
11
+ - **Github repository**: <https://github.com/virtualcell/pyvcell/>
12
+ - **Documentation** <https://virtualcell.github.io/pyvcell/>
13
+
14
+ ## Getting started with your project
15
+
16
+ First, create a repository on GitHub with the same name as this project, and then run the following commands:
17
+
18
+ ```bash
19
+ git init -b main
20
+ git add .
21
+ git commit -m "init commit"
22
+ git remote add origin git@github.com:virtualcell/pyvcell.git
23
+ git push -u origin main
24
+ ```
25
+
26
+ Finally, install the environment and the pre-commit hooks with
27
+
28
+ ```bash
29
+ make install
30
+ ```
31
+
32
+ You are now ready to start development on your project!
33
+ The CI/CD pipeline will be triggered when you open a pull request, merge to main, or when you create a new release.
34
+
35
+ To finalize the set-up for publishing to PyPi or Artifactory, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/publishing/#set-up-for-pypi).
36
+ For activating the automatic documentation with MkDocs, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/mkdocs/#enabling-the-documentation-on-github).
37
+ To enable the code coverage reports, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/codecov/).
38
+
39
+ ## Releasing a new version
40
+
41
+ - Create an API Token on [Pypi](https://pypi.org/).
42
+ - Add the API Token to your projects secrets with the name `PYPI_TOKEN` by visiting [this page](https://github.com/virtualcell/pyvcell/settings/secrets/actions/new).
43
+ - Create a [new release](https://github.com/virtualcell/pyvcell/releases/new) on Github.
44
+ - Create a new tag in the form `*.*.*`.
45
+
46
+ For more details, see [here](https://fpgmaas.github.io/cookiecutter-poetry/features/cicd/#how-to-trigger-a-release).
47
+
48
+ ---
49
+
50
+ Repository initiated with [fpgmaas/cookiecutter-poetry](https://github.com/fpgmaas/cookiecutter-poetry).
@@ -0,0 +1,118 @@
1
+ [tool.poetry]
2
+ name = "pyvcell"
3
+ version = "0.0.1"
4
+ description = "This is the python wrapper for vcell modeling and simulation"
5
+ authors = ["Jim Schaff <fschaff@uchc.edu>"]
6
+ repository = "https://github.com/virtualcell/pyvcell"
7
+ documentation = "https://virtualcell.github.io/pyvcell/"
8
+ readme = "README.md"
9
+ packages = [
10
+ {include = "pyvcell"}
11
+ ]
12
+
13
+ [tool.poetry.dependencies]
14
+ python = ">=3.10,<4.0"
15
+ numexpr = "^2.10.0"
16
+ zarr = "^2.17.2"
17
+ h5py = "^3.11.0"
18
+ numpy = "^1.26.4"
19
+ orjson = "^3.10.3"
20
+ vtk = "^9.3.1"
21
+ pyvcell-fvsolver = "^0.0.4"
22
+ typer = "^0.12.3"
23
+
24
+ [tool.poetry.group.dev.dependencies]
25
+ pytest = "^7.2.0"
26
+ pytest-cov = "^4.0.0"
27
+ deptry = "^0.16.2"
28
+ mypy = "^1.5.1"
29
+ pre-commit = "^3.8.0"
30
+ tox = "^4.11.1"
31
+ h5py-stubs = "^0.1.1"
32
+
33
+ [tool.poetry.group.docs.dependencies]
34
+ mkdocs = "^1.4.2"
35
+ mkdocs-material = "^9.2.7"
36
+ mkdocstrings = {extras = ["python"], version = "^0.23.0"}
37
+
38
+ [build-system]
39
+ requires = ["poetry-core>=1.0.0"]
40
+ build-backend = "poetry.core.masonry.api"
41
+
42
+ [tool.mypy]
43
+ files = ["pyvcell"]
44
+ disallow_untyped_defs = "True"
45
+ disallow_any_unimported = "True"
46
+ no_implicit_optional = "True"
47
+ check_untyped_defs = "True"
48
+ warn_return_any = "True"
49
+ warn_unused_ignores = "True"
50
+ show_error_codes = "True"
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
54
+
55
+ [tool.ruff]
56
+ target-version = "py39"
57
+ line-length = 120
58
+ fix = true
59
+ select = [
60
+ # flake8-2020
61
+ "YTT",
62
+ # flake8-bandit
63
+ "S",
64
+ # flake8-bugbear
65
+ "B",
66
+ # flake8-builtins
67
+ "A",
68
+ # flake8-comprehensions
69
+ "C4",
70
+ # flake8-debugger
71
+ "T10",
72
+ # flake8-simplify
73
+ "SIM",
74
+ # isort
75
+ "I",
76
+ # mccabe
77
+ "C90",
78
+ # pycodestyle
79
+ "E", "W",
80
+ # pyflakes
81
+ "F",
82
+ # pygrep-hooks
83
+ "PGH",
84
+ # pyupgrade
85
+ "UP",
86
+ # ruff
87
+ "RUF",
88
+ # tryceratops
89
+ "TRY",
90
+ ]
91
+ ignore = [
92
+ # LineTooLong
93
+ "E501",
94
+ # DoNotAssignLambda
95
+ "E731",
96
+ # avoid specifiying long messages outside the exception class
97
+ "TRY003",
98
+ # avoid functions which are too complex
99
+ "C901",
100
+ # avoid uses of Uses of `tarfile.extractall()
101
+ "S202",
102
+ # B008 Do not perform function call `typer.Argument` in argument defaults
103
+ "B008",
104
+ ]
105
+
106
+ [tool.ruff.format]
107
+ preview = true
108
+
109
+ [tool.coverage.report]
110
+ skip_empty = true
111
+
112
+ [tool.coverage.run]
113
+ branch = true
114
+ source = ["pyvcell"]
115
+
116
+
117
+ [tool.ruff.per-file-ignores]
118
+ "tests/*" = ["S101"]
File without changes
@@ -0,0 +1,17 @@
1
+ def foo(bar: str) -> str:
2
+ """Summary line.
3
+
4
+ Extended description of function.
5
+
6
+ Args:
7
+ bar: Description of input argument.
8
+
9
+ Returns:
10
+ Description of return value
11
+ """
12
+
13
+ return bar
14
+
15
+
16
+ if __name__ == "__main__": # pragma: no cover
17
+ pass
File without changes
@@ -0,0 +1,34 @@
1
+ from pathlib import Path
2
+
3
+ import typer
4
+
5
+ from pyvcell.simdata.mesh import CartesianMesh
6
+ from pyvcell.simdata.simdata_models import DataFunctions, PdeDataSet
7
+ from pyvcell.simdata.zarr_writer import write_zarr
8
+
9
+ app = typer.Typer()
10
+
11
+
12
+ @app.command(name="vc_to_zarr", help="Convert a VCell FiniteVolume simulation dataset to Zarr")
13
+ def n5_to_zarr(
14
+ sim_data_dir: Path = typer.Argument(..., help="path to vcell dataset directory"),
15
+ sim_id: int = typer.Argument(..., help="simulation id (e.g. 946368938)"),
16
+ job_id: int = typer.Argument(..., help="job id (e.g. 0"),
17
+ zarr_path: Path = typer.Argument(..., help="path to zarr dataset to write to"),
18
+ ) -> None:
19
+ pde_dataset = PdeDataSet(base_dir=sim_data_dir, log_filename=f"SimID_{sim_id}_{job_id}_.log")
20
+ pde_dataset.read()
21
+ data_functions = DataFunctions(function_file=sim_data_dir / f"SimID_{sim_id}_{job_id}_.functions")
22
+ data_functions.read()
23
+ mesh = CartesianMesh(mesh_file=sim_data_dir / f"SimID_{sim_id}_{job_id}_.mesh")
24
+ mesh.read()
25
+
26
+ write_zarr(pde_dataset=pde_dataset, data_functions=data_functions, mesh=mesh, zarr_dir=zarr_path)
27
+
28
+
29
+ def main() -> None:
30
+ app()
31
+
32
+
33
+ if __name__ == "__main__":
34
+ main()
@@ -0,0 +1,224 @@
1
+ import zlib
2
+ from pathlib import Path
3
+
4
+ import numpy as np
5
+
6
+ from pyvcell.simdata.vtk.vismesh import Box3D
7
+
8
+
9
+ class CartesianMesh:
10
+ """
11
+ reads the .mesh file and extracts the mesh information
12
+
13
+ Example .mesh file:
14
+ Version 1.2
15
+ CartesianMesh {
16
+ // X Y Z
17
+ Size 71 71 25
18
+ Extent 74.239999999999995 74.239999999999995 26
19
+ Origin 0 0 0
20
+
21
+ VolumeRegionsMapSubvolume {
22
+ 6
23
+ //VolRegID SubvolID Volume
24
+ 0 0 124767.54117864356 //ec
25
+ 1 1 14855.904388351477 //cytosol
26
+ 2 1 1.2185460680272107 //cytosol
27
+ 3 1 1.2185460680272107 //cytosol
28
+ 4 1 1.2185460680272107 //cytosol
29
+ 5 2 3673.9163951019395 //Nucleus
30
+ }
31
+
32
+ MembraneRegionsMapVolumeRegion {
33
+ 5
34
+ //MemRegID VolReg1 VolReg2 Surface
35
+ 0 1 0 4512.8782874369472
36
+ 1 2 0 1.7113582585034091
37
+ 2 3 0 1.7113582585033937
38
+ 3 4 0 1.711358258503394
39
+ 4 5 1 1306.5985272332098
40
+ }
41
+
42
+ VolumeElementsMapVolumeRegion {
43
+ 126025 Compressed
44
+ 789CEDDD8D72DBC81100612389DFFF9573572A5912B9BF2066A66176B32A57B12CE22B8022E5DD11
45
+ F5EB9799999999999999999999999999999999999999999999999999999999999999999999999999
46
+ ...
47
+ 3333338B8F3625C09A5BE069281EE2BC0BC543D530FA907034666666666666666666666666666666
48
+ 6666666666666666666666667F67FF07ABF56A9C
49
+ }
50
+
51
+ MembraneElements {
52
+ 7817
53
+ //Indx Vol1 Vol2 Conn0 Conn1 Conn2 Conn3 MemRegID
54
+ 0 6710 11751 5 507 493 1 0
55
+ 1 6711 11752 6 0 494 510 0
56
+ 2 6771 11812 10 524 503 3 0
57
+ 3 6772 11813 11 2 505 527 0
58
+ 4 6780 11821 16 533 508 5 0
59
+ ....
60
+ 7808 109155 104114 7807 7805 7792 7806 4
61
+ 7809 104179 104180 7810 7551 7798 7811 4
62
+ 7810 104251 104180 7812 7551 7809 -1 4
63
+ 7811 109221 104180 -1 7809 7799 7813 4
64
+ 7812 104252 104181 7815 7553 7810 -1 4
65
+ 7813 109222 104181 -1 7811 7800 7816 4
66
+ 7814 104183 104182 7815 7556 7802 7816 4
67
+ 7815 104253 104182 7814 7554 7812 -1 4
68
+ 7816 109223 104182 -1 7813 7801 7814 4
69
+ }
70
+ }
71
+ """
72
+
73
+ mesh_file: Path
74
+ size: list[int] # [x, y, z]
75
+ extent: list[float] # [x, y, z]
76
+ origin: list[float] # [x, y, z]
77
+ volume_regions: list[tuple[int, int, float, str]] # list of tuples (vol_reg_id, subvol_id, volume, domain_name)
78
+ membrane_regions: list[tuple[int, int, int, float]] # list of tuples (mem_reg_id, vol_reg1, vol_reg2, surface)
79
+
80
+ # membrane_element[m,:] = [idx, vol1, vol2, conn0, conn1, conn2, conn3, mem_reg_id]
81
+ membrane_elements: np.ndarray # shape (num_membrane_elements, 8)
82
+
83
+ # volume_region_map[m] = vol_reg_id
84
+ volume_region_map: np.ndarray # shape (size[0] * size[1] * size[2],)
85
+
86
+ def __init__(self, mesh_file: Path) -> None:
87
+ self.mesh_file = mesh_file
88
+ self.size = []
89
+ self.extent = []
90
+ self.origin = []
91
+ self.volume_regions = []
92
+ self.membrane_regions = []
93
+ # self.membrane_elements
94
+ self.volume_region_map = np.array([], dtype=np.uint8)
95
+
96
+ @property
97
+ def dimension(self) -> int:
98
+ if self.size[1] == 1 and self.size[2] == 1:
99
+ return 1
100
+ elif self.size[2] == 1:
101
+ return 2
102
+ else:
103
+ return 3
104
+
105
+ def read(self) -> None:
106
+ # read file as lines and parse
107
+ with self.mesh_file.open("r") as f:
108
+ # get line enumerator from f
109
+
110
+ iter_lines = iter(f.readlines())
111
+
112
+ if next(iter_lines) != "Version 1.2\n":
113
+ raise RuntimeError("Expected 'Version 1.2' at the beginning of the file")
114
+ if next(iter_lines) != "CartesianMesh {\n":
115
+ raise RuntimeError("Expected 'CartesianMesh {' after version")
116
+ if next(iter_lines) != "\t// X Y Z\n":
117
+ raise RuntimeError("Expected coordinate comment line")
118
+
119
+ size_line = next(iter_lines).split()
120
+ if size_line[0] == "Size":
121
+ self.size = [int(size_line[1]), int(size_line[2]), int(size_line[3])]
122
+
123
+ extent_line = next(iter_lines).split()
124
+ if extent_line[0] == "Extent":
125
+ self.extent = [float(extent_line[1]), float(extent_line[2]), float(extent_line[3])]
126
+
127
+ origin_line = next(iter_lines).split()
128
+ if origin_line[0] == "Origin":
129
+ self.origin = [float(origin_line[1]), float(origin_line[2]), float(origin_line[3])]
130
+
131
+ while next(iter_lines) != "\tVolumeRegionsMapSubvolume {\n":
132
+ pass
133
+ num_volume_regions = int(next(iter_lines))
134
+ _header_line = next(iter_lines)
135
+ self.volume_regions = []
136
+ for _i in range(num_volume_regions):
137
+ parts = next(iter_lines).split()
138
+ self.volume_regions.append((int(parts[0]), int(parts[1]), float(parts[2]), parts[3].strip("/")))
139
+
140
+ while next(iter_lines) != "\tMembraneRegionsMapVolumeRegion {\n":
141
+ pass
142
+ num_membrane_regions = int(next(iter_lines))
143
+ _header_line = next(iter_lines)
144
+ self.membrane_regions = []
145
+ for _i in range(num_membrane_regions):
146
+ parts = next(iter_lines).split()
147
+ self.membrane_regions.append((int(parts[0]), int(parts[1]), int(parts[2]), float(parts[3])))
148
+
149
+ while next(iter_lines) != "\tVolumeElementsMapVolumeRegion {\n":
150
+ pass
151
+ compressed_line = next(iter_lines).split()
152
+ num_volume_elements = int(compressed_line[0])
153
+ if compressed_line[1] != "Compressed":
154
+ raise ValueError("Expected 'Compressed' in VolumeElementsMapVolumeRegion")
155
+ # read HEX lines until "}" line, and concatenate into one string, then convert to bytes and decompress
156
+ hex_lines = []
157
+ while True:
158
+ line = next(iter_lines)
159
+ if line.strip() == "}":
160
+ break
161
+ hex_lines.append(line.strip())
162
+ hex_string: str = "".join(hex_lines).strip()
163
+ compressed_bytes = bytes.fromhex(hex_string)
164
+ uncompressed_bytes: bytes = zlib.decompress(compressed_bytes)
165
+ self.volume_region_map = np.frombuffer(uncompressed_bytes, dtype="<u2") # unsigned 2-byte integers
166
+ if self.volume_region_map.shape[0] != self.size[0] * self.size[1] * self.size[2]:
167
+ raise ValueError("Expected number of volume elements to match the size of volume region map")
168
+ if num_volume_elements != self.volume_region_map.shape[0]:
169
+ raise ValueError("Expected number of volume elements to match the size of volume region map")
170
+ if set(np.unique(self.volume_region_map)) != {v[0] for v in self.volume_regions}:
171
+ raise ValueError("Expected volume region map to have the same unique values as volume regions")
172
+
173
+ while next(iter_lines).strip() != "MembraneElements {":
174
+ pass
175
+ num_membrane_elements = int(next(iter_lines))
176
+ self.membrane_elements = np.zeros((num_membrane_elements, 8), dtype=np.int32)
177
+ _header_line = next(iter_lines)
178
+ mem_index = 0
179
+ while True:
180
+ line = next(iter_lines)
181
+ if line.strip() == "}":
182
+ break
183
+ parts = line.split()
184
+ idx = int(parts[0])
185
+ vol1 = int(parts[1])
186
+ vol2 = int(parts[2])
187
+ conn0 = int(parts[3])
188
+ conn1 = int(parts[4])
189
+ conn2 = int(parts[5])
190
+ conn3 = int(parts[6])
191
+ mem_reg_id = int(parts[7])
192
+ self.membrane_elements[mem_index, :] = [idx, vol1, vol2, conn0, conn1, conn2, conn3, mem_reg_id]
193
+ mem_index += 1
194
+ if self.membrane_elements.shape != (num_membrane_elements, 8):
195
+ raise RuntimeError("Expected membrane elements to have the correct shape")
196
+ if set(np.unique(self.membrane_elements[:, 7])) != {v[0] for v in self.membrane_regions}:
197
+ raise RuntimeError("Expected volume region ids in membrane elements to match volume regions")
198
+
199
+ def get_volume_element_box(self, i: int, j: int, k: int) -> Box3D:
200
+ x_lo = self.origin[0] + i * self.extent[0] / self.size[0]
201
+ y_lo = self.origin[1] + j * self.extent[1] / self.size[1]
202
+ z_lo = self.origin[2] + k * self.extent[2] / self.size[2]
203
+ x_hi = self.origin[0] + (i + 1) * self.extent[0] / self.size[0]
204
+ y_hi = self.origin[1] + (j + 1) * self.extent[1] / self.size[1]
205
+ z_hi = self.origin[2] + (k + 1) * self.extent[2] / self.size[2]
206
+ return Box3D(x_lo, y_lo, z_lo, x_hi, y_hi, z_hi)
207
+
208
+ def get_membrane_region_index(self, mem_element_index: int) -> int:
209
+ return int(self.membrane_elements[mem_element_index, 7])
210
+
211
+ def get_membrane_region_ids(self, volume_domain_name: str) -> set[int]:
212
+ return {
213
+ mem_reg_id
214
+ for mem_reg_id, vol_reg1, vol_reg2, surface in self.membrane_regions
215
+ if self.volume_regions[vol_reg1][3] == volume_domain_name
216
+ or self.volume_regions[vol_reg2][3] == volume_domain_name
217
+ }
218
+
219
+ def get_volume_region_ids(self, volume_domain_name: str) -> set[int]:
220
+ return {
221
+ vol_reg_id
222
+ for vol_reg_id, subvol_id, volume, domain_name in self.volume_regions
223
+ if domain_name == volume_domain_name
224
+ }