Skip to content

Refactor: instruments have sensors#328

Open
j-atkins wants to merge 58 commits intomainfrom
refactor-sensors
Open

Refactor: instruments have sensors#328
j-atkins wants to merge 58 commits intomainfrom
refactor-sensors

Conversation

@j-atkins
Copy link
Copy Markdown
Collaborator

@j-atkins j-atkins commented Apr 10, 2026

Overview

This PR centralises/abstracts instrument sensor/variable sampling logic so that each instrument declares which sensors it carries (e.g. TEMPERATURE, SALINITY, VELOCITY). Users can configure which sensors are active for each instrument in the expedition YAML.

This helps pave the way for easy addition of BGC sensors to the Argo float in a future PR (#234), and consolidation of CTD + CTD_BGC into a single instrument (#260) with a combined sensor allowlist. Also makes it straightforward to add new sensors to any instrument in the future (e.g., #312, #313), and streamlines them process for developers to add new instruments (i.e. #237)

Major changes

  • sensors.py in instruments/ defines the SensorType class and per-instrument allowlists (so that there is control over which sensors each instrument supports and users cannot configure unsupported sensors).
  • New SensorConfig pydantic model and sensors field in every instrument config in expedition.py.
  • New SensorRegistry in utils.py that maps each SensorType to its FieldSet key, Copernicus variable name, category (phys/bgc), and Parcels particle variable name(s).
    • Per-instrument parcticle classes are now built dynamically at runtime based on which sensors are active, but the fixed/mechanical variables are still hard-coded in the instrument files, e.g. cycle_phase for Argo Floats.

API change

As mentioned above, the instruments config section of the expedition YAML now has a sensors list field, where users specify which sensors are active. For example, below the CTD is configured to sample TEMPERATURE and SALINITY:

ctd_config:
  stationkeeping_time_minutes: 50
  min_depth_meter: -11
  max_depth_meter: -2000
  sensors:
    - TEMPERATURE
    - SALINITY
  • If the sensors list is omitted, it default to all valid sensors for that instrument.
  • By using allowlists for each instrument, it will not allow non-sensical sensor combinations (e.g. BGC sensors on an ADCP).
  • Will also not allow an empty sensor list, at least one sensor must be active.

Additional change

  • Argo Float sampling kernels have been separated from the vertical-movement kernel, making it easier to add BGC sensors in a future PR.

Follow-up PRs

  • The plan CLI tool will need updating to account for sensor configuration options. Currently not broken but doesn't give option to configure sensors. (New issue to be opened)
  • Docs update to clearly communicate which sensors each instrument accepts, and how to configure them in either the plan tool or the expedition YAML. (New issue to be opened)
  • Merge CTD + CTD_BGC into a single instrument. (Unify CTD and CTD_BGC to one instrument #260)
  • Add BGC sampling to Argo Floats (New ARGO_BGC instrument #234)

Tests

  • Update existing tests and add new tests to cover new sensor logic

j-atkins added 30 commits March 25, 2026 13:53
… kernels from the argo vertical movement kernel to enable easier scalability
j-atkins and others added 25 commits April 8, 2026 14:28
@j-atkins j-atkins marked this pull request as ready for review April 10, 2026 13:34
Copy link
Copy Markdown
Member

@erikvansebille erikvansebille left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! A few comments below

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to still be a lot of duplication between CTD and CTD_BGC. Was the intention for this PR not to remove that duplication/ Or will that come in a next PR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indeed it will come in the next PR... the CTD_BGC will soon be removed but I decided not to do it in this PR so as to keep it focused

Variable("lifetime", dtype=np.float32),
]
)
_DRIFTER_FIXED_VARIABLES = [
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure whether FIXED is the right term here (and in other instruments). Suggests they are fixed to the instrument. Would NONSENSOR be a better term? Or do you have another term?

Comment on lines +9 to +18
TEMPERATURE = "TEMPERATURE"
SALINITY = "SALINITY"
VELOCITY = "VELOCITY"
OXYGEN = "OXYGEN"
CHLOROPHYLL = "CHLOROPHYLL"
NITRATE = "NITRATE"
PHOSPHATE = "PHOSPHATE"
PH = "PH"
PHYTOPLANKTON = "PHYTOPLANKTON"
PRIMARY_PRODUCTION = "PRIMARY_PRODUCTION"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this list needed? Can we do without? Seems like an extra place to change when adding a sensor..

Comment on lines +51 to +54
_ST_SENSOR_KERNELS: dict[SensorType, callable] = {
SensorType.TEMPERATURE: _sample_temperature,
SensorType.SALINITY: _sample_salinity,
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why explicitly mention them here, if they are also part of sensors.py? Seems a duplication?

import yaml

from virtualship.errors import InstrumentsConfigError, ScheduleError
from virtualship.instruments.sensors import (
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not import all? So that if a new sensor is added, it can automatically also be imported here?

"""
FieldSet-key → Copernicus-variable mapping for enabled sensors.

VELOCITY is a special case: one sensor provides two FieldSet variables (U and V).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in Parcels, it should sample fieldset.UV, which is one Field?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! Overcomplicated things here unnecessarily

],
description=(
"Sensors fitted to the BGC CTD. "
"Supported: CHLOROPHYLL, NITRATE, OXYGEN, PH, PHOSPHATE, PHYTOPLANKTON, PRIMARY_PRODUCTION. "
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this list the same order as the list above? Otherwise, more difficult to see whether the list is complete

Comment on lines +586 to +598
@pydantic.field_validator("sensors", mode="after")
@classmethod
def _check_sensor_compatibility(cls, value) -> list[SensorConfig]:
return _check_sensor_compatibility(value, XBT_SUPPORTED_SENSORS, "XBT")

@pydantic.field_serializer("sensors")
def _serialize_sensors(self, value: list[SensorConfig], _info):
return _serialize_sensor_list(value)

def active_variables(self) -> dict[str, str]:
"""FieldSet-key → Copernicus-variable mapping for enabled sensors."""
return build_variables_from_sensors(self.sensors)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these functions required for every instrument? Can't they be reused?

Comment on lines +100 to +103
particle_vars=[
"U",
"V",
], # two particle variables associated with one sensor
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not UV?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to add so much more unit testing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants