# RGT/APC Gap-Constructive Solver

This solver is a runnable reproduced solver for `revisit_constellation`.

It combines repeating-ground-track/access-profile-constellation design ideas from Zhang and Lee with the freshness-aware constructive scheduling pattern from Mercado-Martinez, Soret, and Jurado-Navas, adapted to the benchmark's public case and solution contract.

## Citation

```bibtex
@article{zhang2018leo,
  title = {LEO Constellation Design Methodology for Observing Multi-Targets},
  author = {Zhang, Chen and Jin, Jin and Kuang, Linling and Yan, Jian},
  journal = {Astrodynamics},
  volume = {2},
  number = {2},
  pages = {121--131},
  year = {2018},
  doi = {10.1007/s42064-017-0015-4}
}

@article{lee2020apc,
  title = {Satellite Constellation Pattern Optimization for Complex Regional Coverage},
  author = {Lee, Hang Woon and Shimizu, Seiichi and Yoshikawa, Shoji and Ho, Koki},
  journal = {Journal of Spacecraft and Rockets},
  volume = {57},
  number = {6},
  pages = {1309--1327},
  year = {2020},
  doi = {10.2514/1.A34657},
  eprint = {1910.00672},
  archivePrefix = {arXiv}
}

@article{mercado2025energyconstructive,
  title = {Scheduling Agile Earth Observation Satellites with Onboard Processing and Real-Time Monitoring},
  author = {Mercado-Martinez, Antonio M. and Soret, Beatriz and Jurado-Navas, Antonio},
  year = {2025},
  eprint = {2506.11556},
  archivePrefix = {arXiv}
}
```

The solver is standalone. It reads `assets.json` and `mission.json`, writes a benchmark solution JSON, and does not import or execute benchmark, experiment, runtime, or other solver internals.

## Method Summary

The pipeline is:

1. Load the public `revisit_constellation` case files.
2. Construct deterministic J2-aware repeat-ground-track shells inside the case altitude bounds, then expand accepted shells into APC-style RAAN and phase candidates.
3. Sample candidate-target access profiles and group visible samples into observation opportunities.
4. Greedily select the output satellites from that pool by benchmark-style marginal improvement to revisit-gap timelines plus deterministic coverage-diversity ties.
5. Build observation actions with Mercado-style freshness, assignment flexibility, and opportunity-cost priorities.
6. Run solver-local validation, deterministic insertion/removal repair, and bounded high-gap local search.
7. Emit the local-search schedule as `solution.json` and retain no-op, FIFO, constructive, repaired, and local-search mode comparisons as debug evidence.

The final action set contains only `observation` actions. Satellite states are Cartesian GCRF states at mission start.

## Benchmark Adaptation

The benchmark differs from the papers in several important ways:

- Zhang and Lee operate primarily at the constellation/access-profile design layer. The benchmark scores scheduled observation midpoints, so this solver uses RGT/APC access timelines as candidate-design evidence and then schedules concrete observations.
- Lee's APC formulation uses circular convolution between seed access profiles, constellation pattern vectors, and coverage timelines. This solver reproduces the shifted-access-profile idea with bounded phase slots, but does not solve Lee's BILP coverage-satisfaction model.
- Mercado's AoI freshness is adapted to benchmark midpoint revisit gaps. The current target freshness is the target's largest boundary-inclusive gap from mission start, existing observation midpoints, and mission end.
- Assignment flexibility is the count of remaining locally feasible observation options for the target.
- Opportunity cost is the quality-weighted freshness profit of locally conflicting options that would be blocked by choosing an observation.
- The benchmark's hard validity rules require geometry, non-overlap, slew/settle, and battery feasibility. The solver checks these locally and then relies on the benchmark verifier for the authoritative result.

APC visibility/access timelines are not final scheduled observations. They are candidate opportunities. The emitted `solution.json` uses the local-search schedule after repair.

## Solver Contract

```bash
./setup.sh
./solve.sh <case_dir> [config_dir] [solution_dir]
```

`setup.sh` is a no-op when using the project environment.

`solve.sh` writes:

- `solution.json`: primary benchmark solution
- `status.json`: solver summary, timings, local validation, mode comparison, and paper-to-benchmark adaptation notes
- `debug/*`: detailed debug artifacts

## RGT/APC Orbit Library

The orbit library searches integer repeat-day/revolution shells with a secular J2 repeat-ground-track condition, filters shells against the case's initial-orbit altitude bounds, scores analytical closure, and expands accepted shells into deterministic RAAN and mean-anomaly phase slots. Candidates are capped by `orbit_library.max_candidates`, which is intentionally separate from the benchmark's final satellite-output cap.

The default `minmax_architecture` search mode interleaves accepted RGT shells, target-derived inclination bands, and balanced RAAN/mean-anomaly phase slots before the candidate cap binds. This keeps the Lee-style APC idea of shifted access profiles while avoiding the earlier behavior where one nearby base orbit could exhaust the whole candidate cap. `target_diversified` and `legacy_base_first` are available for direct comparison with earlier enumerations.

