ece4-exp 1.0.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.
- ece4_exp/__init__.py +0 -0
- ece4_exp/cli.py +289 -0
- ece4_exp/generate-experiment-config.py +448 -0
- ece4_exp/init_config.py +277 -0
- ece4_exp/paths.py +78 -0
- ece4_exp/save_recipe_from_diff.py +146 -0
- ece4_exp/validate-experiment-config.py +165 -0
- ece4_exp/yaml_util.py +246 -0
- ece4_exp-1.0.0.dist-info/LICENSE +21 -0
- ece4_exp-1.0.0.dist-info/METADATA +205 -0
- ece4_exp-1.0.0.dist-info/RECORD +14 -0
- ece4_exp-1.0.0.dist-info/WHEEL +5 -0
- ece4_exp-1.0.0.dist-info/entry_points.txt +2 -0
- ece4_exp-1.0.0.dist-info/top_level.txt +1 -0
ece4_exp/__init__.py
ADDED
|
File without changes
|
ece4_exp/cli.py
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
CLI entry point for ece4-exp.
|
|
4
|
+
|
|
5
|
+
This replaces the bash wrapper with a proper Python console_scripts entry point.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from . import paths
|
|
14
|
+
from .yaml_util import log_info, log_warn, log_error, COLOR_CYAN, COLOR_NC
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cmd_list(args):
|
|
18
|
+
"""List available recipes."""
|
|
19
|
+
print(f"{COLOR_CYAN}Available Recipes:{COLOR_NC}\n")
|
|
20
|
+
print("Main Recipes:")
|
|
21
|
+
|
|
22
|
+
if paths.RECIPES_DIR.exists():
|
|
23
|
+
recipes = sorted(paths.RECIPES_DIR.glob("*.yml")) + sorted(paths.RECIPES_DIR.glob("*.yaml"))
|
|
24
|
+
for recipe in recipes:
|
|
25
|
+
print(f" - {recipe.name}")
|
|
26
|
+
else:
|
|
27
|
+
log_warn(f"Recipes directory not found: {paths.RECIPES_DIR}")
|
|
28
|
+
|
|
29
|
+
weekly_tests = paths.RECIPES_DIR / "weekly_tests"
|
|
30
|
+
if weekly_tests.exists():
|
|
31
|
+
print("\nWeekly Tests:")
|
|
32
|
+
tests = sorted(weekly_tests.glob("*.yml")) + sorted(weekly_tests.glob("*.yaml"))
|
|
33
|
+
for test in tests:
|
|
34
|
+
print(f" - {test.name}")
|
|
35
|
+
|
|
36
|
+
print()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def cmd_info(args):
|
|
40
|
+
"""Show current configuration info."""
|
|
41
|
+
from .yaml_util import set_quiet_mode
|
|
42
|
+
|
|
43
|
+
if args.quiet:
|
|
44
|
+
set_quiet_mode(True)
|
|
45
|
+
|
|
46
|
+
# Get EXPID from environment or guess
|
|
47
|
+
expid = os.environ.get("EXPID", "unknown")
|
|
48
|
+
if expid == "unknown":
|
|
49
|
+
# Try to guess from path structure
|
|
50
|
+
cwd_parts = Path.cwd().parts
|
|
51
|
+
if len(cwd_parts) >= 3:
|
|
52
|
+
guess = cwd_parts[-3]
|
|
53
|
+
if len(guess) == 4 and guess.isalnum():
|
|
54
|
+
expid = guess
|
|
55
|
+
|
|
56
|
+
conf_path = os.environ.get("CONF_PATH", f"/esarchive/autosubmit/{expid}/conf")
|
|
57
|
+
expdef_file = Path(conf_path) / f"expdef_{expid}.yml"
|
|
58
|
+
jobs_file = Path(conf_path) / f"jobs_{expid}.yml"
|
|
59
|
+
|
|
60
|
+
# Import and run generate with --info flag
|
|
61
|
+
import importlib
|
|
62
|
+
gec = importlib.import_module("ece4_exp.generate-experiment-config")
|
|
63
|
+
|
|
64
|
+
sys.argv = [
|
|
65
|
+
"ece4-exp",
|
|
66
|
+
"--expdef", str(expdef_file),
|
|
67
|
+
"--jobs", str(jobs_file),
|
|
68
|
+
"--info"
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
if args.quiet:
|
|
72
|
+
sys.argv.append("--quiet")
|
|
73
|
+
|
|
74
|
+
gec.main()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def cmd_init_user(args):
|
|
78
|
+
"""Initialize user configuration."""
|
|
79
|
+
log_info("Initializing user configuration in ~/.config/ece4-exp")
|
|
80
|
+
|
|
81
|
+
from . import init_config
|
|
82
|
+
init_config.main()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def cmd_generate(args):
|
|
86
|
+
"""Generate experiment configuration."""
|
|
87
|
+
from .yaml_util import set_quiet_mode
|
|
88
|
+
|
|
89
|
+
if args.quiet:
|
|
90
|
+
set_quiet_mode(True)
|
|
91
|
+
os.environ["COLOR_NC"] = ""
|
|
92
|
+
os.environ["COLOR_GREEN"] = ""
|
|
93
|
+
os.environ["COLOR_CYAN"] = ""
|
|
94
|
+
os.environ["COLOR_YELLOW"] = ""
|
|
95
|
+
os.environ["COLOR_RED"] = ""
|
|
96
|
+
|
|
97
|
+
# Build sys.argv for the generate module
|
|
98
|
+
import importlib
|
|
99
|
+
gec = importlib.import_module("ece4_exp.generate-experiment-config")
|
|
100
|
+
|
|
101
|
+
# Get EXPID for default paths
|
|
102
|
+
expid = args.expid if hasattr(args, 'expid') and args.expid else os.environ.get("EXPID", "unknown")
|
|
103
|
+
conf_path = os.environ.get("CONF_PATH", f"/esarchive/autosubmit/{expid}/conf")
|
|
104
|
+
|
|
105
|
+
sys.argv = ["ece4-exp"]
|
|
106
|
+
|
|
107
|
+
# Add autosubmit files if they exist (backward compatibility)
|
|
108
|
+
expdef_file = Path(conf_path) / f"expdef_{expid}.yml"
|
|
109
|
+
jobs_file = Path(conf_path) / f"jobs_{expid}.yml"
|
|
110
|
+
|
|
111
|
+
if expdef_file.exists() and jobs_file.exists():
|
|
112
|
+
sys.argv.extend(["--expdef", str(expdef_file)])
|
|
113
|
+
sys.argv.extend(["--jobs", str(jobs_file)])
|
|
114
|
+
|
|
115
|
+
# Forward all args to generate module
|
|
116
|
+
if args.recipe:
|
|
117
|
+
sys.argv.extend(["--recipe", args.recipe])
|
|
118
|
+
if args.sim_procs:
|
|
119
|
+
sys.argv.extend(["--sim-procs", str(args.sim_procs)])
|
|
120
|
+
if args.expid:
|
|
121
|
+
sys.argv.extend(["--expid", args.expid])
|
|
122
|
+
if args.platform:
|
|
123
|
+
sys.argv.extend(["--platform", args.platform])
|
|
124
|
+
if args.launcher:
|
|
125
|
+
sys.argv.extend(["--launcher", args.launcher])
|
|
126
|
+
if args.kind:
|
|
127
|
+
sys.argv.extend(["--kind", args.kind])
|
|
128
|
+
if args.account:
|
|
129
|
+
sys.argv.extend(["--account", args.account])
|
|
130
|
+
if args.walltime:
|
|
131
|
+
sys.argv.extend(["--walltime", str(args.walltime)])
|
|
132
|
+
if args.description:
|
|
133
|
+
sys.argv.extend(["--description", args.description])
|
|
134
|
+
if args.repo_owner:
|
|
135
|
+
sys.argv.extend(["--repo-owner", args.repo_owner])
|
|
136
|
+
if args.repo_branch:
|
|
137
|
+
sys.argv.extend(["--repo-branch", args.repo_branch])
|
|
138
|
+
if args.output:
|
|
139
|
+
sys.argv.extend(["--output", args.output])
|
|
140
|
+
if args.dry_run:
|
|
141
|
+
sys.argv.append("--dry-run")
|
|
142
|
+
if args.quiet:
|
|
143
|
+
sys.argv.append("--quiet")
|
|
144
|
+
|
|
145
|
+
gec.main()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def cmd_validate(args):
|
|
149
|
+
"""Validate experiment configuration."""
|
|
150
|
+
import importlib
|
|
151
|
+
vec = importlib.import_module("ece4_exp.validate-experiment-config")
|
|
152
|
+
|
|
153
|
+
config_file = args.config_file
|
|
154
|
+
if not config_file:
|
|
155
|
+
# Default to EXPID_experiment.yml
|
|
156
|
+
expid = os.environ.get("EXPID", "unknown")
|
|
157
|
+
config_file = f"{expid}_experiment.yml"
|
|
158
|
+
|
|
159
|
+
if not Path(config_file).exists():
|
|
160
|
+
log_error(f"Configuration file not found: {config_file}")
|
|
161
|
+
sys.exit(1)
|
|
162
|
+
|
|
163
|
+
sys.argv = ["ece4-exp", config_file]
|
|
164
|
+
vec.main()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def cmd_save(args):
|
|
168
|
+
"""Save changes as a recipe."""
|
|
169
|
+
import importlib
|
|
170
|
+
srd = importlib.import_module("ece4_exp.save_recipe_from_diff")
|
|
171
|
+
|
|
172
|
+
# Get EXPID
|
|
173
|
+
expid = args.expid if args.expid else os.environ.get("EXPID", "unknown")
|
|
174
|
+
conf_path = os.environ.get("CONF_PATH", f"/esarchive/autosubmit/{expid}/conf")
|
|
175
|
+
expdef_file = Path(conf_path) / f"expdef_{expid}.yml"
|
|
176
|
+
|
|
177
|
+
# Output file
|
|
178
|
+
output_file = args.output if args.output else f"{expid}.yml"
|
|
179
|
+
|
|
180
|
+
log_info(f"Saving recipe: {COLOR_CYAN}{output_file}{COLOR_NC} (Expid: {expid})")
|
|
181
|
+
|
|
182
|
+
sys.argv = ["ece4-exp", "--expid", expid]
|
|
183
|
+
|
|
184
|
+
if expdef_file.exists():
|
|
185
|
+
sys.argv.extend(["--expdef", str(expdef_file)])
|
|
186
|
+
|
|
187
|
+
if args.recipe:
|
|
188
|
+
sys.argv.extend(["--recipe", args.recipe])
|
|
189
|
+
|
|
190
|
+
sys.argv.extend(["--output", output_file])
|
|
191
|
+
|
|
192
|
+
srd.main()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def main():
|
|
196
|
+
"""Main CLI entry point."""
|
|
197
|
+
parser = argparse.ArgumentParser(
|
|
198
|
+
prog="ece4-exp",
|
|
199
|
+
description="EC-Earth4 experiment configuration tool",
|
|
200
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
201
|
+
epilog="""
|
|
202
|
+
Examples:
|
|
203
|
+
ece4-exp list
|
|
204
|
+
ece4-exp generate --recipe gcm-sr.yml --sim-procs 1120 --expid test001
|
|
205
|
+
ece4-exp validate myexp.yml
|
|
206
|
+
ece4-exp save --expid myexp --recipe gcm-sr.yml -o my-recipe.yml
|
|
207
|
+
|
|
208
|
+
For more help: ece4-exp <command> --help
|
|
209
|
+
"""
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
213
|
+
|
|
214
|
+
# --- list ---
|
|
215
|
+
parser_list = subparsers.add_parser("list", help="List available recipes")
|
|
216
|
+
parser_list.set_defaults(func=cmd_list)
|
|
217
|
+
|
|
218
|
+
# --- info ---
|
|
219
|
+
parser_info = subparsers.add_parser("info", help="Show current configuration info")
|
|
220
|
+
parser_info.add_argument("--quiet", action="store_true", help="Suppress colored output")
|
|
221
|
+
parser_info.set_defaults(func=cmd_info)
|
|
222
|
+
|
|
223
|
+
# --- init-user ---
|
|
224
|
+
parser_init = subparsers.add_parser("init-user", help="Initialize user configuration")
|
|
225
|
+
parser_init.set_defaults(func=cmd_init_user)
|
|
226
|
+
|
|
227
|
+
# --- generate ---
|
|
228
|
+
parser_gen = subparsers.add_parser("generate", help="Generate experiment configuration")
|
|
229
|
+
|
|
230
|
+
# Core parameters
|
|
231
|
+
parser_gen.add_argument("--recipe", help="Recipe name (e.g. gcm-sr.yml)")
|
|
232
|
+
parser_gen.add_argument("--sim-procs", type=int, dest="sim_procs", help="Number of processors for SIM job")
|
|
233
|
+
parser_gen.add_argument("--expid", help="Experiment ID")
|
|
234
|
+
|
|
235
|
+
# Platform parameters
|
|
236
|
+
parser_gen.add_argument("--platform", help="HPC Platform (e.g. bsc-marenostrum5)")
|
|
237
|
+
parser_gen.add_argument("--launcher", help="Launcher type (e.g. slurm-wrapper-taskset)")
|
|
238
|
+
parser_gen.add_argument("--kind", help="Launcher kind (e.g. CPLD-SR, auto)")
|
|
239
|
+
|
|
240
|
+
# User parameters
|
|
241
|
+
parser_gen.add_argument("--account", help="HPC account/project")
|
|
242
|
+
parser_gen.add_argument("--walltime", type=int, help="Walltime in hours")
|
|
243
|
+
parser_gen.add_argument("--description", help="Experiment description")
|
|
244
|
+
|
|
245
|
+
# Repository parameters
|
|
246
|
+
parser_gen.add_argument("--repo-owner", dest="repo_owner", help="ECE4 repository owner")
|
|
247
|
+
parser_gen.add_argument("--repo-branch", dest="repo_branch", help="ECE4 repository branch")
|
|
248
|
+
|
|
249
|
+
# Output control
|
|
250
|
+
parser_gen.add_argument("-o", "--output", help="Output file name")
|
|
251
|
+
parser_gen.add_argument("--dry-run", action="store_true", help="Preview without writing")
|
|
252
|
+
parser_gen.add_argument("--quiet", action="store_true", help="Suppress colored output")
|
|
253
|
+
|
|
254
|
+
parser_gen.set_defaults(func=cmd_generate)
|
|
255
|
+
|
|
256
|
+
# --- validate ---
|
|
257
|
+
parser_val = subparsers.add_parser("validate", help="Validate experiment configuration")
|
|
258
|
+
parser_val.add_argument("config_file", nargs="?", help="Path to configuration file")
|
|
259
|
+
parser_val.set_defaults(func=cmd_validate)
|
|
260
|
+
|
|
261
|
+
# --- save ---
|
|
262
|
+
parser_save = subparsers.add_parser("save", help="Save changes as a recipe")
|
|
263
|
+
parser_save.add_argument("--expid", help="Experiment ID")
|
|
264
|
+
parser_save.add_argument("--recipe", help="Current user recipe name")
|
|
265
|
+
parser_save.add_argument("-o", "--output", help="Recipe file name")
|
|
266
|
+
parser_save.set_defaults(func=cmd_save)
|
|
267
|
+
|
|
268
|
+
# Parse args
|
|
269
|
+
args = parser.parse_args()
|
|
270
|
+
|
|
271
|
+
if not args.command:
|
|
272
|
+
parser.print_help()
|
|
273
|
+
sys.exit(1)
|
|
274
|
+
|
|
275
|
+
# Run command
|
|
276
|
+
try:
|
|
277
|
+
args.func(args)
|
|
278
|
+
except KeyboardInterrupt:
|
|
279
|
+
print("\nInterrupted by user")
|
|
280
|
+
sys.exit(130)
|
|
281
|
+
except Exception as e:
|
|
282
|
+
log_error(f"Command failed: {e}")
|
|
283
|
+
if "--debug" in sys.argv or os.environ.get("DEBUG"):
|
|
284
|
+
raise
|
|
285
|
+
sys.exit(1)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
if __name__ == "__main__":
|
|
289
|
+
main()
|