43HOT_D, HOT_D_RANGE, HOT_N = 12.5, (10.0, 15.0), 8.0e21
56 ap = argparse.ArgumentParser()
57 ap.add_argument(
"--rows", type=Path, required=
True)
58 ap.add_argument(
"--design", type=Path, default=HERE /
"design/T9_crossover.csv")
59 ap.add_argument(
"--top", type=int, default=15)
60 a = ap.parse_args(argv)
62 theta_of, param_of = {}, {}
63 for d
in csv.DictReader(a.design.open()):
64 theta_of[int(d[
"row_id"])] = int(d[
"theta_id"])
65 param_of[int(d[
"theta_id"])] = d
67 rows = [json.loads(l)
for l
in a.rows.read_text().splitlines()
if l.strip()]
68 by_theta: dict[int, dict] = {}
70 t = theta_of.get(r.get(
"row_id"))
73 by_theta.setdefault(t, {})[r.get(
"condition")] = r
75 print(f
"{len(rows)} rows -> {len(by_theta)} theta vectors "
76 f
"({sum(1 for v in by_theta.values() if len(v) == 2)} with BOTH ends)\n")
79 d111 = [r[
"d_111_nm"]
for r
in rows
if isinstance(r.get(
"d_111_nm"), (int, float))]
81 over = [x
for x
in d111
if x >= D_MIN_TEM]
82 print(f
"d_111 vs the {D_MIN_TEM} nm TEM cutoff over {len(d111)} rows: "
83 f
"min={min(d111):.2f} median={sorted(d111)[len(d111)//2]:.2f} "
84 f
"max={max(d111):.2f} nm")
85 print(f
" rows clearing the cutoff: {len(over)}/{len(d111)} "
86 f
"({100*len(over)/len(d111):.0f} %)"
87 + (
" <-- the widened box DOES move it" if over
else
88 " <-- STRUCTURAL: 1/2<111> invisible everywhere, character"
93 for t, ends
in by_theta.items():
94 c, h = ends.get(COLD), ends.get(HOT)
97 fc, fh = c.get(
"f_100_tem_1"), h.get(
"f_100_tem_1")
98 if fc
is None or fh
is None:
100 cold_ok, hot_ok = fc < 0.5, fh > 0.5
103 margin = min(0.5 - fc, fh - 0.5)
107 terms.append(abs(math.log10(dh / HOT_D)))
108 nh = (h.get(
"N_100_vis_1")
or 0) + (h.get(
"N_111_vis_1")
or 0)
110 terms.append(abs(math.log10(nh / HOT_N)))
112 if dc
and dc > COLD_D_MAX:
113 terms.append(abs(math.log10(dc / COLD_D_MAX)))
114 nc = (c.get(
"N_100_vis_1")
or 0) + (c.get(
"N_111_vis_1")
or 0)
115 if 0 < nc < COLD_N_MIN:
116 terms.append(abs(math.log10(nc / COLD_N_MIN)))
117 size_misfit = sum(terms)
if terms
else float(
"nan")
118 penalty = 0.0
if (cold_ok
and hot_ok)
else 1.0
119 scored.append((penalty + size_misfit, cold_ok, hot_ok, margin,
120 size_misfit, t, c, h))
122 scored.sort(key=
lambda x: (math.isnan(x[0]), x[0]))
123 both = [s
for s
in scored
if s[1]
and s[2]]
124 print(f
"theta vectors reproducing the CROSSOVER (cold<0.5 AND hot>0.5): "
125 f
"{len(both)}/{len(scored)}\n")
127 print(f
"{'theta':>6} {'score':>7} {'cold f100':>10} {'hot f100':>9} "
128 f
"{'d_mean C':>9} {'d_mean H':>9} {'N_hot':>10} {'cross?':>7}")
129 for s
in scored[:a.top]:
130 tot, ck, hk, mg, sm, t, c, h = s
131 nh = (h.get(
"N_100_vis_1")
or 0) + (h.get(
"N_111_vis_1")
or 0)
133 print(f
"{t:6d} {tot:7.3f} {c['f_100_tem_1']:10.4f} {h['f_100_tem_1']:9.4f} "
134 f
"{(dc or float('nan')):9.2f} {(dh or float('nan')):9.2f} {nh:10.2e} "
135 f
"{'YES' if (ck and hk) else ('cold' if ck else ('hot' if hk else 'no')):>7}")
138 print(
"\nCROSSOVER-CORRECT vectors, parameter values "
139 "(* = outside the original elicited prior):")
140 meta = json.loads(a.design.with_suffix(
".meta.json").read_text())
141 samp = meta[
"sampled"]
146 for k, v
in samp.items():
148 op = v.get(
"original_prior")
149 star =
"*" if (op
and not (op[0] <= val <= op[1]))
else ""
150 bits.append(f
"{k}={val:.4g}{star}")
151 print(f
" theta {t}: " +
" ".join(bits))