The solver reports shell-level analytical closure in `debug/closure_search.json` and selected/emitted numerical J2 closure audits in `debug/selected_emitted_closure_audit.json`. The analytical closure evidence is used during shell construction; the numerical audit is reported as diagnostic evidence for the emitted satellites.

When no RGT candidate survives the altitude bounds, the solver falls back to a small deterministic circular-altitude grid. This fallback is reported in `status.json`; it is a robustness path, not a claim of APC optimality.

## Gap-Aware Satellite Selection

Candidate satellites are selected greedily from the larger candidate pool. Each round adds the candidate whose opportunity timeline most improves the benchmark-shaped score:

- capped maximum revisit gap averaged across targets
- worst-target capped maximum revisit gap as a diagnostic tie
- raw maximum revisit gap
- target count above 12 h
- threshold violation count

All gap calculations are boundary-inclusive and use observation midpoints, matching the benchmark scoring convention. Mean revisit gap is reported as a diagnostic only; it is not used as a meaningful optimization objective because adjacent observations can reduce the arithmetic mean without reducing long outages. When scores tie, the selector uses deterministic diversity ties: new target coverage, total target coverage, new latitude-band coverage, phase spread from already selected satellites, and finally candidate ID. The default experiment config continues selecting deterministic support satellites up to the case satellite cap after the optimistic selected envelope stops improving, because the scheduler can use those extra platforms to realize the envelope under hard local feasibility constraints.

## Constructive Scheduling And Repair

The scheduler first builds one observation option per visibility window, anchored near the best local off-nadir/range sample. It then chooses observations by:

- freshness: largest current target revisit gap
- flexibility: fewer remaining target options first
- opportunity cost: lower conflict profit loss first
- deterministic ties: timestamps, satellite IDs, target IDs, and window IDs

Solver-local validation checks unknown references, duration, sampled geometry, same-satellite overlap, required slew/settle gaps, and conservative battery risk.

Repair is deterministic. It removes locally invalid or risky observations with the lowest score damage, then tries to insert feasible observations for high-gap targets. A bounded local-search pass then considers deterministic high-gap insertions and one-for-one swaps. Moves are accepted only when they improve the benchmark-shaped priority order: capped maximum gap averaged across targets, worst-target capped maximum gap, raw maximum gap, target count above 12 h, and threshold violations. The emitted solution uses the local-search mode. The no-op, FIFO, unrepaired constructive, and repaired modes are retained only for reproduction-fidelity diagnostics.

## Configuration

The solver reads optional config from either:

- `<config_dir>/config.yaml`
- `<config_dir>/config.yml`

See [config.example.yaml](./config.example.yaml) for a complete example.

Config files may either provide one direct config, as in [config.example.yaml](./config.example.yaml), or declare `active_profile` plus named `profiles`. When profiles are used, the solver deep-merges the active profile into the shared config and records the resolved settings in `status.json`, `debug/run_profile_summary.json`, and `debug/parameter_sweep_summary.json`. Broader compute envelopes belong in experiment configs rather than the solver-local example.

Key knobs:

- `active_profile`
- `profiles.<name>`
- `parameter_sweep.points`
- `orbit_library.max_candidates`
- `orbit_library.search_mode`
- `orbit_library.max_rgt_days`
- `orbit_library.min_revolutions_per_day`
- `orbit_library.max_revolutions_per_day`
- `orbit_library.raan_slot_count`
- `orbit_library.phase_slot_count`
- `orbit_library.max_shells`
- `orbit_library.max_closure_error_m`
- `orbit_library.j2_closure_tolerance_m`
- `orbit_library.j2_refinement_iterations`
- `orbit_library.fallback_altitude_count`
- `visibility.sample_step_sec`
- `visibility.max_windows`
- `visibility.keep_samples_per_window`
- `visibility.worker_count`
- `selection.max_selected_satellites`
- `selection.require_positive_improvement`
- `scheduling.max_actions`
- `scheduling.max_actions_per_target`
- `scheduling.observation_margin_sec`
- `scheduling.transition_gap_sec`
- `scheduling.require_positive_gap_improvement`
- `scheduling.enforce_simple_energy_budget`
- `scheduling.enable_repair`
- `scheduling.repair_max_iterations`
- `scheduling.enable_local_search`
- `scheduling.local_search_max_iterations`
- `scheduling.local_search_options_per_target`
- `scheduling.local_search_removals_per_option`

Lower visibility sample steps improve opportunity fidelity but increase runtime. `visibility.worker_count: null` auto-selects a bounded candidate-parallel worker count; use `1` for serial deterministic visibility construction. `transition_gap_sec: null` uses a conservative case-derived bang-coast-bang slew/settle gap during option conflict checks.

## Debug Artifacts

Every run writes:

