rc-qlc 0.3.24__cp310-cp310-win_amd64.whl → 0.3.26__cp310-cp310-win_amd64.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.
Files changed (84) hide show
  1. qlc/cli/__init__.py +100 -9
  2. qlc/cli/installer.py +23 -5
  3. qlc/cli/qlc_main.py +54 -32
  4. qlc/cli/qlc_py_main.py +43 -38
  5. qlc/config/json/qlc_config.json +94 -10
  6. qlc/config/nml/mars_A1_sfc.nml +4 -5
  7. qlc/config/nml/mars_A3_sfc.nml +0 -1
  8. qlc/config/nml/mars_B1_pl.nml +2 -2
  9. qlc/{examples/cams_case_1/config/nml/mars_A3_sfc.nml → config/nml/mars_B1_sfc.nml} +7 -8
  10. qlc/config/nml/mars_C1_pl.nml +1 -1
  11. qlc/{examples/cams_case_1/config/nml/mars_C1_pl.nml → config/nml/mars_C1_sfc.nml} +7 -8
  12. qlc/config/nml/mars_C2_pl.nml +1 -1
  13. qlc/{examples/cams_case_1/config/qlc_cams.conf → config/qlc.conf} +80 -18
  14. qlc/doc/README.md +98 -62
  15. qlc/doc/USAGE.md +68 -29
  16. qlc/examples/cams_case_1/mod/b2rn/2018/b2rn_20181201-20181221_B1_pl.grb +0 -0
  17. qlc/examples/cams_case_1/mod/b2rn/2018/b2rn_20181201-20181221_C1_sfc.grb +0 -0
  18. qlc/examples/cams_case_1/mod/b2ro/2018/b2ro_20181201-20181221_B1_pl.grb +0 -0
  19. qlc/examples/cams_case_1/mod/b2ro/2018/b2ro_20181201-20181221_C1_sfc.grb +0 -0
  20. qlc/install.py +260 -106
  21. qlc/py/__main__.cp310-win_amd64.pyd +0 -0
  22. qlc/py/averaging.cp310-win_amd64.pyd +0 -0
  23. qlc/py/bias_plots.cp310-win_amd64.pyd +0 -0
  24. qlc/py/control.cp310-win_amd64.pyd +0 -0
  25. qlc/py/io.cp310-win_amd64.pyd +0 -0
  26. qlc/py/loadmod.cp310-win_amd64.pyd +0 -0
  27. qlc/py/loadobs.cp310-win_amd64.pyd +0 -0
  28. qlc/py/logging_utils.cp310-win_amd64.pyd +0 -0
  29. qlc/py/map_plots.cp310-win_amd64.pyd +0 -0
  30. qlc/py/matched.cp310-win_amd64.pyd +0 -0
  31. qlc/py/plot_config.cp310-win_amd64.pyd +0 -0
  32. qlc/py/plotting.cp310-win_amd64.pyd +0 -0
  33. qlc/py/plugin_loader.cp310-win_amd64.pyd +0 -0
  34. qlc/py/processing.cp310-win_amd64.pyd +0 -0
  35. qlc/py/scatter_plots.cp310-win_amd64.pyd +0 -0
  36. qlc/py/stations.cp310-win_amd64.pyd +0 -0
  37. qlc/py/statistics.cp310-win_amd64.pyd +0 -0
  38. qlc/py/style.cp310-win_amd64.pyd +0 -0
  39. qlc/py/timeseries_plots.cp310-win_amd64.pyd +0 -0
  40. qlc/py/utils.cp310-win_amd64.pyd +0 -0
  41. qlc/py/version.cp310-win_amd64.pyd +0 -0
  42. qlc/sh/qlc_A1.sh +29 -11
  43. qlc/sh/qlc_B1a.sh +1 -18
  44. qlc/sh/qlc_B2.sh +8 -1
  45. qlc/sh/qlc_C5.sh +19 -30
  46. qlc/sh/qlc_D1.sh +291 -51
  47. qlc/sh/qlc_Z1.sh +6 -6
  48. qlc/sh/qlc_batch.sh +61 -0
  49. qlc/sh/qlc_common_functions.sh +17 -29
  50. qlc/sh/qlc_main.sh +32 -26
  51. qlc/sh/tex_template/beamercolorthemeCAMS2_35.sty +51 -0
  52. qlc/sh/tex_template/beamerfontthemeCAMS2_35.sty +166 -0
  53. qlc/sh/tex_template/beamerthemeCAMS2_35.sty +25 -0
  54. qlc/sh/tex_template/subcaption.sty +170 -0
  55. qlc/sh/tex_template/template.tex +109 -0
  56. rc_qlc-0.3.26.dist-info/METADATA +178 -0
  57. rc_qlc-0.3.26.dist-info/RECORD +102 -0
  58. qlc/config/json/qlc_config_example_1a_all-obs.json +0 -237
  59. qlc/config/json/qlc_config_example_1b_all-mod.json +0 -353
  60. qlc/config/json/qlc_config_example_1c_all-coll.json +0 -266
  61. qlc/config/json/qlc_config_example_2a_all-obs.json +0 -237
  62. qlc/config/json/qlc_config_example_2b_all-mod.json +0 -353
  63. qlc/config/json/qlc_config_example_2c_all-coll.json +0 -265
  64. qlc/config/json/qlc_config_example_3a-us_obs.json +0 -82
  65. qlc/config/json/qlc_config_example_3b-us_mod.json +0 -122
  66. qlc/config/json/qlc_config_example_3c-us_coll.json +0 -46
  67. qlc/config/json/qlc_config_example_4a_eu-obs.json +0 -41
  68. qlc/config/json/qlc_config_example_4b_eu-mod.json +0 -122
  69. qlc/config/json/qlc_config_example_4c_eu-coll.json +0 -45
  70. qlc/config/qlc_cams.conf +0 -26
  71. qlc/config/qlc_test.conf +0 -26
  72. qlc/config/qlc_tex.conf +0 -107
  73. qlc/examples/cams_case_1/config/json/qlc_config.json +0 -41
  74. qlc/examples/cams_case_1/config/nml/mars_B1_pl.nml +0 -19
  75. qlc/examples/cams_case_1/mod/b2ro/2018/b2ro_20181215-20181231_A3_sfc.grb +0 -0
  76. qlc/examples/cams_case_1/mod/iqi9/2018/iqi9_20181215-20181231_A3_sfc.grb +0 -0
  77. qlc/sh/qlc_start.sh +0 -23
  78. qlc/sh/qlc_start_batch.sh +0 -46
  79. rc_qlc-0.3.24.dist-info/METADATA +0 -142
  80. rc_qlc-0.3.24.dist-info/RECORD +0 -113
  81. {rc_qlc-0.3.24.dist-info → rc_qlc-0.3.26.dist-info}/WHEEL +0 -0
  82. {rc_qlc-0.3.24.dist-info → rc_qlc-0.3.26.dist-info}/entry_points.txt +0 -0
  83. {rc_qlc-0.3.24.dist-info → rc_qlc-0.3.26.dist-info}/licenses/LICENSE +0 -0
  84. {rc_qlc-0.3.24.dist-info → rc_qlc-0.3.26.dist-info}/top_level.txt +0 -0
