gpse 0.0.2__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.
- gpse/__init__.py +11 -0
- gpse/batch/__init__.py +3 -0
- gpse/batch/cli.py +72 -0
- gpse/batch/runner.py +206 -0
- gpse/cli.py +215 -0
- gpse/config/__init__.py +15 -0
- gpse/config/_topsis_config.py +205 -0
- gpse/config/constants.py +127 -0
- gpse/config/default.yaml +15 -0
- gpse/config/software.yaml +20 -0
- gpse/config/topsis.yaml +18 -0
- gpse/convert/__init__.py +11 -0
- gpse/convert/cli.py +65 -0
- gpse/convert/external.py +400 -0
- gpse/convert/genotype_matrix.py +349 -0
- gpse/convert/phenotype.py +409 -0
- gpse/convert/processor.py +815 -0
- gpse/convert/qc.py +424 -0
- gpse/convert/validators.py +181 -0
- gpse/convert/workflow.py +214 -0
- gpse/models/__init__.py +30 -0
- gpse/models/classification_model_optimizer.py +393 -0
- gpse/models/classification_models.py +8 -0
- gpse/models/model_optimizers.py +14 -0
- gpse/models/regression_model_optimizer.py +679 -0
- gpse/predict/__init__.py +5 -0
- gpse/predict/__main__.py +3 -0
- gpse/predict/cli.py +95 -0
- gpse/predict/core.py +315 -0
- gpse/tasks/__init__.py +12 -0
- gpse/tasks/classification.py +565 -0
- gpse/tools/__init__.py +1 -0
- gpse/tools/analyze_phenotypes.py +140 -0
- gpse/train/__init__.py +26 -0
- gpse/train/_cv_manager.py +234 -0
- gpse/train/_data_io.py +261 -0
- gpse/train/_ensemble.py +235 -0
- gpse/train/_feature_selection.py +287 -0
- gpse/train/_fold_training.py +304 -0
- gpse/train/_model_pipeline.py +125 -0
- gpse/train/_model_tools.py +97 -0
- gpse/train/_optimization.py +244 -0
- gpse/train/_pipeline.py +490 -0
- gpse/train/_repeat_training.py +665 -0
- gpse/train/_results.py +262 -0
- gpse/train/cli.py +408 -0
- gpse/train/predictor.py +305 -0
- gpse/train/stacking.py +230 -0
- gpse/train/topsis.py +214 -0
- gpse/train/workflow.py +416 -0
- gpse/utils/__init__.py +54 -0
- gpse/utils/cli_display.py +529 -0
- gpse/utils/configuration.py +192 -0
- gpse/utils/dependency_checker.py +280 -0
- gpse/utils/feature_manifest.py +63 -0
- gpse/utils/genomic_utils.py +812 -0
- gpse/utils/log_utils.py +569 -0
- gpse/utils/logo.py +235 -0
- gpse/utils/paralle.py +266 -0
- gpse/utils/print_utils.py +75 -0
- gpse/utils/snp_ids.py +61 -0
- gpse/utils/version.py +159 -0
- gpse-0.0.2.dist-info/METADATA +1123 -0
- gpse-0.0.2.dist-info/RECORD +67 -0
- gpse-0.0.2.dist-info/WHEEL +4 -0
- gpse-0.0.2.dist-info/entry_points.txt +3 -0
- gpse-0.0.2.dist-info/licenses/LICENSE +21 -0
gpse/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""gpse — Genomic Prediction with Stacking Ensemble for horticultural crops."""
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from importlib.metadata import version as _get_version
|
|
5
|
+
except ImportError:
|
|
6
|
+
from importlib_metadata import version as _get_version # type: ignore[no-redef]
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
__version__ = _get_version("gpse")
|
|
10
|
+
except Exception:
|
|
11
|
+
__version__ = "unknown"
|
gpse/batch/__init__.py
ADDED
gpse/batch/cli.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Command-line interface for ``gpse batch``."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
_EPILOG = """\
|
|
11
|
+
YAML config schema:
|
|
12
|
+
defaults:
|
|
13
|
+
<any gpse train option>: value # inherited by every trait
|
|
14
|
+
results_root: /path/to/output # per-trait output = <results_root>/<name>
|
|
15
|
+
traits:
|
|
16
|
+
- name: FT # required; becomes --target_trait
|
|
17
|
+
task_type: classification # any train option may be overridden
|
|
18
|
+
n_classes: 3
|
|
19
|
+
models: [rf_clf, xgboost_clf]
|
|
20
|
+
results_dir: /custom/output # optional; overrides results_root
|
|
21
|
+
enabled: false # optional; skip this trait
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main(
|
|
26
|
+
argv: list[str] | None = None,
|
|
27
|
+
*,
|
|
28
|
+
formatter_class=None,
|
|
29
|
+
prog: str | None = None,
|
|
30
|
+
help_action=None,
|
|
31
|
+
parents: list[argparse.ArgumentParser] | None = None,
|
|
32
|
+
) -> int:
|
|
33
|
+
"""Run the GPSE batch training CLI."""
|
|
34
|
+
raw_args = list(sys.argv[1:] if argv is None else argv)
|
|
35
|
+
parser = argparse.ArgumentParser(
|
|
36
|
+
prog=prog or "python -m gpse.batch.cli",
|
|
37
|
+
description="Train GPSE models for multiple traits from one YAML config.",
|
|
38
|
+
formatter_class=formatter_class or argparse.RawDescriptionHelpFormatter,
|
|
39
|
+
epilog=_EPILOG,
|
|
40
|
+
parents=parents or [],
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--config",
|
|
44
|
+
required=True,
|
|
45
|
+
help="Path to the YAML batch config file",
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--dry_run",
|
|
49
|
+
action="store_true",
|
|
50
|
+
help="Print the generated 'gpse train' commands without running them",
|
|
51
|
+
)
|
|
52
|
+
args = parser.parse_args(raw_args)
|
|
53
|
+
|
|
54
|
+
# Initialize the standard GPSE console logger so batch messages share the
|
|
55
|
+
# same "[HH:MM:SS] INFO ..." style as `gpse train`. Without this, batch
|
|
56
|
+
# logs fall back to whatever handler was installed at import time (e.g.
|
|
57
|
+
# the rich_color_ext box sink pulled in transitively by the logo).
|
|
58
|
+
from gpse.utils.log_utils import logger_init
|
|
59
|
+
|
|
60
|
+
logger_init(log_level=getattr(args, "log_level", "INFO"))
|
|
61
|
+
|
|
62
|
+
from gpse.batch.runner import run_batch
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
return run_batch(args.config, dry_run=args.dry_run)
|
|
66
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
67
|
+
print(f"[ERROR] {exc}", file=sys.stderr)
|
|
68
|
+
return 1
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
sys.exit(main())
|
gpse/batch/runner.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Batch Training Runner
|
|
5
|
+
=====================
|
|
6
|
+
Loads a YAML batch config and runs ``gpse train`` once per trait.
|
|
7
|
+
|
|
8
|
+
Config schema::
|
|
9
|
+
|
|
10
|
+
defaults:
|
|
11
|
+
<any gpse train option>: value # inherited by every trait
|
|
12
|
+
results_root: /path/to/output # per-trait output = <results_root>/<name>
|
|
13
|
+
traits:
|
|
14
|
+
- name: FT # required; becomes --target_trait
|
|
15
|
+
task_type: classification # any train option may be overridden
|
|
16
|
+
n_classes: 3
|
|
17
|
+
models: [rf_clf, xgboost_clf]
|
|
18
|
+
results_dir: /custom/output # overrides results_root
|
|
19
|
+
enabled: false # optional; skip this trait
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import traceback
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
import yaml
|
|
30
|
+
from loguru import logger as main_logger
|
|
31
|
+
|
|
32
|
+
# Keys consumed by the batch runner itself; everything else must be a
|
|
33
|
+
# ``gpse train`` long option and is passed through.
|
|
34
|
+
RESERVED_KEYS = {"name", "enabled", "results_root", "target_trait"}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _train_option_actions() -> dict[str, argparse.Action]:
|
|
38
|
+
"""Map every ``gpse train`` long option name to its argparse action."""
|
|
39
|
+
from gpse.train.workflow import _build_parser
|
|
40
|
+
|
|
41
|
+
parser = _build_parser()
|
|
42
|
+
actions: dict[str, argparse.Action] = {}
|
|
43
|
+
for action in parser._actions:
|
|
44
|
+
for option in action.option_strings:
|
|
45
|
+
if option.startswith("--"):
|
|
46
|
+
actions[option[2:]] = action
|
|
47
|
+
return actions
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def load_batch_config(config_path: str | Path) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
51
|
+
"""Load and validate the YAML batch config; return (defaults, traits)."""
|
|
52
|
+
config_path = Path(config_path)
|
|
53
|
+
if not config_path.exists():
|
|
54
|
+
raise FileNotFoundError(f"Batch config not found: {config_path}")
|
|
55
|
+
with open(config_path, encoding="utf-8") as handle:
|
|
56
|
+
data = yaml.safe_load(handle)
|
|
57
|
+
if not isinstance(data, dict):
|
|
58
|
+
raise ValueError(f"Batch config must be a YAML mapping: {config_path}")
|
|
59
|
+
|
|
60
|
+
defaults = data.get("defaults") or {}
|
|
61
|
+
if not isinstance(defaults, dict):
|
|
62
|
+
raise ValueError("'defaults' must be a mapping of gpse train options")
|
|
63
|
+
|
|
64
|
+
traits = data.get("traits")
|
|
65
|
+
if not isinstance(traits, list) or not traits:
|
|
66
|
+
raise ValueError("'traits' must be a non-empty list")
|
|
67
|
+
for index, trait in enumerate(traits, start=1):
|
|
68
|
+
if not isinstance(trait, dict):
|
|
69
|
+
raise ValueError(f"traits entry #{index} must be a mapping")
|
|
70
|
+
name = trait.get("name")
|
|
71
|
+
if not name or not isinstance(name, str):
|
|
72
|
+
raise ValueError(f"traits entry #{index} is missing a string 'name'")
|
|
73
|
+
return defaults, traits
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_trait_argv(
|
|
77
|
+
defaults: dict[str, Any],
|
|
78
|
+
trait: dict[str, Any],
|
|
79
|
+
actions: dict[str, argparse.Action] | None = None,
|
|
80
|
+
) -> list[str]:
|
|
81
|
+
"""Merge defaults with one trait entry and build a ``gpse train`` argv."""
|
|
82
|
+
actions = actions or _train_option_actions()
|
|
83
|
+
name = trait["name"]
|
|
84
|
+
|
|
85
|
+
if "target_trait" in defaults or "target_trait" in trait:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"trait '{name}': 'target_trait' is derived from 'name'; do not set it directly"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
merged = {**defaults, **{k: v for k, v in trait.items() if k not in RESERVED_KEYS}}
|
|
91
|
+
results_root = trait.get("results_root", defaults.get("results_root"))
|
|
92
|
+
merged.pop("results_root", None)
|
|
93
|
+
|
|
94
|
+
if not merged.get("results_dir"):
|
|
95
|
+
if not results_root:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"trait '{name}': define 'results_dir' or set 'results_root' in defaults"
|
|
98
|
+
)
|
|
99
|
+
merged["results_dir"] = str(Path(str(results_root)) / name)
|
|
100
|
+
merged["target_trait"] = name
|
|
101
|
+
|
|
102
|
+
argv: list[str] = []
|
|
103
|
+
for key, value in merged.items():
|
|
104
|
+
if value is None:
|
|
105
|
+
continue
|
|
106
|
+
action = actions.get(key)
|
|
107
|
+
if action is None:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"trait '{name}': unknown option '{key}' (not a 'gpse train' flag)"
|
|
110
|
+
)
|
|
111
|
+
flag = f"--{key}"
|
|
112
|
+
if isinstance(action, argparse.BooleanOptionalAction):
|
|
113
|
+
argv.append(flag if bool(value) else f"--no-{key}")
|
|
114
|
+
elif isinstance(action, argparse._StoreTrueAction):
|
|
115
|
+
if bool(value):
|
|
116
|
+
argv.append(flag)
|
|
117
|
+
elif action.nargs in ("+", "*"):
|
|
118
|
+
values = value if isinstance(value, (list, tuple)) else [value]
|
|
119
|
+
argv.append(flag)
|
|
120
|
+
argv.extend(str(item) for item in values)
|
|
121
|
+
else:
|
|
122
|
+
argv.append(flag)
|
|
123
|
+
argv.append(str(value))
|
|
124
|
+
return argv
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _format_train_command(argv: list[str]) -> str:
|
|
128
|
+
"""Render a ``gpse train`` argv as a multi-line copy-pasteable command.
|
|
129
|
+
|
|
130
|
+
Each flag is grouped with its values on its own line, joined with shell
|
|
131
|
+
line-continuations, so long paths stay intact instead of wrapping
|
|
132
|
+
mid-token at the terminal edge.
|
|
133
|
+
"""
|
|
134
|
+
groups: list[list[str]] = []
|
|
135
|
+
for token in argv:
|
|
136
|
+
if token.startswith("--") or not groups:
|
|
137
|
+
groups.append([token])
|
|
138
|
+
else:
|
|
139
|
+
groups[-1].append(token)
|
|
140
|
+
lines = ["gpse train \\"]
|
|
141
|
+
lines.extend(f" {' '.join(group)} \\" for group in groups[:-1])
|
|
142
|
+
lines.append(f" {' '.join(groups[-1])}")
|
|
143
|
+
return "\n".join(lines)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def run_batch(config_path: str | Path, dry_run: bool = False) -> int:
|
|
147
|
+
"""Run ``gpse train`` for every enabled trait; return 0 when all succeed."""
|
|
148
|
+
defaults, traits = load_batch_config(config_path)
|
|
149
|
+
actions = _train_option_actions()
|
|
150
|
+
|
|
151
|
+
runnable = [trait for trait in traits if trait.get("enabled", True)]
|
|
152
|
+
skipped = [trait["name"] for trait in traits if not trait.get("enabled", True)]
|
|
153
|
+
if skipped:
|
|
154
|
+
main_logger.info(f"Skipping disabled traits: {skipped}")
|
|
155
|
+
if not runnable:
|
|
156
|
+
main_logger.error("No enabled traits to run")
|
|
157
|
+
return 1
|
|
158
|
+
|
|
159
|
+
main_logger.info(f"Batch training: {len(runnable)} trait(s) from {config_path}")
|
|
160
|
+
outcomes: list[tuple[str, str]] = []
|
|
161
|
+
for index, trait in enumerate(runnable, start=1):
|
|
162
|
+
name = trait["name"]
|
|
163
|
+
try:
|
|
164
|
+
argv = build_trait_argv(defaults, trait, actions)
|
|
165
|
+
except ValueError as exc:
|
|
166
|
+
main_logger.error(str(exc))
|
|
167
|
+
outcomes.append((name, "config-error"))
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
if dry_run:
|
|
171
|
+
print(f"[{index}/{len(runnable)}] {name}")
|
|
172
|
+
print(_format_train_command(argv))
|
|
173
|
+
outcomes.append((name, "dry-run"))
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
main_logger.info("=" * 70)
|
|
177
|
+
main_logger.info(f"[{index}/{len(runnable)}] Training trait: {name}")
|
|
178
|
+
main_logger.info("=" * 70)
|
|
179
|
+
from gpse.train.cli import main as train_main
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
exit_code = train_main(argv, prog="gpse train")
|
|
183
|
+
except SystemExit as exc:
|
|
184
|
+
exit_code = exc.code if isinstance(exc.code, int) else 1
|
|
185
|
+
except Exception:
|
|
186
|
+
main_logger.error(f"Trait {name} failed with an unexpected error:")
|
|
187
|
+
main_logger.error(traceback.format_exc())
|
|
188
|
+
exit_code = 1
|
|
189
|
+
|
|
190
|
+
if exit_code == 130:
|
|
191
|
+
main_logger.warning("Batch interrupted by user (Ctrl+C).")
|
|
192
|
+
outcomes.append((name, "interrupted"))
|
|
193
|
+
_log_summary(outcomes)
|
|
194
|
+
return 130
|
|
195
|
+
outcomes.append((name, "ok" if exit_code == 0 else f"failed({exit_code})"))
|
|
196
|
+
|
|
197
|
+
_log_summary(outcomes)
|
|
198
|
+
return 0 if all(status in {"ok", "dry-run"} for _, status in outcomes) else 1
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _log_summary(outcomes: list[tuple[str, str]]) -> None:
|
|
202
|
+
main_logger.info("=" * 70)
|
|
203
|
+
main_logger.info("Batch summary:")
|
|
204
|
+
for name, status in outcomes:
|
|
205
|
+
main_logger.info(f" {name}: {status}")
|
|
206
|
+
main_logger.info("=" * 70)
|
gpse/cli.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
GPSE Command-Line Interface
|
|
5
|
+
|
|
6
|
+
This module is intentionally thin: it only defines argument parsers and routes
|
|
7
|
+
the execution flow. Training business logic lives in ``gpse.train``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
# Configure native thread pools before importing modules that may load
|
|
15
|
+
# numpy/scipy/sklearn. BLAS/MKL/OpenMP read these environment variables only
|
|
16
|
+
# when their pools are initialized, so this has to happen at the top.
|
|
17
|
+
_thread_pre_parser = argparse.ArgumentParser(add_help=False)
|
|
18
|
+
_thread_pre_parser.add_argument("--n_jobs", type=int, default=1)
|
|
19
|
+
_thread_pre_args, _ = _thread_pre_parser.parse_known_args()
|
|
20
|
+
_thread_n = str(_thread_pre_args.n_jobs)
|
|
21
|
+
for _env_var in (
|
|
22
|
+
"OMP_NUM_THREADS",
|
|
23
|
+
"MKL_NUM_THREADS",
|
|
24
|
+
"OPENBLAS_NUM_THREADS",
|
|
25
|
+
"NUMEXPR_NUM_THREADS",
|
|
26
|
+
"VECLIB_MAXIMUM_THREADS",
|
|
27
|
+
"BLIS_NUM_THREADS",
|
|
28
|
+
):
|
|
29
|
+
os.environ[_env_var] = _thread_n
|
|
30
|
+
del _thread_pre_parser, _thread_pre_args, _thread_n, _env_var
|
|
31
|
+
|
|
32
|
+
# CLI display helpers are imported after thread setup because they can
|
|
33
|
+
# eventually touch package modules that import numerical dependencies.
|
|
34
|
+
from gpse.utils.cli_display import (
|
|
35
|
+
ULTIMATE_QUESTION,
|
|
36
|
+
_build_root_parser,
|
|
37
|
+
_show_logo_for_command,
|
|
38
|
+
print_easter_egg,
|
|
39
|
+
show_gpse_logo,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Shared argparse action: prints the logo before root/subcommand help.
|
|
43
|
+
from gpse.config.constants import _LogoHelpAction
|
|
44
|
+
|
|
45
|
+
# Prefer rich-formatted help when available; keep argparse as fallback so the
|
|
46
|
+
# CLI still works in minimal environments.
|
|
47
|
+
try:
|
|
48
|
+
from rich_argparse import RichHelpFormatter
|
|
49
|
+
except ImportError:
|
|
50
|
+
RichHelpFormatter = argparse.HelpFormatter
|
|
51
|
+
|
|
52
|
+
# Console is optional. All user-facing error paths below have plain print()
|
|
53
|
+
# fallbacks for environments without rich.
|
|
54
|
+
try:
|
|
55
|
+
from rich.console import Console
|
|
56
|
+
_console = Console()
|
|
57
|
+
except ImportError:
|
|
58
|
+
_console = None
|
|
59
|
+
|
|
60
|
+
# Common root options are passed into subcommand parsers as parents. This keeps
|
|
61
|
+
# flags such as --version and --log-level consistent across gpse commands.
|
|
62
|
+
_common_parent = argparse.ArgumentParser(add_help=False)
|
|
63
|
+
_common_parent.add_argument(
|
|
64
|
+
"-v", "--version",
|
|
65
|
+
action="store_true",
|
|
66
|
+
help="Show version information and exit",
|
|
67
|
+
)
|
|
68
|
+
_common_parent.add_argument(
|
|
69
|
+
"-l", "--log-level",
|
|
70
|
+
default="INFO",
|
|
71
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
72
|
+
help="Set logging level (default: INFO)",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main(argv: list[str] | None = None) -> int:
|
|
77
|
+
"""
|
|
78
|
+
Main entry point for the GPSE CLI.
|
|
79
|
+
|
|
80
|
+
argv is injectable for tests and internal calls. When argv is None, the
|
|
81
|
+
function reads the real command-line arguments from sys.argv.
|
|
82
|
+
"""
|
|
83
|
+
# Normalize inputs to a list copy so downstream slicing and checks are
|
|
84
|
+
# stable even when tests pass tuples or other sequences.
|
|
85
|
+
raw_args = list(sys.argv[1:] if argv is None else argv)
|
|
86
|
+
|
|
87
|
+
# Build the lightweight root router. Detailed workflow arguments are
|
|
88
|
+
# defined inside gpse.convert.cli, gpse.train.cli, and gpse.predict.cli.
|
|
89
|
+
root_parser = _build_root_parser(
|
|
90
|
+
formatter_class=RichHelpFormatter,
|
|
91
|
+
parents=[_common_parent],
|
|
92
|
+
help_action=_LogoHelpAction,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# No subcommand was provided. Show the logo and root help, then fail with
|
|
96
|
+
# exit code 1 because this is an invalid invocation.
|
|
97
|
+
if not raw_args:
|
|
98
|
+
show_gpse_logo()
|
|
99
|
+
if _console is not None:
|
|
100
|
+
_console.print("\n[bold red][ERROR] No command provided. Use convert, train, predict, or batch.[/bold red]\n")
|
|
101
|
+
else:
|
|
102
|
+
print("\n[ERROR] No command provided. Use convert, train, predict, or batch.\n")
|
|
103
|
+
root_parser.print_help()
|
|
104
|
+
return 1
|
|
105
|
+
|
|
106
|
+
# Root help is handled by the root parser so the custom help action can
|
|
107
|
+
# print the logo before the help text.
|
|
108
|
+
if raw_args[0] in {"-h", "--help"}:
|
|
109
|
+
root_parser.parse_args(raw_args)
|
|
110
|
+
return 0
|
|
111
|
+
|
|
112
|
+
# Root version delegates to the train CLI because that module owns the
|
|
113
|
+
# current version display implementation.
|
|
114
|
+
if raw_args[0] in {"-v", "--version"}:
|
|
115
|
+
from gpse.train.cli import main as train_main
|
|
116
|
+
|
|
117
|
+
return train_main(["--version"], prog="gpse")
|
|
118
|
+
|
|
119
|
+
# Easter egg shortcuts are handled before subcommand dispatch, so commands
|
|
120
|
+
# like `gpse 42` do not get treated as unknown subcommands.
|
|
121
|
+
if len(raw_args) == 1 and raw_args[0] == "42":
|
|
122
|
+
print_easter_egg(show_question=False)
|
|
123
|
+
return 0
|
|
124
|
+
|
|
125
|
+
# Also accept the full question, ignoring case and trailing punctuation.
|
|
126
|
+
joined_args = " ".join(raw_args).strip().lower().rstrip("?!. ")
|
|
127
|
+
if joined_args == ULTIMATE_QUESTION:
|
|
128
|
+
print_easter_egg(show_question=True)
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
# Split root command from the arguments that belong to the selected
|
|
132
|
+
# workflow. The workflow modules receive only their own arguments.
|
|
133
|
+
command = raw_args[0]
|
|
134
|
+
command_args = raw_args[1:]
|
|
135
|
+
|
|
136
|
+
# Convert workflow: import lazily so gpse --help and gpse --version stay
|
|
137
|
+
# lightweight and do not load workflow dependencies unnecessarily.
|
|
138
|
+
if command == "convert":
|
|
139
|
+
# Display the logo before convert/predict help.
|
|
140
|
+
_show_logo_for_command(command_args)
|
|
141
|
+
|
|
142
|
+
from gpse.convert.cli import main as convert_main
|
|
143
|
+
|
|
144
|
+
# convert/predict do not own version rendering; route to the shared
|
|
145
|
+
# version implementation while preserving the command name in prog.
|
|
146
|
+
if command_args and command_args[0] in {"-v", "--version"}:
|
|
147
|
+
from gpse.train.cli import main as train_main
|
|
148
|
+
|
|
149
|
+
return train_main(["--version"], prog="gpse convert")
|
|
150
|
+
return convert_main(
|
|
151
|
+
command_args,
|
|
152
|
+
formatter_class=RichHelpFormatter,
|
|
153
|
+
prog="gpse convert",
|
|
154
|
+
help_action=_LogoHelpAction,
|
|
155
|
+
parents=[_common_parent],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Train workflow.
|
|
159
|
+
if command == "train":
|
|
160
|
+
# Display the logo before convert/predict help.
|
|
161
|
+
_show_logo_for_command(command_args)
|
|
162
|
+
|
|
163
|
+
from gpse.train.cli import main as train_main
|
|
164
|
+
|
|
165
|
+
return train_main(
|
|
166
|
+
command_args,
|
|
167
|
+
formatter_class=RichHelpFormatter,
|
|
168
|
+
prog="gpse train",
|
|
169
|
+
help_action=_LogoHelpAction,
|
|
170
|
+
parents=[_common_parent],
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Predict workflow.
|
|
174
|
+
if command == "predict":
|
|
175
|
+
# Display the logo before convert/predict help.
|
|
176
|
+
_show_logo_for_command(command_args)
|
|
177
|
+
|
|
178
|
+
from gpse.predict.cli import main as predict_main
|
|
179
|
+
|
|
180
|
+
if command_args and command_args[0] in {"-v", "--version"}:
|
|
181
|
+
from gpse.train.cli import main as train_main
|
|
182
|
+
|
|
183
|
+
return train_main(["--version"], prog="gpse predict")
|
|
184
|
+
return predict_main(
|
|
185
|
+
command_args,
|
|
186
|
+
formatter_class=RichHelpFormatter,
|
|
187
|
+
prog="gpse predict",
|
|
188
|
+
help_action=_LogoHelpAction,
|
|
189
|
+
parents=[_common_parent],
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Batch workflow: train many traits from one YAML config.
|
|
193
|
+
if command == "batch":
|
|
194
|
+
_show_logo_for_command(command_args)
|
|
195
|
+
|
|
196
|
+
from gpse.batch.cli import main as batch_main
|
|
197
|
+
|
|
198
|
+
if command_args and command_args[0] in {"-v", "--version"}:
|
|
199
|
+
from gpse.train.cli import main as train_main
|
|
200
|
+
|
|
201
|
+
return train_main(["--version"], prog="gpse batch")
|
|
202
|
+
return batch_main(
|
|
203
|
+
command_args,
|
|
204
|
+
formatter_class=RichHelpFormatter,
|
|
205
|
+
prog="gpse batch",
|
|
206
|
+
parents=[_common_parent],
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Any other first token is not a supported workflow command.
|
|
210
|
+
root_parser.error(f"Unknown command: {command}. Use train, convert, predict, or batch.")
|
|
211
|
+
return 2
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
if __name__ == "__main__":
|
|
215
|
+
sys.exit(main())
|
gpse/config/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""gpse.config — Configuration constants and model data classes."""
|
|
2
|
+
|
|
3
|
+
from .constants import (
|
|
4
|
+
ModelConstants,
|
|
5
|
+
ModelConfig,
|
|
6
|
+
ClassificationModelConfig,
|
|
7
|
+
NumpyEncoder,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ModelConstants",
|
|
12
|
+
"ModelConfig",
|
|
13
|
+
"ClassificationModelConfig",
|
|
14
|
+
"NumpyEncoder",
|
|
15
|
+
]
|