chemparseplot 1.10.1__tar.gz → 1.11.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.
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/PKG-INFO +1 -1
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/_version.py +2 -2
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/plot/disconnectivity.py +6 -16
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/plot/landscape.py +33 -14
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/plot/neb.py +41 -12
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/pyproject.toml +1 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/plot/test_landscape_disconnectivity.py +4 -12
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/plot/test_strip_rendering.py +37 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/.gitignore +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/LICENSE +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/api.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/chemgp_hdf5.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/chemgp_jsonl.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/converter.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/eon/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/eon/_trajectory_common.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/eon/con_io.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/eon/dimer_trajectory.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/eon/frame_series.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/eon/gprd.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/eon/min_trajectory.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/eon/minimization.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/eon/neb.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/eon/saddle_search.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/eon/stitch.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/file_.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/grammar/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/grammar/_deps.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/grammar/orca_text.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/grammar/xyz.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/neb_utils.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/orca/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/orca/_opi.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/orca/geomscan.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/orca/neb/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/orca/neb/interp.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/orca/neb/opi_parser.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/patterns.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/plumed.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/projection.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/sella/saddle_search.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/trajectory/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/trajectory/hdf5.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/trajectory/neb.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/parse/types.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/plot/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/plot/chemgp.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/plot/geomscan.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/plot/optimization.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/plot/plumed.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/plot/structs.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/plot/theme.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/units.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/chemparseplot/util.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/readme.md +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/_optional_imports.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/conftest.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/fixtures/h2mol.xyz +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/fixtures/orca_energy_coords_snippet.out +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/fixtures_geomscan_snippet.txt +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/_con_fixtures.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/grammar/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/grammar/test_grammar_deps.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/grammar/test_orca_text_grammar.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/grammar/test_xyz_grammar.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/orca/test_geomscan.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/orca/test_interp.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_chemgp_hdf5.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_chemgp_jsonl_types.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_con_io.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_converter.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_dimer_trajectory.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_eon_neb_types.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_eon_trajectory_common.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_frame_series.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_min_trajectory.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_minimization_result.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_neb_stitch.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_neb_utils.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_orca_public_api.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_parser_types.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_patterns.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_plumed.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_projection.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_readcon_boundary.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_saddle_results.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_sella_saddle.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_trajectory_hdf5.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/parse/test_trajectory_neb.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/plot/__init__.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/plot/test_chemgp_utils.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/plot/test_neb_helpers.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/plot/test_neb_renderers.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/plot/test_optimization.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/plot/test_orca_neb_payload.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/plot/test_projection_refactor.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/plot/test_structs_units.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/plot/test_surface_auto_thin.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/test_coverage_batch.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/test_full_coverage.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/test_public_api.py +0 -0
- {chemparseplot-1.10.1 → chemparseplot-1.11.0}/tests/tutorials/test_chemparseplot.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chemparseplot
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.11.0
|
|
4
4
|
Summary: Parsers and plotting tools for computational chemistry
|
|
5
5
|
Project-URL: Documentation, https://chemparseplot.rgoswami.me
|
|
6
6
|
Project-URL: Issues, https://github.com/HaoZeke/chemparseplot/issues
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.
|
|
22
|
-
__version_tuple__ = version_tuple = (1,
|
|
21
|
+
__version__ = version = '1.11.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 11, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -27,9 +27,7 @@ from scipy.sparse.csgraph import connected_components, laplacian
|
|
|
27
27
|
|
|
28
28
|
from chemparseplot.plot.theme import RUHI_COLORS
|
|
29
29
|
|
|
30
|
-
BASIN_PALETTE = [
|
|
31
|
-
RUHI_COLORS[c] for c in ["teal", "sky", "magenta", "coral", "sunshine"]
|
|
32
|
-
]
|
|
30
|
+
BASIN_PALETTE = [RUHI_COLORS[c] for c in ["teal", "sky", "magenta", "coral", "sunshine"]]
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
def dedup(matrix, energies, tol):
|
|
@@ -86,9 +84,7 @@ def bridge_components(adjacency, matrix, weight=0.1):
|
|
|
86
84
|
for b in range(a + 1, n_comp):
|
|
87
85
|
ia = np.where(labels == a)[0]
|
|
88
86
|
ib = np.where(labels == b)[0]
|
|
89
|
-
d = np.linalg.norm(
|
|
90
|
-
matrix[ia][:, None, :] - matrix[ib][None, :, :], axis=-1
|
|
91
|
-
)
|
|
87
|
+
d = np.linalg.norm(matrix[ia][:, None, :] - matrix[ib][None, :, :], axis=-1)
|
|
92
88
|
i, j = np.unravel_index(int(np.argmin(d)), d.shape)
|
|
93
89
|
adjacency[ia[i], ib[j]] = weight
|
|
94
90
|
adjacency[ib[j], ia[i]] = weight
|
|
@@ -119,9 +115,7 @@ def spectral_basins(adjacency, n_basins, rng, matrix=None):
|
|
|
119
115
|
first = int(rng.integers(len(x)))
|
|
120
116
|
centers = [x[first]]
|
|
121
117
|
while len(centers) < k:
|
|
122
|
-
d = np.min(
|
|
123
|
-
[np.linalg.norm(x - c, axis=1) for c in centers], axis=0
|
|
124
|
-
)
|
|
118
|
+
d = np.min([np.linalg.norm(x - c, axis=1) for c in centers], axis=0)
|
|
125
119
|
centers.append(x[int(np.argmax(d))])
|
|
126
120
|
centers = np.array(centers)
|
|
127
121
|
for _ in range(60):
|
|
@@ -147,7 +141,7 @@ class Node:
|
|
|
147
141
|
```
|
|
148
142
|
"""
|
|
149
143
|
|
|
150
|
-
__slots__ = ("
|
|
144
|
+
__slots__ = ("argmin", "children", "emin", "leaf", "level", "x")
|
|
151
145
|
|
|
152
146
|
def __init__(self, level, children=None, leaf=None):
|
|
153
147
|
self.level = level
|
|
@@ -177,9 +171,7 @@ def merge_tree(adjacency, energies, levels):
|
|
|
177
171
|
if not mask.any():
|
|
178
172
|
continue
|
|
179
173
|
sub = np.where(mask)[0]
|
|
180
|
-
_, labels = connected_components(
|
|
181
|
-
adjacency[sub][:, sub], directed=False
|
|
182
|
-
)
|
|
174
|
+
_, labels = connected_components(adjacency[sub][:, sub], directed=False)
|
|
183
175
|
comps = {}
|
|
184
176
|
for local, g in enumerate(sub):
|
|
185
177
|
comps.setdefault(labels[local], []).append(g)
|
|
@@ -196,9 +188,7 @@ def merge_tree(adjacency, energies, levels):
|
|
|
196
188
|
if not merged:
|
|
197
189
|
node = leaves[extra[0]]
|
|
198
190
|
for m in extra[1:]:
|
|
199
|
-
node = Node(
|
|
200
|
-
max(energies[m], node.level), children=[node, leaves[m]]
|
|
201
|
-
)
|
|
191
|
+
node = Node(max(energies[m], node.level), children=[node, leaves[m]])
|
|
202
192
|
else:
|
|
203
193
|
node = merged[0] if len(merged) == 1 else Node(lev, children=merged)
|
|
204
194
|
for m in extra:
|
|
@@ -28,7 +28,7 @@ import matplotlib as mpl
|
|
|
28
28
|
import matplotlib.pyplot as plt
|
|
29
29
|
import numpy as np
|
|
30
30
|
|
|
31
|
-
from chemparseplot.plot.theme import
|
|
31
|
+
from chemparseplot.plot.theme import RUHI_THEME
|
|
32
32
|
|
|
33
33
|
ARM_MARKERS = ["o", "^", "s", "P", "X"]
|
|
34
34
|
|
|
@@ -170,9 +170,16 @@ def run_sketchmap(
|
|
|
170
170
|
for line in lm_raw.splitlines()
|
|
171
171
|
if line.strip() and not line.startswith("#")
|
|
172
172
|
]
|
|
173
|
-
idx =
|
|
173
|
+
idx = [int(r[0]) for r in rows]
|
|
174
|
+
# Guarantee the pinned rows: dimlandmark's -ifirst only seeds the greedy
|
|
175
|
+
# walk, and farthest-point sampling skips anything near an already-kept
|
|
176
|
+
# point, which is exactly where a pinned reference's twin sits.
|
|
177
|
+
selected = [i for i in idx if i >= n_pinned]
|
|
178
|
+
keep = list(range(n_pinned)) + selected
|
|
179
|
+
keep = keep[: max(n_landmark, n_pinned)]
|
|
180
|
+
idx = np.array(keep)
|
|
174
181
|
(workdir / "landmarks.dat").write_text(
|
|
175
|
-
"\n".join(" ".join(
|
|
182
|
+
"\n".join(" ".join(f"{v:.8f}" for v in matrix[i]) for i in keep) + "\n"
|
|
176
183
|
)
|
|
177
184
|
proj = subprocess.run( # noqa: S603
|
|
178
185
|
[
|
|
@@ -232,14 +239,12 @@ def plot_landscape_surface(ax, coords, energies, cmap=None, levels=20):
|
|
|
232
239
|
np.linspace(coords[:, 0].min(), coords[:, 0].max(), 240),
|
|
233
240
|
np.linspace(coords[:, 1].min(), coords[:, 1].max(), 240),
|
|
234
241
|
)
|
|
235
|
-
surface = griddata(
|
|
236
|
-
coords, np.clip(energies, vmin, vmax), (gx, gy), method="linear"
|
|
237
|
-
)
|
|
242
|
+
surface = griddata(coords, np.clip(energies, vmin, vmax), (gx, gy), method="linear")
|
|
238
243
|
lv = np.linspace(vmin, vmax, levels)
|
|
239
|
-
cf = ax.contourf(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
244
|
+
cf = ax.contourf(
|
|
245
|
+
gx, gy, surface, levels=lv, cmap=cmap, vmin=vmin, vmax=vmax, alpha=0.85
|
|
246
|
+
)
|
|
247
|
+
ax.contour(gx, gy, surface, levels=lv, colors="black", linewidths=0.3, alpha=0.45)
|
|
243
248
|
ax.set_facecolor(RUHI_THEME.gridcolor)
|
|
244
249
|
return cf
|
|
245
250
|
|
|
@@ -250,8 +255,14 @@ def margin_inset_slots(n):
|
|
|
250
255
|
```{versionadded} 1.10.0
|
|
251
256
|
```
|
|
252
257
|
"""
|
|
253
|
-
slots = [
|
|
254
|
-
|
|
258
|
+
slots = [
|
|
259
|
+
(-0.26, 0.82),
|
|
260
|
+
(-0.26, 0.22),
|
|
261
|
+
(1.30, 0.82),
|
|
262
|
+
(1.30, 0.22),
|
|
263
|
+
(-0.26, 0.52),
|
|
264
|
+
(1.30, 0.52),
|
|
265
|
+
]
|
|
255
266
|
return slots[:n]
|
|
256
267
|
|
|
257
268
|
|
|
@@ -296,8 +307,16 @@ def place_margin_insets(fig, ax, entries, zoom=0.14, renderer="xyzrender"):
|
|
|
296
307
|
renderer=renderer,
|
|
297
308
|
xyzrender_config="paton",
|
|
298
309
|
)
|
|
299
|
-
ax.scatter(
|
|
300
|
-
|
|
310
|
+
ax.scatter(
|
|
311
|
+
[x],
|
|
312
|
+
[y],
|
|
313
|
+
marker="o",
|
|
314
|
+
s=120,
|
|
315
|
+
facecolors="none",
|
|
316
|
+
edgecolors="black",
|
|
317
|
+
linewidths=1.4,
|
|
318
|
+
zorder=7,
|
|
319
|
+
)
|
|
301
320
|
if entry.get("label"):
|
|
302
321
|
ax.annotate(
|
|
303
322
|
entry["label"],
|
|
@@ -560,6 +560,7 @@ def plot_structure_strip(
|
|
|
560
560
|
max_display_height_px: float | None = None,
|
|
561
561
|
width_fill_fraction=0.82,
|
|
562
562
|
prefer_single_row: bool = True,
|
|
563
|
+
label_ax=None,
|
|
563
564
|
) -> Any:
|
|
564
565
|
"""Renders a horizontal gallery of atomic structures.
|
|
565
566
|
|
|
@@ -580,6 +581,10 @@ def plot_structure_strip(
|
|
|
580
581
|
If True (default), pack up to 16 structures in one row. If False,
|
|
581
582
|
honour ``max_cols`` so e.g. 12 images with ``max_cols=6`` become two
|
|
582
583
|
rows of larger molecules.
|
|
584
|
+
label_ax : matplotlib.axes.Axes or None
|
|
585
|
+
Optional caption-only axis. When supplied, structure images use the
|
|
586
|
+
full height of ``ax`` and labels are placed in ``label_ax`` at matching
|
|
587
|
+
normalized column and row positions.
|
|
583
588
|
|
|
584
589
|
```{versionadded} 0.1.0
|
|
585
590
|
```
|
|
@@ -601,6 +606,8 @@ def plot_structure_strip(
|
|
|
601
606
|
labels = []
|
|
602
607
|
|
|
603
608
|
ax.axis("off")
|
|
609
|
+
if label_ax is not None:
|
|
610
|
+
label_ax.axis("off")
|
|
604
611
|
n_plot = len(atoms_list)
|
|
605
612
|
# Single-row packing is optional: landscape strips with many images often
|
|
606
613
|
# want max_cols=6 → two rows of larger structures.
|
|
@@ -623,13 +630,26 @@ def plot_structure_strip(
|
|
|
623
630
|
ax.set_xlim(0, ax_w_px)
|
|
624
631
|
ax.set_ylim(0, ax_h_px)
|
|
625
632
|
|
|
633
|
+
label_w_px = 0
|
|
634
|
+
label_h_px = 0
|
|
635
|
+
if label_ax is not None:
|
|
636
|
+
label_bbox = label_ax.get_window_extent(mpl_renderer)
|
|
637
|
+
label_w_px = max(1, int(round(label_bbox.width)))
|
|
638
|
+
label_h_px = max(1, int(round(label_bbox.height)))
|
|
639
|
+
label_ax.set_xlim(0, label_w_px)
|
|
640
|
+
label_ax.set_ylim(0, label_h_px)
|
|
641
|
+
|
|
626
642
|
per_col_px = ax_w_px / max(n_cols, 1)
|
|
627
643
|
# The band must hold the tallest label: multiline labels previously
|
|
628
644
|
# overflowed a fixed 1.8-line reservation into the structure images.
|
|
629
|
-
max_label_lines =
|
|
630
|
-
(str(lbl).count("\n") + 1 for lbl in labels), default=1
|
|
631
|
-
)
|
|
632
|
-
label_band_px =
|
|
645
|
+
max_label_lines = (
|
|
646
|
+
max((str(lbl).count("\n") + 1 for lbl in labels), default=1) if labels else 1
|
|
647
|
+
)
|
|
648
|
+
label_band_px = (
|
|
649
|
+
0.0
|
|
650
|
+
if label_ax is not None
|
|
651
|
+
else label_fontsize * fig.dpi / 72 * (0.9 + 1.35 * max_label_lines)
|
|
652
|
+
)
|
|
633
653
|
top_padding_px = 6.0
|
|
634
654
|
bottom_padding_px = 6.0
|
|
635
655
|
row_gap_px = 10.0
|
|
@@ -689,16 +709,25 @@ def plot_structure_strip(
|
|
|
689
709
|
_alpha_blit_rgba(canvas, resized, x0, y0)
|
|
690
710
|
|
|
691
711
|
if labels and i < len(labels):
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
712
|
+
text_ax = label_ax if label_ax is not None else ax
|
|
713
|
+
if label_ax is not None:
|
|
714
|
+
label_slot_width = label_w_px / max(n_cols, 1)
|
|
715
|
+
label_row_height = label_h_px / max(n_rows, 1)
|
|
716
|
+
label_x = (col + 0.5) * label_slot_width
|
|
717
|
+
label_y = label_h_px - (row + 0.5) * label_row_height
|
|
718
|
+
label_va = "center"
|
|
719
|
+
else:
|
|
720
|
+
# Each row owns its caption baseline so column mates cannot
|
|
721
|
+
# occupy the same text position.
|
|
722
|
+
label_x = slot_center_x
|
|
723
|
+
label_y = max(2.0, slot_bottom_y + 3.0)
|
|
724
|
+
label_va = "bottom"
|
|
725
|
+
text_ax.text(
|
|
726
|
+
x=label_x,
|
|
727
|
+
y=label_y,
|
|
699
728
|
s=labels[i],
|
|
700
729
|
ha="center",
|
|
701
|
-
va=
|
|
730
|
+
va=label_va,
|
|
702
731
|
fontsize=label_fontsize,
|
|
703
732
|
color=theme_color,
|
|
704
733
|
fontweight="bold",
|
|
@@ -211,6 +211,7 @@ ban-relative-imports = "all"
|
|
|
211
211
|
"PLW2901",
|
|
212
212
|
]
|
|
213
213
|
"chemparseplot/plot/geomscan.py" = ["PLC0415"]
|
|
214
|
+
"chemparseplot/plot/landscape.py" = ["PLC0415"] # optional plotting deps stay lazy
|
|
214
215
|
"chemparseplot/units.py" = [
|
|
215
216
|
"F822", # lazy Q_/ureg via __getattr__
|
|
216
217
|
"EM102",
|
|
@@ -48,9 +48,7 @@ def test_cn_matrix_species_restriction():
|
|
|
48
48
|
pos = np.zeros((4, 3))
|
|
49
49
|
pos[:, 0] = [0.0, 1.0, 5.0, 6.0]
|
|
50
50
|
full = cn_matrix([pos.ravel()], ["O", "H", "O", "H"], cutoff=1.5)
|
|
51
|
-
only_o = cn_matrix(
|
|
52
|
-
[pos.ravel()], ["O", "H", "O", "H"], cutoff=1.5, cn_species="O"
|
|
53
|
-
)
|
|
51
|
+
only_o = cn_matrix([pos.ravel()], ["O", "H", "O", "H"], cutoff=1.5, cn_species="O")
|
|
54
52
|
assert full.shape[1] == 4
|
|
55
53
|
assert only_o.shape[1] == 2
|
|
56
54
|
|
|
@@ -106,9 +104,7 @@ def test_merge_tree_deep_chain_no_recursion():
|
|
|
106
104
|
|
|
107
105
|
def test_spectral_basins_shapes():
|
|
108
106
|
rng = np.random.default_rng(7)
|
|
109
|
-
m = np.vstack(
|
|
110
|
-
[rng.normal(0, 0.1, size=(10, 2)), rng.normal(5, 0.1, size=(10, 2))]
|
|
111
|
-
)
|
|
107
|
+
m = np.vstack([rng.normal(0, 0.1, size=(10, 2)), rng.normal(5, 0.1, size=(10, 2))])
|
|
112
108
|
adjacency = knn_graph(m, k=3)
|
|
113
109
|
assign = spectral_basins(adjacency, 2, rng, matrix=m)
|
|
114
110
|
assert assign.shape == (20,)
|
|
@@ -119,12 +115,8 @@ def test_spectral_basins_shapes():
|
|
|
119
115
|
|
|
120
116
|
def test_plot_disconnectivity_end_to_end():
|
|
121
117
|
rng = np.random.default_rng(9)
|
|
122
|
-
m = np.vstack(
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
es = np.concatenate(
|
|
126
|
-
[rng.uniform(-10, -8, size=15), rng.uniform(-9, -7, size=15)]
|
|
127
|
-
)
|
|
118
|
+
m = np.vstack([rng.normal(0, 0.2, size=(15, 3)), rng.normal(4, 0.2, size=(15, 3))])
|
|
119
|
+
es = np.concatenate([rng.uniform(-10, -8, size=15), rng.uniform(-9, -7, size=15)])
|
|
128
120
|
fig, ax = plt.subplots()
|
|
129
121
|
reps, root = plot_disconnectivity(ax, m, es, dedup_tol=0.01, knn=3)
|
|
130
122
|
assert len(reps) <= 30
|
|
@@ -197,6 +197,43 @@ class TestPlotStructureStrip:
|
|
|
197
197
|
assert len(ax.artists) == 0
|
|
198
198
|
plt.close(fig)
|
|
199
199
|
|
|
200
|
+
def test_separate_label_axis_keeps_text_out_of_image_band(
|
|
201
|
+
self, monkeypatch, small_molecules
|
|
202
|
+
):
|
|
203
|
+
fig = plt.figure(figsize=(6, 2.4))
|
|
204
|
+
grid = fig.add_gridspec(2, 1, height_ratios=(4, 1), hspace=0.08)
|
|
205
|
+
image_ax = fig.add_subplot(grid[0])
|
|
206
|
+
label_ax = fig.add_subplot(grid[1])
|
|
207
|
+
|
|
208
|
+
def _fake_render(*_args, **_kwargs):
|
|
209
|
+
img = np.zeros((60, 80, 4), dtype=np.float32)
|
|
210
|
+
img[10:50, 20:60, :3] = 1.0
|
|
211
|
+
img[10:50, 20:60, 3] = 1.0
|
|
212
|
+
return img
|
|
213
|
+
|
|
214
|
+
labels = ["Cage\n0.00 eV", "Prism\n0.10 eV", "Book\n0.20 eV"]
|
|
215
|
+
monkeypatch.setattr("chemparseplot.plot.neb._render_atoms", _fake_render)
|
|
216
|
+
plot_structure_strip(
|
|
217
|
+
image_ax,
|
|
218
|
+
small_molecules,
|
|
219
|
+
labels,
|
|
220
|
+
label_ax=label_ax,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
fig.canvas.draw()
|
|
224
|
+
mpl_renderer = fig.canvas.get_renderer()
|
|
225
|
+
image_bbox = image_ax.get_window_extent(mpl_renderer)
|
|
226
|
+
label_bbox = label_ax.get_window_extent(mpl_renderer)
|
|
227
|
+
|
|
228
|
+
assert len(image_ax.texts) == 0
|
|
229
|
+
assert [text.get_text() for text in label_ax.texts] == labels
|
|
230
|
+
for text in label_ax.texts:
|
|
231
|
+
text_bbox = text.get_window_extent(mpl_renderer)
|
|
232
|
+
assert label_bbox.contains(text_bbox.x0, text_bbox.y0)
|
|
233
|
+
assert label_bbox.contains(text_bbox.x1, text_bbox.y1)
|
|
234
|
+
assert text_bbox.y1 <= image_bbox.y0
|
|
235
|
+
plt.close(fig)
|
|
236
|
+
|
|
200
237
|
|
|
201
238
|
class TestPlotStructureInset:
|
|
202
239
|
def test_basic_inset(self, h2o):
|
|
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
|
|
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
|