Constellation Design Simulation
A simulation framework for optimizing satellite constellation parameters.
Designing a single satellite's orbit is a solved, well-understood problem. Designing a constellation of them, dozens or hundreds of satellites working together for continuous coverage, is a different kind of problem entirely, because now every parameter you pick trades off against every other one, and the interactions aren't always intuitive. I built this simulator to actually explore that tradeoff space instead of hand-deriving coverage geometry for every candidate design one spreadsheet at a time.
Why this needed to be a simulator, not a spreadsheet
Altitude affects coverage footprint, revisit time, radiation exposure, and launch cost, all at once and not always in the direction you'd guess. Plane count and satellites per plane affect coverage uniformity and total cost. Inclination interacts with all of it in ways that are easy to get wrong by hand, a small change in inclination can disproportionately hurt coverage at high latitudes in a way that doesn't show up until you actually compute it. Doing this in a spreadsheet means manually deriving coverage geometry for every candidate design, and it's exactly the kind of process where cross-parameter interactions quietly get missed. I wanted something that could sweep a real parameter space and report the actual tradeoffs back, not something that required me to already know the answer before I could check it.
Three layers, each doing the part it's good at
The simulator splits cleanly into three pieces. An orbit propagation core in C++ handles two-body and J2-perturbed propagation for every satellite, using Walker-pattern generation to turn high-level constellation parameters into actual orbital elements for each satellite in the constellation. An analysis layer in Python takes the ephemeris that core exports and computes ground tracks, coverage, sunlight and eclipse exposure, and revisit-time statistics, where Python's plotting and numerical tooling genuinely earns its keep. On top of both sits an optimization driver that sweeps the design space against a coverage-and-cost objective and surfaces Pareto-efficient designs, the actual frontier of good tradeoffs, rather than pretending there's one single best answer to hand back.
Propagation is the performance-critical inner loop here, run once per satellite per timestep across every candidate design in a sweep, so it stays in C++ where that cost is manageable. Everything downstream of the raw ephemeris, where the interesting analysis and visualization work happens, lives in Python, because that's where the right tools already exist and reimplementing them in C++ would have bought nothing.
Getting from parameters to a real constellation
Orbital elements come from standard Walker constellation notation (i:T/P/F, inclination, total satellites, planes, phasing factor), then get propagated with a J2-perturbed model so that secular drift, nodal regression and apsidal rotation, stays consistent across the whole simulation window instead of silently accumulating error. Ground coverage is computed by discretizing Earth's surface into a grid and, at every timestep, checking each satellite's visibility to each grid point against a minimum elevation angle, the same geometric test any single-satellite pass-prediction tool uses, just run across an entire constellation and grid simultaneously. A cylindrical shadow model flags eclipse periods per satellite too, which feeds directly into power-system sizing conversations even though power modeling itself was out of scope for this project.
The two problems that actually slowed me down
Performance was the first one. Naive nested loops over satellites, time steps, and ground points scale badly, and a full parameter sweep across dozens of candidate constellations turns into an overnight batch job fast if you're not careful. Vectorizing the coverage computation and keeping propagation itself in C++ instead of Python was what turned a full sweep into something interactive enough to actually iterate on.
Presentation was the second, and it surprised me more. Raw coverage numbers, a table of percentages and revisit times, don't communicate a tradeoff on their own, even to someone who fully understands orbital mechanics. Comparative plots, coverage against altitude, coverage against satellite count, made the actual tradeoffs legible in a way the raw numbers never did, for technical and non-technical reviewers alike. It was a genuine reminder that simulation work is as much about designing outputs someone can act on as it is about getting the physics right in the first place.
What it's good for
The framework reproduces the coverage trends you'd expect (diminishing returns per added satellite at a fixed altitude, for instance) and, more usefully, makes it possible to lay candidate constellations side by side and compare them in minutes instead of days of manual analysis. Keeping the performance-critical propagation code cleanly separated from the analysis and visualization code paid off directly. Each layer got to use whichever language and toolset actually fit the job, instead of one language doing double duty and doing neither job especially well.
Where I'd take it next: J4 and drag perturbation terms for longer-duration simulations where secular drift matters more, ground-station downlink windows folded into the optimizer alongside coverage, and inter-satellite link feasibility for constellations that are meant to work as a mesh network rather than independent satellites sharing an orbit shell.