#!/usr/bin/env python3 # # ens_reduc.py --- Creating HYSPLIT Shapefiles # See HYSPLIT Tutorial Sec. 12.6 # # Change history: # 28 Dec 2025 (BB) - standard-aligned, cross-platform version # 18 Jun 2026 (SZ) - standardize code for run_cmd(), convert_html_to_img(), # etc. across Python scripts # - ensure encoding is specified when opening files import os import sys import platform from pathlib import Path import subprocess if sys.version_info < (3, 8): print("Please use Python 3.8 or higher.") sys.exit(1) system = platform.system().lower() if system == "windows": PGM = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("C:\\hysplit") OUT = Path(sys.argv[2]) if len(sys.argv) > 2 else PGM / "working" DSP = sys.argv[3] if len(sys.argv) > 3 else "YES" TTR = Path(os.environ.get("TTR", "C:\\Tutorial")) exe_suffix = ".exe" else: PGM = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.home() / "hysplit" OUT = Path(sys.argv[2]) if len(sys.argv) > 2 else PGM / "working" DSP = sys.argv[3] if len(sys.argv) > 3 else "YES" TTR = Path(os.environ.get("TTR", Path.home() / "tutorial")) exe_suffix = "" if not PGM.exists(): script = Path(__file__).name if "__file__" in globals() else "ens_reduc.py" print(f"ERROR: HYSPLIT not found at {PGM}") print(f""" Please specify the path to your HYSPLIT installation using a command-line argument: python {script} YOUR_HYSPLIT_DIRECTORY [OUT] [DSP (YES|NO)] Example: python {script} C:\\hysplit C:\\hysplit\\working YES """) sys.exit(1) if not TTR.exists(): print(f"ERROR: HYSPLIT tutorial not found at {TTR}") print(""" Please set the TTR environment variable to point to your HYSPLIT tutorial directory. For Windows users (Command Prompt): SET TTR=YOUR_HYSPLIT_TUTORIAL_DIRECTORY For Linux users (bash shell): export TTR='YOUR_HYSPLIT_TUTORIAL_DIRECTORY' For Linux users (csh or tcsh shell): setenv TTR 'YOUR_HYSPLIT_TUTORIAL_DIRECTORY'""") sys.exit(1) os.chdir(OUT) # ------------------------------------------------------------------ # Helper function # ------------------------------------------------------------------ def run_cmd(cmd, desc="", allow_fail=False, stdout_file=None, append=False): if desc: print(f"{desc}...") exitcode = 0 with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) as p: if stdout_file: mode="a" if append else "w" with open(stdout_file, mode, encoding="utf-8") as f_out: for ln in p.stdout: print(ln, end="") f_out.write(ln) else: with p.stdout as f: for ln in iter(f.readline, b'\n'): if len(ln) == 0: break print(ln.rstrip()) done = False while not done: try: exitcode = p.wait(timeout=1) done = True except subprocess.TimeoutExpired: print("WARN: subprocess timed out.") if exitcode != 0: if allow_fail: print(f"WARN: command failed with exit code {exitcode} -> {' '.join(cmd)}") else: print(f"ERROR: command failed with exit code {exitcode} -> {' '.join(cmd)}") sys.exit(1) # ------------------------------------------------------------------ # Script configuration (lowercase required) # ------------------------------------------------------------------ inp = "hysplit2" members = ["001", "002", "003", "004", "005"] met = TTR / "captex" meas = met / "captex2_meas.txt" # ------------------------------------------------------------------ # Verify required datem files # ------------------------------------------------------------------ def check_datem_files(): for m in members: fpath = OUT / f"datem.{m}" if not fpath.exists(): print(f"Missing input file datem.{m}") print("Re-run ens_stats to create output file!") sys.exit(1) datem6 = OUT / "datem.006" if datem6.exists(): datem6.unlink() # ------------------------------------------------------------------ # Accumulated divergence # ------------------------------------------------------------------ def run_accudiv(): run_cmd( [ str(PGM / "exec" / f"accudiv{exe_suffix}"), "-bdatem", f"-m{meas}", "-oreduc.txt", ], desc="Running accudiv" ) print(Path("reduc.txt").read_text(encoding="ascii")) # ------------------------------------------------------------------ # Build reduced ensemble (members 001 + 005) # ------------------------------------------------------------------ def build_reduced_mean(): infile = OUT / "INFILE" if infile.exists(): infile.unlink() selected = ["001", "005"] for m in selected: fname = OUT / f"{inp}.{m}" if fname.exists(): infile.write_text(infile.read_text(encoding="ascii") + f"{fname.name}\n" if infile.exists() else f"{fname.name}\n", encoding="ascii") mul = "0.50" # two members → mean multiplier temp1 = "temp1" temp2 = "temp2" in1 = None out = temp1 run = 1 for line in infile.read_text(encoding="ascii").splitlines(): if run == 1: in1 = line else: in2 = line run_cmd( [ str(PGM / "exec" / f"concadd{exe_suffix}"), f"-i{in1}", f"-b{in2}", f"-o{out}", "-p0", ], desc=f"concadd {in1} + {in2}" ) if out == temp1: in1 = temp1 out = temp2 else: in1 = temp2 out = temp1 run += 1 run_cmd( [ str(PGM / "exec" / f"conlight{exe_suffix}"), f"-i{out}", "-ohysplit2.007", f"-m{mul}", ], desc="Running conlight (reduced mean)" ) for f in ["INFILE", "temp1", "temp2"]: p = OUT / f if p.exists(): p.unlink() # ------------------------------------------------------------------ # Statistics loop # ------------------------------------------------------------------ def run_stats(): sumstat = OUT / "sumstat.txt" if sumstat.exists(): sumstat.unlink() for m in members + ["006", "007"]: run_cmd( [ str(PGM / "exec" / f"c2datem{exe_suffix}"), f"-i{inp}.{m}", "-ohysplit.txt", "-c1.0E+12", f"-m{meas}", "-xi", "-s", ], desc=f"c2datem member {m}" ) run_cmd( [ str(PGM / "exec" / f"statmain{exe_suffix}"), "-t0", "-rhysplit.txt", f"-d{meas}", "-l10.0", "-o1", "-ssumstat.txt", ], desc=f"statmain member {m}" ) print(sumstat.read_text(encoding="ascii")) # ------------------------------------------------------------------ # MAIN # ------------------------------------------------------------------ def main(): print("=== HYSPLIT Tutorial: Ensemble Reduction & Statistics ===") print(f"PGM: {PGM}") print(f"OUT: {OUT}") print(f"TTR: {TTR}") check_datem_files() run_accudiv() build_reduced_mean() run_stats() if __name__ == "__main__": main()