qlc/install.py CHANGED
@@ -23,12 +23,13 @@ def read_version_json(version_json_path: Path) -> dict:
23
23
  def get_bin_path():
24
24
  return Path(sys.executable).resolve().parent
25
25
 
26
- def copy_or_link(src, dst, symlink=False):
26
+ def copy_or_link(src, dst, symlink=False, relative=False):
27
27
  dst.parent.mkdir(parents=True, exist_ok=True)
28
28
  if dst.exists() or dst.is_symlink():
29
29
  dst.unlink()
30
30
  if symlink:
31
- dst.symlink_to(src.resolve())
31
+ link_target = os.path.relpath(src.resolve(), dst.parent) if relative else src.resolve()
32
+ dst.symlink_to(link_target, target_is_directory=src.is_dir())
32
33
  else:
33
34
  shutil.copy(src, dst)
34
35
 
@@ -49,41 +50,39 @@ def copytree_with_symlinks(src: Path, dst: Path):
49
50
  else:
50
51
  shutil.copy2(s, d)
51
52
 
52
- def safe_move_and_link(source: Path, target: Path):
53
+ def safe_move_and_link(src: Path, dst: Path, relative: bool = False, backup: bool = True):
53
54
  """
54
- Ensures `target` is a symlink to `source`.
55
- If `target` exists but points elsewhere (or is a dir), it gets backed up first.
55
+ Safely create a symlink from dst to src.
56
+ If dst exists, it's backed up (if backup=True) before the new link is created.
57
+ If dst is already a symlink pointing to src, do nothing.
56
58
  """
