Models: overview (experimental)¶
This area models the sky as observed: a description of the sky, pushed through the instrument (resampling onto each detector, PSF, bandpass, background, noise), compared with the recorded pixels. Because every step is a torch operation, the comparison has gradients, and you fit the sky, the instrument, or both by gradient descent instead of first coadding the data.
What experimental means
torchsky.scene, torchsky.field, and the likelihood, query, solver,
evidence, and native-response modules under torchsky.map are
experimental:
- Names, defaults, result objects, and serialized model formats may change between minor releases without a deprecation period. Pin a version if you depend on them.
- They are tested for bounded API, gradient, exact-adjoint, and device contracts, like the rest of torchsky.
- They carry no claim of scientific accuracy on real data. Evaluation on real surveys runs in torchsky-harness and is not yet a release gate.
The geometry and mapmaking layers they build on (WCS, maps, sphere) are core and stable. The stability tier of each namespace is listed in the API reference.
The Core Idea: A Forward Model With Gradients¶
flowchart LR
S["sky model<br/>tensor grid, HEALPix,<br/>or field F(RA, Dec, λ)"] --> A["observation operator A<br/>resample · PSF · bandpass<br/>· background"]
A --> P["predicted pixels<br/>per exposure"]
D["recorded pixels<br/>+ noise model"] --> L["likelihood<br/>or loss"]
P --> L
L -.->|"autograd /<br/>exact adjoint Aᵀ"| S
- An observation operator \(A\) maps the sky model to one exposure's
pixels. Its adjoint \(A^T\), the transpose that maps pixel residuals back to
the sky, is exact, so normal equations \(A^T N^{-1} A\) can be solved without
forming a matrix (\(N\) is the noise covariance). Operators for one exposure
are grouped in an
ObservationOperatorBundle; learned instrument models such asLearnedInstrumentOperatorandConstrainedKernelPSFlive intorchsky.map.operators. - A field is a continuous function of sky position and wavelength, such as a neural network (an implicit neural representation, INR) or a spectral Power Foam (a partition of the sky into cells with spectral appearance). It is fitted jointly to all bands and exposures.
- A likelihood scores predicted pixels against data under a stated noise law, for example Gaussian read noise plus Poisson photon noise.
Choose a Page¶
| Task | Page | Entry point |
|---|---|---|
| Describe observations and a target; apply operators with exact adjoints; solve normal equations | Scenes | scene.render, scene.operator_adapter_for_observation |
| Fit a continuous multi-band sky model to images | Fields | field.fit, field.FieldObservation |
| Forced photometry, star stamps, and pixel likelihoods without a global field | Likelihoods | GaussianLikelihoodFactor |
| Orient PSF kernels and separate source flux from uniform sky | PSF conventions | — |
| Sky and wavelength feature encoders, photometric redshift tokens | Encoders | torchsky.encoders (core tier) |
| The inverse problem and modelling assumptions behind fields | Formulation | — |
Fit a Sky Through an Operator¶
The exposure is the 13 × 13 pixel Legacy Surveys g-band cutout of a star in NGC 288 used in the quick start. The sky model lives on a different grid: same pixel size, rotated by 30° and centred on the Gaia position of the star. The operator resamples the model onto the exposure; Adam fits the model to the recorded pixels by differentiating through it, and the fitted star lands on the Gaia position.
import math
import torch
from torchsky import scene
from torchsky.map import ImageObservation
stamp = torch.tensor([ # Legacy Surveys DR10 g band, nanomaggies per pixel
[0.05, 0.06, 0.08, 0.10, 0.13, 0.16, 0.16, 0.16, 0.14, 0.11, 0.09, 0.07, 0.05],
[0.06, 0.08, 0.12, 0.17, 0.22, 0.28, 0.31, 0.30, 0.25, 0.19, 0.14, 0.10, 0.07],
[0.07, 0.11, 0.17, 0.27, 0.40, 0.54, 0.63, 0.59, 0.48, 0.34, 0.23, 0.15, 0.09],
[0.10, 0.15, 0.25, 0.43, 0.71, 1.04, 1.23, 1.19, 0.92, 0.60, 0.35, 0.21, 0.13],
[0.11, 0.19, 0.34, 0.64, 1.17, 1.82, 2.23, 2.12, 1.59, 0.96, 0.53, 0.28, 0.16],
[0.13, 0.22, 0.43, 0.85, 1.64, 2.68, 3.32, 3.07, 2.27, 1.33, 0.68, 0.34, 0.18],
[0.14, 0.24, 0.45, 0.91, 1.81, 2.95, 3.77, 3.45, 2.58, 1.47, 0.73, 0.37, 0.20],
[0.13, 0.22, 0.41, 0.81, 1.55, 2.52, 3.28, 3.07, 2.21, 1.27, 0.66, 0.33, 0.18],
[0.12, 0.18, 0.32, 0.58, 1.03, 1.60, 1.97, 1.88, 1.40, 0.86, 0.48, 0.27, 0.15],
[0.10, 0.15, 0.24, 0.38, 0.60, 0.85, 1.00, 0.96, 0.75, 0.51, 0.32, 0.20, 0.13],
[0.09, 0.12, 0.17, 0.24, 0.33, 0.43, 0.49, 0.47, 0.40, 0.29, 0.20, 0.14, 0.09],
[0.10, 0.11, 0.12, 0.15, 0.19, 0.23, 0.25, 0.25, 0.21, 0.17, 0.13, 0.10, 0.07],
[0.13, 0.10, 0.10, 0.10, 0.12, 0.14, 0.14, 0.14, 0.13, 0.10, 0.08, 0.07, 0.05],
])
exposure = ImageObservation(data=stamp, wcs={
"NAXIS": 2, "NAXIS1": 13, "NAXIS2": 13, "CTYPE1": "RA---TAN", "CTYPE2": "DEC--TAN",
"CRVAL1": 13.12, "CRVAL2": -26.5525, "CRPIX1": 113.5, "CRPIX2": 106.5,
"CD1_1": -7.27777777777778e-05, "CD1_2": 0.0, "CD2_1": 0.0, "CD2_2": 7.27777777777778e-05,
})
s, t = 0.262 / 3600, math.radians(30.0)
target = scene.target_from_header({
"NAXIS": 2, "NAXIS1": 15, "NAXIS2": 15, "CTYPE1": "RA---TAN", "CTYPE2": "DEC--TAN",
"CRVAL1": 13.1286345, "CRVAL2": -26.5597488, "CRPIX1": 8.0, "CRPIX2": 8.0, # Gaia position
"CD1_1": -s * math.cos(t), "CD1_2": s * math.sin(t), "CD2_1": s * math.sin(t), "CD2_2": s * math.cos(t),
}, value_semantics="surface_brightness")
forward = scene.operator_adapter_for_observation(
exposure, resampling_kernel="bilinear"
).bind_image_plane_target(target)
sky = torch.zeros(15, 15, requires_grad=True)
optimizer = torch.optim.Adam([sky], lr=0.1)
for _ in range(200):
optimizer.zero_grad()
loss = (forward.apply(sky) - stamp).square().sum()
loss.backward()
optimizer.step()
assert loss.item() < 0.01 * stamp.square().sum().item()
peak = int(sky.detach().argmax())
assert abs(peak // 15 - 7) <= 1 and abs(peak % 15 - 7) <= 1 # the star sits at CRPIX
Conventions and Pitfalls¶
- Value semantics are declared. A target is surface brightness or flux, exactly as in maps; PSF kernels follow the orientation in PSF conventions.
- Normalize coordinates before float32. Absolute RA/Dec cast to float32
loses subpixel structure. Fields normalize sky and wavelength through a
Domain; custom models should subtract the sky centre first. - MPS. Fields run in float32 there; fine angular structure on MPS is not verified.
- Identifiability. A good fit to data does not prove the sky model is unique. Hold out exposures or bands, and read the formulation for what each model can and cannot constrain.
Connections¶
Models compose the core areas: each exposure carries a WCS; operators reuse the resampling kernels and coadd machinery; HEALPix and harmonic sky representations come from the celestial sphere; and passband integrals come from synthetic photometry. For a classical stacked product, start with maps instead.
Examples on real data: one continuous field fitted to Legacy Surveys images of the Cartwheel, and forced photometry of Gaia stars in NGC 288.