Skip to content

Commit

Permalink
Swap to gwemopt v0.2 and pydantic v2
Browse files Browse the repository at this point in the history
  • Loading branch information
robertdstein committed May 28, 2024
1 parent abf759f commit 8c1e4a6
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 5,119 deletions.
5 changes: 0 additions & 5 deletions .gitmodules

This file was deleted.

12 changes: 4 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,21 @@ We suggest using a conda environment to install `snipergw`, with python>=3.10.
You can then install the package using `pip` and `poetry`:

```
git clone --recurse-submodules [email protected]:robertdstein/snipergw.git
git clone [email protected]:robertdstein/snipergw.git
cd snipergw
pip install poetry
poetry install
pip install -e .
pre-commit install
```

Make sure not to miss the `--recurse-submodules` flag, as this is required to download the `gwemopt` submodule.

Sometimes, if you are using a conda environment, you might need to run `poetry install` twice.
If you still have problems, try installing troublesome packages with `conda`, and then do `pip install -e .` instead of `poetry install`.

Note for ARM-based macs: The installation of `fiona` might fail if you do not have [gdal](https://gdal.org/) installed. In that case, consider using a `conda` and running `conda install -c conda-forge gdal` before running `poetry install`.
Note for ARM-based macs: The installation of `fiona` might fail if you do not have [gdal](https://gdal.org/) installed. In that case, consider using a `conda` and running `conda install -c conda-forge gdal` before running `pip install`.

If you want to generate movies, you also need to install `ffmpeg`, which you can do via `brew install ffmpeg` or `conda install -c conda-forge ffmpeg`.

## Usage

To use this functionality, you must first configure the connection details. Thos is instrument-specific.
To use this functionality, you must first configure the connection details. This is instrument-specific.

### ZTF
You need both an API token, and to know the address of the Kowalski host address. You can then set these as environment variables:
Expand Down
4,956 changes: 0 additions & 4,956 deletions poetry.lock

This file was deleted.

86 changes: 54 additions & 32 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,39 +1,65 @@
[tool.poetry]
[build-system]
requires = ["setuptools>=45", "setuptools-scm"]
build-backend = "setuptools.build_meta"

[project]
name = "snipergw"
version = "0.2.0"
version = "1.0.0"
description = ""
authors = ["Robert Stein <[email protected]>"]
license = "MIT"
authors = [
{name = "Robert Stein", email = "[email protected]"}
]
license = {text = "MIT"}
readme = "README.md"
include = ["gwemopt/bin/**"]
requires-python = ">=3.10"
classifiers = [
"Programming Language :: Python :: 3",
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Intended Audience :: Science/Research',
'Intended Audience :: End Users/Desktop',
'Intended Audience :: Developers',
'Natural Language :: English',
'Topic :: Scientific/Engineering',
'Topic :: Scientific/Engineering :: Astronomy',
'Topic :: Scientific/Engineering :: Physics',
'Operating System :: POSIX',
'Operating System :: Unix',
'Operating System :: MacOS',
]
dependencies = [
"requests",
"backoff",
"ligo-gracedb",
"lxml",
"wget",
"astropy",
"numpy",
"gwemopt==0.2.2",
"pandas",
"pydantic>=2.2.0",
"planobs",
"winterapi >= 1.4.0",
]
[project.optional-dependencies]
dev = [
"black == 24.4.2",
"isort == 5.13.2",
"pylint == 3.2.2",
"coveralls",
]

[project.urls]
Homepage = "https://github.com/winter-telescope/winterapi"

[tool.setuptools]
packages = ["snipergw"]

[tool.poetry.dependencies]
python = ">3.10,<3.12"
requests = "^2.31.0"
backoff = "^2.2.1"
ligo-gracedb = "^2.11.0"
jupyter = "^1.0.0"
lxml = "^4.9.2"
wget = "^3.2"
astropy = "^5.3"
numpy = "1.24.3"
gwemopt = { path = "./snipergw/gwemopt", develop = true }
pandas = ">1.4.0"
pydantic = "^1.10.9"
planobs = "^0.7.2"
black = "^23.3.0"
isort = {extras = ["pyproject"], version = "^5.12.0"}
pre-commit = "^3.3.3"
coveralls = {extras = ["toml"], version = "^3.3.1"}
winterapi = "^0.2.0"
[tool.coverage.run]
source = ["snipergw"]

[tool.isort]
profile = "black"
skip = ["snipergw/gwemopt"]

[tool.black]
exclude = "snipergw/gwemopt"

[tool.coverage.report]
# Regexes for lines to exclude from consideration
Expand All @@ -59,7 +85,3 @@ exclude_lines = [
"raise"
]
ignore_errors = true

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
1 change: 0 additions & 1 deletion snipergw/gwemopt
Submodule gwemopt deleted from a962b1
82 changes: 47 additions & 35 deletions snipergw/model.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
"""
This module contains the pydantic models used to validate the input
"""

from pathlib import Path
from typing import Any

from astropy import units as u
from astropy.time import Time
from pydantic import BaseModel, validator
from pydantic import (
BaseModel,
ConfigDict,
ValidationInfo,
field_validator,
model_validator,
validator,
)
from typing_extensions import Self

from snipergw.paths import base_output_dir

Expand Down Expand Up @@ -39,54 +49,56 @@ class TelescopeDefault(BaseModel):
class PlanConfig(BaseModel):
output_dir: Path = base_output_dir
telescope: str = DEFAULT_TELESCOPE
filters: str = None
exposuretime: float = None
filters: str | None = None
exposuretime: float | None = None
cache: bool = False
starttime: Time = DEFAULT_STARTTIME
subprogram: str = "EMGW"
use_both_grids: bool = False

@validator("telescope")
@field_validator("telescope")
@classmethod
def telescope_must_be_known(cls, v):
if v not in all_telescopes:
raise ValueError(f"telescope must be in {all_telescopes}")
return v

@validator("filters", always=True)
def set_default_filter(cls, field_value, values, field):
if field_value is None:
if values["telescope"] == "ZTF":
return ztf_default.filters
elif values["telescope"] == "WINTER":
return winter_default.filters
@model_validator(mode="after")
def set_default_filter(self) -> Any:
if self.filters is None:
if self.telescope == "ZTF":
self.filters = ztf_default.filters
elif self.telescope == "WINTER":
self.filters = winter_default.filters
else:
raise ValueError(f"Unknown telescope {values['telescope']}")

for filter_name in field_value.split(","):
if values["telescope"] == "ZTF":
assert (
filter_name in ztf_default.all_filters
), f"Unknown filter {filter_name} for telescope {values['telescope']}, acceptable filters are {ztf_default.all_filters}"
elif values["telescope"] == "WINTER":
assert (
filter_name in winter_default.all_filters
), f"Unknown filter {filter_name} for telescope {values['telescope']}, acceptable filters are {winter_default.all_filters}"
raise ValueError(f"Unknown telescope {self.telescope}")

for filter_name in self.filters.split(","):
if self.telescope == "ZTF":
assert filter_name in ztf_default.all_filters, (
f"Unknown filter {filter_name} for telescope {self.telescope}, "
f"acceptable filters are {ztf_default.all_filters}"
)
elif self.telescope == "WINTER":
assert filter_name in winter_default.all_filters, (
f"Unknown filter {filter_name} for telescope {self.telescope}, "
f"acceptable filters are {winter_default.all_filters}"
)
else:
raise ValueError(f"Unknown telescope {values['telescope']}")
raise ValueError(f"Unknown telescope {self.telescope}")

return field_value
return self

@validator("exposuretime", always=True)
def set_default_exposure(cls, field_value, values, field):
if field_value is None:
if values["telescope"] == "ZTF":
return ztf_default.exposuretime
elif values["telescope"] == "WINTER":
return winter_default.exposuretime
@model_validator(mode="after")
def set_default_exposure(self) -> Self:
if self.exposuretime is None:
if self.telescope == "ZTF":
self.exposuretime = ztf_default.exposuretime
elif self.telescope == "WINTER":
self.exposuretime = winter_default.exposuretime
else:
raise ValueError(f"Unknown telescope {values['telescope']}")
raise ValueError(f"Unknown telescope {self.telescope}")

return field_value
return self

class Config:
arbitrary_types_allowed = True
model_config = ConfigDict(arbitrary_types_allowed=True)
73 changes: 36 additions & 37 deletions snipergw/plan.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
"""
Module to plan observations with gwemopt
"""

import logging
import subprocess

import numpy as np
import pandas as pd
import pytz
from astropy.time import Time
from gwemopt.run import run

from snipergw.model import PlanConfig
from snipergw.paths import base_output_dir, gwemopt_dir
from snipergw.skymap import Skymap

gwemopt_run_path = gwemopt_dir.joinpath("bin/gwemopt_run")
gwemopt_config_dir = gwemopt_dir.joinpath("config")
gwemopt_tiling_dir = gwemopt_dir.joinpath("tiling")


logger = logging.getLogger(__name__)

timezone_format = "%Y-%m-%dT%H:%M:%S"
Expand All @@ -44,51 +41,53 @@ def run_gwemopt(
[str(plan_config.exposuretime) for _ in plan_config.filters.split(",")]
)

cmd = (
f"python {gwemopt_run_path} --telescopes {plan_config.telescope} "
f"--doTiles --doPlots --doSchedule --doSkymap "
f"--timeallocationType powerlaw "
f"--scheduleType greedy -o '{gwemopt_output_dir}' "
f"--gpstime {plan_config.starttime.gps} "
f"--skymap {skymap.skymap_path} --filters {plan_config.filters} "
f"--exposuretimes {exposures} --doSingleExposure "
f"--tilingDir {gwemopt_tiling_dir} "
f"--doBalanceExposure --configDirectory {gwemopt_config_dir} "
f"--powerlaw_cl 0.9 "
f"--airmass 2.5 --mindiff 30 "
)
gwemopt_args += [
"--telescopes",
plan_config.telescope,
"--doTiles",
"--doPlots",
"--doSchedule",
"--timeallocationType",
"powerlaw",
"--scheduleType",
"greedy",
"-o",
f"{gwemopt_output_dir}",
"--gpstime",
f"{plan_config.starttime.gps}",
"--event",
f"{skymap.skymap_path}",
"--filters",
f"{plan_config.filters}",
"--exposuretimes",
f"{exposures}",
"--doSingleExposure",
"--doBalanceExposure",
]

if not plan_config.telescope == "DECam":
extra_cmd = "--doAlternatingFilters "
cmd += extra_cmd
if "--airmass" not in gwemopt_args:
gwemopt_args += ["--airmass", "2.5"]

if not plan_config.use_both_grids and plan_config.telescope == "ZTF":
gwemopt_args.append("--doUsePrimary")
if "--mindiff" not in gwemopt_args:
gwemopt_args += ["--mindiff", "30"]

if skymap.is_3d:
gwemopt_args.append("--do3D")
if "--powerlaw_cl" not in gwemopt_args:
gwemopt_args += ["--powerlaw_cl", "0.9"]

cmd += " ".join(gwemopt_args)
if not plan_config.telescope == "DECam":
gwemopt_args += ["--doAlternatingFilters"]

if not plan_config.cache:
logger.info(f"Running gwemopt with command '{cmd}'")
subprocess.run(
cmd,
shell=True,
# stdout=subprocess.DEVNULL,
check=True,
)
logger.info(f"Running gwemopt with arguments: {gwemopt_args}")

run(gwemopt_args)
else:
logger.info("Using cached schedule")

coverage = output_dir.joinpath("tiles_coverage.pdf")
coverage.unlink(missing_ok=True)
coverage.symlink_to(gwemopt_output_dir.joinpath("tiles_coverage.pdf"))

movie = output_dir.joinpath("coverage.mpg")
movie.unlink(missing_ok=True)
movie.symlink_to(gwemopt_output_dir.joinpath("coverage.mpg"))

logger.info(f"See coverage at {coverage}")

schedule_path = gwemopt_output_dir.joinpath(f"schedule_{plan_config.telescope}.dat")
Expand Down
5 changes: 3 additions & 2 deletions snipergw/run.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
This module contains the main function for running snipergw.
"""

import logging

import numpy as np
Expand Down Expand Up @@ -42,15 +43,15 @@ def run_snipergw(
if plan_config.telescope == "ZTF":
submit_too_ztf(
schedule,
event_name=event,
event_name=event.event,
plan_config=plan_config,
submit=submit,
delete=delete,
)
elif plan_config.telescope == "WINTER":
submit_too_winter(
schedule,
event_name=event,
event_name=event.event,
plan_config=plan_config,
submit=submit,
delete=delete,
Expand Down
Loading

0 comments on commit 8c1e4a6

Please sign in to comment.