diff --git a/.gitignore b/.gitignore index c7a49bfcc..1d367cd68 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ MANIFEST # Documentation etc: Sphinx and ReadTheDocs reference/ effects_docstrings/ +docs/_build/ +docs/source/_autosummary/ *.ipynb_checkpoints diff --git a/docs/source/_ext/scopesim_sphinx_ext.py b/docs/source/_ext/scopesim_sphinx_ext.py index 4ab146d1e..c977ce387 100644 --- a/docs/source/_ext/scopesim_sphinx_ext.py +++ b/docs/source/_ext/scopesim_sphinx_ext.py @@ -24,7 +24,7 @@ def setup(app): eff_type_strs[base_name] += new_str for eff_type, strs in eff_type_strs.items(): - with open(os.path.join(output_dir, f"{eff_type}.rst"), "w") as f: + with open(os.path.join(output_dir, f"{eff_type}.rst"), "w", encoding="utf-8") as f: f.write(strs) diff --git a/docs/source/concepts.rst b/docs/source/concepts.rst new file mode 100644 index 000000000..548fdf249 --- /dev/null +++ b/docs/source/concepts.rst @@ -0,0 +1,215 @@ +ScopeSim concepts and architecture +==================================== + +This page explains the key objects in ScopeSim and how they fit together. +Reading it will help you understand what happens during a simulation and how +to customise it. + +--- + +The simulation pipeline +------------------------ + +Every ScopeSim simulation follows the same pipeline: + +.. code-block:: text + + Source ──► OpticalTrain ──► Detector ──► FITS output + ↑ ↑ + (target) (telescope + instrument + + atmosphere + sky) + +1. A **Source** object describes the on-sky target: spatial positions and + spectra. +2. The **OpticalTrain** applies a sequence of **Effects** — PSFs, transmission + curves, noise sources, detector geometry — that transform the source signal + as photons travel from the sky to the detector. +3. The **Detector** integrates the focal-plane image, adds readout noise and + dark current, and produces the final pixel array. +4. The result is returned as an ``astropy.fits.HDUList``. + +The ``Simulation`` class is a convenience wrapper that bundles steps 2–4 +together and reads the instrument configuration from the IRDB packages. + +--- + +The five core objects +---------------------- + +Source +~~~~~~ + +A ``Source`` represents an on-sky target. It holds: + +- **Spatial information** — positions of emitting elements (point sources or + images) +- **Spectral information** — one or more spectra associated with those elements +- **Flux scaling** — brightnesses in physical units or magnitudes + +Sources are created using ``scopesim_templates`` functions for common +astronomical objects, or directly from FITS images, tables, or spectra:: + + import scopesim_templates as st + + # Point sources + src = st.stellar.star(mag=20, filter_name="K") + src = st.stellar.cluster(mass=1e4, distance=50000) + + # Extended source from a FITS image + from scopesim import Source + src = Source(image_hdu=my_fits_image_hdu, spectra=my_spectrum) + + # Combine sources with + + combined = star_src + galaxy_src + +OpticalTrain +~~~~~~~~~~~~~ + +The ``OpticalTrain`` is the heart of ScopeSim. It models everything between +the sky and the detector: atmosphere, telescope mirrors, AO system, instrument +optics, filters, PSF, and detectors. It contains an ordered list of +**Effects** that are applied in sequence when ``observe()`` is called. + +You rarely create an ``OpticalTrain`` directly — the ``Simulation`` class does +it for you. But you can inspect and customise it:: + + sim = Simulation("MICADO", mode=["MCAO_4mas", "IMG"]) + ot = sim.optical_train + + ot.effects # view all effects + ot["detector_linearity"].include = False # disable an effect + ot.image_planes[0].data # access intermediate focal-plane image + +Effect +~~~~~~~ + +Effects are the building blocks of the optical model. Each ``Effect`` subclass +models one physical process: + +- **SurfaceList** — mirrors and optical surfaces (transmission, emission) +- **FieldConstantPSF**, **FieldVaryingPSF** — PSF from a FITS cube +- **AnisocadoConstPSF** — AnisoCADO-based AO PSF +- **SeeingPSF**, **GaussianDiffractionPSF** — analytical PSFs +- **FilterCurve**, **QuantumEfficiencyCurve** — spectral transmission/efficiency +- **ShotNoise**, **DarkCurrent**, **ReadoutNoise** — detector noise sources +- **DetectorList** — detector geometry, pixel scale, gaps +- **AutoExposure**, **SummedExposure** — exposure logic + +Effects are defined in the instrument YAML files in the IRDB. You can also +write custom effects — see the :doc:`examples/3_custom_effects` notebook. + +UserCommands / settings +~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``UserCommands`` object (accessible as ``sim.settings``) is a nested +dictionary that holds all simulation parameters. It is constructed from the +instrument YAML files but any parameter can be overridden at runtime. + +Parameters are accessed using **bang-string** (``!``) notation:: + + sim.settings["!OBS.dit"] = 60 # integration time [s] + sim.settings["!OBS.ndit"] = 10 # number of integrations + sim.settings["!OBS.filter_name"] = "K" # filter selection + sim.settings["!ATMO.seeing"] = 0.7 # seeing FWHM [arcsec] + sim.settings["!SIM.spectral.wave_min"] = 1.9 # wavelength range [µm] + +The ``!`` prefix and dot notation resolve paths through nested YAML +dictionaries. The top-level aliases are: + +.. list-table:: + :widths: 15 85 + :header-rows: 1 + + * - Alias + - Covers + * - ``!OBS`` + - Observation parameters (DIT, NDIT, filter, mode) + * - ``!SIM`` + - Simulation parameters (wavelength range, pixel oversampling, file paths) + * - ``!ATMO`` + - Atmospheric parameters (seeing, background, transmission) + * - ``!TEL`` + - Telescope parameters (collecting area, emissivity) + * - ``!INST`` + - Instrument parameters (pixel scale, plate scale) + * - ``!DET`` + - Detector parameters (readout mode, gain, dark current) + +Detector +~~~~~~~~~ + +The ``Detector`` object manages the focal-plane array. It holds one or more +``DetectorWindow`` instances (individual chips), integrates signal from the +focal plane, and applies per-chip noise models and non-linearity corrections. +You typically interact with the output HDUList rather than the ``Detector`` +directly. + +--- + +Fields of View (FOVs) +---------------------- + +When ``OpticalTrain.observe()`` is called, ScopeSim divides the simulation +volume into small *Fields of View* (FOVs). Each FOV covers a spatial +sub-region and a wavelength slice, chosen to match the spectral resolution of +the current setup. Effects are applied independently within each FOV, then +the results are assembled into the full focal-plane image. + +This design enables: + +- Efficient memory usage for large detector arrays +- Wavelength-dependent PSFs (field-varying PSF cubes) +- Multi-chip detectors with gaps and offsets +- Spectroscopic and IFU modes with curved/tilted traces + +You rarely need to interact with FOVs directly. If you need noiseless +intermediate images, use ``sim.optical_train.image_planes[0].data``. + +--- + +Instrument packages and YAML files +------------------------------------ + +All instrument-specific data lives in the IRDB, downloaded via:: + + scopesim.download_packages(["Armazones", "ELT", "MORFEO", "MICADO"]) + +Each package is a directory of YAML files and associated data (FITS tables, +transmission curves, PSF cubes). A ``default.yaml`` defines which YAML files +to load for each observing mode. The YAML files list the ``effects`` that make +up the optical train, with their parameters. + +You can browse the downloaded packages in ``./inst_pkgs/`` to understand what +parameters are available and what data files are used. + +--- + +Putting it all together +------------------------ + +A complete simulation:: + + import scopesim + import scopesim_templates as st + from scopesim import Simulation + + # Download instrument packages (once) + scopesim.download_packages(["Armazones", "ELT", "MORFEO", "MICADO"]) + + # 1. Create the on-sky target + src = st.stellar.cluster(mass=1e4, distance=50000, filter_name="V") + + # 2. Load the optical train for the chosen mode + sim = Simulation("MICADO", mode=["MCAO_4mas", "IMG"]) + + # 3. Override any parameters you want to change + sim.settings["!OBS.dit"] = 120 + sim.settings["!OBS.ndit"] = 5 + sim.settings["!OBS.filter_name"] = "Ks" + + # 4. Run the simulation — returns an astropy HDUList + hdu = sim(src) + hdu.writeto("my_simulation.fits", overwrite=True) + + # Optional: inspect the noiseless focal-plane image + noiseless = sim.optical_train.image_planes[0].data diff --git a/docs/source/faqs/faq_filters.md b/docs/source/faqs/faq_filters.md new file mode 100644 index 000000000..5ee8cef3a --- /dev/null +++ b/docs/source/faqs/faq_filters.md @@ -0,0 +1,102 @@ +# Filters + +## How do I list available filters? + +```python +sim.optical_train["filter_wheel"].filters +``` + +This returns a table of filter names and their wavelength coverage. The exact +name of the filter wheel effect may differ per instrument — use +`sim.optical_train.effects` to see what effects are loaded and find the +relevant one. + +--- + +## How do I change the filter? + +```python +sim.settings["!OBS.filter_name"] = "Ks" +``` + +Available filter names are shown by `sim.optical_train["filter_wheel"].filters`. +The parameter path (`!OBS.filter_name`) may differ slightly between instruments +— check `sim.settings["!OBS"]` if the default path does not work. + +--- + +## How do I plot a filter transmission curve? + +```python +filt = sim.optical_train["filter_wheel"].current_filter +filt.plot() +``` + +Or access the transmission table directly: + +```python +filt.table # astropy Table with "wavelength" and "transmission" columns +``` + +--- + +## How do I use a custom filter? + +Create a `FilterCurve` effect from a two-column ASCII file (wavelength [µm], +transmission [0–1]): + +```python +from scopesim.effects import FilterCurve + +my_filter = FilterCurve( + filename="my_filter.dat", + name="my_custom_filter", +) +sim.optical_train.optics_manager.add_effect(my_filter) +``` + +Or pass a filter directly as a `synphot.SpectralElement`: + +```python +from synphot import SpectralElement, Empirical1D +import astropy.units as u +import numpy as np + +wave = np.linspace(2.0, 2.5, 100) * u.um +trans = np.exp(-0.5 * ((wave.value - 2.2) / 0.1) ** 2) +sp = SpectralElement(Empirical1D, points=wave, lookup_table=trans) +``` + +--- + +## What filters are available for MICADO? + +MICADO's standard filter set includes: + +| Name | Wavelength range | Notes | +|---|---|---| +| `J` | 1.17–1.33 µm | Broadband J | +| `H` | 1.49–1.78 µm | Broadband H | +| `Ks` | 2.00–2.37 µm | Broadband Ks | +| `Y` | 0.97–1.07 µm | Y-band | +| `z` | 0.85–0.95 µm | z-band | +| `Br-gamma` | 2.16 µm | Narrow-band Brγ emission | +| `H2` | 2.12 µm | Narrow-band H₂ emission | +| `FeII` | 1.64 µm | Narrow-band [Fe II] emission | + +Use `sim.optical_train["filter_wheel"].filters` after loading the MICADO +package for the authoritative current list. + +--- + +## Why does my flux change when I change wavelength range? + +ScopeSim integrates over wavelengths. If `!SIM.spectral.wave_min` or +`wave_max` is set narrower than the filter bandpass, flux outside the +simulation range is silently dropped. Always ensure the simulation wavelength +range covers at least the full filter bandpass: + +```python +sim.settings["!SIM.spectral.wave_min"] = 1.9 # µm +sim.settings["!SIM.spectral.wave_max"] = 2.5 # µm +``` diff --git a/docs/source/faqs/faq_sources.md b/docs/source/faqs/faq_sources.md new file mode 100644 index 000000000..c2a8ada13 --- /dev/null +++ b/docs/source/faqs/faq_sources.md @@ -0,0 +1,135 @@ +# Sources + +## What packages do I need to create sources? + +For common astronomical sources use +[ScopeSim Templates](https://scopesim-templates.readthedocs.io/en/latest/): + +```bash +pip install scopesim_templates +``` + +For spectral libraries use +[SpeXtra](https://spextra.readthedocs.io/en/latest/) (many catalogues) or +[Pyckles](https://pyckles.readthedocs.io/en/latest/) (Pickles stellar library). + +For hand-crafted sources the `scopesim.Source` class accepts arrays and FITS +images directly. + +--- + +## How do I create a single star? + +```python +import scopesim_templates as st + +src = st.stellar.star(mag=20, filter_name="Ks", spec_type="A0V") +``` + +--- + +## How do I create a star field? + +```python +src = st.stellar.star_field( + n=200, + mmin=18, + mmax=24, + width=60, # [arcsec] square field side length + filter_name="Ks", +) +``` + +--- + +## How do I create a stellar cluster? + +```python +src = st.stellar.cluster( + mass=1e4, # [M_sun] total stellar mass + distance=50000, # [pc] + filter_name="V", + core_radius=0.3, # [pc] +) +``` + +--- + +## How do I create a galaxy? + +```python +src = st.extragalactic.elliptical( + total_magnitude=18, + filter_name="Ks", + pixel_scale=0.004, # [arcsec/pix] output pixel scale + r_eff=0.5, # [arcsec] effective radius + n=4, # Sérsic index + ellip=0.3, + theta=45, +) +``` + +--- + +## How do I combine multiple sources? + +Use the `+` operator: + +```python +stars = st.stellar.star_field(100, "V", 18, 24, width=30) +galaxy = st.extragalactic.elliptical(20, "Ks", ...) + +combined = stars + galaxy +sim(combined) +``` + +--- + +## How do I create a source from a FITS image? + +```python +from astropy.io import fits +from scopesim import Source +import spextra as sp + +# Load your image +hdu = fits.open("my_image.fits")[0] + +# Need a spectrum — use SpeXtra or synphot +spectrum = sp.Spextrum("pickles/a0v") # example: A0V star spectrum + +src = Source(image_hdu=hdu, spectra=[spectrum]) +``` + +The FITS image header must contain WCS keywords (``CDELT``, ``CRPIX``, +``CRVAL``) defining the pixel scale in arcsec/pix. Pixel values are treated +as flux weights multiplied by the given spectrum. + +--- + +## How do I set the position of a source? + +Source coordinates are in arcsec relative to the field centre: + +```python +src = st.stellar.star(mag=20, filter_name="Ks") +src.shift(dx=2.5, dy=-1.0) # offset in arcsec +``` + +For point-source arrays, positions are set via the `x` and `y` arguments of +most template functions. + +--- + +## How do I check what my source looks like? + +```python +src.plot() # spatial footprint +src.spectra[0].plot() # first spectrum +``` + +Or view the source table: + +```python +src.source_table # x, y positions and spectral references +``` diff --git a/docs/source/faqs/faq_troubleshooting.md b/docs/source/faqs/faq_troubleshooting.md new file mode 100644 index 000000000..2554da534 --- /dev/null +++ b/docs/source/faqs/faq_troubleshooting.md @@ -0,0 +1,133 @@ +# Troubleshooting + +Common errors and how to fix them. + +--- + +## `File cannot be found: default.yaml` + +**Cause:** ScopeSim cannot find the instrument packages. They have not been +downloaded, or you are running from the wrong directory. + +**Fix:** Download the packages into your current working directory: + +```python +import scopesim +scopesim.download_packages(["Armazones", "ELT", "MORFEO", "MICADO"]) +``` + +Or tell ScopeSim where the packages live: + +```python +scopesim.rc.__config__["!SIM.file.local_packages_path"] = "/path/to/inst_pkgs" +``` + +--- + +## `RuntimeError: No package named X found` + +**Cause:** The package `X` has not been downloaded or the name is wrong. + +**Fix:** Check available packages: + +```python +scopesim.list_packages() # packages available on the server +scopesim.list_packages(local=True) # packages already installed locally +``` + +--- + +## Simulation returns all zeros / black image + +**Cause 1:** The source is outside the field of view. + +**Fix:** Check your source coordinates. The field centre is typically at +`(0, 0)` arcsec unless you have set a WCS offset. Point sources need +`x` and `y` in arcsec relative to the field centre. + +**Cause 2:** The source spectrum has no flux in the simulation wavelength +range. + +**Fix:** Check `sim.settings["!SIM.spectral.wave_min"]` and `wave_max` match +the filter you are using. + +--- + +## `KeyError: !OBS.some_parameter` + +**Cause:** The bang-string path does not exist in the loaded YAML package. + +**Fix:** Inspect the settings to find the correct key: + +```python +sim.settings["!OBS"] # view all OBS-level parameters +sim.settings # view the full settings dict +``` + +Bang-strings are case-sensitive. Use `!OBS.dit` not `!OBS.DIT`. + +--- + +## Simulation is very slow + +**Cause 1:** The spatial oversampling is high. The default chunk size may +create many small FOV tiles. + +**Fix:** Increase the chunk size: + +```python +sim.settings["!SIM.computing.chunk_size"] = 4096 # default 2048 +``` + +**Cause 2:** A `FieldVaryingPSF` with many spatial positions is loaded. + +**Fix:** Substitute a simpler PSF for exploratory runs: + +```python +sim.optical_train["psf"].include = False +``` + +**Cause 3:** Sub-pixel mode is on. + +**Fix:** + +```python +sim.settings["!SIM.sub_pixel.flag"] = False +``` + +--- + +## `AttributeError: 'Simulation' object has no attribute 'X'` + +**Cause:** You are calling a SimCADO-style attribute on a ScopeSim object. + +**Fix:** See the [SimCADO migration guide](../simcado_migration.rst) for the +ScopeSim equivalent. + +--- + +## Output FITS has unexpected number of extensions + +Each chip in the detector array produces one FITS extension. A 3×3 H2RG +array (like MICADO) produces 9 science extensions plus a primary HDU, giving +10 extensions total. Access them by index: + +```python +hdu = sim(src) +len(hdu) # total number of extensions +hdu[1].data # chip 1 pixel data +hdu[1].header # chip 1 header with WCS +``` + +--- + +## Getting a bug report + +When reporting issues, always include: + +```python +import scopesim +print(scopesim.bug_report()) +``` + +File issues at https://github.com/AstarVienna/ScopeSim/issues diff --git a/docs/source/index.rst b/docs/source/index.rst index c431552a6..8fbced9a9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -51,12 +51,25 @@ ScopeSim_ is on pip:: .. toctree:: :maxdepth: 2 - :caption: Contents: + :caption: Getting Started: getting_started + concepts + simcado_migration + +.. toctree:: + :maxdepth: 2 + :caption: Reference: + + psfs + faqs/index + +.. toctree:: + :maxdepth: 2 + :caption: Examples: + examples/index 5_liners/index - faqs/index .. warning:: July 2022: The downloadable content server was retired and the data migrated to a new server. diff --git a/docs/source/psfs.rst b/docs/source/psfs.rst new file mode 100644 index 000000000..e3733514f --- /dev/null +++ b/docs/source/psfs.rst @@ -0,0 +1,174 @@ +Point Spread Functions +======================= + +ScopeSim models the PSF as one or more **PSF effects** in the optical train. +Several PSF classes are available, each suited to different simulation +scenarios. Instrument packages in the IRDB configure the appropriate PSF +automatically, but this page explains the options so you can understand what +is active and how to modify it. + +--- + +Available PSF effect types +--------------------------- + +FieldConstantPSF +~~~~~~~~~~~~~~~~~ + +A PSF that is the same across the entire field of view, read from a +FITS file. The FITS file may contain a single kernel or a wavelength- +dependent PSF cube (one kernel per wavelength slice). + +**Best for:** Instruments with a relatively uniform PSF, or when you want a +single representative PSF for speed. + +**YAML example:** + +.. code-block:: yaml + + - name: micado_psf + class: FieldConstantPSF + kwargs: + filename: "PSF_MICADO_SCAO_1.5mas.fits" + +FieldVaryingPSF +~~~~~~~~~~~~~~~~ + +A PSF that varies with position across the field, read from a FITS file +that contains PSF kernels on a spatial grid (and optionally also as a +function of wavelength). ScopeSim interpolates between grid points to +evaluate the PSF at any detector position. + +**Best for:** AO PSFs that degrade away from the guide star, such as SCAO +systems where the PSF varies strongly across the MICADO field. + +.. note:: + + Field-varying PSF files for MICADO (SCAO modes) are large (~1–5 GB) and + must be downloaded separately from the regular instrument packages:: + + scopesim.download_psfs(psf_name="MICADO_SCAO_4mas_FV") + +AnisocadoConstPSF +~~~~~~~~~~~~~~~~~~ + +A field-constant AO PSF generated on-the-fly from the +`AnisoCADO `_ package. +AnisoCADO computes SCAO PSFs for the ELT using a semi-analytical model +parameterised by atmospheric conditions and guide star separation. + +**Best for:** Generating realistic MICADO SCAO PSFs without downloading +large pre-computed files. Useful for exploring how the PSF depends on +observing conditions. + +**Requirements:** ``pip install anisocado`` + +**Example:** + +.. code-block:: python + + from scopesim.effects import AnisocadoConstPSF + + psf = AnisocadoConstPSF( + filename=None, + strehl_ratio=0.5, + wavelength=2.15, # [µm] + pixel_scale=0.004, # [arcsec/pix] + ) + +SeeingPSF +~~~~~~~~~~ + +An analytical seeing-limited PSF modelled as a 2D Gaussian with FWHM +read from ``!ATMO.seeing``. Simple and fast — no PSF file needed. + +**Best for:** Seeing-limited simulations, or as a quick substitute while +developing a simulation before loading the real AO PSF. + +**Example (override the seeing FWHM):** + +.. code-block:: python + + sim.settings["!ATMO.seeing"] = 0.8 # [arcsec] + +GaussianDiffractionPSF +~~~~~~~~~~~~~~~~~~~~~~~ + +An analytical PSF combining a Gaussian seeing component with a diffraction- +limited Airy disk. Parameterised by the telescope diameter and atmospheric +seeing. + +**Best for:** Quick exploratory simulations or instruments without a dedicated +PSF file. + +--- + +Disabling or replacing the PSF +-------------------------------- + +To check what PSF effect is loaded:: + + sim.optical_train.effects # look for effects of type *PSF + +To disable the PSF (simulate a delta-function PSF):: + + sim.optical_train["psf_effect_name"].include = False + +Replace with a simpler PSF for exploratory runs:: + + from scopesim.effects import SeeingPSF + sim.optical_train.optics_manager.add_effect( + SeeingPSF(fwhm=0.8, name="quick_psf") + ) + sim.optical_train["original_psf"].include = False + +--- + +Field-varying PSFs for MICADO +------------------------------- + +MICADO SCAO observations use a field-varying PSF — the PSF is excellent +near the guide star and degrades toward the field edge. Pre-computed +field-varying PSF cubes (generated with AnisoCADO from ESO SCAO simulations) +are available for download separately from the IRDB package. + +.. list-table:: + :widths: 30 30 40 + :header-rows: 1 + + * - PSF file + - Pixel scale + - Description + * - ``MICADO_SCAO_4mas_FV`` + - 4 mas/pix + - 7×7 spatial grid, H+K bands + * - ``MICADO_SCAO_1.5mas_FV`` + - 1.5 mas/pix + - 7×7 spatial grid, H+K bands + +These files are several GB each. Use ``FieldConstantPSF`` (the package +default) for most simulations; switch to ``FieldVaryingPSF`` when the +spatial variation of the PSF matters for your science case (e.g. astrometric +calibration, PSF-subtracted imaging). + +--- + +PSF file format +---------------- + +ScopeSim reads PSF FITS files in two formats: + +**Single kernel** — a 2D FITS image with: + +- ``PIXSCALE`` keyword in the header giving the kernel pixel scale [arcsec] +- Pixel values representing the PSF intensity (will be normalised to sum=1) + +**Wavelength cube** — a 3D FITS cube with: + +- First axis: wavelength +- Second and third axes: spatial kernel +- ``WAVE0``, ``DWAVE``, ``WAVEUNIT`` header keywords defining the wavelength axis + +For field-varying PSFs, the FITS file contains a grid of kernels arranged by +spatial position. See the IRDB documentation for the specific format used by +MICADO field-varying PSF files. diff --git a/docs/source/simcado_migration.rst b/docs/source/simcado_migration.rst new file mode 100644 index 000000000..a5c86fee6 --- /dev/null +++ b/docs/source/simcado_migration.rst @@ -0,0 +1,212 @@ +Migrating from SimCADO +====================== + +ScopeSim is the successor to SimCADO. While the underlying simulation +philosophy is the same, the architecture has been redesigned to be instrument- +agnostic. This page maps the old SimCADO API onto ScopeSim equivalents. + +.. note:: + + SimCADO only simulated MICADO. ScopeSim is a general framework — to + simulate MICADO you need the ScopeSim engine plus the MICADO instrument + package from the IRDB. + +--- + +The minimum working example +---------------------------- + +**SimCADO** :: + + import simcado + src = simcado.source.star_field(100, 15, 20, width=10) + simcado.run(src, filename="output.fits") + +**ScopeSim** :: + + import scopesim + import scopesim_templates as st + from scopesim import Simulation + + scopesim.download_packages(["Armazones", "ELT", "MORFEO", "MICADO"]) + + src = st.stellar.star_field(100, "V", 15, 20, width=10) + sim = Simulation("MICADO", mode=["MCAO_4mas", "IMG"]) + sim(src, dit=60, ndit=10) + + +--- + +API mapping +----------- + +Installation / setup +~~~~~~~~~~~~~~~~~~~~ + +.. list-table:: + :widths: 40 60 + :header-rows: 1 + + * - SimCADO + - ScopeSim equivalent + * - ``pip install simcado`` + - ``pip install scopesim scopesim_templates`` + * - ``simcado.get_extras()`` + - ``scopesim.download_packages(["Armazones", "ELT", "MORFEO", "MICADO"])`` + * - Data files bundled with package + - Instrument packages live in ``./inst_pkgs/`` after download + +Source creation +~~~~~~~~~~~~~~~ + +.. list-table:: + :widths: 40 60 + :header-rows: 1 + + * - SimCADO + - ScopeSim equivalent + * - ``simcado.source.star(mag, filter_name)`` + - ``scopesim_templates.stellar.star(mag, filter_name, ...)`` + * - ``simcado.source.stars(mags, x, y, filter_name)`` + - ``scopesim_templates.stellar.stars(filter_name, mags, ...)`` + * - ``simcado.source.star_grid(n, mag_min, mag_max)`` + - ``scopesim_templates.stellar.star_grid(n, mag_min, mag_max, ...)`` + * - ``simcado.source.cluster(mass, distance)`` + - ``scopesim_templates.stellar.cluster(mass, distance, ...)`` + * - ``simcado.source.source_from_image(imgs, lam, spectra)`` + - ``scopesim.Source(image_hdu=fits_image, spectra=spectra, ...)`` + * - ``src1 + src2`` + - ``src1 + src2`` (unchanged) + +Configuration / commands +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. list-table:: + :widths: 40 60 + :header-rows: 1 + + * - SimCADO + - ScopeSim equivalent + * - ``cmds = simcado.UserCommands()`` + - ``sim.settings`` (accessed from the ``Simulation`` object) + * - ``cmds["OBS_EXPTIME"] = 3600`` + - ``sim.settings["!OBS.dit"] = 360; sim.settings["!OBS.ndit"] = 10`` + * - ``cmds["OBS_DIT"] = 60`` + - ``sim.settings["!OBS.dit"] = 60`` + * - ``cmds["OBS_NDIT"] = 10`` + - ``sim.settings["!OBS.ndit"] = 10`` + * - ``cmds["INST_FILTER_TC"] = "K"`` + - ``sim.settings["!OBS.filter_name"] = "K"`` + * - ``cmds["SIM_PIXEL_SCALE"] = 0.004`` + - Determined by the chosen observing mode (e.g. ``"MCAO_4mas"``) + * - ``cmds["ATMO_SEEING"] = 0.8`` + - ``sim.settings["!ATMO.seeing"] = 0.8`` + * - Keyword names like ``OBS_EXPTIME`` + - Bang-string notation: ``!OBS.dit``, ``!SIM.spectral.wave_min``, etc. + +Running simulations +~~~~~~~~~~~~~~~~~~~ + +.. list-table:: + :widths: 40 60 + :header-rows: 1 + + * - SimCADO + - ScopeSim equivalent + * - ``simcado.run(src, cmds=cmds, filename="out.fits")`` + - ``sim(src)`` — returns an ``astropy.fits.HDUList`` + * - ``ot = simcado.OpticalTrain(cmds); ot.observe(src)`` + - ``sim.optical_train.observe(src); sim.optical_train.readout()`` + * - ``det = simcado.Detector(cmds); det.read_out()`` + - Handled internally by ``sim(src)`` + * - ``det.array`` (raw pixel data) + - ``sim(src)[1].data`` (FITS extension 1) + +Observing modes +~~~~~~~~~~~~~~~ + +SimCADO was MICADO-specific and configured via flat keywords. ScopeSim uses +named observing modes defined in the YAML package files. + +.. list-table:: + :widths: 40 60 + :header-rows: 1 + + * - SimCADO configuration + - ScopeSim equivalent + * - ``cmds["SIM_PIXEL_SCALE"] = 0.004`` (4 mas) + - ``Simulation("MICADO", mode=["MCAO_4mas", "IMG"])`` + * - ``cmds["SIM_PIXEL_SCALE"] = 0.0015`` (1.5 mas) + - ``Simulation("MICADO", mode=["SCAO_1.5mas", "IMG"])`` + * - ``cmds["SCOPE_USE_MIRROR_BG"] = True`` + - Controlled by an ``SurfaceList`` effect in the YAML package + +Listing available options:: + + sim.settings.modes # available observing modes + sim.optical_train["filter_wheel"].filters # available filters + sim.optical_train.effects # all active effects + +Optical train inspection +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. list-table:: + :widths: 40 60 + :header-rows: 1 + + * - SimCADO + - ScopeSim equivalent + * - ``ot.apply_optical_train(src, det)`` + - ``sim.optical_train.observe(src)`` + * - Accessing intermediate images: not straightforward + - ``sim.optical_train.image_planes[0].data`` + * - Turn effect off: not directly supported + - ``sim.optical_train["effect_name"].include = False`` + * - List effects: not supported + - ``sim.optical_train.effects`` + +--- + +Key conceptual changes +----------------------- + +Modular instrument packages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +SimCADO was self-contained — all MICADO data was shipped with the Python +package. ScopeSim separates the simulation engine from the instrument data: + +- **ScopeSim** — the simulation engine (this package) +- **IRDB** — instrument packages downloaded on first use +- **ScopeSim Templates** — helper functions for creating on-sky sources + +This means you must call ``scopesim.download_packages(...)`` before running any +real instrument simulation. The packages are cached in ``./inst_pkgs/`` and +only need to be downloaded once. + +Bang-string parameter access +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +SimCADO used flat uppercase keyword names (``OBS_EXPTIME``, ``INST_FILTER_TC``). +ScopeSim organises parameters in a nested YAML hierarchy accessed with +bang-string notation:: + + sim.settings["!OBS.dit"] # observation DIT + sim.settings["!OBS.filter_name"] # filter selection + sim.settings["!ATMO.seeing"] # atmospheric seeing + sim.settings["!SIM.spectral.wave_min"] # simulation wavelength range + +The ``!`` prefix signals a YAML-path lookup. Keys are structured as +``!.
.``. + +MICADO now needs MORFEO +~~~~~~~~~~~~~~~~~~~~~~~~ + +In SimCADO, MICADO's adaptive optics (MAORY at the time) was implicit. In +ScopeSim, MICADO works with MORFEO as a separate instrument package that must +be downloaded explicitly:: + + scopesim.download_packages(["Armazones", "ELT", "MORFEO", "MICADO"]) + +For purely diffraction-limited or seeing-limited simulations (no AO), use the +standalone MICADO modes.