Skip to content

Commit 5b3a781

Browse files
committed
Add report generation functionality and CI configuration
- Implement report generation with `ccw-report` command. - Create report configuration and writing functions. - Add tests for report generation and validation. - Configure GitHub Actions for running tests on multiple Python versions.
1 parent 6fd5e57 commit 5b3a781

File tree

7 files changed

+321
-0
lines changed

7 files changed

+321
-0
lines changed

.github/workflows/pytest.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: pytest
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
test:
9+
runs-on: ubuntu-latest
10+
strategy:
11+
fail-fast: false
12+
matrix:
13+
python-version: ["3.10", "3.11", "3.12"]
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Set up Python
19+
uses: actions/setup-python@v5
20+
with:
21+
python-version: ${{ matrix.python-version }}
22+
23+
- name: Install
24+
run: |
25+
python -m pip install --upgrade pip
26+
python -m pip install -e ".[dev]"
27+
28+
- name: Run tests
29+
run: |
30+
python -m pytest

docs/TASKS.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Task list: next steps (cosmological constant workbench)
2+
3+
As of Jan 10, 2026:
4+
- We have **not** solved the cosmological constant problem.
5+
- We *have* built a reproducible baseline + toy-mechanism/sweep framework.
6+
- The next steps are to (1) harden reproducibility, (2) encode falsifiable constraints, and (3) only then explore mechanisms.
7+
8+
## Status legend
9+
10+
- `[ ]` not started
11+
- `[-]` in progress
12+
- `[x]` complete
13+
14+
---
15+
16+
## Phase A — Reproducibility & auditability (researcher-friendly)
17+
18+
1. [x] Add CI to run tests on GitHub Actions.
19+
- Validate: clean install + `pytest` on at least Python 3.10–3.12.
20+
21+
2. [x] Add a `ccw-report` command that produces a small, paper-friendly summary (JSON + Markdown):
22+
- Baseline observed $(\rho_\Lambda, \Lambda)$
23+
- Naive QFT estimates for multiple cutoffs
24+
- Optional mechanism sweep summary (min/max, ratio vs observed)
25+
- Validate: deterministic outputs with fixed inputs.
26+
27+
3. [x] Add “known-number” regression tests against standard reference values:
28+
- Example: $(H_0, \Omega_\Lambda)=(67.4,0.6889)$ should yield $\rho_\Lambda\sim 6\times 10^{-10}$ J/m³ and $\Lambda\sim 10^{-52}$ m⁻².
29+
- Validate: tight enough to catch accidental unit regressions, loose enough to avoid false precision.
30+
31+
---
32+
33+
## Phase B — Constraints (what any proposal must satisfy)
34+
35+
4. [ ] Add a minimal cosmology observable layer (flat FRW):
36+
- Implement $H(z)$ for ΛCDM and for mechanisms providing $\rho_{DE}(z)$.
37+
- Add distances: comoving distance and luminosity distance.
38+
- Validate: sanity checks vs ΛCDM limits.
39+
40+
5. [ ] Encode “must-not-break” constraints as unit tests (model-agnostic):
41+
- $\rho_{DE}(z) > 0$ over a chosen redshift range (configurable)
42+
- If a mechanism provides $w(z)$, ensure it stays within declared bounds
43+
- Smoothness/continuity checks (no singularities for default params)
44+
45+
---
46+
47+
## Phase C — Mechanism deepening (only after A+B)
48+
49+
6. [ ] Replace the CPL-only quintessence placeholder with an actual scalar-field evolution (toy but explicit):
50+
- Choose 1–2 potentials (e.g., exponential, inverse power-law)
51+
- Integrate background evolution (ODE) to get $\rho_{DE}(z)$ and $w(z)$
52+
- Validate: reproduces ΛCDM-like behavior in an appropriate parameter limit.
53+
54+
7. [ ] Add “sequestering-like” and “unimodular-like” *explicit* toy bookkeeping that yields a derived residual (not just a constant addend):
55+
- Keep it transparent; no hidden tuning.
56+
57+
---
58+
59+
## Phase D — Integration + comparison
60+
61+
8. [ ] Add an optional adapter that can *compare* against outputs from `lqg-cosmological-constant-predictor` (without making it a dependency).
62+
- Validate: adapter is guarded and never breaks core install.
63+
64+
---
65+
66+
## Paper readiness gate
67+
68+
We only start a paper draft if we can claim one of the following, with reproducible evidence:
69+
- A mechanism that **suppresses vacuum energy gravitation** without fine-tuning and produces at least one **distinctive, testable prediction**.
70+
- A **new constraint** (e.g., a bound on a parameter family) derived from null results / consistency requirements.
71+
72+
Right now: **no novel discovery** suitable for a strong paper claim.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dev = ["pytest>=7.4"]
2929
[project.scripts]
3030
ccw-baseline = "ccw.cli:main"
3131
ccw-sweep = "ccw.cli_sweep:main"
32+
ccw-report = "ccw.cli_report:main"
3233

3334
[tool.setuptools.packages.find]
3435
where = ["src"]

