zero2geospatial3d-sdk 0.1.0__py3-none-any.whl

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.
Files changed (89) hide show
  1. zero2geospatial3d/__init__.py +105 -0
  2. zero2geospatial3d/acoustics.py +284 -0
  3. zero2geospatial3d/classify.py +379 -0
  4. zero2geospatial3d/cli.py +367 -0
  5. zero2geospatial3d/comfort.py +687 -0
  6. zero2geospatial3d/data/epw_stations.json.gz +0 -0
  7. zero2geospatial3d/daylight.py +337 -0
  8. zero2geospatial3d/dispersion.py +249 -0
  9. zero2geospatial3d/epw.py +162 -0
  10. zero2geospatial3d/geo.py +138 -0
  11. zero2geospatial3d/grid.py +119 -0
  12. zero2geospatial3d/microclimate.py +388 -0
  13. zero2geospatial3d/morphology.py +283 -0
  14. zero2geospatial3d/nsga2.py +315 -0
  15. zero2geospatial3d/osm.py +450 -0
  16. zero2geospatial3d/osm_tags.py +180 -0
  17. zero2geospatial3d/py.typed +0 -0
  18. zero2geospatial3d/results.py +231 -0
  19. zero2geospatial3d/road_sources.py +141 -0
  20. zero2geospatial3d/scene.py +555 -0
  21. zero2geospatial3d/server.py +332 -0
  22. zero2geospatial3d/shading.py +443 -0
  23. zero2geospatial3d/solar.py +207 -0
  24. zero2geospatial3d/terrain.py +610 -0
  25. zero2geospatial3d/urban_form.py +326 -0
  26. zero2geospatial3d/walkability.py +380 -0
  27. zero2geospatial3d/weather.py +373 -0
  28. zero2geospatial3d/web/index.html +50 -0
  29. zero2geospatial3d/web/src/analysis/climate.js +278 -0
  30. zero2geospatial3d/web/src/analysis/compliance.js +345 -0
  31. zero2geospatial3d/web/src/analysis/gpu.js +431 -0
  32. zero2geospatial3d/web/src/analysis/optimize.js +208 -0
  33. zero2geospatial3d/web/src/analysis/runners.js +1644 -0
  34. zero2geospatial3d/web/src/analysis/terrain_results.js +145 -0
  35. zero2geospatial3d/web/src/app.js +630 -0
  36. zero2geospatial3d/web/src/core/acoustics.js +341 -0
  37. zero2geospatial3d/web/src/core/classify.js +158 -0
  38. zero2geospatial3d/web/src/core/comfort.js +686 -0
  39. zero2geospatial3d/web/src/core/daylight_rule.js +199 -0
  40. zero2geospatial3d/web/src/core/dispersion.js +264 -0
  41. zero2geospatial3d/web/src/core/microclimate.js +372 -0
  42. zero2geospatial3d/web/src/core/morphology.js +153 -0
  43. zero2geospatial3d/web/src/core/nsga2.js +181 -0
  44. zero2geospatial3d/web/src/core/party_walls.js +60 -0
  45. zero2geospatial3d/web/src/core/road_sources.js +127 -0
  46. zero2geospatial3d/web/src/core/solar.js +112 -0
  47. zero2geospatial3d/web/src/core/terrain_analysis.js +267 -0
  48. zero2geospatial3d/web/src/core/terrain_math.js +279 -0
  49. zero2geospatial3d/web/src/core/urban_form.js +405 -0
  50. zero2geospatial3d/web/src/core/walkability.js +316 -0
  51. zero2geospatial3d/web/src/core/weather.js +225 -0
  52. zero2geospatial3d/web/src/core/wind.js +131 -0
  53. zero2geospatial3d/web/src/export/check_pdf.js +74 -0
  54. zero2geospatial3d/web/src/export/exporters.js +556 -0
  55. zero2geospatial3d/web/src/export/pdf.js +149 -0
  56. zero2geospatial3d/web/src/export/snapshot.js +234 -0
  57. zero2geospatial3d/web/src/export/snapshot_scene.js +150 -0
  58. zero2geospatial3d/web/src/main.js +75 -0
  59. zero2geospatial3d/web/src/scene/objects.js +645 -0
  60. zero2geospatial3d/web/src/scene/overlay.js +145 -0
  61. zero2geospatial3d/web/src/scene/terrain.js +546 -0
  62. zero2geospatial3d/web/src/scene/viewer.js +275 -0
  63. zero2geospatial3d/web/src/scene/wind_streamlines.js +336 -0
  64. zero2geospatial3d/web/src/style.css +225 -0
  65. zero2geospatial3d/web/src/ui/building_report.js +404 -0
  66. zero2geospatial3d/web/src/ui/charts.js +441 -0
  67. zero2geospatial3d/web/src/ui/colorize.js +101 -0
  68. zero2geospatial3d/web/src/ui/compare.js +362 -0
  69. zero2geospatial3d/web/src/ui/dom.js +104 -0
  70. zero2geospatial3d/web/src/ui/panel_analyze.js +742 -0
  71. zero2geospatial3d/web/src/ui/panel_compare.js +294 -0
  72. zero2geospatial3d/web/src/ui/panel_layers.js +172 -0
  73. zero2geospatial3d/web/src/ui/panel_terrain.js +277 -0
  74. zero2geospatial3d/web/src/ui/qgis_link.js +127 -0
  75. zero2geospatial3d/web/src/ui/sunclock.js +45 -0
  76. zero2geospatial3d/web/src/ui/tools.js +197 -0
  77. zero2geospatial3d/web/src/ui/viewtools.js +173 -0
  78. zero2geospatial3d/web/vendor/three/addons/controls/OrbitControls.js +1417 -0
  79. zero2geospatial3d/web/vendor/three/addons/exporters/GLTFExporter.js +3309 -0
  80. zero2geospatial3d/web/vendor/three/addons/utils/TextureUtils.js +98 -0
  81. zero2geospatial3d/web/vendor/three/three.module.js +53044 -0
  82. zero2geospatial3d/wind.py +178 -0
  83. zero2geospatial3d_sdk-0.1.0.dist-info/METADATA +219 -0
  84. zero2geospatial3d_sdk-0.1.0.dist-info/RECORD +89 -0
  85. zero2geospatial3d_sdk-0.1.0.dist-info/WHEEL +5 -0
  86. zero2geospatial3d_sdk-0.1.0.dist-info/entry_points.txt +2 -0
  87. zero2geospatial3d_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
  88. zero2geospatial3d_sdk-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +52 -0
  89. zero2geospatial3d_sdk-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,315 @@
