# Toy transit fit on a synthetic light curve

Status: offline simulation and fit. Every number below comes from synthetic
data generated inside this repository. The result is not a planet detection,
not a measurement of a real star, and not a claim about any TESS target.

## Why the committed light curve was not used

The repository already contains
`research/tess-timing/data/lightcurve-run/synthetic_lightcurve.csv`. It is the
twelve-row loader fixture described in
`research/tess-timing/src/tess_timing/synthetic.py`: twelve input rows, three
removed by the default quality mask, one non-finite flux, and eight retained
cadences. Its flux rises monotonically and contains no transit-shaped dip. It
is useful for testing the loader and useless for a transit fit.

The task therefore generates a new, clearly labelled synthetic light curve. It
is saved with the fit products so the fit can be reproduced without the
generator.

## Synthetic data

`research/tess-timing/scripts/toy_transit_fit.py` generates the curve with a
seeded NumPy generator and writes
`research/tess-timing/data/transit-fit-run/synthetic_transit.csv`.

| Quantity | Value |
| --- | --- |
| Seed | 20261022 |
| Cadence | 120 s (2 min) |
| Baseline | 27.0 d |
| Cadences | 19441 |
| Injected mid-transit time | 3000.6 BTJD |
| Injected period | 3.5 d |
| Injected depth | 0.012 |
| Injected first-to-fourth contact duration | 0.16 d |
| Injected baseline offset | 2.0e-4 |
| Ingress plus egress fraction | 0.2 of the full duration |
| Gaussian noise sigma | 2.0e-4 per cadence |

The noise is white and the stated uncertainty equals the injected noise
sigma. No trend, correlated noise, dilution, limb darkening, or transit timing
variation is added. The CSV header is `time_btjd,flux,flux_err`.

## Model

The model is a trapezoid in relative flux:

```
F(t) = 1 + baseline - depth * shape(t)
```

The folded time is the time from the nearest transit centre,
`phase = ((t - t0) / P + 0.5) mod 1 - 0.5`, measured in days. With
`half_total = duration / 2` and
`half_flat = duration * (1 - ingress_fraction) / 2`, the shape is

```
shape = 1                                      if |phase| <= half_flat
shape = (half_total - |phase|) /               if half_flat < |phase| < half_total
        (half_total - half_flat)
shape = 0                                      if |phase| >= half_total
```

The flat bottom is therefore exactly `depth` below the out-of-transit flux.
With `ingress_fraction = 0` the model reduces to an exact box; the tests check
that limit.

This is not a physical transit model. It does not use the projected circular
orbit, limb darkening, or the Mandel-Agol equations. It is a deliberately
simple shape whose depth, duration, period, and timing can be checked by hand.

## Fit method

1. A box matched-filter search runs over periods from 1.0 to 8.0 d in steps of
   0.002 d and over five trial durations from 0.08 to 0.24 d. For each trial,
   the score is the weighted out-of-transit mean minus the weighted in-transit
   mean, divided by the standard error of that difference. The phase reference
   is the time of the deepest cadence in the light curve.
2. The best search period, duration, depth, and time initialize a
   `scipy.optimize.least_squares` fit.
3. The least-squares fit frees five parameters: `t0`, `period`, `depth`,
   `duration`, and `baseline_offset`. The ingress fraction is fixed at 0.2.
4. The covariance is `(J^T J)^-1` from the Jacobian at the optimum, scaled by
   the reduced chi-square of the flux residuals. The reported one-sigma
   uncertainties are the square roots of its diagonal.

Mid-transit time is defined only modulo the period. The fit returned an epoch
14.000011 d after the injected epoch, which is four periods later. The table
below shifts that epoch by four periods so it can be compared with the
injected time; the shift is an integer number of periods and does not change
the model.

## Results

Fit diagnostics: 19441 cadences, 920 in transit, 19436 degrees of freedom,
RMS 199.60 ppm, chi-square per degree of freedom 0.9962, optimizer converged
with status 2 (`ftol`).

| Parameter | Injected | Recovered | One-sigma uncertainty | Difference / sigma |
| --- | ---: | ---: | ---: | ---: |
| `t0` (BTJD) | 3000.6 | 3000.600030 | 0.0000366 | +1.48 |
| Period (d) | 3.5 | 3.499995 | 0.00000250 | -1.97 |
| Depth | 0.012 | 0.0119997 | 0.00000738 | -0.04 |
| Duration (d) | 0.16 | 0.1599928 | 0.0000455 | -0.16 |
| Baseline offset | 0.0002 | 0.00019933 | 0.00000147 | -0.46 |

The recovered values agree with the injected values within about two reported
sigma. That agreement is expected because the same trapezoid generated the data
and fitted it; it is a code-recovery check, not evidence that the model is
correct for a real transit.

The figure is `docs/figures/transit-fit.svg`, generated by the same script. The
top panel shows the full 27-day light curve in 10-minute display bins with the
fitted model. The bottom panel shows the phase-folded curve in 0.01-period bins
with the fit and the injected model.

## Assumptions

- The light curve is synthetic and the noise is white Gaussian noise with a
  known, constant sigma.
- The transit is a trapezoid with a fixed ingress plus egress fraction of 0.2.
- The out-of-transit flux is constant apart from the fitted baseline offset.
- The period is constant and there are no transit timing variations.
- The flux uncertainties supplied to the fit are correct.
- The orbital period lies inside the searched range, 1.0 to 8.0 d.
- The fit uncertainties are local, linearized, and derived from the
  least-squares Jacobian.

## Limitations

- The model is not a physical transit model. A real transit has curved ingress
  and egress, limb darkening, and a projected orbit; a trapezoid can bias depth
  and duration when fitted to real data.
- The period search is coarse. It only initializes the least-squares fit; it is
  not a periodogram detection statistic and its peak is not a significance.
- The covariance does not include correlated noise, red noise, or systematic
  errors. Real TESS photometry has correlated noise that this fit ignores.
- There is no dilution, blending, eccentricity, or grazing-transit treatment.
- A fit to a transit-like dip is not a planet detection. No such claim is made
  here, and no real target is identified.

## Reproduce

```bash
python research/tess-timing/scripts/toy_transit_fit.py
python -m pytest research/tess-timing/tests/test_toy_transit.py -q
```

The script makes no network request. It writes the CSV, the JSON record
`research/tess-timing/data/transit-fit-run/transit_fit.json`, and the SVG
figure.