57
- if target.is_symlink():
58
- current = target.resolve()
59
- if current == source.resolve():
60
- print(f"[SKIP] {target} already links to {source}")
61
- return
59
+ if dst.is_symlink():
60
+ try:
61
+ if dst.resolve() == src.resolve():
62
+ print(f"[SKIP] Link {dst} already points to {src}")
63
+ return
64
+ else:
65
+ print(f"[INFO] Unlinking existing symlink {dst} -> {dst.readlink()}")
66
+ dst.unlink()
67
+ except FileNotFoundError:
68
+ # This handles broken symlinks
69
+ print(f"[INFO] Removing broken symlink {dst}")
70
+ dst.unlink()
71
+
72
+ elif dst.exists():
73
+ if backup:
74
+ backup_dst = dst.with_name(f"{dst.name}_backup_link")
75
+ print(f"[BACKUP] Moving existing path {dst} -> {backup_dst}")
76
+ shutil.move(str(dst), str(backup_dst))
62
77
  else:
63
- print(f"[BACKUP] {target} symlink points to {current}, backing up")
64
- elif target.exists():
65
- if target.is_dir():
66
- # Only backup if it's not the install root (we don't want to mv source itself)
67
- if target.resolve() != source.resolve():
68
- backup = target.with_name(f"{target.name}_backup")
69
- count = 1
70
- while backup.exists():
71
- backup = target.with_name(f"{target.name}_backup{count}")
72
- count += 1
73
- print(f"[BACKUP] Moving existing directory {target} → {backup}")
74
- shutil.move(str(target), str(backup))
75
- else:
76
- # File or other type — always back up
77
- backup = target.with_name(f"{target.name}_backup")
78
- count = 1
79
- while backup.exists():
80
- backup = target.with_name(f"{target.name}_backup{count}")
81
- count += 1
82
- print(f"[BACKUP] Moving existing file {target} → {backup}")
83
- shutil.move(str(target), str(backup))
84
-
85
- print(f"[LINK] {target} → {source}")
86
- target.symlink_to(source, target_is_directory=True)
78
+ print(f"[INFO] Removing existing path {dst}")
79
+ if dst.is_dir():
80
+ shutil.rmtree(dst)
81
+ else:
82
+ dst.unlink()
83
+
84
+ # Create the new link
85
+ copy_or_link(src, dst, symlink=True, relative=relative)
87
86
 
88
87
  def update_qlc_version(config_path: Path, version: str):
89
88
  if not config_path.exists():
@@ -106,7 +105,97 @@ def update_qlc_version(config_path: Path, version: str):
106
105
  config_path.write_text("\n".join(new_lines) + "\n", encoding="utf-8")
107
106
  print(f"[UPDATED] QLC_VERSION set to {version} in {config_path}")
108
107
 
109
- def setup(mode, config_file=None, version="latest"):
108
+ def setup_data_directories(root: Path, mode: str):
109
+ """
110
+ Creates the two-stage symlink structure for data-heavy directories WITHIN a mode's root.
111
+ - Creates <root>/data
112
+ - Populates it with either real directories (test) or symlinks (cams)
113
+ - Creates top-level symlinks from <root>/* -> <root>/data/*
114
+ """
115
+ print("[SETUP] Configuring data directories...")
116
+ data_dir = root / "data"
117
+ data_dir.mkdir(exist_ok=True)
118
+
119
+ # Define the mapping for CAMS mode
120
+ cams_env_map = {
121
+ "Results": "SCRATCH",
122
+ "Analysis": "HPCPERM",
123
+ "Plots": "PERM",
124
+ "Presentations": "PERM",
125
+ "log": "PERM",
126
+ "run": "PERM",
127
+ "output": "PERM"
128
+ }
129
+
130
+ data_heavy_dirs = ["Results", "Analysis", "Plots", "Presentations", "log", "run", "output"]
131
+
132
+ for d in data_heavy_dirs:
133
+ data_subdir = data_dir / d
134
+
135
+ # Robustly remove existing path before creating a new one
136
+ if data_subdir.is_symlink():
137
+ data_subdir.unlink()
138
+ elif data_subdir.is_dir():
139
+ shutil.rmtree(data_subdir)
140
+ elif data_subdir.exists():
141
+ data_subdir.unlink()
142
+
143
+ if mode == "cams":
144
+ env_var = cams_env_map.get(d)
145
+ target_base_path = os.environ.get(env_var)
146
+
147
+ if target_base_path:
148
+ target_path = Path(target_base_path) / d
149
+ target_path.mkdir(parents=True, exist_ok=True)
150
+ data_subdir.symlink_to(target_path, target_is_directory=True)
151
+ print(f"[LINK] {data_subdir} -> {target_path}")
152
+ else:
153
+ print(f"[WARN] Environment variable ${env_var} not set for {d}. Creating local directory.")
154
+ data_subdir.mkdir(exist_ok=True)
155
+ else: # test mode
156
+ data_subdir.mkdir(exist_ok=True)
157
+ print(f"[MKDIR] {data_subdir}")
158
+
159
+ # Create the top-level symlink, e.g., <root>/Results -> <root>/data/Results
160
+ top_level_link = root / d
161
+ if top_level_link.is_symlink() or top_level_link.exists():
162
+ top_level_link.unlink()
163
+
164
+ # --- Create a relative symlink ---
165
+ # The target is data_subdir, and the link is created at top_level_link.
166
+ # We need the path of the target relative to the link's parent directory.
167
+ relative_target = os.path.relpath(data_subdir, top_level_link.parent)
168
+ top_level_link.symlink_to(relative_target, target_is_directory=True)
169
+ print(f"[LINK] {top_level_link} -> {relative_target}")
170
+
171
+ def link_model_experiments(mod_data_src_root: Path, results_dst_root: Path, debug: bool = False):
172
+ """
173
+ Links model experiment files from mod_data_src_root to results_dst_root.
174
+ Creates absolute symlinks for the model data files.
175
+ """
176
+ if not mod_data_src_root.is_dir():
177
+ print(f"[WARN] Source directory not found for model experiments: {mod_data_src_root}")
178
+ return
179
+
180
+ for exp_dir in mod_data_src_root.iterdir():
181
+ if exp_dir.is_dir():
182
+ # This is an experiment dir, e.g., /path/to/test/mod/b2ro
183
+ results_exp_dir = results_dst_root / exp_dir.name
184
+ results_exp_dir.mkdir(exist_ok=True)
185
+
186
+ # Find all .grb files, searching recursively through year folders
187
+ for year_dir in exp_dir.iterdir():
188
+ if year_dir.is_dir():
189
+ for src_file in year_dir.glob('*.grb'):
190
+ dst_file = results_exp_dir / src_file.name
191
+ dst_file.parent.mkdir(parents=True, exist_ok=True)
192
+ # Use absolute paths for these links as they point from the data dir to the mod dir,
193
+ # which can be far apart.
194
+ copy_or_link(src_file, dst_file, symlink=True, relative=False)
195
+ if debug:
196
+ print(f"[LINK] {dst_file} -> {src_file}")
197
+
198
+ def setup(mode: str, version: str, debug: bool = False, config_file: str = None):
110
199
 
