sqq 0.1.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.
sqq-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,212 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqq
3
+ Version: 0.1.0
4
+ Summary: Shell Quant Qualifier for water-shell topology analysis in MD trajectories.
5
+ Author-email: pimooni <pimooni@gmail.com>
6
+ Project-URL: Homepage, https://github.com/pimooni/sqq
7
+ Project-URL: Repository, https://github.com/pimooni/sqq
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: numpy
14
+ Requires-Dist: pandas
15
+ Requires-Dist: openpyxl
16
+ Requires-Dist: pyyaml
17
+ Requires-Dist: tqdm
18
+ Requires-Dist: MDAnalysis
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest; extra == "dev"
21
+ Requires-Dist: networkx; extra == "dev"
22
+
23
+ # SQQ
24
+
25
+ **SQQ (Shell Quant Qualifier)** is a Python program for identifying and quantifying water-shell topologies in molecular dynamics trajectories.
26
+
27
+ It analyzes water, ice, and hydrate-like structures from MD frames by building a water network, finding rings, cups (half-cages), closed cages, guest occupancy, F3/F4 order metrics, and ice-like waters.
28
+
29
+ ## Install
30
+
31
+ Development install from this directory:
32
+
33
+ ```powershell
34
+ pip install -e .
35
+ ```
36
+
37
+ After installation, use the unified command:
38
+
39
+ ```powershell
40
+ sqq init -o config.yaml
41
+ sqq analyze -i ./gro -c config.yaml -o ./result_sqq
42
+ ```
43
+
44
+ During local development without installation:
45
+
46
+ ```powershell
47
+ python -m sqq init -o config.yaml
48
+ python -m sqq analyze -i ./gro -c config.yaml -o ./result_sqq
49
+ ```
50
+
51
+ ## Quick Start
52
+
53
+ Single GRO file:
54
+
55
+ ```powershell
56
+ sqq analyze -i test1.gro -c config.yaml -o ./result_sqq
57
+ ```
58
+
59
+ Directory of GRO frames:
60
+
61
+ ```powershell
62
+ sqq analyze -i ./gro --pattern "*.gro" -c config.yaml -o ./result_sqq
63
+ ```
64
+
65
+ Parallel standalone GRO/XYZ frames:
66
+
67
+ ```powershell
68
+ sqq analyze -i ./gro --n-jobs 4 -c config.yaml -o ./result_sqq
69
+ ```
70
+
71
+ XTC/TRR trajectory with a topology/structure file:
72
+
73
+ ```powershell
74
+ sqq analyze -i traj.xtc --top topol.gro -c config.yaml -o ./result_sqq
75
+ ```
76
+
77
+ ## Important Defaults
78
+
79
+ ```yaml
80
+ input:
81
+ xtc_stride: 1
82
+
83
+ graph:
84
+ bond_mode: auto
85
+ oo_cutoff_nm: 0.35
86
+ hbond_distance_nm: 0.35
87
+ hbond_angle_deg: 30.0
88
+ pair_file: null
89
+ pair_id: resid
90
+
91
+ ring:
92
+ sizes: [5, 6]
93
+ primitive: true
94
+ chordless: true
95
+
96
+ cup:
97
+ mode: general
98
+ enabled: true
99
+ base_sizes: auto
100
+ side_sizes: auto
101
+
102
+ cage:
103
+ enabled: true
104
+ ring_sizes: [5, 6]
105
+ target_types: [512, 51262, 51263, 51264]
106
+ output_other: false
107
+ other_max_faces: 20
108
+ search_mode: grow
109
+ seed_mode: ring
110
+ occupancy_mode: polyhedron
111
+
112
+ order:
113
+ focus_waters: []
114
+
115
+ parallel:
116
+ n_jobs: auto
117
+
118
+ output:
119
+ write_gro: true
120
+ write_tsv: false
121
+ write_vmd: false
122
+ write_xlsx_summary: true
123
+ ```
124
+
125
+ Configuration priority:
126
+
127
+ ```text
128
+ built-in defaults < config.yaml < command-line options
129
+ ```
130
+
131
+ ## Output
132
+
133
+ SQQ writes HA-style per-frame folders plus global summaries:
134
+
135
+ ```text
136
+ result_sqq/
137
+ summary.xlsx
138
+ run_config.yaml
139
+ test1/
140
+ test1_info.md
141
+ test1_ring5.gro
142
+ test1_ring6.gro
143
+ test1_cup5_55555.gro
144
+ test1_512.gro
145
+ test1_512_empty.gro
146
+ test1_512_occupied.gro
147
+ test1_ice.gro
148
+ ```
149
+
150
+ Ring and cup outputs are free objects after cage ownership is removed. Cage GRO files include cage waters, CNT center atoms, and assigned guest molecules. Summary tables include per-cage-type empty/occupied/multi/guest columns and readable cage-isomer breakdowns.
151
+ Optional `*_membership.tsv` and `*_f3f4.tsv` files can be enabled with `output.write_tsv: true`.
152
+
153
+ ## Implemented Analysis
154
+
155
+ - GRO and XYZ input in the source tree; XTC/TRR input through MDAnalysis when installed.
156
+ - Orthorhombic minimum-image PBC.
157
+ - Water and guest selection by residue and atom names.
158
+ - Shared water graph for ring, cup, cage, F3/F4, and ice metrics.
159
+ - `bond_mode=auto/hbond/oo/pairs`; `pairs` reads a user-provided water-neighbor file.
160
+ - Non-recursive DFS ring search for primitive/chordless rings.
161
+ - Ring search supports 4/5/6/7-member rings, but defaults to `[5, 6]`.
162
+ - General cup search from base rings and side-ring closure; by default cup base/side ring sizes follow `ring.sizes`, so `[4, 5, 6]` searches 4/5/6 cups and `[4, 5, 6, 7]` searches 4/5/6/7 cups.
163
+ - Default cage search by ring-face grow mode from ring seeds: grow connected face patches along open boundary edges, then validate a closed polyhedron by edge degree and Euler characteristic. Cage search supports 4/5/6-member faces through `cage.ring_sizes`, but defaults to `[5, 6]`; 7-member faces are intentionally not used for cage detection.
164
+ - Optional `cage.output_other: true` adds Euler-compatible 4/5/6 unconventional cage targets up to `cage.other_max_faces`; the default only searches `512`, `51262`, `51263`, and `51264`.
165
+ - Optional `cage.search_mode=pair` for HA/GRADE-style cup-pair comparison only.
166
+ - Default guest occupancy by `cage.occupancy_mode=polyhedron`, using an oriented solid-angle point-in-polyhedron test; `center` and `auto` modes are available for comparison/fallback.
167
+ - Cage isomer labels describe adjacent 6-ring face patterns, such as `6adj`, `6chain3`, `6star3`, or `6tri3+single`, instead of opaque `iso01` names.
168
+ - VMD helper colors: `512` blue, `51262` green, `51263` orange, `51264` red; ring centers use R4 gray, R5 purple, R6 tan, and R7 black.
169
+ - F3/F4 order metrics using the shared water graph and the reference-script formulas, including optional `order.focus_waters` averages.
170
+ - CHILL-style ice output: total ice-like waters, ice-I-like waters, and interfacial/intermediate ice waters.
171
+ - Explicit frame-level parallel execution for independent `.gro`/`.xyz` files via `--n-jobs N`.
172
+
173
+ ## Main Parameters
174
+
175
+ | Parameter | Purpose |
176
+ | --- | --- |
177
+ | `water.resnames` | Water molecule residue names |
178
+ | `water.oxygen_names` | Oxygen atom names used as water graph nodes |
179
+ | `water.hydrogen_names` | Hydrogen atom names used for hydrogen-bond geometry |
180
+ | `guest.resnames` | Guest molecule residue names |
181
+ | `graph.bond_mode` | `auto`, `hbond`, `oo`, or `pairs` |
182
+ | `graph.pair_file` | Pair file used when `bond_mode=pairs`; each non-comment line has two ids |
183
+ | `graph.pair_id` | How pair ids are interpreted: `resid`, `oxygen_index`, or `atomid` |
184
+ | `ring.sizes` | Ring sizes to search; default `[5, 6]` |
185
+ | `cup.base_sizes`, `cup.side_sizes` | `auto` by default, meaning use `ring.sizes`; set explicit lists to restrict cup search |
186
+ | `cage.ring_sizes` | Ring-face sizes allowed in cage search; default `[5, 6]`, optional `[4, 5, 6]` |
187
+ | `cage.output_other` | Enable unconventional 4/5/6 cage targets; default `false` |
188
+ | `cage.other_max_faces` | Maximum face count for generated unconventional cage targets; default `20` |
189
+ | `cage.search_mode` | `grow` by default; `pair` for comparison/debugging |
190
+ | `cage.seed_mode` | `ring` by default for speed; `cup` starts grow from detected cups, `auto` uses cup seeds when cups exist and falls back to ring |
191
+ | `cage.occupancy_mode` | `polyhedron` by default; `center` and `auto` are also available |
192
+ | `input.xtc_stride` | Read every Nth XTC/TRR frame |
193
+ | `order.focus_waters` | Residue ids whose mean F3/F4 should also be reported |
194
+ | `parallel.n_jobs` / `--n-jobs` | Parallel worker count for independent `.gro`/`.xyz` files; default `auto` runs serially |
195
+ | `output.write_gro` | Write structure files for visualization |
196
+
197
+ ## Current Limits
198
+
199
+ - CHILL-style ice classification is implemented as a topology/coordination classifier; separate atomistic Ih/Ic stacking assignment can be refined later if needed.
200
+ - Only orthorhombic boxes are supported in the implemented PBC path.
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+
sqq-0.1.0/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # SQQ
2
+
3
+ **SQQ (Shell Quant Qualifier)** is a Python program for identifying and quantifying water-shell topologies in molecular dynamics trajectories.
4
+
5
+ It analyzes water, ice, and hydrate-like structures from MD frames by building a water network, finding rings, cups (half-cages), closed cages, guest occupancy, F3/F4 order metrics, and ice-like waters.
6
+
7
+ ## Install
8
+
9
+ Development install from this directory:
10
+
11
+ ```powershell
12
+ pip install -e .
13
+ ```
14
+
15
+ After installation, use the unified command:
16
+
17
+ ```powershell
18
+ sqq init -o config.yaml
19
+ sqq analyze -i ./gro -c config.yaml -o ./result_sqq
20
+ ```
21
+
22
+ During local development without installation:
23
+
24
+ ```powershell
25
+ python -m sqq init -o config.yaml
26
+ python -m sqq analyze -i ./gro -c config.yaml -o ./result_sqq
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ Single GRO file:
32
+
33
+ ```powershell
34
+ sqq analyze -i test1.gro -c config.yaml -o ./result_sqq
35
+ ```
36
+
37
+ Directory of GRO frames:
38
+
39
+ ```powershell
40
+ sqq analyze -i ./gro --pattern "*.gro" -c config.yaml -o ./result_sqq
41
+ ```
42
+
43
+ Parallel standalone GRO/XYZ frames:
44
+
45
+ ```powershell
46
+ sqq analyze -i ./gro --n-jobs 4 -c config.yaml -o ./result_sqq
47
+ ```
48
+
49
+ XTC/TRR trajectory with a topology/structure file:
50
+
51
+ ```powershell
52
+ sqq analyze -i traj.xtc --top topol.gro -c config.yaml -o ./result_sqq
53
+ ```
54
+
55
+ ## Important Defaults
56
+
57
+ ```yaml
58
+ input:
59
+ xtc_stride: 1
60
+
61
+ graph:
62
+ bond_mode: auto
63
+ oo_cutoff_nm: 0.35
64
+ hbond_distance_nm: 0.35
65
+ hbond_angle_deg: 30.0
66
+ pair_file: null
67
+ pair_id: resid
68
+
69
+ ring:
70
+ sizes: [5, 6]
71
+ primitive: true
72
+ chordless: true
73
+
74
+ cup:
75
+ mode: general
76
+ enabled: true
77
+ base_sizes: auto
78
+ side_sizes: auto
79
+
80
+ cage:
81
+ enabled: true
82
+ ring_sizes: [5, 6]
83
+ target_types: [512, 51262, 51263, 51264]
84
+ output_other: false
85
+ other_max_faces: 20
86
+ search_mode: grow
87
+ seed_mode: ring
88
+ occupancy_mode: polyhedron
89
+
90
+ order:
91
+ focus_waters: []
92
+
93
+ parallel:
94
+ n_jobs: auto
95
+
96
+ output:
97
+ write_gro: true
98
+ write_tsv: false
99
+ write_vmd: false
100
+ write_xlsx_summary: true
101
+ ```
102
+
103
+ Configuration priority:
104
+
105
+ ```text
106
+ built-in defaults < config.yaml < command-line options
107
+ ```
108
+
109
+ ## Output
110
+
111
+ SQQ writes HA-style per-frame folders plus global summaries:
112
+
113
+ ```text
114
+ result_sqq/
115
+ summary.xlsx
116
+ run_config.yaml
117
+ test1/
118
+ test1_info.md
119
+ test1_ring5.gro
120
+ test1_ring6.gro
121
+ test1_cup5_55555.gro
122
+ test1_512.gro
123
+ test1_512_empty.gro
124
+ test1_512_occupied.gro
125
+ test1_ice.gro
126
+ ```
127
+
128
+ Ring and cup outputs are free objects after cage ownership is removed. Cage GRO files include cage waters, CNT center atoms, and assigned guest molecules. Summary tables include per-cage-type empty/occupied/multi/guest columns and readable cage-isomer breakdowns.
129
+ Optional `*_membership.tsv` and `*_f3f4.tsv` files can be enabled with `output.write_tsv: true`.
130
+
131
+ ## Implemented Analysis
132
+
133
+ - GRO and XYZ input in the source tree; XTC/TRR input through MDAnalysis when installed.
134
+ - Orthorhombic minimum-image PBC.
135
+ - Water and guest selection by residue and atom names.
136
+ - Shared water graph for ring, cup, cage, F3/F4, and ice metrics.
137
+ - `bond_mode=auto/hbond/oo/pairs`; `pairs` reads a user-provided water-neighbor file.
138
+ - Non-recursive DFS ring search for primitive/chordless rings.
139
+ - Ring search supports 4/5/6/7-member rings, but defaults to `[5, 6]`.
140
+ - General cup search from base rings and side-ring closure; by default cup base/side ring sizes follow `ring.sizes`, so `[4, 5, 6]` searches 4/5/6 cups and `[4, 5, 6, 7]` searches 4/5/6/7 cups.
141
+ - Default cage search by ring-face grow mode from ring seeds: grow connected face patches along open boundary edges, then validate a closed polyhedron by edge degree and Euler characteristic. Cage search supports 4/5/6-member faces through `cage.ring_sizes`, but defaults to `[5, 6]`; 7-member faces are intentionally not used for cage detection.
142
+ - Optional `cage.output_other: true` adds Euler-compatible 4/5/6 unconventional cage targets up to `cage.other_max_faces`; the default only searches `512`, `51262`, `51263`, and `51264`.
143
+ - Optional `cage.search_mode=pair` for HA/GRADE-style cup-pair comparison only.
144
+ - Default guest occupancy by `cage.occupancy_mode=polyhedron`, using an oriented solid-angle point-in-polyhedron test; `center` and `auto` modes are available for comparison/fallback.
145
+ - Cage isomer labels describe adjacent 6-ring face patterns, such as `6adj`, `6chain3`, `6star3`, or `6tri3+single`, instead of opaque `iso01` names.
146
+ - VMD helper colors: `512` blue, `51262` green, `51263` orange, `51264` red; ring centers use R4 gray, R5 purple, R6 tan, and R7 black.
147
+ - F3/F4 order metrics using the shared water graph and the reference-script formulas, including optional `order.focus_waters` averages.
148
+ - CHILL-style ice output: total ice-like waters, ice-I-like waters, and interfacial/intermediate ice waters.
149
+ - Explicit frame-level parallel execution for independent `.gro`/`.xyz` files via `--n-jobs N`.
150
+
151
+ ## Main Parameters
152
+
153
+ | Parameter | Purpose |
154
+ | --- | --- |
155
+ | `water.resnames` | Water molecule residue names |
156
+ | `water.oxygen_names` | Oxygen atom names used as water graph nodes |
157
+ | `water.hydrogen_names` | Hydrogen atom names used for hydrogen-bond geometry |
158
+ | `guest.resnames` | Guest molecule residue names |
159
+ | `graph.bond_mode` | `auto`, `hbond`, `oo`, or `pairs` |
160
+ | `graph.pair_file` | Pair file used when `bond_mode=pairs`; each non-comment line has two ids |
161
+ | `graph.pair_id` | How pair ids are interpreted: `resid`, `oxygen_index`, or `atomid` |
162
+ | `ring.sizes` | Ring sizes to search; default `[5, 6]` |
163
+ | `cup.base_sizes`, `cup.side_sizes` | `auto` by default, meaning use `ring.sizes`; set explicit lists to restrict cup search |
164
+ | `cage.ring_sizes` | Ring-face sizes allowed in cage search; default `[5, 6]`, optional `[4, 5, 6]` |
165
+ | `cage.output_other` | Enable unconventional 4/5/6 cage targets; default `false` |
166
+ | `cage.other_max_faces` | Maximum face count for generated unconventional cage targets; default `20` |
167
+ | `cage.search_mode` | `grow` by default; `pair` for comparison/debugging |
168
+ | `cage.seed_mode` | `ring` by default for speed; `cup` starts grow from detected cups, `auto` uses cup seeds when cups exist and falls back to ring |
169
+ | `cage.occupancy_mode` | `polyhedron` by default; `center` and `auto` are also available |
170
+ | `input.xtc_stride` | Read every Nth XTC/TRR frame |
171
+ | `order.focus_waters` | Residue ids whose mean F3/F4 should also be reported |
172
+ | `parallel.n_jobs` / `--n-jobs` | Parallel worker count for independent `.gro`/`.xyz` files; default `auto` runs serially |
173
+ | `output.write_gro` | Write structure files for visualization |
174
+
175
+ ## Current Limits
176
+
177
+ - CHILL-style ice classification is implemented as a topology/coordination classifier; separate atomistic Ih/Ic stacking assignment can be refined later if needed.
178
+ - Only orthorhombic boxes are supported in the implemented PBC path.
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 77.0.3", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sqq"
7
+ version = "0.1.0"
8
+ description = "Shell Quant Qualifier for water-shell topology analysis in MD trajectories."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [
12
+ { name = "pimooni", email = "pimooni@gmail.com" },
13
+ ]
14
+ dependencies = [
15
+ "numpy",
16
+ "pandas",
17
+ "openpyxl",
18
+ "pyyaml",
19
+ "tqdm",
20
+ "MDAnalysis",
21
+ ]
22
+ classifiers = [
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3 :: Only",
25
+ "Operating System :: OS Independent",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ dev = ["pytest", "networkx"]
30
+
31
+ [project.scripts]
32
+ sqq = "sqq.cli:main"
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/pimooni/sqq"
36
+ Repository = "https://github.com/pimooni/sqq"
37
+
38
+ [tool.setuptools.package-data]
39
+ sqq = ["example/gro/*.gro"]
40
+
41
+ [tool.pytest.ini_options]
42
+ testpaths = ["tests"]
sqq-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """SQQ: Shell Quant Qualifier."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,8 @@
1
+ """Run SQQ with `python -m sqq`."""
2
+
3
+ from .cli import main
4
+
5
+
6
+ if __name__ == "__main__":
7
+ # Delegate to the same entry point used by the installed console script.
8
+ raise SystemExit(main())
sqq-0.1.0/sqq/cli.py ADDED
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ """Command-line interface for SQQ."""
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+ from .config import write_default_config
9
+ from .pipeline import analyze
10
+
11
+
12
+ HELP_BANNER = """
13
+ +----------------------------+
14
+ | Shell Quant Qualifier |
15
+ +----------------------------+
16
+
17
+ SQQ for MD water-shell topology analysis.
18
+ """.strip()
19
+
20
+
21
+ def build_parser() -> argparse.ArgumentParser:
22
+ """Create the two-command CLI: init and analyze."""
23
+ parser = argparse.ArgumentParser(
24
+ prog="sqq",
25
+ description=HELP_BANNER,
26
+ formatter_class=argparse.RawDescriptionHelpFormatter,
27
+ )
28
+ subparsers = parser.add_subparsers(dest="command", required=True)
29
+
30
+ init_parser = subparsers.add_parser("init", help="Write a default config.yaml file.")
31
+ init_parser.add_argument("-o", "--output", default="config.yaml", help="Output config path.")
32
+
33
+ analyze_parser = subparsers.add_parser("analyze", help="Analyze MD frames.")
34
+ analyze_parser.add_argument("-i", "--input", required=True, help="Input file or directory.")
35
+ analyze_parser.add_argument("--pattern", help="Input pattern when --input is a directory.")
36
+ analyze_parser.add_argument("--top", "--topology", dest="topology", help="Topology file for xtc/trr.")
37
+ analyze_parser.add_argument("-c", "--config", help="YAML config file.")
38
+ analyze_parser.add_argument("-o", "--output", default="result_sqq", help="Output directory.")
39
+ analyze_parser.add_argument("--recursive", action="store_true", help="Read input directory recursively.")
40
+ analyze_parser.add_argument("--pairs", help="Pair file for bond_mode=pairs; each line contains two water ids.")
41
+ analyze_parser.add_argument("--pair-id", choices=["resid", "oxygen_index", "atomid"], help="How ids in --pairs are interpreted.")
42
+ analyze_parser.add_argument("--n-jobs", default=None, help="Frame-level worker count for independent GRO/XYZ files; default auto runs serially.")
43
+ analyze_parser.add_argument("--strict", action="store_true", help="Stop on the first failed frame.")
44
+ analyze_parser.add_argument("--no-gro", action="store_true", help="Disable GRO structure output.")
45
+ analyze_parser.add_argument("--no-xlsx", action="store_true", help="Disable summary.xlsx output.")
46
+ return parser
47
+
48
+
49
+ def main(argv: list[str] | None = None) -> int:
50
+ """Dispatch the selected SQQ subcommand."""
51
+ args = build_parser().parse_args(argv)
52
+ if args.command == "init":
53
+ out = Path(args.output)
54
+ write_default_config(out)
55
+ print(f"Wrote default SQQ config: {out}")
56
+ return 0
57
+ if args.command == "analyze":
58
+ analyze(args)
59
+ return 0
60
+ raise AssertionError(f"Unhandled command: {args.command}")
61
+
62
+
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ """Configuration defaults and YAML/JSON loading."""
4
+
5
+ from copy import deepcopy
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ try:
11
+ import yaml
12
+ except ImportError: # pragma: no cover - exercised in minimal source-tree runs.
13
+ yaml = None
14
+
15
+
16
+ # Defaults are intentionally explicit so a run can be reproduced from
17
+ # run_config.yaml without relying on hidden command-line assumptions.
18
+ DEFAULT_CONFIG: dict[str, Any] = {
19
+ "input": {
20
+ "pattern": "*.gro",
21
+ "recursive": False,
22
+ "first_file_time_ps": 0.0,
23
+ "frame_time_step_ps": 100.0,
24
+ "xtc_stride": 1,
25
+ },
26
+ "water": {
27
+ "resnames": ["SOL", "TIP", "WAT", "HOH"],
28
+ "oxygen_names": ["OW", "O", "OH2"],
29
+ "hydrogen_names": ["HW1", "HW2", "H1", "H2", "HW", "HT1", "HT2"],
30
+ },
31
+ "guest": {
32
+ "resnames": ["CH4", "CO2", "MET", "ETH"],
33
+ "center_atoms": {"CH4": ["C"], "CO2": ["C"]},
34
+ "center_mode": "center_atom",
35
+ },
36
+ "graph": {
37
+ "bond_mode": "auto",
38
+ "oo_cutoff_nm": 0.35,
39
+ "hbond_distance_nm": 0.35,
40
+ "hbond_angle_deg": 30.0,
41
+ "pair_file": None,
42
+ "pair_id": "resid",
43
+ },
44
+ "pbc": {
45
+ "box_mode": "orthorhombic",
46
+ },
47
+ "ring": {
48
+ "sizes": [5, 6],
49
+ "primitive": True,
50
+ "chordless": True,
51
+ },
52
+ "cup": {
53
+ "mode": "general",
54
+ "enabled": True,
55
+ "base_sizes": "auto",
56
+ "side_sizes": "auto",
57
+ "max_combinations_per_base": 50000,
58
+ },
59
+ "cage": {
60
+ "ring_sizes": [5, 6],
61
+ "target_types": ["512", "51262", "51263", "51264"],
62
+ "output_other": False,
63
+ "other_max_faces": 20,
64
+ "enabled": True,
65
+ "search_mode": "grow",
66
+ "seed_mode": "ring",
67
+ "max_states_per_seed": 2000,
68
+ "max_total_states": 250000,
69
+ "occupancy_mode": "polyhedron",
70
+ "occupancy_radius_nm": 0.5,
71
+ },
72
+ "order": {
73
+ "f3f4_enabled": True,
74
+ "focus_waters": [],
75
+ },
76
+ "ice": {
77
+ "enabled": True,
78
+ "method": "chill",
79
+ "min_six_rings": 2,
80
+ "require_four_coord_neighbors": True,
81
+ },
82
+ "output": {
83
+ "write_gro": True,
84
+ "write_tsv": False,
85
+ "write_vmd": False,
86
+ "write_xlsx_summary": True,
87
+ "write_empty_files": False,
88
+ "gro_atom_mode": "full_water",
89
+ "center_resname": "CNT",
90
+ },
91
+ "parallel": {
92
+ "n_jobs": "auto",
93
+ },
94
+ "debug": {
95
+ "use_networkx_checks": False,
96
+ },
97
+ }
98
+
99
+
100
+ def load_config(path: Path | None) -> dict[str, Any]:
101
+ """Load a config file and merge it over built-in defaults."""
102
+ config = deepcopy(DEFAULT_CONFIG)
103
+ if path is None:
104
+ return config
105
+ with path.open("r", encoding="utf-8-sig") as handle:
106
+ text = handle.read()
107
+ if yaml is not None:
108
+ user_config = yaml.safe_load(text) or {}
109
+ else:
110
+ # Source-tree smoke tests can run without PyYAML; installed SQQ uses YAML.
111
+ try:
112
+ user_config = json.loads(text) if text.strip() else {}
113
+ except json.JSONDecodeError as exc:
114
+ raise RuntimeError("Reading YAML config files requires PyYAML. Install with `pip install -e .`.") from exc
115
+ if not isinstance(user_config, dict):
116
+ raise ValueError(f"Config file must contain a YAML mapping: {path}")
117
+ return merge_config(config, user_config)
118
+
119
+
120
+ def merge_config(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
121
+ """Recursively merge user configuration into defaults."""
122
+ for key, value in override.items():
123
+ if isinstance(value, dict) and isinstance(base.get(key), dict):
124
+ merge_config(base[key], value)
125
+ else:
126
+ base[key] = value
127
+ return base
128
+
129
+
130
+ def write_default_config(path: Path) -> None:
131
+ """Write the default configuration template."""
132
+ path.parent.mkdir(parents=True, exist_ok=True)
133
+ with path.open("w", encoding="utf-8", newline="\n") as handle:
134
+ dump_config(DEFAULT_CONFIG, handle)
135
+
136
+
137
+ def dump_config(config: dict[str, Any], handle) -> None:
138
+ """Write YAML when available, otherwise a JSON-compatible fallback."""
139
+ if yaml is not None:
140
+ yaml.safe_dump(config, handle, allow_unicode=True, sort_keys=False)
141
+ else:
142
+ json.dump(config, handle, ensure_ascii=False, indent=2)
143
+ handle.write("\n")
144
+
145
+
146
+
147
+
148
+
149
+
150
+
@@ -0,0 +1 @@
1
+ """Core SQQ topology algorithms."""