Snapshot from this documentation build. SHA-256: 7bb02325e4e65099495bf5dc4bcef59e0a6e11ff1a42d1a984bc956183e3a924
1 "SHA256 of all implementation sources and dependency declarations, independent of checkout path."
2 function source_fingerprint(root::AbstractString=PACKAGE_ROOT)
3 files = String[]
4 for folder in ("src", "lib", "scripts")
5 for (directory, _, names) in walkdir(joinpath(root, folder))
6 for name in names
7 (endswith(name,".jl") || name == "Project.toml") &&
8 push!(files, relpath(joinpath(directory,name),root))
9 end
10 end
11 end
12 push!(files,"Project.toml")
13 io = IOBuffer()
14 for name in sort(files)
15 print(io,name,'\0',bytes2hex(sha256(read(joinpath(root,name)))),'\n')
16 end
17 bytes2hex(sha256(take!(io)))
18 end
19
20 function _run_metadata(config, env; status="RUNNING")
21 project = Base.active_project()
22 manifest = project === nothing ? "" : joinpath(dirname(project),"Manifest.toml")
23 solver = lowercase(get(env,"MHD_SOLVER","glm"))
24 ctnote = solver == "ct" ? Dict{String,Any}(
25 "ct_step_glm_limited"=>false,"ct_glm_project_every"=>0,
26 "ct_note"=>"CT dt is the plain CFL dt (not shrunk by ch=glm_ch_fac*smax) and B is never " *
27 "projected; GLM radiation runs project every cycle. Expected difference at equal CFL.") :
28 Dict{String,Any}()
29 merge(Dict("schema_version"=>1,"status"=>status,"mhd_solver"=>solver,"created_utc"=>string(now(UTC)),
30 "machine"=>gethostname(),"kernel"=>string(Sys.KERNEL),"architecture"=>string(Sys.ARCH),
31 "julia_version"=>string(VERSION),"julia_threads"=>Threads.nthreads(),
32 "source_fingerprint_sha256"=>source_fingerprint(),
33 "active_project"=>something(project,""),
34 "manifest_sha256"=>isfile(manifest) ? bytes2hex(sha256(read(manifest))) : "unresolved",
35 "configuration"=>config.settings,"environment"=>env),ctnote)
36 end
37
38 function _completion_status(log::AbstractString,env)
39 haskey(env,"MHD_ZSTART") || return "COMPLETED"
40 matches = collect(eachmatch(r"COSMO final_z=([+\-0-9.eE]+)",log))
41 isempty(matches) && return "INCOMPLETE"
42 z = parse(Float64,last(matches).captures[1])
43 target = parse(Float64,get(env,"MHD_ZEND","20"))
44 z <= target + 1e-6*max(1,target) ? "COMPLETED" : "STOPPED_AT_LIMIT"
45 end
46
47 "CT_INITIAL_FACES numbers from the first run segment of `run.log` (empty if absent)."
48 function _ct_initial_faces(log::AbstractString)
49 m = match(r"CT_INITIAL_FACES cell_b_rel_l2_change=([^ ]+) magnetic_energy_ratio=([^ ]+) divb_face_dx_over_brms=([^ ]+)",log)
50 m === nothing && return Dict{String,Any}()
51 Dict{String,Any}("ct_initial_cell_b_rel_l2_change"=>parse(Float64,m.captures[1]),
52 "ct_initial_magnetic_energy_ratio"=>parse(Float64,m.captures[2]),
53 "ct_initial_divb_face_dx_over_brms"=>parse(Float64,m.captures[3]))
54 end
55
56 const RESTART_CONTROL_OPTIONS = Set([
57 "MHD_ZEND", "MHD_STEPS", "MHD_MAX_CYCLES", "MHD_CHECKPOINT_RESUME",
58 "MHD_CHECKPOINT_EVERY", "MHD_CHECKPOINT_FINAL", "MHD_PRINT_EVERY",
59 "MHD_CYCLE_PRINT_EVERY", "MHD_CHECK_EVERY", "MHD_PK_ZS", "MHD_PK_EVERY",
60 ])
61
62 function _validate_restart_environment(previous,current)
63 changed = sort!([String(key) for key in union(keys(previous),keys(current))
64 if !(key in RESTART_CONTROL_OPTIONS) &&
65 get(previous,key,nothing) != get(current,key,nothing)])
66 isempty(changed) || error("restart changes evolution options: " * join(changed,", ") *
67 "; use the original physical settings or start a new run")
68 nothing
69 end
70
71 """
72 run_simulation(config; output_dir, dry_run=false)
73
74 Run the full production model in a fresh Julia process using the current active
75 environment. A fresh process isolates the legacy driver's compile-time options
76 and device caches. `metadata.toml` and `resolved-environment.toml` record inputs;
77 `run.log` is flushed by the production driver. Existing runs require explicit
78 `run.resume=true` and a checkpoint. Run directories are never silently replaced.
79 """
80 function run_simulation(c::RunConfig; output_dir::AbstractString, dry_run::Bool=false)
81 env = run_environment(c; output_dir)
82 dry_run && return env
83 out = abspath(output_dir)
84 existing = isfile(joinpath(out,"metadata.toml"))
85 resuming = get(env,"MHD_CHECKPOINT_RESUME","false") in ("true","1")
86 existing && !resuming && error("output already contains a run; choose a new directory or resume=true")
87 resuming && !isfile(env["MHD_CHECKPOINT_PATH"]) && error("resume requires checkpoint.jls")
88 mkpath(out)
89 if existing
90 previous = TOML.parsefile(joinpath(out,"metadata.toml"))
91 previous["source_fingerprint_sha256"] == source_fingerprint() ||
92 error("checkpoint source fingerprint differs; migration requires explicit review")
93 _validate_restart_environment(previous["environment"],env)
94 end
95 metadata = _run_metadata(c,env)
96 function write_toml(path,payload)
97 open(io->TOML.print(io,payload;sorted=true),path*".tmp","w")
98 mv(path*".tmp",path;force=true)
99 end
100 write_toml(joinpath(out,"metadata.toml"),metadata)
101 write_toml(joinpath(out,"resolved-environment.toml"),env)
102 active = Base.active_project()
103 if active !== nothing
104 cp(active,joinpath(out,"Project.toml");force=true)
105 manifest = joinpath(dirname(active),"Manifest.toml")
106 isfile(manifest) && cp(manifest,joinpath(out,"Manifest.toml");force=true)
107 end
108 # Prevent unrelated shell MHD options from silently modifying the science case.
109 child_env = Dict(k=>v for (k,v) in ENV if !startswith(k,"MHD_"))
110 merge!(child_env,env)
111 project = dirname(something(Base.active_project(),joinpath(PACKAGE_ROOT,"Project.toml")))
112 script = joinpath(PACKAGE_ROOT,"scripts","_production.jl")
113 cmd = setenv(`$(Base.julia_cmd()) --project=$project --threads=$(Threads.nthreads()) $script`,child_env)
114 println("PMFGLM run: ",out,"\nLog: ",joinpath(out,"run.log")); flush(stdout)
115 t0 = time()
116 try
117 open(joinpath(out,"run.log"),resuming ? "a" : "w") do io
118 process = run(pipeline(ignorestatus(cmd);stdout=io,stderr=io))
119 success(process) || error("production process exited with status $(process.exitcode); see $(joinpath(out,"run.log"))")
120 end
121 log = read(joinpath(out,"run.log"),String)
122 metadata["status"] = _completion_status(log,env)
123 metadata["mhd_solver"] == "ct" && merge!(metadata,_ct_initial_faces(log))
124 catch error
125 metadata["status"] = "FAILED"
126 metadata["error"] = sprint(showerror,error)
127 rethrow()
128 finally
129 metadata["wall_seconds"] = time()-t0
130 metadata["finished_utc"] = string(now(UTC))
131 write_toml(joinpath(out,"metadata.toml"),metadata)
132 end
133 (; output_dir=out, status=metadata["status"], wall_seconds=metadata["wall_seconds"])
134 end