Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 84 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,96 @@
SOCAT
=====

The Simons Observatory source CATalog. Contains information about sources
that the Simons Observatory is monitorning.
The Simons Observatory source CATalog. Contains information about sources that the Simons Observatory is monitoring.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The SO in SOCAT is actually for SOurce

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

create_ephem doesn't raise an error but somethign else did further down the line. It might have just been get_forced_photometry_sources or something. which I could have skirted by doing flux selection later

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you write a test that fails or otherwise give me something I can use to reproduce?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok, just added test -- the cause is in SourceGenerator

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah ok well I'll run this but almost certainly the problem is that flux is one of the interpolated values in SourceGenerator. It was asked that I add this feature to support the ability to filter sources by flux. This is then sort of a high level question of what we want to do with sources without flux estimates. We can simply return None for the flux value if the source in socat has no flux estimate [i.e., return (x,y, None)], or we can change the shape of the return type to be only (x,y) as opposed to (x,y,flux.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we want to just return None for the flux; i.e. .at_time() would return (postition,None) and we don't have to worry about interpolating it.

We will have to decide what that means for forced photometry.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Addressed in PR #29



BUILDING YOUR VERY OWN SOCAT
=====

Once installed, you can read in catalogs with custom ingestion scripts found in socat/ingest/

Set your environment variables to point to the location/name of the database you want to construct:

```
export socat_client_client_type=db
export socat_model_database_name=socat.db
```

then run `socat-migrate` to create the socat.db in your working dir.

Once created, you can add sources via the following example ingest scripts:

`socat-act-fits` was written to read in an ACT point source catalog which is simply stored as a fits file with certain columns.

`socat-jpl-parquet` was written to read in a parquet file containing JPL Horizons ephemerides for solar system objects.

These scripts store either FixedRegisteredSources, or SolarSystemObjects.


USING SOCAT
=====

Once sources are ingested into your db, you can use the socat to query sources within a box on the sky within some time range.

Make sure you environment variables point to the correct db, as above.

```
from socat.client.settings import SOCatClientSettings
from astropy.coordinates import SkyCoord
from astropy.time import Time
from astropy import units as u

settings = SOCatClientSettings()
catalog = settings.client

sso_client = catalog.sso
ephem_client = catalog.ephem

sky_box = [
SkyCoord(ra=0*u.deg,dec=-20*u.deg),
SkyCoord(ra=120*u.deg,dec=20*u.deg)
]

results = sso_client.get_box(
lower_left=sky_box[0],
upper_right=sky_box[1],
t_min=Time("2019-06-08T00:00:00Z"),
t_max=Time("2019-06-11T12:00:00Z"),
source_cat=catalog,
ephem_cat=ephem_client,
)

```

This returns a list of SourceGenerator objects, which you can query for positions and fluxes at time t
by initializing the interpolator (whose valid range is the t_min, t_max you input above)


```
t = Time("2019-01-01T00:00:00Z")

for r in results:
r.init_interp(ephem_cat=ephem_client)

sources = [r.at_time(t=t) for r in results]

```

So that each element of sources is a tuple of (position, flux) with position given as a SkyCoord object.
In this way, you can query fixed astrophysical sources and moving objects in the same unified calls.

The sotrplib library (github.com/simonsobs/sotrplib) has functional examples of loading in sources within
map boundaries, and can be found in `sotrplib/source_catalog/socat.py`.


Project Leads:

- Jack OS
- Josh B

Project Helpers:
- AF

Status
------

Expand Down
16 changes: 13 additions & 3 deletions socat/client/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,9 +394,13 @@ def __init__(
engine: Engine | None = None,
session_factory: sessionmaker | None = None,
):
if session_factory is None:
session_factory = create_sync_session_factory(
db_url=db_url,
engine=engine,
)
self._session_factory = session_factory
self._get_session = create_sync_session_interface(
db_url=db_url,
engine=engine,
session_factory=session_factory,
)

Expand Down Expand Up @@ -463,7 +467,13 @@ def get_box(
)

return [
SourceGenerator(source=s, t_min=t_min, t_max=t_max, ephem_cat=ephem_cat)
SourceGenerator(
source=s,
t_min=t_min,
t_max=t_max,
ephem_cat=ephem_cat,
session_factory=self._session_factory,
)
for s in fixed_sources + sso_sources
]

