Scheduling (pyobs.robotic.scheduler)
This page documents the data model and scheduling logic of the robotic system. For a conceptual overview see Robotic mode (pyobs.robotic). For a worked setup example see Setting up a minimal robotic observation system.
Task and Observation
Task is the fundamental unit of work. It is a pydantic model, so it is
fully described by its YAML representation and can be validated, serialised, and round-tripped
without any custom code:
name: Observe M51
duration: 120.0
priority: 1.0
target:
class: pyobs.robotic.scheduler.targets.SiderealTarget
ra: 202.47
dec: 47.20
constraints:
- class: pyobs.robotic.scheduler.constraints.AirmassConstraint
max_airmass: 2.0
merits:
- class: pyobs.robotic.scheduler.merits.TransitMerit
jd0: 2459000.5
period: 3.14
duration: 7200
script:
class: myobs.scripts.ObserveScript
camera: camera
telescope: telescope
An Observation is a scheduled instance of a task — it adds a
concrete start and end time, a priority score, and an
ObservationState. The scheduler produces Observation objects;
the mastermind consumes them.
- class Task(*, id: Any | None = None, name: str = '', project: str = '', duration: Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=84000.0)] = 0.0, priority: Annotated[float | None, ~annotated_types.Ge(ge=0.0), ~annotated_types.Le(le=9999.0)] = 1.0, constraints: list[Constraint] = <factory>, merits: list[Merit] = <factory>, target: Target | None = None, script: dict[str, ~typing.Any]=<factory>, active: bool = True)[source]
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- async can_run(data: TaskData | None) bool[source]
Checks whether this task could run now.
- Returns:
True, if the task can run now.
- property can_start_late: bool
Whether this tasks is allowed to start later than the user-set time, e.g. for flatfields.
- Returns:
True, if task can start late.
- get_fits_headers(namespaces: list[str] | None = None) dict[str, tuple[Any, str]][source]
Returns FITS header for the current status of this module.
- Parameters:
namespaces – If given, only return FITS headers for the given namespaces.
- Returns:
Dictionary containing FITS headers.
- model_config = {'arbitrary_types_allowed': True, 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- async resolve_target(time: Time, task: Task, data: DataProvider) bool[source]
Resolve dynamic target. Returns False if no valid target found.
- class Project(*, code: str, name: str = '', priority: Annotated[float | None, Ge(ge=0.0), Le(le=9999.0)] = 1.0)[source]
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class TaskData(task: 'Task', observation_archive: 'ObservationArchive | None' = None, task_archive: 'TaskArchive | None' = None)[source]
- class Observation(*, id: Any | None = None, task: Task | Any, start: Annotated[Time, _AstropyTimePydanticTypeAnnotation], end: Annotated[Time, _AstropyTimePydanticTypeAnnotation], state: ObservationState = ObservationState.PENDING, priority: float | None = None, target: Target | None = None)[source]
A scheduled task.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- async fetch_task(task_archive: TaskArchive) None[source]
Fetch a task from the task archive.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_dump(use_task_id: bool = False, **kwargs: Any) dict[str, Any][source]
- !!! abstract “Usage Documentation”
[model_dump](../concepts/serialization.md#python-mode)
Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.
- Parameters:
mode – The mode in which to_python should run. If mode is ‘json’, the output will only contain JSON serializable types. If mode is ‘python’, the output may contain non-JSON-serializable Python objects.
include – A set of fields to include in the output.
exclude – A set of fields to exclude from the output.
context – Additional context to pass to the serializer.
by_alias – Whether to use the field’s alias in the dictionary key if defined.
exclude_unset – Whether to exclude fields that have not been explicitly set.
exclude_defaults – Whether to exclude fields that are set to their default value.
exclude_none – Whether to exclude fields that have a value of None.
exclude_computed_fields – Whether to exclude computed fields. While this can be useful for round-tripping, it is usually recommended to use the dedicated round_trip parameter instead.
round_trip – If True, dumped values should be valid as input for non-idempotent types such as Json[T].
warnings – How to handle serialization errors. False/”none” ignores them, True/”warn” logs errors, “error” raises a [PydanticSerializationError][pydantic_core.PydanticSerializationError].
fallback – A function to call when an unknown value is encountered. If not provided, a [PydanticSerializationError][pydantic_core.PydanticSerializationError] error is raised.
serialize_as_any – Whether to serialize fields with duck-typing serialization behavior.
polymorphic_serialization – Whether to use model and dataclass polymorphic serialization for this call.
- Returns:
A dictionary representation of the model.
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
Targets
A Target defines where to point the telescope. It is a
PolymorphicBaseModel, so any subclass can appear in
the task YAML via the class: key.
The coordinates(time) method accepts a Time and returns a
SkyCoord, which lets non-sidereal targets compute their position
on the fly:
target:
class: pyobs.robotic.scheduler.targets.SiderealTarget
name: M51
ra: 202.47
dec: 47.20
- class Target(*, name: str)[source]
Bases:
PolymorphicBaseModelCreate a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class SiderealTarget(*, name: str, ra: float, dec: float)[source]
Bases:
TargetCreate a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
Constraints
A Constraint answers a binary question: may this
task run at this time? If any constraint returns False, the task is excluded from scheduling
entirely for that slot.
Constraints appear in the task YAML under the constraints key (per-task) or under the
OnDemandScheduler.constraints key (global, applied to every task).
To write a custom constraint, subclass Constraint
and implement __call__ and to_astroplan:
from pyobs.robotic.scheduler.constraints import Constraint
class MyConstraint(Constraint):
min_elevation: float = 30.0
def to_astroplan(self):
return astroplan.AltitudeConstraint(min=self.min_elevation * u.deg)
async def __call__(self, time, task, data) -> bool:
if task.target is None:
return False
alt = data.observer.altaz(time, task.target).alt.deg
return alt >= self.min_elevation
- class Constraint(*, cost: float = 1.0, target_dependent: bool = False)[source]
Bases:
PolymorphicBaseModelCreate a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class AirmassConstraint(*, cost: float = 2.0, target_dependent: bool = True, max_airmass: Annotated[float, Ge(ge=1.0), Le(le=9.9)] = 1.3)[source]
Bases:
ConstraintAirmass constraint.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class SolarElevationConstraint(*, cost: float = 1.0, target_dependent: bool = False, min_elevation: Annotated[float, Ge(ge=-90), Le(le=90)] = -90.0, max_elevation: Annotated[float, Ge(ge=-90), Le(le=90)] = -18.0, direction: Literal['rising', 'setting', 'both'] = 'both')[source]
Bases:
ConstraintSolar elevation constraint.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class MoonIlluminationConstraint(*, cost: float = 3.0, target_dependent: bool = False, max_phase: Annotated[float, Ge(ge=0.0), Le(le=1.0)] = 0.0)[source]
Bases:
ConstraintMoon illumination constraint.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class MoonSeparationConstraint(*, cost: float = 3.0, target_dependent: bool = True, min_distance: Annotated[float, Ge(ge=0.0), Le(le=180.0)] = 30.0)[source]
Bases:
ConstraintMoon separation constraint.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class TimeConstraint(*, cost: float = 1.0, target_dependent: bool = False, start: Time, ~astropydantic.time._AstropyTimePydanticTypeAnnotation]=<factory>, end: Time, ~astropydantic.time._AstropyTimePydanticTypeAnnotation]=<factory>)[source]
Bases:
ConstraintTime constraint.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
Merits
A Merit answers a continuous question: how desirable is
it to run this task right now? It returns a float in the range [0, N]. All merit values for a
task are multiplied together along with the task’s priority and the project’s priority to
produce a single score. The task with the highest score wins each time slot.
A merit returning 0.0 has the same effect as a constraint returning False — the task is
excluded from that slot. This lets merits double as soft constraints when needed.
Every merit’s __call__ receives three arguments: time, task, and data. The
data argument is a DataProvider which gives
access to:
data.observer— theObserverfor the sitedata.last_sunset(time)/data.last_sunrise(time)— cached sunrise/sunset timesdata.night(time)— the calendar date of the observing nightdata.archive— anObservationArchiveEvolutionthat provides historical and simulated-future observations
To query past observations from within a merit, use data.archive.get_observations() rather
than accessing the archive directly. This ensures the lookahead cache is used during scheduling:
from pyobs.robotic.scheduler.merits import Merit
from pyobs.robotic.observation import ObservationState
class MyMerit(Merit):
min_days: float = 7.0
async def __call__(self, time, task, data) -> float:
from astropy.time import TimeDelta
import astropy.units as u
observations = await data.archive.get_observations(
task=task,
state=ObservationState.COMPLETED,
start_after=time - TimeDelta(self.min_days * u.day),
)
return 0.0 if len(observations) > 0 else 1.0
Class |
Returns 1.0 when… |
|---|---|
Always. Useful as a baseline or placeholder. |
|
The current time is after the configured time. |
|
The current time is before the configured time. |
|
The current time falls within one of the configured windows. |
|
A transit is imminent (based on period and JD of first transit). |
|
Enough time has passed since the last observation of this task. |
|
The task has not yet reached its maximum observations-per-night count. |
|
A specified other task has already completed this night. |
|
Always (but with added Gaussian noise — useful for breaking ties randomly). |
- class Merit[source]
Bases:
PolymorphicBaseModelMerit class.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class ConstantMerit(*, merit: Annotated[float, Ge(ge=0.0), Le(le=100.0)] = 1.0)[source]
Bases:
MeritMerit function that returns a constant value.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class AfterTimeMerit(*, time: Time, ~astropydantic.time._AstropyTimePydanticTypeAnnotation]=<factory>)[source]
Bases:
MeritMerit function that gives 1 after a given time.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class BeforeTimeMerit(*, time: Time, ~astropydantic.time._AstropyTimePydanticTypeAnnotation]=<factory>)[source]
Bases:
MeritMerit function that gives 1 before a given time.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class TimeWindowMerit(*, windows: list[TimeWindow], inverse: bool = False)[source]
Bases:
MeritMerit function that uses time windows.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class TransitMerit(*, jd0: Annotated[float, Ge(ge=2400000), Le(le=2499999)] = 2450000, period: Annotated[float, Ge(ge=0.01), Le(le=9999)] = 1.0, duration: Annotated[int, Ge(ge=1), Le(le=99999)] = 1, ingress: Annotated[float, Ge(ge=0), Le(le=5)] = 0.2, over: Annotated[float, Ge(ge=0), Le(le=5)] = 0.0)[source]
Bases:
MeritMerit function for observing transits.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class IntervalMerit(*, interval: Annotated[float, Ge(ge=0.0), Le(le=31536000.0)] = 0.0, unit: Literal['s', 'min', 'h', 'hr', 'd', 'wk', 'yr'] = 's')[source]
Bases:
MeritMerit function that enforces an interval between observations.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class PerNightMerit(*, count: Annotated[int, Ge(ge=0), Le(le=999)] = 0)[source]
Bases:
MeritMerit functions for defining a max number of observations per night.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class FollowMerit(*, task_id: Any = <factory>)[source]
Bases:
MeritMerit functions that only returns after another given task has run this night.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class RandomMerit(*, std: Annotated[float, Ge(ge=0.0), Le(le=999.0)] = 1.0)[source]
Bases:
MeritMerit functions for a random normal-distributed number.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
Scheduler
pyobs-core ships two TaskScheduler implementations. Both are
configured as a nested object inside the Scheduler module:
scheduler:
class: pyobs.robotic.scheduler.OnDemandScheduler # or AstroplanScheduler
twilight: astronomical
constraints:
- class: pyobs.robotic.scheduler.constraints.SolarElevationConstraint
max_solar_elevation: -12.0
The constraints block defines global constraints applied to every task in addition to each
task’s own constraints. Note that global constraints are only supported by
OnDemandScheduler — AstroplanScheduler
applies only per-task constraints.
Class |
Strategy |
|---|---|
Greedy, on-demand scheduling. Evaluates constraints and merits at each time step and picks the highest-scoring task. Robust to interruptions — re-runs from the current moment. Supports merits, global constraints, and lookahead to avoid missing higher-priority tasks. |
|
Full-night planning via |
- class TaskScheduler(vfs: VirtualFileSystem | dict[str, Any] | None = None, comm: Comm | dict[str, Any] | None = None, timezone: str | datetime.tzinfo | None = 'utc', location: str | dict[str, Any] | EarthLocation | None = None, observer: Observer | None = None, **kwargs: Any)[source]
Bases:
ObjectAbstract base class for tasks scheduler.
This class provides a
VirtualFileSystem, a timezone and a location. From the latter two, an observer object is automatically created.Object also adds support for easily adding threads using the
add_background_task()method as well as a watchdog thread that automatically restarts threads, if requested.Using
add_child_object(), other objects can be (created an) attached to this object, which then automatically handles calls toopen()andclose()on those objects.- Parameters:
vfs – VFS to use (either object or config)
comm – Comm object to use
timezone – Timezone at observatory.
location – Location of observatory, either a name or a dict containing latitude, longitude, and elevation.
- class OnDemandScheduler(twilight: str = 'astronomical', observation_archive: ObservationArchive | dict[str, Any] | None = None, constraints: list[Constraint] | list[dict[str, Any]] | None = None, **kwargs: Any)
Bases:
TaskSchedulerScheduler based on merits.
Initialize a new scheduler.
- Parameters:
twilight – astronomical or nautical
- async evaluate_constraints(task: Task, start: Time, end: Time, data: DataProvider) bool[source]
Loops all constraints. If any evaluates to False, return False. Otherwise, return True.
- Parameters:
task – Task to evaluate.
start – Start time.
end – End time.
data – Data provider.
- Returns:
True if all constraints evaluate True, False otherwise.
- async evaluate_merits(task: Task, start: Time, end: Time, data: DataProvider) float[source]
Loop all merits, evaluate them and multiply the results. If any evaluates to 0, abort and return 0.
- Parameters:
task – Task to evaluate.
start – Start time.
end – End time.
data – Data provider.
- Returns:
The final merit for this task.
- class AstroplanScheduler(twilight: str = 'astronomical', **kwargs: Any)[source]
Bases:
TaskSchedulerScheduler based on astroplan.
Initialize a new scheduler.
- Parameters:
twilight – astronomical or nautical
Task and Observation archives
TaskArchive and
ObservationArchive are abstract base classes defining
the interface that the Scheduler and
Mastermind modules depend on. pyobs-core ships three concrete
implementations of each — see Archive implementations below.
- class TaskArchive(on_tasks_changed: Callable[[], Coroutine[Any, Any, None]] | None = None, **kwargs: Any)[source]
Bases:
ObjectThis class provides a
VirtualFileSystem, a timezone and a location. From the latter two, an observer object is automatically created.Object also adds support for easily adding threads using the
add_background_task()method as well as a watchdog thread that automatically restarts threads, if requested.Using
add_child_object(), other objects can be (created an) attached to this object, which then automatically handles calls toopen()andclose()on those objects.- Parameters:
vfs – VFS to use (either object or config)
comm – Comm object to use
timezone – Timezone at observatory.
location – Location of observatory, either a name or a dict containing latitude, longitude, and elevation.
- abstractmethod async get_projects() list[Project][source]
Returns list of projects.
- Returns:
List of projects.
- abstractmethod async get_schedulable_tasks() list[Task][source]
Returns list of schedulable tasks.
- Returns:
List of schedulable tasks
- class ObservationArchive(**kwargs: Any)[source]
Bases:
ObjectThis class provides a
VirtualFileSystem, a timezone and a location. From the latter two, an observer object is automatically created.Object also adds support for easily adding threads using the
add_background_task()method as well as a watchdog thread that automatically restarts threads, if requested.Using
add_child_object(), other objects can be (created an) attached to this object, which then automatically handles calls toopen()andclose()on those objects.- Parameters:
vfs – VFS to use (either object or config)
comm – Comm object to use
timezone – Timezone at observatory.
location – Location of observatory, either a name or a dict containing latitude, longitude, and elevation.
- abstractmethod async add_observations(tasks: ObservationList) None[source]
Add the list of scheduled tasks to the schedule.
- Parameters:
tasks – Scheduled tasks.
- abstractmethod async clear_schedule(start_time: Time) None[source]
Clear schedule after given start time.
- Parameters:
start_time – Start time to clear from.
- abstractmethod async get_current_observation(task_archive: TaskArchive | None = None) Observation | None[source]
Returns the currently running observation.
- Parameters:
task_archive – Task archive to get task from.
- Returns:
Currently running observation.
- abstractmethod async get_next_observation(time: Time, task_archive: TaskArchive | None = None) Observation | None[source]
Returns the active scheduled task at the given time.
- Parameters:
time – Time to return task for.
task_archive – Task archive to get task from.
- Returns:
Scheduled task at the given time.
- abstractmethod async get_observations(task: Task | None = None, state: ObservationState | None = None, start_before: Time | None = None, start_after: Time | None = None, end_before: Time | None = None, end_after: Time | None = None) ObservationList[source]
Returns a list of observations matching the given filters.
- Parameters:
task – If given, only return observations for this task.
state – If given, only return observations in this state.
start_before – If given, only return observations that start before this time.
start_after – If given, only return observations that start after this time.
end_before – If given, only return observations that end before this time.
end_after – If given, only return observations that end after this time.
- Returns:
List of matching observations.
- abstractmethod async get_schedule() ObservationList[source]
Fetch schedule from portal.
- Returns:
Dictionary with tasks.
- Raises:
Timeout – If request timed out.
ValueError – If something goes wrong.
- abstractmethod async update_observation(observation: Observation) None[source]
Updates observation. :Parameters: observation – Observation to update.
Archive implementations
Filesystem (pyobs.robotic.filesystem)
Tasks are YAML files in a directory; observations are YAML files named by night. No external services required — the simplest setup for a single telescope.
Backend (pyobs.robotic.backend)
Tasks and observations are managed by the pyobs-robotic-backend HTTP service. Enables multi-telescope coordination, a web UI for queue management, and centralised logging.
- class BackendTaskArchive(url: str, token: str, auto_update: bool = True, **kwargs: Any)[source]
Bases:
TaskArchiveTask archive based on pyobs-robotic-backend.
Creates a new task archive.
- Parameters:
url – URL of pyobs-robotic-backend.
token – Auth token.
- async get_schedulable_tasks() list[Task][source]
Returns list of schedulable tasks.
- Returns:
List of schedulable tasks
- class BackendObservationArchive(url: str, token: str, mode: Literal['day', 'night'] = 'night', auto_update: bool = True, **kwargs: Any)[source]
Bases:
ObservationArchiveObservation archive based on pyobs-robotic-backend.
This class provides a
VirtualFileSystem, a timezone and a location. From the latter two, an observer object is automatically created.Object also adds support for easily adding threads using the
add_background_task()method as well as a watchdog thread that automatically restarts threads, if requested.Using
add_child_object(), other objects can be (created an) attached to this object, which then automatically handles calls toopen()andclose()on those objects.- Parameters:
vfs – VFS to use (either object or config)
comm – Comm object to use
timezone – Timezone at observatory.
location – Location of observatory, either a name or a dict containing latitude, longitude, and elevation.
- async add_observations(tasks: ObservationList) None[source]
Add the list of scheduled tasks to the schedule.
- Parameters:
tasks – Scheduled tasks.
- async clear_schedule(start_time: Time) None[source]
Clear schedule after given start time.
- Parameters:
start_time – Start time to clear from.
- async get_current_observation(task_archive: TaskArchive | None = None) Observation | None[source]
Returns the currently running observation.
- Parameters:
task_archive – Task archive to get task from.
- Returns:
Currently running observation.
- async get_next_observation(time: Time, task_archive: TaskArchive | None = None) Observation | None[source]
Returns the active scheduled task at the given time.
- Parameters:
time – Time to return task for.
task_archive – Task archive to get task from.
- Returns:
Scheduled task at the given time.
- async get_observations(task: Task | None = None, state: ObservationState | None = None, start_before: Time | None = None, start_after: Time | None = None, end_before: Time | None = None, end_after: Time | None = None) ObservationList[source]
Returns a list of observations matching the given filters.
- Parameters:
task – If given, only return observations for this task.
state – If given, only return observations in this state.
start_before – If given, only return observations that start before this time.
start_after – If given, only return observations that start after this time.
end_before – If given, only return observations that end before this time.
end_after – If given, only return observations that end after this time.
- Returns:
List of matching observations.
- async get_schedule() ObservationList[source]
Fetch schedule from portal.
- Returns:
Dictionary with tasks.
- Raises:
Timeout – If request timed out.
ValueError – If something goes wrong.
- async update_observation(observation: Observation) None[source]
Updates observation. :Parameters: observation – Observation to update.
Las Cumbres Observatory (pyobs.robotic.lco)
Integration with the Las Cumbres Observatory observation portal. Tasks are
fetched from the LCO portal API using an instrument type and authorisation token; observations are
read from and written back to the LCO schedule. Also includes
LcoTaskRunner, which maps LCO request configurations to the
appropriate Script subclass based on a configurable scripts map.
- class LcoTaskArchive(url: str, token: str, instrument_type: str | list[str], **kwargs: Any)[source]
Bases:
TaskArchiveScheduler for using the LCO portal
Creates a new LCO scheduler.
- Parameters:
url – URL to portal
token – Authorization token for portal
instrument_type – Type of instrument to use.
scripts – External scripts
- class LcoObservationArchive(url: str, configdb: str, site: str, token: str, enclosure: str, telescope: str, period: int = 24, mode: Literal['read', 'write', 'readwrite'] = 'readwrite', **kwargs: Any)[source]
Bases:
ObservationArchiveScheduler for using the LCO portal
Creates a new LCO scheduler.
- Parameters:
url – URL to portal
configdb – URL to configdb
site – Site filter for fetching requests
token – Authorization token for portal
enclosure – Enclosure for new schedules.
telescope – Telescope for new schedules.
instrument – Instrument for new schedules.
period – Period to schedule in hours
- async add_observations(tasks: ObservationList) None[source]
Add the list of scheduled tasks to the schedule.
- Parameters:
tasks – Scheduled tasks.
- async clear_schedule(start_time: Time) None[source]
Clear schedule after given start time.
- Parameters:
start_time – Start time to clear from.
- async get_current_observation(task_archive: TaskArchive | None = None) Observation | None[source]
Returns the currently running observation.
- async get_next_observation(time: Time, task_archive: TaskArchive | None = None) Observation | None[source]
Returns the active scheduled task at the given time.
- Parameters:
time – Time to return an observation for.
- Returns:
Scheduled task at the given time.
- async get_observations(task: Task | None = None, state: ObservationState | None = None, start_before: Time | None = None, start_after: Time | None = None, end_before: Time | None = None, end_after: Time | None = None) ObservationList[source]
Returns a list of observations matching the given filters.
The LCO portal requires a request id, so a task is mandatory for this archive.
- Parameters:
task – Task to get observations for (required for the LCO archive).
state – If given, only return observations in this state.
start_before – If given, only return observations that start before this time.
start_after – If given, only return observations that start after this time.
end_before – If given, only return observations that end before this time.
end_after – If given, only return observations that end after this time.
- Returns:
List of matching observations.
- async get_schedule() ObservationList[source]
Fetch schedule from the portal.
- Returns:
Dictionary with tasks.
- Raises:
Timeout – If request timed out.
ValueError – If something goes wrong.
- async observations_for_night(date: date) ObservationList[source]
Returns a list of observations for the given task.
- Parameters:
date – Date of night to get observations for.
- Returns:
List of observations for the given task.
- async send_update(status_id: int | None, status: dict[str, Any]) None[source]
Send a report to the LCO portal
- Parameters:
status_id – id of config status
status – Status dictionary
- async update_observation(observation: Observation) None[source]
Updates observation state in the portal.
- class LcoTaskRunner(scripts: dict[str, Any], **kwargs: Any)[source]
Bases:
TaskRunnerCreates a new LCO task runner.
- Parameters:
scripts – External scripts
Image archives
Archive is the base class used by
ArchiveSkyflatPriorities to query historical
observations when calculating flat-field priorities. Concrete implementations are configured via
the class: key like any other polymorphic model.
- class Archive
Bases:
PolymorphicBaseModelBase class for image archives.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context: Any, /) None
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class PyobsArchive(*, url: str, token: str, proxies: dict[str, str] | None = None)
Bases:
ArchiveConnector class to running pyobs-archive instance.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(_PyobsArchive__context: Any) None[source]
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
- class LocalArchive(*, root: str)
Bases:
ArchiveConnector class to a local image archive.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
- model_config = {'arbitrary_types_allowed': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(_LocalArchive__context: Any) None[source]
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self – The BaseModel instance.
context – The context.
Scheduling internals
These classes are used internally by the scheduler and are relevant mainly when writing custom merit functions.
DataProvider is passed to every
Merit and
Constraint during a scheduling run. It provides
cached access to site geometry (sunrise, sunset, night boundaries) and to the observation
history via its archive attribute.
ObservationArchiveEvolution wraps
the real ObservationArchive with two additions:
Caching — observations for each task are fetched from the archive once per scheduling run and cached in memory, avoiding repeated HTTP requests during evaluation of many time slots.
Lookahead simulation — as the scheduler plans ahead and tentatively assigns tasks to future slots, it calls
evolve()to record those assignments. Subsequent merit evaluations for the same task then see those simulated observations, soIntervalMeritandPerNightMeritcorrectly prevent the same task from being scheduled twice in one run.
- class DataProvider(observer: Observer, archive: ObservationArchiveEvolution | None = None)[source]
Bases:
objectData provider for Merit classes.
- class ObservationArchiveEvolution(observer: Observer, obs_archive: ObservationArchive | None = None)[source]
Bases:
object