"""F5-dev-01: Cobb-Douglas consumer - closed form + independent numerical validation. Run from workspace root: ~/wp29/venv/bin/python out/analysis.py Deterministic; no randomness used (random_seed = null).""" import json, math, os import numpy as np import pandas as pd from scipy.optimize import minimize_scalar, minimize DATA = "data/parameters.csv" OUT = "out" raw = pd.read_csv(DATA) n_raw = len(raw) issues = [] dups = raw["parameter"][raw["parameter"].duplicated()].tolist() if dups: issues.append({"issue": f"duplicate parameters {dups}", "handling": "first occurrence used"}) p = raw.drop_duplicates("parameter").set_index("parameter")["value"].astype(float).to_dict() required = ["alpha", "p1", "p2", "m"] missing = [k for k in required if k not in p or not math.isfinite(p[k])] if missing: raise SystemExit(f"missing/non-finite parameters: {missing}") a, p1, p2, m = (p[k] for k in required) assert 0 < a < 1 and p1 > 0 and p2 > 0 and m > 0, "parameters outside closed-form validity range" def u(x1, x2): return a * np.log(x1) + (1 - a) * np.log(x2) # ---- closed form (Lagrangian FOCs: a/x1 = lam p1, (1-a)/x2 = lam p2, budget binds -> lam = 1/m) x1 = a * m / p1 x2 = (1 - a) * m / p2 v = float(u(x1, x2)) resid = p1 * x1 + p2 * x2 - m mrs = (a / x1) / ((1 - a) / x2) price_ratio = p1 / p2 share1 = p1 * x1 / m # ---- numerical validation (does NOT use the closed form) # (a) bounded 1-D maximisation along the budget line x2 = (m - p1 x1)/p2 f = lambda t: -u(t, (m - p1 * t) / p2) eps = 1e-12 * m / p1 r1 = minimize_scalar(f, bounds=(eps, m / p1 - eps), method="bounded", options={"xatol": 1e-12, "maxiter": 1000}) # (b) dense grid on the budget line grid = np.linspace(m / p1 * 1e-6, m / p1 * (1 - 1e-6), 2_000_001) ug = u(grid, (m - p1 * grid) / p2) ig = int(np.argmax(ug)) # (c) 2-D SLSQP with inequality budget constraint (starts at an arbitrary feasible point) r3 = minimize(lambda z: -u(z[0], z[1]), x0=[0.1 * m / p1, 0.1 * m / p2], method="SLSQP", bounds=[(1e-9, None), (1e-9, None)], constraints=[{"type": "ineq", "fun": lambda z: m - p1 * z[0] - p2 * z[1]}], options={"ftol": 1e-12, "maxiter": 1000}) v_num = float(max(-r1.fun, ug[ig], -r3.fun)) validation = { "bounded_scalar": {"x1": float(r1.x), "x2": float((m - p1 * r1.x) / p2), "u": float(-r1.fun), "success": bool(r1.success)}, "grid": {"n_points": int(grid.size), "x1": float(grid[ig]), "x2": float((m - p1 * grid[ig]) / p2), "u": float(ug[ig])}, "slsqp_2d": {"x1": float(r3.x[0]), "x2": float(r3.x[1]), "u": float(-r3.fun), "success": bool(r3.success), "budget_slack": float(m - p1 * r3.x[0] - p2 * r3.x[1])}, "abs_diff_v_closed_vs_numerical": abs(v - v_num), } results = { "task_id": "F5-dev-01", "x1_demand": x1, "x2_demand": x2, "indirect_utility": v, "budget_identity_residual": resid, "mrs_at_optimum": mrs, "price_ratio": price_ratio, "expenditure_share_good1": share1, "indirect_utility_numerical": v_num, "feasibility": "complete", "feasibility_reason": None, "data_issues": issues, "n_input_rows": {"parameters.csv": n_raw}, "n_analysed": len(p), "random_seed": None, "numerical_validation": validation, } os.makedirs(OUT, exist_ok=True) with open(os.path.join(OUT, "results.json"), "w") as fh: json.dump(results, fh, indent=2, sort_keys=False) pd.DataFrame([{"step": "load", "rule": "read data/parameters.csv; drop duplicate parameter names; require finite alpha,p1,p2,m", "n_before": n_raw, "n_after": len(p), "n_removed": n_raw - len(p), "rationale": "no rows removed unless duplicated"}]).to_csv(os.path.join(OUT, "exclusions.csv"), index=False) print(json.dumps(results, indent=2))