1
+ """NSGA-II multi-objective optimiser.
2
+
3
+ Deb, Pratap, Agarwal and Meyarivan (2002), "A fast and elitist multiobjective
4
+ genetic algorithm: NSGA-II", IEEE Trans. Evol. Comput. 6(2):182-197,
5
+ doi:10.1109/4235.996017, with constraint domination, simulated binary
6
+ crossover and polynomial mutation. All objectives are minimised. The run is
7
+ seeded (mulberry32, bit-identical to the plugin's viewer) so it can be
8
+ repeated exactly.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import math
14
+ from dataclasses import dataclass, field, replace
15
+ from typing import Any, Callable, Optional, Sequence, Union
16
+
17
+ __all__ = [
18
+ "Individual",
19
+ "NSGA2Result",
20
+ "Problem",
21
+ "crowding_distance",
22
+ "dominates",
23
+ "make_rng",
24
+ "non_dominated_sort",
25
+ "nsga2",
26
+ ]
27
+
28
+ _M32 = 0xFFFFFFFF
29
+
30
+
31
+ def _imul(a: int, b: int) -> int:
32
+ return (a * b) & _M32
33
+
34
+
35
+ def make_rng(seed: int = 1) -> Callable[[], float]:
36
+ """mulberry32: small, fast, seedable PRNG returning floats in ``[0, 1)``."""
37
+ state = [seed & _M32]
38
+
39
+ def rng() -> float:
40
+ state[0] = (state[0] + 0x6D2B79F5) & _M32
41
+ t = state[0]
42
+ t = _imul(t ^ (t >> 15), t | 1)
43
+ t = (t ^ ((t + _imul(t ^ (t >> 7), t | 61)) & _M32)) & _M32
44
+ return ((t ^ (t >> 14)) & _M32) / 4294967296
45
+
46
+ return rng
47
+
48
+
49
+ @dataclass
50
+ class Individual:
51
+ """One candidate solution."""
52
+
53
+ x: list[float] = field(default_factory=list)
54
+ objectives: list[float] = field(default_factory=list)
55
+ violation: float = 0.0
56
+ extra: Any = None
57
+ rank: int = 0
58
+ crowding: float = 0.0
59
+
60
+
61
+ EvalResult = Union[dict, Sequence[float]]
62
+
63
+
64
+ @dataclass
65
+ class Problem:
66
+ """Optimisation problem.
67
+
68
+ ``evaluate(x)`` returns either a sequence of objectives or a dict with
69
+ ``objectives`` and optional ``violation`` (>= 0, 0 = feasible) and
70
+ ``extra``.
71
+ """
72
+
73
+ lower: Sequence[float]
74
+ upper: Sequence[float]
75
+ evaluate: Callable[[list[float]], EvalResult]
76
+ integer: bool = False
77
+ seeds: Optional[Sequence[Sequence[float]]] = None
78
+
79
+
80
+ @dataclass
81
+ class NSGA2Result:
82
+ """Final population, its first front, evaluation count and per-generation history."""
83
+
84
+ population: list[Individual]
85
+ front: list[Individual]
86
+ evaluations: int
87
+ history: list[dict]
88
+
89
+
90
+ def dominates(a: Individual, b: Individual) -> bool:
91
+ """``a`` dominates ``b`` (constraint domination: feasibility first)."""
92
+ if a.violation > 0 or b.violation > 0:
93
+ return a.violation < b.violation
94
+ better = False
95
+ for ai, bi in zip(a.objectives, b.objectives):
96
+ if ai > bi:
97
+ return False
98
+ if ai < bi:
99
+ better = True
100
+ return better
101
+
102
+
103
+ def non_dominated_sort(pop: Sequence[Individual]) -> list[list[int]]:
104
+ """Fast non-dominated sort: list of fronts (lists of indices); sets ``rank``."""
105
+ n = len(pop)
106
+ dominated_by: list[list[int]] = [[] for _ in range(n)]
107
+ count = [0] * n
108
+ fronts: list[list[int]] = [[]]
109
+ for p in range(n):
110
+ for q in range(n):
111
+ if p == q:
112
+ continue
113
+ if dominates(pop[p], pop[q]):
114
+ dominated_by[p].append(q)
115
+ elif dominates(pop[q], pop[p]):
116
+ count[p] += 1
117
+ if count[p] == 0:
118
+ pop[p].rank = 0
119
+ fronts[0].append(p)
120
+ i = 0
121
+ while fronts[i]:
122
+ nxt: list[int] = []
123
+ for p in fronts[i]:
124
+ for q in dominated_by[p]:
125
+ count[q] -= 1
126
+ if count[q] == 0:
127
+ pop[q].rank = i + 1
128
+ nxt.append(q)
129
+ i += 1
130
+ fronts.append(nxt)
131
+ fronts.pop()
132
+ return fronts
133
+
134
+
135
+ def crowding_distance(pop: Sequence[Individual], front: Sequence[int]) -> None:
136
+ """Crowding distance within one front; sets ``crowding``."""
137
+ for i in front:
138
+ pop[i].crowding = 0.0
139
+ if len(front) <= 2:
140
+ for i in front:
141
+ pop[i].crowding = math.inf
142
+ return
143
+ m = len(pop[front[0]].objectives)
144
+ for k in range(m):
145
+ srt = sorted(front, key=lambda i: pop[i].objectives[k]) # noqa: B023
146
+ lo = pop[srt[0]].objectives[k]
147
+ hi = pop[srt[-1]].objectives[k]
148
+ pop[srt[0]].crowding = math.inf
149
+ pop[srt[-1]].crowding = math.inf
150
+ if hi - lo <= 0:
151
+ continue
152
+ for j in range(1, len(srt) - 1):
153
+ pop[srt[j]].crowding += (
154
+ pop[srt[j + 1]].objectives[k] - pop[srt[j - 1]].objectives[k]
155
+ ) / (hi - lo)
156
+
157
+
158
+ def _better(a: Individual, b: Individual) -> bool:
159
+ if a.rank != b.rank:
160
+ return a.rank < b.rank
161
+ return a.crowding > b.crowding
162
+
163
+
164
+ def _sbx(
165
+ rng: Callable[[], float], x1: float, x2: float, lo: float, hi: float, eta: float
166
+ ) -> tuple[float, float]:
167
+ if rng() > 0.5 or abs(x1 - x2) < 1e-12:
168
+ return x1, x2
169
+ u = rng()
170
+ if u <= 0.5:
171
+ beta = (2 * u) ** (1 / (eta + 1))
172
+ else:
173
+ beta = (1 / (2 * (1 - u))) ** (1 / (eta + 1))
174
+ c1 = 0.5 * ((1 + beta) * x1 + (1 - beta) * x2)
175
+ c2 = 0.5 * ((1 - beta) * x1 + (1 + beta) * x2)
176
+ return min(hi, max(lo, c1)), min(hi, max(lo, c2))
177
+
178
+
179
+ def _poly_mutate(rng: Callable[[], float], x: float, lo: float, hi: float, eta: float) -> float:
180
+ span = hi - lo
181
+ if span <= 0:
182
+ return x
183
+ u = rng()
184
+ if u < 0.5:
185
+ delta = (2 * u) ** (1 / (eta + 1)) - 1
186
+ else:
187
+ delta = 1 - (2 * (1 - u)) ** (1 / (eta + 1))
188
+ return min(hi, max(lo, x + delta * span))
189
+
190
+
191
+ def nsga2(
192
+ problem: Problem,
193
+ population_size: int = 24,
194
+ generations: int = 20,
195
+ seed: int = 1,
196
+ eta_c: float = 15,
197
+ eta_m: float = 20,
198
+ crossover_prob: float = 0.9,
199
+ on_generation: Optional[Callable[[dict], None]] = None,
200
+ should_stop: Optional[Callable[[], bool]] = None,
201
+ ) -> NSGA2Result:
202
+ """Run NSGA-II on ``problem`` and return the final population and front."""
203
+ rng = make_rng(seed)
204
+ lower = [float(v) for v in problem.lower]
205
+ upper = [float(v) for v in problem.upper]
206
+ n = len(lower)
207
+ pm = 1 / max(1, n)
208
+
209
+ def rnd(x: list[float]) -> list[float]:
210
+ if not problem.integer:
211
+ return x
212
+ return [min(upper[i], max(lower[i], math.floor(v + 0.5))) for i, v in enumerate(x)]
213
+
214
+ cache: dict[tuple, Individual] = {}
215
+ evaluations = 0
216
+
217
+ def evaluate(x: list[float]) -> Individual:
218
+ nonlocal evaluations
219
+ key = tuple(x)
220
+ if key in cache:
221
+ return replace(cache[key])
222
+ r = problem.evaluate(list(x))
223
+ evaluations += 1
224
+ if isinstance(r, dict):
225
+ objs = list(r["objectives"])
226
+ viol = max(0.0, float(r.get("violation") or 0.0))
227
+ extra = r.get("extra")
228
+ else:
229
+ objs, viol, extra = list(r), 0.0, None
230
+ ind = Individual(x=list(x), objectives=objs, violation=viol, extra=extra)
231
+ cache[key] = ind
232
+ return replace(ind)
233
+
234
+ pop: list[Individual] = []
235
+ for s in problem.seeds or []:
236
+ if len(pop) < population_size:
237
+ pop.append(evaluate(rnd([float(v) for v in s])))
238
+ while len(pop) < population_size:
239
+ x = [lo + rng() * (upper[i] - lo) for i, lo in enumerate(lower)]
240
+ pop.append(evaluate(rnd(x)))
241
+
242
+ def rank_all(p: list[Individual]) -> list[list[int]]:
243
+ fronts = non_dominated_sort(p)
244
+ for f in fronts:
245
+ crowding_distance(p, f)
246
+ return fronts
247
+
248
+ history: list[dict] = []
249
+ rank_all(pop)
250
+ for g in range(generations):
251
+ if should_stop is not None and should_stop():
252
+ break
253
+
254
+ def tournament(pool: list[Individual]) -> Individual:
255
+ a = pool[int(math.floor(rng() * len(pool)))]
256
+ b = pool[int(math.floor(rng() * len(pool)))]
257
+ return a if _better(a, b) else b
258
+
259
+ children: list[Individual] = []
260
+ while len(children) < population_size:
261
+ p1 = tournament(pop).x
262
+ p2 = tournament(pop).x
263
+ c1 = list(p1)
264
+ c2 = list(p2)
265
+ if rng() < crossover_prob:
266
+ for i in range(n):
267
+ c1[i], c2[i] = _sbx(rng, p1[i], p2[i], lower[i], upper[i], eta_c)
268
+ for i in range(n):
269
+ if rng() < pm:
270
+ c1[i] = _poly_mutate(rng, c1[i], lower[i], upper[i], eta_m)
271
+ if rng() < pm:
272
+ c2[i] = _poly_mutate(rng, c2[i], lower[i], upper[i], eta_m)
273
+ children.append(evaluate(rnd(c1)))
274
+ if len(children) < population_size:
275
+ children.append(evaluate(rnd(c2)))
276
+ seen: set = set()
277
+ unique: list[Individual] = []
278
+ dupes: list[Individual] = []
279
+ for ind in pop + children:
280
+ key = tuple(ind.x)
281
+ if key in seen:
282
+ dupes.append(ind)
283
+ else:
284
+ seen.add(key)
285
+ unique.append(ind)
286
+ merged = (
287
+ unique
288
+ if len(unique) >= population_size
289
+ else unique + dupes[: population_size - len(unique)]
290
+ )
291
+ fronts = rank_all(merged)
292
+ nxt: list[Individual] = []
293
+ for f in fronts:
294
+ if len(nxt) + len(f) <= population_size:
295
+ nxt.extend(merged[i] for i in f)
296
+ continue
297
+ srt = sorted(f, key=lambda i: -merged[i].crowding)
298
+ nxt.extend(merged[i] for i in srt[: population_size - len(nxt)])
299
+ break
300
+ pop = nxt
301
+ rank_all(pop)
302
+ front = [p for p in pop if p.rank == 0 and p.violation == 0]
303
+ history.append({"generation": g + 1, "evaluations": evaluations, "front_size": len(front)})
304
+ if on_generation is not None:
305
+ on_generation(
306
+ {
307
+ "generation": g + 1,
308
+ "generations": generations,
309
+ "population": pop,
310
+ "front": front,
311
+ "evaluations": evaluations,
312
+ }
313
+ )
314
+ front = [p for p in pop if p.rank == 0]
315
+ return NSGA2Result(population=pop, front=front, evaluations=evaluations, history=history)