111
200
  # Define source root
112
201
  qlc_root = Path(__file__).resolve().parent.parent
@@ -117,73 +206,92 @@ def setup(mode, config_file=None, version="latest"):
117
206
 
118
207
  from qlc.py.version import QLC_VERSION as version
119
208
 
120
- # Select appropriate configuration file
121
- if mode == "cams":
122
- perm_path = Path(os.environ.get("PERM", "/perm")) / os.environ.get("USER", "user")
123
- user_home = perm_path
124
- selected_conf = config_src / "qlc_cams.conf"
125
- elif mode == "test":
126
- user_home = Path.home()
127
- selected_conf = config_src / "qlc_test.conf"
128
- elif mode == "interactive":
129
- if not config_file:
130
- raise ValueError("You must provide a config file via --interactive=<path>")
131
- selected_conf = Path(config_file)
132
- else:
133
- raise ValueError(f"Unsupported mode: {mode}")
134
-
135
- source = user_home / f"qlc_v{version}"
209
+ # --- QLC paths are now consistently based on $HOME ---
210
+ install_base = Path.home()
211
+ user_home = install_base
212
+ print(f"[INFO] Using $HOME as installation base: {install_base}")
213
+
214
+ # The stable link path, always $HOME/qlc
215
+ qlc_stable_link = user_home / "qlc"
216
+
217
+ # The versioned installation directory, e.g., $HOME/qlc_v0.3.25
218
+ versioned_install_dir = user_home / f"qlc_v{version}"
219
+
220
+ # The mode-specific root, e.g., $HOME/qlc_v0.3.25/test
221
+ root = versioned_install_dir / mode
136
222
 
137
- if source.exists() and not source.is_symlink():
138
- backup = source.with_name(f"{source.name}_backup")
223
+ # --- Backup Logic: Back up the entire versioned directory if the specific mode being installed already exists ---
224
+ if root.exists():
225
+ backup_name = f"{versioned_install_dir.name}_backup"
226
+ backup = versioned_install_dir.with_name(backup_name)
139
227
  count = 1
140
228
  while backup.exists():
141
- backup = source.with_name(f"{source.name}_backup{count}")
229
+ backup = versioned_install_dir.with_name(f"{backup_name}{count}")
142
230
  count += 1