src/ccw/cli_report.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
import json
5+
from pathlib import Path
6+
7+
from .report import ReportConfig, generate_report, write_report
8+
9+
10+
def _parse_grid_json(grid_json: str | None) -> list[dict] | None:
11+
if grid_json is None:
12+
return None
13+
obj = json.loads(grid_json)
14+
if not isinstance(obj, list) or any(not isinstance(x, dict) for x in obj):
15+
raise ValueError("--grid-json must be a JSON array of objects")
16+
return obj
17+
18+
19+
def build_parser() -> argparse.ArgumentParser:
20+
p = argparse.ArgumentParser(description="ccw report: baseline + optional mechanism summary")
21+
p.add_argument("--h0", type=float, default=67.4)
22+
p.add_argument("--omega-lambda", type=float, default=0.6889)
23+
p.add_argument("--omega-m", type=float, default=0.3111)
24+
p.add_argument("--omega-r", type=float, default=0.0)
25+
p.add_argument("--omega-k", type=float, default=0.0)
26+
27+
p.add_argument("--cutoffs", type=str, default="electroweak,1TeV,planck", help="Comma-separated cutoff names")
28+
29+
p.add_argument("--mechanism", type=str, default=None, help="Optional: cpl | rvm | unimodular | sequestering")
30+
p.add_argument("--grid-json", type=str, default=None, help="Optional JSON array of mechanism param dicts")
31+
p.add_argument("--z", type=str, default="0,1,2", help="Comma-separated z values")
32+
33+
p.add_argument("--out-json", type=str, default="results/report.json")
34+
p.add_argument("--out-md", type=str, default="results/report.md")
35+
return p
36+
37+
38+
def main(argv: list[str] | None = None) -> None:
39+
args = build_parser().parse_args(argv)
40+
41+
cutoffs = tuple(s.strip() for s in str(args.cutoffs).split(",") if s.strip())
42+
z_values = tuple(float(s.strip()) for s in str(args.z).split(",") if s.strip())
43+
44+
cfg = ReportConfig(
45+
h0_km_s_mpc=args.h0,
46+
omega_lambda=args.omega_lambda,
47+
omega_m=args.omega_m,
48+
omega_r=args.omega_r,
49+
omega_k=args.omega_k,
50+
cutoffs=cutoffs,
51+
mechanism=args.mechanism,
52+
grid=_parse_grid_json(args.grid_json),
53+
z_values=z_values,
54+
)
55+
56+
report = generate_report(cfg)
57+
write_report(report, Path(args.out_json), Path(args.out_md))
58+
print(f"Wrote {args.out_json} and {args.out_md}")

