aegiss 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.
- aegiss/__init__.py +37 -0
- aegiss/_version.py +24 -0
- aegiss/active_space/__init__.py +21 -0
- aegiss/active_space/aegiss.py +446 -0
- aegiss/active_space/ao_projection.py +622 -0
- aegiss/active_space/entropy_screening.py +279 -0
- aegiss/cli.py +154 -0
- aegiss/config_dataclasses/__init__.py +22 -0
- aegiss/config_dataclasses/dmrg_config.py +159 -0
- aegiss/config_dataclasses/entropy_config.py +69 -0
- aegiss/config_dataclasses/output_config.py +58 -0
- aegiss/config_dataclasses/projection_config.py +170 -0
- aegiss/initial_guess/__init__.py +19 -0
- aegiss/initial_guess/scf.py +173 -0
- aegiss/localization/__init__.py +35 -0
- aegiss/localization/fnos.py +66 -0
- aegiss/localization/nos.py +77 -0
- aegiss/localization/orbital_localization.py +380 -0
- aegiss/py.typed +0 -0
- aegiss/utils/__init__.py +59 -0
- aegiss/utils/banner.py +48 -0
- aegiss/utils/dmrg_utils.py +274 -0
- aegiss/utils/dump.py +119 -0
- aegiss/utils/env.py +110 -0
- aegiss/utils/logging.py +91 -0
- aegiss/utils/mo_composition.py +353 -0
- aegiss/utils/mol_tagger/README.md +34 -0
- aegiss/utils/mol_tagger/__init__.py +93 -0
- aegiss/utils/mol_tagger/_ao_suggest.py +439 -0
- aegiss/utils/mol_tagger/_atoms.py +248 -0
- aegiss/utils/mol_tagger/_constants.py +136 -0
- aegiss/utils/mol_tagger/_renderer.py +172 -0
- aegiss/utils/mol_tagger/_window.py +647 -0
- aegiss/utils/orb_display.py +210 -0
- aegiss/utils/orbital_indices.py +244 -0
- aegiss/utils/plotting.py +981 -0
- aegiss/wavefunction/__init__.py +20 -0
- aegiss/wavefunction/dmrg_subprocess.py +289 -0
- aegiss/wavefunction/methods.py +102 -0
- aegiss-0.1.0.dist-info/METADATA +223 -0
- aegiss-0.1.0.dist-info/RECORD +45 -0
- aegiss-0.1.0.dist-info/WHEEL +4 -0
- aegiss-0.1.0.dist-info/entry_points.txt +2 -0
- aegiss-0.1.0.dist-info/licenses/LICENSE +201 -0
- aegiss-0.1.0.dist-info/licenses/NOTICE +6 -0
aegiss/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copyright 2026 Algorithmiq
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""AEGISS: Atomic Orbital and Entropy based Guided Inference for Space Selection."""
|
|
16
|
+
|
|
17
|
+
from aegiss._version import __version__
|
|
18
|
+
from aegiss.config_dataclasses import (
|
|
19
|
+
DMRGConfig,
|
|
20
|
+
EntropyConfig,
|
|
21
|
+
OutputConfig,
|
|
22
|
+
ProjectionConfig,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"DMRGConfig",
|
|
27
|
+
"EntropyConfig",
|
|
28
|
+
"OutputConfig",
|
|
29
|
+
"ProjectionConfig",
|
|
30
|
+
"__version__",
|
|
31
|
+
"active_space",
|
|
32
|
+
"config_dataclasses",
|
|
33
|
+
"initial_guess",
|
|
34
|
+
"localization",
|
|
35
|
+
"utils",
|
|
36
|
+
"wavefunction",
|
|
37
|
+
]
|
aegiss/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright 2026 Algorithmiq
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Active space selection methods: entropy screening and AO projection."""
|
|
16
|
+
|
|
17
|
+
from aegiss.active_space.aegiss import select_aegiss
|
|
18
|
+
from aegiss.active_space.ao_projection import select_by_ao_projection
|
|
19
|
+
from aegiss.active_space.entropy_screening import select_by_entropy
|
|
20
|
+
|
|
21
|
+
__all__ = ["select_aegiss", "select_by_ao_projection", "select_by_entropy"]
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
# Copyright 2026 Algorithmiq
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Full AEGISS active space selection procedure."""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import TYPE_CHECKING, Any
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
|
|
25
|
+
from aegiss.active_space.ao_projection import select_by_ao_projection
|
|
26
|
+
from aegiss.active_space.entropy_screening import (
|
|
27
|
+
select_by_entropy,
|
|
28
|
+
select_by_spin_entropy,
|
|
29
|
+
)
|
|
30
|
+
from aegiss.utils.dump import save_orbitals, save_orbitals_from_entropies
|
|
31
|
+
from aegiss.utils.orbital_indices import get_subspaces_from_cas
|
|
32
|
+
from aegiss.utils.plotting import (
|
|
33
|
+
_make_soe_plots,
|
|
34
|
+
plot_pie_contribution,
|
|
35
|
+
)
|
|
36
|
+
from aegiss.wavefunction.dmrg_subprocess import run_dmrg_subprocess
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from aegiss.config_dataclasses import (
|
|
40
|
+
DMRGConfig,
|
|
41
|
+
EntropyConfig,
|
|
42
|
+
OutputConfig,
|
|
43
|
+
ProjectionConfig,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
# A per-spin entropy window: (occupied, virtual) index/entropy pairs.
|
|
49
|
+
_OccVirt = tuple[list[tuple[int, float]], list[tuple[int, float]]]
|
|
50
|
+
# Entropy screening output: one window (spin-summed) or one window per spin
|
|
51
|
+
# channel (spin-resolved).
|
|
52
|
+
_Screened = _OccVirt | dict[str, _OccVirt]
|
|
53
|
+
# A restricted_space handed to select_by_ao_projection: a single occupied/virtual
|
|
54
|
+
# list (spin-summed subset), a whole (occ, virt) window, or a per-spin window.
|
|
55
|
+
_RestrictedSpace = list[tuple[int, float]] | _OccVirt | dict[str, _OccVirt]
|
|
56
|
+
# Maps a spin-channel name to its row in an unrestricted ``mo_coeff``.
|
|
57
|
+
_CHANNEL_SPIN = {"alpha": 0, "beta": 1}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _screen_by_entropy(
|
|
61
|
+
entropy_config: EntropyConfig,
|
|
62
|
+
active_orbs: np.ndarray,
|
|
63
|
+
virt_orbs: np.ndarray,
|
|
64
|
+
soe1: np.ndarray | None,
|
|
65
|
+
*,
|
|
66
|
+
spin_resolved: bool,
|
|
67
|
+
) -> _Screened:
|
|
68
|
+
"""Screen the DMRG active window by single-orbital entropy.
|
|
69
|
+
|
|
70
|
+
Dispatches to the spin-summed or spin-resolved screener according to
|
|
71
|
+
*spin_resolved* (from :meth:`DMRGConfig.spin_resolved`).
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
entropy_config (EntropyConfig): Screening fraction / ranking settings.
|
|
75
|
+
active_orbs (np.ndarray): Occupied indices of the DMRG active window.
|
|
76
|
+
virt_orbs (np.ndarray): Virtual indices of the DMRG active window.
|
|
77
|
+
soe1 (np.ndarray or None): Single-orbital entropies -- 1-D (spin-summed)
|
|
78
|
+
or ``(2, ncas)`` (spin-resolved).
|
|
79
|
+
spin_resolved (bool): Whether to screen the alpha/beta channels apart.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
screened (_Screened): A spin-summed ``(occ, virt)`` window, or a
|
|
83
|
+
spin-resolved ``{"alpha": (occ, virt), "beta": (occ, virt)}`` mapping.
|
|
84
|
+
"""
|
|
85
|
+
if spin_resolved:
|
|
86
|
+
return select_by_spin_entropy(
|
|
87
|
+
occ_orbs=active_orbs,
|
|
88
|
+
virt_orbs=virt_orbs,
|
|
89
|
+
soe1=soe1,
|
|
90
|
+
fraction=entropy_config.fraction,
|
|
91
|
+
fraction_type=entropy_config.fraction_type,
|
|
92
|
+
ranking=entropy_config.ranking,
|
|
93
|
+
)
|
|
94
|
+
return select_by_entropy(
|
|
95
|
+
occ_orbs=active_orbs,
|
|
96
|
+
virt_orbs=virt_orbs,
|
|
97
|
+
soe1=soe1,
|
|
98
|
+
ranking=entropy_config.ranking,
|
|
99
|
+
fraction=entropy_config.fraction,
|
|
100
|
+
fraction_type=entropy_config.fraction_type,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _screening_empty(screened: _Screened) -> bool:
|
|
105
|
+
"""Return ``True`` if no orbital survived entropy screening (any channel)."""
|
|
106
|
+
if isinstance(screened, dict):
|
|
107
|
+
return all(not occ and not virt for occ, virt in screened.values())
|
|
108
|
+
occ, virt = screened
|
|
109
|
+
return not occ and not virt
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _ao_projection_passes(
|
|
113
|
+
screened: _Screened,
|
|
114
|
+
ranking: str,
|
|
115
|
+
) -> list[tuple[str, _RestrictedSpace]]:
|
|
116
|
+
"""Build the ``(save_suffix, restricted_space)`` AO-projection passes.
|
|
117
|
+
|
|
118
|
+
For ``'global'`` ranking the occupied and virtual windows are projected in a
|
|
119
|
+
single pass; for ``'subset'`` they are projected separately. Spin-resolved
|
|
120
|
+
input keeps its ``{"alpha", "beta"}`` structure so each pass projects both
|
|
121
|
+
channels on their own window (each half wrapped as an ``(occ, virt)`` window
|
|
122
|
+
with the other half empty, to keep a uniform per-channel value type).
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
screened (_Screened): Entropy-screening output.
|
|
126
|
+
ranking (str): ``'global'`` or ``'subset'``.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
passes (list of tuple[str, _RestrictedSpace]): Save-path suffix and the
|
|
130
|
+
*restricted_space* to hand to :func:`select_by_ao_projection`.
|
|
131
|
+
"""
|
|
132
|
+
if ranking != "subset":
|
|
133
|
+
return [("", screened)]
|
|
134
|
+
|
|
135
|
+
passes: list[tuple[str, _RestrictedSpace]] = []
|
|
136
|
+
for pos, suffix in ((0, "_occ"), (1, "_virt")):
|
|
137
|
+
space: _RestrictedSpace
|
|
138
|
+
if isinstance(screened, dict):
|
|
139
|
+
space = {ch: _half_window(win, pos) for ch, win in screened.items()}
|
|
140
|
+
nonempty = any(win[pos] for win in screened.values())
|
|
141
|
+
else:
|
|
142
|
+
space = screened[pos]
|
|
143
|
+
nonempty = bool(space)
|
|
144
|
+
if nonempty:
|
|
145
|
+
passes.append((suffix, space))
|
|
146
|
+
return passes
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _half_window(window: _OccVirt, pos: int) -> _OccVirt:
|
|
150
|
+
"""Keep only the occupied (``pos=0``) or virtual (``pos=1``) half of a window."""
|
|
151
|
+
return (window[0], []) if pos == 0 else ([], window[1])
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _collect_active_indices(
|
|
155
|
+
mf: Any,
|
|
156
|
+
projection_config: ProjectionConfig,
|
|
157
|
+
passes: list[tuple[str, _RestrictedSpace]],
|
|
158
|
+
base_path: str | None,
|
|
159
|
+
) -> np.ndarray | dict[str, np.ndarray]:
|
|
160
|
+
"""Run the AO-projection passes and merge their selected indices.
|
|
161
|
+
|
|
162
|
+
Indices from different passes (e.g. the occupied and virtual subsets) are
|
|
163
|
+
unioned *within* each spin channel. For an unrestricted mean-field the alpha
|
|
164
|
+
and beta selections are returned as a split ``{"alpha", "beta"}`` mapping and
|
|
165
|
+
are deliberately **not** unioned across channels: index *k* refers to a
|
|
166
|
+
different MO in each spin channel, so a single spatial active space is not
|
|
167
|
+
defined here (the caller consumes the split directly).
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
mf (pyscf.scf.hf.SCF): Mean-field object.
|
|
171
|
+
projection_config (ProjectionConfig): AO-projection settings.
|
|
172
|
+
passes (list of tuple[str, _RestrictedSpace]): Output of
|
|
173
|
+
:func:`_ao_projection_passes`.
|
|
174
|
+
base_path (str or None): Plot path stem, or ``None`` for no plotting.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
active_indices (np.ndarray, or dict[str, np.ndarray]): Sorted spatial
|
|
178
|
+
indices (restricted), or a split alpha/beta mapping (unrestricted).
|
|
179
|
+
"""
|
|
180
|
+
channels: dict[str, set[int]] = {"alpha": set(), "beta": set()}
|
|
181
|
+
flat: set[int] = set()
|
|
182
|
+
is_split = False
|
|
183
|
+
for suffix, restricted_space in passes:
|
|
184
|
+
save_path = base_path + suffix if base_path else None
|
|
185
|
+
result = select_by_ao_projection(
|
|
186
|
+
mf,
|
|
187
|
+
ao_labels=projection_config.ao_labels,
|
|
188
|
+
contribution=projection_config.contribution,
|
|
189
|
+
projected_basis=projection_config.basis,
|
|
190
|
+
contribution_type=projection_config.contribution_type,
|
|
191
|
+
top_n=projection_config.top_n,
|
|
192
|
+
restricted_space=restricted_space,
|
|
193
|
+
save_path=save_path,
|
|
194
|
+
)
|
|
195
|
+
if isinstance(result, dict):
|
|
196
|
+
is_split = True
|
|
197
|
+
for channel, indices in result.items():
|
|
198
|
+
channels[channel].update(int(i) for i in indices)
|
|
199
|
+
else:
|
|
200
|
+
flat.update(int(i) for i in result)
|
|
201
|
+
|
|
202
|
+
if is_split:
|
|
203
|
+
return {
|
|
204
|
+
channel: np.asarray(sorted(indices))
|
|
205
|
+
for channel, indices in channels.items()
|
|
206
|
+
}
|
|
207
|
+
return np.asarray(sorted(flat))
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _maybe_plot_soe(
|
|
211
|
+
output_config: OutputConfig,
|
|
212
|
+
active_orbs: np.ndarray,
|
|
213
|
+
virt_orbs: np.ndarray,
|
|
214
|
+
soe1: np.ndarray | None,
|
|
215
|
+
) -> None:
|
|
216
|
+
"""Write the single-orbital-entropy plot when enabled and spin-summed.
|
|
217
|
+
|
|
218
|
+
Spin-resolved ``(2, ncas)`` entropies have no plotter yet, so that case is
|
|
219
|
+
skipped with a log note rather than raising.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
output_config (OutputConfig): Output settings (gates on ``save_plots``).
|
|
223
|
+
active_orbs (np.ndarray): Occupied indices of the DMRG active window.
|
|
224
|
+
virt_orbs (np.ndarray): Virtual indices of the DMRG active window.
|
|
225
|
+
soe1 (np.ndarray or None): Single-orbital entropies.
|
|
226
|
+
"""
|
|
227
|
+
if not output_config.save_plots or soe1 is None:
|
|
228
|
+
return
|
|
229
|
+
if np.ndim(soe1) != 1:
|
|
230
|
+
logger.info(
|
|
231
|
+
"Spin-resolved single-orbital entropies are not plotted; skipping "
|
|
232
|
+
"the SOE plot."
|
|
233
|
+
)
|
|
234
|
+
return
|
|
235
|
+
_make_soe_plots(
|
|
236
|
+
active_orbs,
|
|
237
|
+
virt_orbs,
|
|
238
|
+
soe1,
|
|
239
|
+
output_config.saving_folder,
|
|
240
|
+
output_config.saving_prefix,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _save_entropy_orbitals(
|
|
245
|
+
mol: Any,
|
|
246
|
+
mf: Any,
|
|
247
|
+
screened: _Screened,
|
|
248
|
+
output_config: OutputConfig,
|
|
249
|
+
) -> None:
|
|
250
|
+
"""Dump cube files for the entropy-screened orbitals of every window.
|
|
251
|
+
|
|
252
|
+
For an unrestricted reference each channel is saved from its own spin block
|
|
253
|
+
of ``mf.mo_coeff`` with a channel-tagged prefix.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
mol (pyscf.gto.Mole): Molecule.
|
|
257
|
+
mf (pyscf.scf.hf.SCF): Mean-field object.
|
|
258
|
+
screened (_Screened): Entropy-screening output.
|
|
259
|
+
output_config (OutputConfig): Output settings.
|
|
260
|
+
"""
|
|
261
|
+
mo_coeff = np.asarray(mf.mo_coeff)
|
|
262
|
+
if isinstance(screened, dict):
|
|
263
|
+
for channel, window in screened.items():
|
|
264
|
+
coeff = mo_coeff[_CHANNEL_SPIN[channel]]
|
|
265
|
+
for subspace in window:
|
|
266
|
+
save_orbitals_from_entropies(
|
|
267
|
+
mol,
|
|
268
|
+
coeff,
|
|
269
|
+
subspace,
|
|
270
|
+
filename_prefix=f"{output_config.saving_prefix}_entropy_{channel}",
|
|
271
|
+
folder=output_config.saving_folder,
|
|
272
|
+
)
|
|
273
|
+
return
|
|
274
|
+
for subspace in screened:
|
|
275
|
+
save_orbitals_from_entropies(
|
|
276
|
+
mol,
|
|
277
|
+
mo_coeff,
|
|
278
|
+
subspace,
|
|
279
|
+
filename_prefix=output_config.saving_prefix + "_entropy",
|
|
280
|
+
folder=output_config.saving_folder,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _save_active_orbitals(
|
|
285
|
+
mol: Any,
|
|
286
|
+
mf: Any,
|
|
287
|
+
active_indices: np.ndarray | dict[str, np.ndarray],
|
|
288
|
+
output_config: OutputConfig,
|
|
289
|
+
) -> None:
|
|
290
|
+
"""Dump cube files for the final AEGISS active orbitals.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
mol (pyscf.gto.Mole): Molecule.
|
|
294
|
+
mf (pyscf.scf.hf.SCF): Mean-field object.
|
|
295
|
+
active_indices (np.ndarray, or dict[str, np.ndarray]): Selected indices;
|
|
296
|
+
a split alpha/beta mapping for an unrestricted reference.
|
|
297
|
+
output_config (OutputConfig): Output settings.
|
|
298
|
+
"""
|
|
299
|
+
mo_coeff = np.asarray(mf.mo_coeff)
|
|
300
|
+
if isinstance(active_indices, dict):
|
|
301
|
+
for channel, indices in active_indices.items():
|
|
302
|
+
save_orbitals(
|
|
303
|
+
mol,
|
|
304
|
+
mo_coeff[_CHANNEL_SPIN[channel]],
|
|
305
|
+
indices,
|
|
306
|
+
filename_prefix=f"{output_config.saving_prefix}_AEGISS_{channel}",
|
|
307
|
+
folder=output_config.saving_folder,
|
|
308
|
+
)
|
|
309
|
+
return
|
|
310
|
+
save_orbitals(
|
|
311
|
+
mol,
|
|
312
|
+
mo_coeff,
|
|
313
|
+
active_indices,
|
|
314
|
+
filename_prefix=output_config.saving_prefix + "_AEGISS",
|
|
315
|
+
folder=output_config.saving_folder,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _plot_active(
|
|
320
|
+
mol: Any,
|
|
321
|
+
mf: Any,
|
|
322
|
+
active_indices: np.ndarray | dict[str, np.ndarray],
|
|
323
|
+
projection_config: ProjectionConfig,
|
|
324
|
+
output_config: OutputConfig,
|
|
325
|
+
) -> None:
|
|
326
|
+
"""Plot the AO pie contribution for the final active orbitals.
|
|
327
|
+
|
|
328
|
+
The pie plotter takes a single flat orbital list, so the split alpha/beta
|
|
329
|
+
selection of an unrestricted reference is skipped with a log note.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
mol (pyscf.gto.Mole): Molecule.
|
|
333
|
+
mf (pyscf.scf.hf.SCF): Mean-field object.
|
|
334
|
+
active_indices (np.ndarray, or dict[str, np.ndarray]): Selected indices.
|
|
335
|
+
projection_config (ProjectionConfig): AO-projection settings.
|
|
336
|
+
output_config (OutputConfig): Output settings.
|
|
337
|
+
"""
|
|
338
|
+
if isinstance(active_indices, dict):
|
|
339
|
+
logger.info(
|
|
340
|
+
"AO pie-contribution plotting is not available for the split "
|
|
341
|
+
"alpha/beta selection; skipping."
|
|
342
|
+
)
|
|
343
|
+
return
|
|
344
|
+
plot_pie_contribution(
|
|
345
|
+
mol,
|
|
346
|
+
mf,
|
|
347
|
+
ao_labels=projection_config.ao_labels,
|
|
348
|
+
labels_names=projection_config.ao_labels,
|
|
349
|
+
orbs=active_indices,
|
|
350
|
+
folder=output_config.saving_folder,
|
|
351
|
+
filename=output_config.saving_prefix + "_composition",
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def select_aegiss(
|
|
356
|
+
mol: Any,
|
|
357
|
+
mf: Any,
|
|
358
|
+
dmrg_config: DMRGConfig,
|
|
359
|
+
entropy_config: EntropyConfig,
|
|
360
|
+
projection_config: ProjectionConfig,
|
|
361
|
+
output_config: OutputConfig,
|
|
362
|
+
) -> np.ndarray | dict[str, np.ndarray]:
|
|
363
|
+
"""Run the full AEGISS active space selection pipeline.
|
|
364
|
+
|
|
365
|
+
Runs DMRG on the specified CAS, then filters the resulting orbitals
|
|
366
|
+
through entropy screening and AO projection. An orbital is included
|
|
367
|
+
in the final active space when it has been selected by *both* criteria.
|
|
368
|
+
Therefore, only highly entangled orbitals are kept. These orbitals
|
|
369
|
+
must also have significant contribution on the specified AOs.
|
|
370
|
+
|
|
371
|
+
For an unrestricted (UHF) mean-field the whole pipeline runs per spin
|
|
372
|
+
channel: when ``dmrg_config.spin_soe`` resolves to spin-resolved the alpha
|
|
373
|
+
and beta channels are screened on their own entropy windows, otherwise they
|
|
374
|
+
share one spin-summed window; either way the AO projection is done per spin.
|
|
375
|
+
The alpha and beta selections are returned split and are **not** reconciled
|
|
376
|
+
into a single spatial active space -- index *k* denotes a different MO in
|
|
377
|
+
each channel, so no common spatial index set is defined here.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
mol (pyscf.gto.Mole): Molecular geometry and basis set.
|
|
381
|
+
mf (pyscf.scf.hf.SCF): Converged mean-field object.
|
|
382
|
+
dmrg_config (DMRGConfig): Configuration for the DMRG calculation (CAS size, bond dimension, symmetry).
|
|
383
|
+
entropy_config (EntropyConfig): Configuration for the entropy-based screening step.
|
|
384
|
+
projection_config (ProjectionConfig): Configuration for the AO projection-based screening step.
|
|
385
|
+
output_config (OutputConfig): Configuration for output options (file saving, plots).
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
active_indices (np.ndarray of int, or dict[str, np.ndarray]): Sorted
|
|
389
|
+
orbital indices of the selected active space. A single array for a
|
|
390
|
+
restricted mean-field; a split ``{"alpha": ..., "beta": ...}``
|
|
391
|
+
mapping (kept separate, not reconciled) for an unrestricted one.
|
|
392
|
+
When no orbital passes the entropy screening, an empty flat
|
|
393
|
+
``np.ndarray`` is returned, also for an unrestricted reference.
|
|
394
|
+
"""
|
|
395
|
+
active_orbs, virt_orbs = get_subspaces_from_cas(
|
|
396
|
+
mf, ncas=dmrg_config.ncas, nelecas=dmrg_config.nelecas
|
|
397
|
+
)
|
|
398
|
+
spin_resolved = dmrg_config.spin_resolved()
|
|
399
|
+
_energy, soe1 = run_dmrg_subprocess(
|
|
400
|
+
mf,
|
|
401
|
+
ncas=dmrg_config.ncas,
|
|
402
|
+
nelecas=dmrg_config.nelecas,
|
|
403
|
+
symm_type=dmrg_config.symm_type,
|
|
404
|
+
max_bond_dim=dmrg_config.max_bond_dim,
|
|
405
|
+
verbosity=dmrg_config.verbosity,
|
|
406
|
+
unrestricted=dmrg_config.unrestricted,
|
|
407
|
+
reorder=dmrg_config.reorder,
|
|
408
|
+
spin_resolved=spin_resolved,
|
|
409
|
+
)
|
|
410
|
+
_maybe_plot_soe(output_config, active_orbs, virt_orbs, soe1)
|
|
411
|
+
|
|
412
|
+
screened = _screen_by_entropy(
|
|
413
|
+
entropy_config,
|
|
414
|
+
active_orbs,
|
|
415
|
+
virt_orbs,
|
|
416
|
+
soe1,
|
|
417
|
+
spin_resolved=spin_resolved,
|
|
418
|
+
)
|
|
419
|
+
if _screening_empty(screened):
|
|
420
|
+
logger.info(
|
|
421
|
+
"No orbitals passed the entropy screening step. Consider adjusting the "
|
|
422
|
+
"EntropyConfig.fraction or using 'global' ranking for a more inclusive selection."
|
|
423
|
+
)
|
|
424
|
+
return np.array([], dtype=int)
|
|
425
|
+
|
|
426
|
+
if output_config.save_orb:
|
|
427
|
+
_save_entropy_orbitals(mol, mf, screened, output_config)
|
|
428
|
+
|
|
429
|
+
base_path = (
|
|
430
|
+
str(Path(output_config.saving_folder) / output_config.saving_prefix)
|
|
431
|
+
if output_config.save_plots
|
|
432
|
+
else None
|
|
433
|
+
)
|
|
434
|
+
logger.info(
|
|
435
|
+
"Ranking orbitals for AO projection based on %s contribution.",
|
|
436
|
+
projection_config.ranking,
|
|
437
|
+
)
|
|
438
|
+
passes = _ao_projection_passes(screened, projection_config.ranking)
|
|
439
|
+
active_indices = _collect_active_indices(mf, projection_config, passes, base_path)
|
|
440
|
+
|
|
441
|
+
if output_config.save_orb:
|
|
442
|
+
_save_active_orbitals(mol, mf, active_indices, output_config)
|
|
443
|
+
if output_config.save_plots:
|
|
444
|
+
_plot_active(mol, mf, active_indices, projection_config, output_config)
|
|
445
|
+
|
|
446
|
+
return active_indices
|