143
- print(f"[BACKUP] Moving existing install root {source} → {backup}")
144
- shutil.move(str(source), str(backup))
145
-
146
- root = source / mode
147
- # safe_move_and_link(source, Path.home() / f"qlc_v{version}")
231
+ print(f"[BACKUP] Moving existing install root {versioned_install_dir} → {backup}")
232
+ shutil.move(str(versioned_install_dir), str(backup))
148
233
 
149
234
  print(f"[SETUP] Mode: {mode}, Version: {version}")
150
- print(f"[PATHS] QLC Root: {root}")
235
+ print(f"[PATHS] QLC Install Root: {root}")
236
+
237
+ # Create essential directories for the mode
238
+ root.mkdir(parents=True, exist_ok=True)
151
239
 
152
- # Prepare paths
240
+ # Prepare paths inside the versioned, mode-specific directory
153
241
  config_dst = root / "config"
154
242
  example_dst = root / "examples"
155
243
  bin_dst = root / "bin"
156
- log_dst = root / "log"
157
244
  mod_dst = root / "mod"
158
245
  obs_dst = root / "obs"
159
246
  doc_dst = root / "doc"
160
- run_dst = root / "run"
161
- out_dst = root / "output"
162
247
  plug_dst = root / "plugin"
163
-
164
- # Create distribution directories
165
- for path in [bin_dst, log_dst, mod_dst, obs_dst, doc_dst, run_dst, out_dst, plug_dst, example_dst, config_dst]:
248
+
249
+ # Create non-data directories inside the mode-specific root
250
+ # NOTE: 'run' and 'output' are now handled by setup_data_directories
251
+ for path in [config_dst, example_dst, bin_dst, mod_dst, obs_dst, doc_dst, plug_dst]:
166
252
  path.mkdir(parents=True, exist_ok=True)
167
253
 
254
+ # --- Setup the new data directory structure INSIDE the mode root ---
255
+ setup_data_directories(root, mode)
256
+
168
257
  # Copy config files
169
- if config_dst.exists():
170
- copytree_with_symlinks(config_src, config_dst)
258
+ shutil.copytree(config_src, config_dst, dirs_exist_ok=True)
259
+ print(f"[COPY] {config_src} -> {config_dst}")
171
260
 
172
- # Copy example files
261
+ # Link example directories instead of copying
173
262
  if example_src.exists():
174
- # shutil.copytree(example_src, example_dst, dirs_exist_ok=True, symlinks=True)
175
- copytree_with_symlinks(example_src, example_dst)
263
+ for item in example_src.iterdir():
264
+ if item.is_dir(): # Only link directories
265
+ dst_link = example_dst / item.name
266
+ if dst_link.exists() or dst_link.is_symlink():
267
+ if dst_link.is_dir() and not dst_link.is_symlink():
268
+ shutil.rmtree(dst_link)
269
+ else:
270
+ dst_link.unlink()
271
+ dst_link.symlink_to(item.resolve(), target_is_directory=True)
272
+ print(f"[LINK] {dst_link} -> {item}")
176
273
 
177
274
  # Link all documentation files
178
275
  # shutil.copytree(doc_src, doc_dst, dirs_exist_ok=True)
179
276
  for doc_file in doc_src.glob("*"):
180
277
  dst = doc_dst / doc_file.name
181
- copy_or_link(doc_file, dst, symlink=True)
278
+ copy_or_link(doc_file, dst, symlink=True, relative=False)
279
+ print(f"[LINK] {dst} -> {doc_file.resolve()}")
182
280
 
183
281
  # Link all *.sh files to bin_dst (helpers included)
184
282
  for sh_file in sh_src.glob("*.sh"):
185
283
  dst = bin_dst / sh_file.name
186
- copy_or_link(sh_file, dst, symlink=True)
284
+ copy_or_link(sh_file, dst, symlink=True, relative=False)
285
+ print(f"[LINK] {dst} -> {sh_file.resolve()}")
286
+
287
+ # Copy the TeX template directory to bin_dst
288
+ tex_template_src = sh_src / "tex_template"
289
+ tex_template_dst = bin_dst / "tex_template"
290
+ if tex_template_src.is_dir():
291
+ if tex_template_dst.exists():
292
+ shutil.rmtree(tex_template_dst)
293
+ shutil.copytree(tex_template_src, tex_template_dst)
294
+ print(f"[COPY] {tex_template_src} -> {tex_template_dst}")
187
295
 
188
296
  # Create shell tool links (now handled by entry_points in setup.py)
189
297
  pass
@@ -198,17 +306,16 @@ def setup(mode, config_file=None, version="latest"):
198
306
  obs_data_dest_dir = root / "obs/data/ver0d/ebas_daily"
199
307
  obs_data_dest_dir.mkdir(parents=True, exist_ok=True)