Expand Down
8 changes: 4 additions & 4 deletions socat/ingest/jplparquet.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,19 @@ def ingest_jpl_parquet_file(
sso = client.sso.get_sso_MPC_id(MPC_id=mpc_id)[0]

for _, row in tqdm(
rows[::10].iterrows(), desc=f"Ingesting {designation}", total=len(rows[:10])
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.

Oops, I must have left that in

rows.iterrows(), desc=f"Ingesting {designation}", total=len(rows)
):
flux = None
flux = 200 * u.mJy # Default flux if not provided
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.

Who came up with this and why? Probably should be configurable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

me - and yes itshoudl be but a flux of None causes it to fail so I had to give it some finite value

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

or - alternatively we have a 'monitored' flag that allows us to do forced photometry on sources that we don't have fluxes for (i.e. asteroids or transients / dave's favorite sources)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Uh what error does it raise? socat.db.ephem.create_ephem' should accept flux as None`.

if "flux_mJy" in row.index and pd.notna(row["flux_mJy"]):
flux = row["flux_mJy"] * u.mJy

unix_time = Time(row["julian_day"], format="jd").unix
unix_time = Time(row["julian_day"], format="jd")

client.ephem.create_ephem(
sso_id=sso.sso_id,
MPC_id=mpc_id,
name=name,
time=int(unix_time),
time=unix_time,
position=ICRS(
ra=float(row["ra_deg"]) * u.deg,
dec=float(row["dec_deg"]) * u.deg,
Expand Down
73 changes: 73 additions & 0 deletions tests/test_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,79 @@ async def test_gen(database_async_sessionmaker):
await core.delete_source(source.source_id, session=session)


@pytest.mark.asyncio
async def test_gen_no_flux(database_async_sessionmaker):
t_min = Time("2025-02-01T00:00:00.00")
t_max = t_min + 1100 * u.s
async with database_async_sessionmaker() as session:
sso = await core.create_sso(name="Davida", MPC_id=511, session=session)

position = ICRS(1 * u.deg, 1 * u.deg)
source = await core.create_source(
position,
session=session,
name="mySrc",
flux=None,
)

for i in range(10):
position = ICRS(i * u.deg, 1.5 * i * u.deg)
flux = None
time = t_min + (100 * i) * u.s
await core.create_ephem(
sso_id=sso.sso_id,
MPC_id=511,
name="Davida",
time=time,
position=position,
flux=flux,
session=session,
)

ephem_list = await core.get_ephem_points(sso, t_min, t_max, session=session)

for ephem in ephem_list:
assert t_min <= ephem.time
assert ephem.time <= t_max
assert len(ephem_list) == 10

async with database_async_sessionmaker() as session:
gen = generator.SourceGenerator(
source, Time("2025-01-01T00:00:00.00"), Time("2026-01-01T00:00:00.00")
)
await gen.init_interp(session=session)

position, flux = gen.at_time(t=Time("2025-06-01T00:00:00.00"))

assert position.ra.value == 1
assert position.dec.value == 1
assert flux is None

async with database_async_sessionmaker() as session:
gen = generator.SourceGenerator(sso, t_min=t_min, t_max=t_max)
await gen.init_interp(session=session)

position, flux = gen.at_time(Time("2025-02-01 00:04:10.000000"))

assert position.ra.value == 2.5
assert position.dec.value == 3.75
assert flux is None

# Check asking out of bounds doesn't work
with pytest.raises(ValueError):
gen.at_time(t_max + 100 * u.s)

# Check not initializing interp raises error
with pytest.raises(RuntimeError):
async with database_async_sessionmaker() as session:
gen = generator.SourceGenerator(sso, t_min=t_min, t_max=t_max)
gen.at_time(Time("2025-02-01 00:04:10.000000"))

async with database_async_sessionmaker() as session:
await core.delete_sso(sso.sso_id, session=session)
await core.delete_source(source.source_id, session=session)


@pytest.mark.asyncio
async def test_get_box(database_async_sessionmaker):
t_min = Time("2025-02-01T00:00:00.00")
Expand Down
Loading