src/ccw/report.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from dataclasses import asdict, dataclass
5+
from pathlib import Path
6+
from typing import Any, Dict, List, Optional
7+
8+
from .baseline import BaselineInputs, BaselineOutputs, compute_baseline
9+
from .mechanisms import CosmologyBackground
10+
from .sweep import SweepRow, run_sweep
11+
12+
13+
@dataclass(frozen=True)
14+
class ReportConfig:
15+
h0_km_s_mpc: float = 67.4
16+
omega_lambda: float = 0.6889
17+
omega_m: float = 0.3111
18+
omega_r: float = 0.0
19+
omega_k: float = 0.0
20+
cutoffs: tuple[str, ...] = ("electroweak", "1TeV", "planck")
21+
22+
mechanism: Optional[str] = None
23+
grid: Optional[List[Dict[str, Any]]] = None
24+
z_values: tuple[float, ...] = (0.0, 1.0, 2.0)
25+
26+
27+
def _markdown_table(rows: List[Dict[str, Any]], headers: List[str]) -> str:
28+
lines = ["| " + " | ".join(headers) + " |", "| " + " | ".join(["---"] * len(headers)) + " |"]
29+
for r in rows:
30+
lines.append("| " + " | ".join(str(r.get(h, "")) for h in headers) + " |")
31+
return "\n".join(lines)
32+
33+
34+
def _stringify_params(rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
35+
out: List[Dict[str, Any]] = []
36+
for r in rows:
37+
r2 = dict(r)
38+
if isinstance(r2.get("params"), dict):
39+
r2["params"] = json.dumps(r2["params"], sort_keys=True)
40+
out.append(r2)
41+
return out
42+
43+
44+
def generate_report(cfg: ReportConfig) -> Dict[str, Any]:
45+
bg = CosmologyBackground(
46+
h0_km_s_mpc=cfg.h0_km_s_mpc,
47+
omega_lambda=cfg.omega_lambda,
48+
omega_m=cfg.omega_m,
49+
omega_r=cfg.omega_r,
50+
omega_k=cfg.omega_k,
51+
)
52+
53+
baselines: List[Dict[str, Any]] = []
54+
for c in cfg.cutoffs:
55+
out: BaselineOutputs = compute_baseline(
56+
BaselineInputs(
57+
h0_km_s_mpc=cfg.h0_km_s_mpc,
58+
omega_lambda=cfg.omega_lambda,
59+
cutoff_name=c,
60+
)
61+
)
62+
baselines.append({"cutoff": c, **out.naive_qft})
63+
64+
report: Dict[str, Any] = {
65+
"inputs": asdict(cfg),
66+
"observed": {
67+
"h0_km_s_mpc": cfg.h0_km_s_mpc,
68+
"omega_lambda": cfg.omega_lambda,
69+
"rho_lambda0_j_m3": bg.rho_lambda0_j_m3,
70+
},
71+
"naive_qft": baselines,
72+
}
73+
74+
if cfg.mechanism:
75+
grid = cfg.grid if cfg.grid is not None else [{}]
76+
rows: List[SweepRow] = run_sweep(mechanism=cfg.mechanism, grid=grid, z_values=cfg.z_values, bg=bg)
77+
report["mechanism"] = {
78+
"name": cfg.mechanism,
79+
"z_values": list(cfg.z_values),
80+
"grid": grid,
81+
"rows": [asdict(r) for r in rows],
82+
}
83+
84+
return report
85+
86+
87+
def write_report(report: Dict[str, Any], out_json: Path, out_md: Path) -> None:
88+
out_json.parent.mkdir(parents=True, exist_ok=True)
89+
out_md.parent.mkdir(parents=True, exist_ok=True)
90+
91+
out_json.write_text(json.dumps(report, indent=2, sort_keys=True))
92+
93+
md_lines: List[str] = []
94+
md_lines.append("# CCW report")
95+
md_lines.append("")
96+
md_lines.append("## Observed")
97+
md_lines.append(f"- H0: {report['observed']['h0_km_s_mpc']} km/s/Mpc")
98+
md_lines.append(f"- ΩΛ: {report['observed']['omega_lambda']}")
99+
md_lines.append(f"- ρΛ,0: {report['observed']['rho_lambda0_j_m3']:.3e} J/m³")
100+
md_lines.append("")
101+
102+
md_lines.append("## Naive QFT cutoffs")
103+
table_rows = [
104+
{
105+
"cutoff": r.get("cutoff"),
106+
"E_cut (J)": f"{r.get('e_cutoff_joule', 0.0):.3e}",
107+
"rho_naive (J/m³)": f"{r.get('rho_naive_j_m3', 0.0):.3e}",
108+
"ratio": f"{r.get('rho_naive_over_rho_lambda', 0.0):.3e}",
109+
}
110+
for r in report.get("naive_qft", [])
111+
]
112+
md_lines.append(_markdown_table(table_rows, ["cutoff", "E_cut (J)", "rho_naive (J/m³)", "ratio"]))
113+
md_lines.append("")
114+
115+
if "mechanism" in report:
116+
md_lines.append("## Mechanism")
117+
md_lines.append(f"- name: {report['mechanism']['name']}")
118+
md_lines.append("")
119+
md_lines.append(
120+
_markdown_table(
121+
_stringify_params(report["mechanism"]["rows"]),
122+
["mechanism", "z", "rho_de_j_m3", "w_de", "params"],
123+
)
124+
)
125+
126+
out_md.write_text("\n".join(md_lines) + "\n")

tests/test_baseline.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ def test_observed_lambda_reasonable_magnitude() -> None:
1414
assert 1e-53 < lam < 1e-51
1515

1616

17+
def test_observed_lambda_regression_band_planck2018_like() -> None:
18+
# A slightly tighter regression band to catch unit mistakes.
19+
out = compute_baseline(BaselineInputs(h0_km_s_mpc=67.4, omega_lambda=0.6889, cutoff_name="electroweak"))
20+
rho_lambda = out.observed["rho_lambda_j_m3"]
21+
lam = out.observed["lambda_m_inv2"]
22+
23+
# Typical reference values are around:
24+
# ρ_Λ ~ 6e-10 J/m^3 and Λ ~ 1.1e-52 m^-2.
25+
assert 4e-10 < rho_lambda < 9e-10
26+
assert 7e-53 < lam < 2e-52
27+
28+
1729
def test_naive_qft_cutoff_scaling_is_huge() -> None:
1830
out = compute_baseline(BaselineInputs(cutoff_name="planck"))
1931
ratio = out.naive_qft["rho_naive_over_rho_lambda"]

tests/test_report.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
5+
from ccw.report import ReportConfig, generate_report, write_report
6+
7+
8+
def test_generate_report_has_expected_keys(tmp_path: Path) -> None:
9+
cfg = ReportConfig(mechanism="cpl", cutoffs=("electroweak", "planck"), z_values=(0.0, 1.0))
10+
report = generate_report(cfg)
11+
12+
assert "observed" in report
13+
assert "naive_qft" in report
14+
assert isinstance(report["naive_qft"], list)
15+
16+
out_json = tmp_path / "r.json"
17+
out_md = tmp_path / "r.md"
18+
write_report(report, out_json, out_md)
19+
20+
assert out_json.exists()
21+
assert out_md.exists()
22+
assert out_md.read_text().startswith("# CCW report")

0 commit comments

Comments
 (0)