200
308
 
201
- # Create the v_20240216 symlink
309
+ # Create the v_20240216 symlink with an absolute path
202
310
  link1_target = obs_data_dest_dir / "v_20240216"
203
- if link1_target.exists() or link1_target.is_symlink():
204
- link1_target.unlink()
205
- link1_target.symlink_to(obs_data_source.resolve(), target_is_directory=True)
206
- print(f"[LINK] {link1_target} -> {obs_data_source}")
311
+ copy_or_link(obs_data_source, link1_target, symlink=True, relative=False)
312
+ print(f"[LINK] {link1_target} -> {obs_data_source.resolve()}")
207
313
 
208
314
  # Create the latest symlink, relative to its location
209
315
  link2_target = obs_data_dest_dir / "latest"
210
316
  if link2_target.exists() or link2_target.is_symlink():
211
317
  link2_target.unlink()
318
+ # This one MUST be relative by its nature
212
319
  link2_target.symlink_to("v_20240216", target_is_directory=True)
213
320
  print(f"[LINK] {link2_target} -> v_20240216")
214
321
 
@@ -220,48 +327,95 @@ def setup(mode, config_file=None, version="latest"):
220
327
  print(f"[COPY] {station_file_dest}")
221
328
 
222
329
 
223
- # Link sample model data (unchanged)
330
+ # Link sample model data, ensuring absolute paths
224
331
  mod_data_src_root = root / "examples" / "cams_case_1" / "mod"
225
332
  mod_data_dst_root = root / "mod"
226
333
  if mod_data_src_root.is_dir():
227
334
  for model_dir in mod_data_src_root.iterdir():
228
335
  if model_dir.is_dir():
229
- mod_data_dst = mod_data_dst_root / model_dir.name
230
- if mod_data_dst.exists() or mod_data_dst.is_symlink():
231
- mod_data_dst.unlink()
232
- mod_data_dst.symlink_to(model_dir.resolve(), target_is_directory=True)
233
- print(f"[LINK] {mod_data_dst} -> {model_dir}")
234
-
235
- # Create a generic qlc.conf symlink
236
- config_dir = root / "config"
237
- generic_config_path = config_dir / "qlc.conf"
238
- if generic_config_path.exists() or generic_config_path.is_symlink():
239
- generic_config_path.unlink()
240
-
241
- if selected_conf.exists():
242
- generic_config_path.symlink_to(selected_conf.name)
243
- print(f"[LINK] {generic_config_path} -> {selected_conf.name}")
336
+ dst_link = mod_data_dst_root / model_dir.name
337
+ # Use copy_or_link to create an absolute symlink
338
+ copy_or_link(model_dir, dst_link, symlink=True, relative=False)
339
+ print(f"[LINK] {dst_link} -> {model_dir.resolve()}")
340
+
341
+ # Link model experiment files to the 'Results' directory (relative)
342
+ results_dst_root = root / "Results"
343
+ results_dst_root.mkdir(exist_ok=True)
344
+ print(f"[SETUP] Linking model experiments to {results_dst_root}")
345
+
346
+ if mod_data_dst_root.is_dir():
347
+ for exp_dir in mod_data_dst_root.iterdir():
348
+ if exp_dir.is_dir():
349
+ # This is an experiment dir, e.g., /path/to/test/mod/b2ro
350
+ results_exp_dir = results_dst_root / exp_dir.name
351
+ results_exp_dir.mkdir(exist_ok=True)
352
+
353
+ # Find all .grb files, searching recursively through year folders
354
+ for year_dir in exp_dir.iterdir():
355
+ if year_dir.is_dir():
356
+ for src_file in year_dir.glob('*.grb'):
357
+ dst_file = results_exp_dir / src_file.name
358
+ dst_file.parent.mkdir(parents=True, exist_ok=True)
359
+ # Use absolute paths for these links as they point from the data dir to the mod dir,
360
+ # which can be far apart.
361
+ copy_or_link(src_file, dst_file, symlink=True, relative=False)
362
+ if debug:
363
+ print(f"[LINK] {dst_file} -> {src_file}")
364
+
365
+ # In CAMS mode: link to operational directories
366
+ if mode == "cams":
367
+ # Link obs data directory
368
+ cams_obs_src = Path("/ec/vol/cams/qlc/obs")
369
+ if obs_dst.is_symlink() or obs_dst.is_symlink():
370
+ obs_dst.unlink()
371
+ elif obs_dst.is_dir():
372
+ shutil.rmtree(obs_dst)
373
+ obs_dst.symlink_to(cams_obs_src, target_is_directory=True)
374
+ print(f"[LINK] {obs_dst} -> {cams_obs_src}")
375
+
376
+ # Link mod directory to this mode's internal Results directory
377
+ results_link = root / "Results"
378
+ if mod_dst.is_symlink() or mod_dst.is_symlink():
379
+ mod_dst.unlink()
380
+ elif mod_dst.is_dir():
381
+ shutil.rmtree(mod_dst)
382
+ mod_dst.symlink_to(results_link, target_is_directory=True)
383
+ print(f"[LINK] {mod_dst} -> {results_link}")
384
+
385
+ # The config is now static, just need to update the version
386
+ generic_config_path = config_dst / "qlc.conf"
387
+ update_qlc_version(generic_config_path, version)
388
+
389
+ # --- Setup master symlinks to point to this installation (relative) ---
390
+ # Link qlc_latest to the new version-specific root
391
+ qlc_latest_link = Path(user_home) / "qlc_latest"
392
+ safe_move_and_link(root, qlc_latest_link, relative=True, backup=False)
393
+
394
+ # Ensure $HOME/qlc points to qlc_latest
395
+ qlc_stable_link = Path(user_home) / "qlc"
396
+ if not qlc_stable_link.exists() or not qlc_stable_link.is_symlink():
397
+ safe_move_and_link(qlc_latest_link, qlc_stable_link, relative=True, backup=False)
398
+ else:
399
+ try:
400
+ if not qlc_stable_link.resolve() == qlc_latest_link.resolve():
401
+ safe_move_and_link(qlc_latest_link, qlc_stable_link, relative=True, backup=False)
402
+ else:
403
+ print(f"[SKIP] {qlc_stable_link} already links to {qlc_latest_link.name}")
404
+ except FileNotFoundError: # Handle broken link
405
+ safe_move_and_link(qlc_latest_link, qlc_stable_link, relative=True, backup=False)
244
406
 