- `debug/orbit_candidates.json`: generated candidate satellite states
- `debug/closure_search.json`: accepted/rejected J2 RGT shell diagnostics
- `debug/selected_emitted_closure_audit.json`: selected/emitted numerical J2 closure diagnostics
- `debug/visibility_windows.json`: sampled candidate-target access windows
- `debug/selection_rounds.json`: greedy satellite-selection rounds and marginal improvements
- `debug/target_coverage.json`: target-level candidate and selected coverage before scheduling
- `debug/candidate_coverage.json`: candidate-level target/opportunity coverage diagnostics
- `debug/scheduling_decisions.json`: constructive scheduling decisions, priorities, scores, and improvements
- `debug/scheduling_rejections.json`: skipped options and solver-local reasons
- `debug/local_validation.json`: final local hard-validity and high-gap report
- `debug/repair_steps.json`: deterministic removal/insertion repair log
- `debug/local_search_moves.json`: accepted and rejected bounded local-search moves
- `debug/scheduling_summary.json`: compact option, action, rejection, repair, high-gap, and mode counts
- `debug/baseline_summary.json`: compact profiling, mode, target coverage, and high-gap evidence for comparisons across runs
- `debug/opportunity_envelope.json`: all-generated, closure-filtered, selected, hard-feasible, and final schedule envelope metrics
- `debug/high_gap_intervals.json`: per-target high-gap interval and blocker diagnostics
- `debug/run_profile_summary.json`: active profile, available profiles, and resolved compute-critical knobs
- `debug/parameter_sweep_summary.json`: optional sweep points and their resolved knobs
- `debug/mode_comparison.json`: solver-local no-op, FIFO, constructive, repaired, and local-search comparison metrics
- `debug/adaptation_notes.json`: paper concepts mapped to benchmark mechanics

These artifacts are intended to answer:

- whether candidate coverage exists before scheduling
- which satellites improved the revisit timeline
- why a target remained high-gap or unobserved
- whether repair or local search changed the constructive solution
- how FIFO/no-op compare with the constructive, repaired, and local-search modes
- which paper components are reproduced and which are benchmark adaptations

## Running It

Direct setup:

```bash
./solvers/revisit_constellation/rgt_apc_gap_constructive/setup.sh
```

Direct solve on a public smoke case:

```bash
./solvers/revisit_constellation/rgt_apc_gap_constructive/solve.sh \
  benchmarks/revisit_constellation/dataset/cases/test/case_0001
```

Direct solve with a config directory:

```bash
./solvers/revisit_constellation/rgt_apc_gap_constructive/solve.sh \
  benchmarks/revisit_constellation/dataset/cases/test/case_0001 \
  /path/to/config_dir \
  /tmp/revisit_rgt_apc_solution
```

Smoke verification through `main_solver`:

```bash
uv run python experiments/main_solver/run.py \
  --benchmark revisit_constellation \
  --solver revisit_constellation_rgt_apc_gap_constructive \
  --case test/case_0001
```

Aggregate experiment results:

```bash
uv run python experiments/main_solver/aggregate.py
```

## Sanity Baseline

The literature reports coverage and AoI-style scheduling behavior, not benchmark `capped_max_revisit_gap_hours` on these public cases. Treat the papers as method references, not as a numeric target table.

What matters here is:

- benchmark verification passes
- selected satellite count respects the case cap
- local validation is clean before benchmark verification
- constructive/repaired modes improve the primary capped-max metric over no-op
- repair does not collapse the schedule
- high-gap and unobserved targets are visible in debug summaries

On the latest recorded smoke run (`test/case_0001`), the benchmark verifier passed with 18 satellites, 147 observation actions, no hard-validity violations, and `capped_max_revisit_gap_hours = 9.345108695652174`. The all-generated, closure-filtered, and selected envelopes were all `8.876086956521739 h`, while hard local feasibility and the final schedule were `9.345108695652174 h`. The remaining smoke loss is therefore scheduling and hard-feasibility limited, not selected-envelope limited.

The same smoke run has `max_revisit_gap_hours = 11.966666666666667`, zero targets above 12 h, and 12 targets above their stricter 8 h expected revisit period. The solver remains a valid adapted reproduction baseline, not a solved benchmark optimum.

## Known Limitations

- This is a faithful method-family reproduction adapted to the benchmark, not a reproduction of every table or exact optimization model in Zhang, Lee, or Mercado.
- The Lee APC/BILP coverage-satisfaction model is not solved exactly; RGT/APC is used as deterministic candidate generation and access-profile evidence.
- The solver uses analytical J2 shell closure for construction and reports a selected/emitted numerical J2 audit. On the latest smoke run, selected numerical closure residuals were still hundreds of kilometers, so the numerical audit should be treated as an honest diagnostic rather than proof of benchmark-propagator closure.
- The solver uses circular J2-RGT or fallback circular candidates only; it may miss asymmetric non-RGT or elliptical designs that score better.
- Visibility windows are sampled, so very short opportunities can be missed or approximated.
- Battery feasibility is handled by conservative solver-local validation and repair, while the benchmark verifier remains authoritative.
- Full public-case runs are slower than the focused smoke case because visibility sampling dominates runtime. Public-case timing and validity evidence belong in experiment configs and results, not in the solver registry.

## Evidence And Registry Status

`experiments/main_solver` records this as `evidence_type: reproduced_solver`. `solvers/finished_solvers.json` records only solver-contract CI metadata; the solver is registered there with `repro_ci: false` because full reproduction runs are comparatively expensive, while solver-local tests are exposed through `test.sh`.