245
407
  # Write install info
246
408
  info = {
247
409
  "version": version,
248
410
  "mode": mode,
249
- "config": selected_conf.name
411
+ "config": "qlc.conf"
250
412
  }
251
413
 
252
- # Preemptively remove 'qlc_latest' symlink to ensure clean update, mimicking `ln -sf`.
253
- qlc_latest_link = Path.home() / "qlc_latest"
254
- if qlc_latest_link.is_symlink():
255
- qlc_latest_link.unlink()
256
-
257
- safe_move_and_link(root, qlc_latest_link)
258
- safe_move_and_link(qlc_latest_link, Path.home() / "qlc")
259
-
260
414
  (root / "VERSION.json").write_text(json.dumps(info, indent=2))
261
415
  print(f"[WRITE] VERSION.json at {root}")
262
416
 
263
- version_info = read_version_json(Path.home() / "qlc" / "VERSION.json")
264
- update_qlc_version(generic_config_path, version_info["version"])
417
+ # Final version update on the stable link path
418
+ update_qlc_version(qlc_stable_link / "config" / "qlc.conf", version)
265
419
 
266
420
  print("\n[INFO] QLC installation complete.")
267
421
  print("[INFO] To get started, you may need to open a new terminal or run 'rehash'.")
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
qlc/sh/qlc_A1.sh CHANGED
@@ -1,4 +1,4 @@
1
- #!/bin/sh -e
1
+ #!/bin/bash -e
2
2
 
3
3
  # Source the configuration file to load the settings
4
4
  . "$CONFIG_FILE"
@@ -34,16 +34,34 @@ for exp in $exps ; do
34
34
  mkdir -p $MARS_RETRIEVAL_DIRECTORY/$exp
35
35
  fi
36
36
 
37
- EXPCLASS=`echo ${exp} | cut -c 1`
38
- if [ "${EXPCLASS}" == "b" ]; then
39
- XCLASS="nl"
40
- elif [ "${EXPCLASS}" == "a" ]; then
41
- XCLASS="be"
42
- else
43
- XCLASS="rd"
44
- fi
45
- log ${exp} ${XCLASS}
46
-
37
+ # Map the experiment prefix to the corresponding ECMWF MARS class.
38
+ # This is required for retrieving data from the MARS archive.
39
+ EXPCLASS=$(echo "${exp}" | cut -c 1)
40
+
41
+ case "${EXPCLASS}" in
42
+ a) XCLASS="be" ;; # Belgium
43
+ b) XCLASS="nl" ;; # Netherlands
44
+ c) XCLASS="fr" ;; # France
45
+ d) XCLASS="de" ;; # Germany
46
+ e) XCLASS="es" ;; # Spain
47
+ f) XCLASS="fi" ;; # Finland
48
+ g) XCLASS="gr" ;; # Greece
49
+ h) XCLASS="hu" ;; # Hungary
50
+ i) XCLASS="it" ;; # Italy
51
+ k) XCLASS="dk" ;; # Denmark
52
+ l) XCLASS="pt" ;; # Portugal
53
+ m) XCLASS="at" ;; # Austria
54
+ n) XCLASS="no" ;; # Norway
55
+ s) XCLASS="se" ;; # Sweden
56
+ t) XCLASS="tr" ;; # Turkey
57
+ u) XCLASS="uk" ;; # United Kingdom
58
+ w) XCLASS="ch" ;; # Switzerland
59
+ *) XCLASS="rd" ;; # Default to Research Department
60
+ esac
61
+ log "${exp}" "${XCLASS}"
62
+
63
+ # --------------------------------------------------------------------
64
+ # 2. MARS request for sfc data
47
65
  for name in "${MARS_RETRIEVALS[@]}"; do
48
66
  nml_name="mars_${name}.nml"
49
67
  log "Processing subscript: $nml_name"
qlc/sh/qlc_B1a.sh CHANGED
@@ -1,4 +1,4 @@
1
- #!/bin/sh -e
1
+ #!/bin/bash -e
2
2
 
3
3
  # Source the configuration file to load the settings
4
4
  . "$CONFIG_FILE"
@@ -65,7 +65,6 @@ for exp in $exps ; do
65
65
 
66
66
  for name in "${MARS_RETRIEVALS[@]}"; do
67
67
 
68
- set +e
69
68
  # List available GRIB files for selected exp and time period
70
69
  # grbfiles=($(ls *${mDate}*_${name}_*.grb))
71
70
  grbfiles=($(ls *${mDate}*_${name}*.grb))
@@ -93,21 +92,6 @@ for exp in $exps ; do
93
92
  ls -lh $ncfile
94
93
  # ncdump -h $ncfile
95
94
  fi
96
- # conversion of tine averaged grib files to netcdf (seasmean)
97
- ncfil2="${gribfile%.grb}_tavg.nc"
98
- if [ ! -f "$ncfil2" ]; then
99
- log "cdo timavg ${gribfile} ${gribfile%.grb}.grbtavg"
100
- cdo timavg ${gribfile} ${gribfile%.grb}.grbtavg
101
- log "cdo -f nc copy ${gribfile%.grb}.grbtavg $ncfil2"
102
- cdo -f nc copy ${gribfile%.grb}.grbtavg $ncfil2
103
- # rm -f "$gribfile" # Clean up GRIB file
104
- ls -lh $ncfil2
105
- ncdump -h $ncfil2
106
- else
107
- log "Nothing to do! NC-file already exists: $ncfil2"
108
- ls -lh $ncfil2
109
- # ncdump -h $ncfil2
110
- fi
111
95
  done # file
112
96
  log "----------------------------------------------------------------------------------------"
113
97
  fi
@@ -120,4 +104,3 @@ log "End ${SCRIPT} at `date`"
120
104
  log "________________________________________________________________________________________"
121
105
 
122
106
  exit 0
123
-
qlc/sh/qlc_B2.sh CHANGED
@@ -1,4 +1,4 @@
1
- #!/bin/sh -e
1
+ #!/bin/bash -e
2
2
 
3
3
  # Source the configuration file to load the settings
4
4
  . "$CONFIG_FILE"
@@ -64,6 +64,8 @@ for exp in $exps ; do
64
64
  mkdir -p "$tpath"
65
65
  fi
66
66
 
67
+ log "----------------------------------------------------------------------------------------"
68
+
67
69
  for name in "${MARS_RETRIEVALS[@]}"; do
68
70
 
69
71
  # Define the corresponding arrays based on the name
@@ -76,12 +78,16 @@ for exp in $exps ; do
76
78
  ncvar=("${!ncvar_var}")
77
79
  myvar=("${!myvar_var}")
78
80
 
81
+ log "ipath : ${ipath}"
82
+ log "tpath : ${tpath}"
83
+
79
84
  log "name : ${name}"
80
85
  log "param: ${param}"
81
86
  log "ncvar: ${ncvar}"
82
87
  log "myvar: ${myvar}"
83
88
 
84
89
  cd $ipath
90
+ pwd -P
85
91
 
86
92
  set +e
87
93
  # List available NC-files
@@ -92,6 +98,7 @@ for exp in $exps ; do
92
98
  log "ncfiles : ${ncfiles}"
93
99
 
94
100
  cd $tpath
101
+ pwd -P
95
102
 
96
103
  # Loop through the variables for this $name
97
104
  for ((i = 0; i < ${#ncvar[@]}; i++)); do