Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
f07bf52
WIP: Optimize fids for guide selection
jeanconn Nov 7, 2025
06e1c97
Move get_t_ccds_bonus into proseco
jeanconn Nov 12, 2025
71bae76
Use local get_t_ccds_bonus and remove initial duplication
jeanconn Nov 12, 2025
2f59a32
Put guide candidates as fid attr. Improve names and optimize a bit
jeanconn Nov 12, 2025
3ed9c46
Ruff
jeanconn Nov 12, 2025
db7afff
Update comment
jeanconn Nov 12, 2025
819e999
Remove early-exit code for get_guide_catalog
jeanconn Nov 12, 2025
e0b1b25
Ruff
jeanconn Nov 12, 2025
a06ddd0
Ruff format
jeanconn Nov 12, 2025
8688e74
Improve docs
jeanconn Dec 15, 2025
d678ca4
Make fid trap spoiler calc optional if candidates not passed to get_f…
jeanconn Dec 15, 2025
286961d
Refactor and add tests
jeanconn Dec 17, 2025
f7088c4
Ruff
jeanconn Dec 17, 2025
2c39e68
Misc fixes
jeanconn Dec 18, 2025
3bd36c5
More ruff
jeanconn Dec 18, 2025
b802cd9
Fix double-call of process_monitors_pre()
jeanconn Dec 18, 2025
46f6285
Fix issues in excluding stars
jeanconn Dec 18, 2025
b098a03
Move expensive imp_mag processing and redo monitor star keepout logic
jeanconn Dec 18, 2025
b77b42b
Handle guide_cands as fid attribute
jeanconn Dec 18, 2025
4f759ac
Run proseco tests without kadi trying to use the internet
jeanconn Dec 18, 2025
8c2a70c
Small fixes
jeanconn Dec 18, 2025
97bff83
Cleanup
jeanconn Dec 18, 2025
4e6618f
Update docstrings
jeanconn Dec 18, 2025
bfa05f4
Update logic around fid spoiler score and fix test
jeanconn Dec 18, 2025
5c829d2
Use new fid_trap_spoiler flag instead of adding to spoiler score
jeanconn Dec 19, 2025
d0d622b
Remove stray print and comment that wasn't helping anything
jeanconn Dec 19, 2025
b984475
Update comments
jeanconn Dec 19, 2025
01dec2d
More docstring update
jeanconn Dec 19, 2025
797c012
Move an import
jeanconn Dec 19, 2025
aed74b6
Reorganize moved import
jeanconn Dec 19, 2025
074eb21
Remove extra import
jeanconn Dec 19, 2025
d746413
Add another monitor star test case
jeanconn Dec 21, 2025
cad40bc
Be more consistent with the guide star candidates columns
jeanconn Dec 21, 2025
99db2cc
Remove a newline
jeanconn Dec 21, 2025
0aef5cc
Ruff
jeanconn Dec 21, 2025
f00bc10
Add another generic test of MON_AUTO behavior
jeanconn Dec 23, 2025
4dd25b8
Rename some vars, move t_ccd_bonus tests, ruff
jeanconn Dec 23, 2025
db05074
Short circuit fid spoiler checks sooner
jeanconn Dec 30, 2025
72d0bb7
Play with different optimization order
jeanconn Dec 30, 2025
18ab110
Revert "Play with different optimization order"
jeanconn Dec 30, 2025
3974e0b
Add notebook to help generate regress data
jeanconn Dec 31, 2025
3113b97
Add notebook reviewing PR410 catalogs
jeanconn Dec 31, 2025
56165ea
Simplify interface for get_guide_candidates
jeanconn Jan 1, 2026
b2801e8
Clear stages and stat values after filtering
jeanconn Jan 1, 2026
3855245
Short circuit guide include ids if there aren't any
jeanconn Jan 1, 2026
ecacbc4
Keep new_stars and cand_guides in same colname order
jeanconn Jan 1, 2026
eb4a3e4
Add more guide tests related to candidate handling
jeanconn Jan 1, 2026
b23ec45
Update notebook
jeanconn Jan 1, 2026
86549d6
Add a test of guide stars excluded by monitor star
jeanconn Jan 2, 2026
55766b7
Update monitor keepout to at least exclude old alg range
jeanconn Jan 2, 2026
e5ee6d8
Update keepout test for new ranges
jeanconn Jan 2, 2026
3c28edb
Update validation notebook for record
jeanconn Jan 2, 2026
90bf0be
Add comment
jeanconn Jan 2, 2026
4e1c6d2
Fix typo and improve comments in notebook
jeanconn Jan 9, 2026
c8f6638
Generalize fid.spoils for acq or guide stars
jeanconn Jan 9, 2026
e2bbbd6
Ruff
jeanconn Jan 9, 2026
5488320
Add references to fid trap web page
jeanconn Jan 9, 2026
7ba8392
Update notebook to include p2/guide_count impact
jeanconn Jan 12, 2026
20b7039
Save initial guide candidates in immutable dataclass
jeanconn Jan 13, 2026
2380d8a
Reduce scope of jupiter spoiler filter
jeanconn Jan 13, 2026
f94bd3c
Remove unnecessary stage/stat handling
jeanconn Jan 13, 2026
6667d0b
Minor ruff
jeanconn Jan 13, 2026
61d9f4d
Update comment on get_guide_candidates call
jeanconn Jan 13, 2026
1348def
Convert the immutable guide candidates in catalog.py
jeanconn Jan 13, 2026
b918c74
Fix long comment
jeanconn Jan 13, 2026
1ab0852
Simplify logging (suggestion from @taldcroft)
jeanconn Jan 13, 2026
7103f01
Simplify logging 2 (suggestion from @taldcroft)
jeanconn Jan 13, 2026
15ba760
Use better indexing in optimization (suggestion @taldcroft)
jeanconn Jan 13, 2026
86f09db
Fixup
jeanconn Jan 13, 2026
484d806
Use t_ccds_bonus name for consistency
jeanconn Jan 13, 2026
bab429a
Update comment
jeanconn Jan 13, 2026
09c230b
Remove redundant keys()
jeanconn Jan 13, 2026
f2cb2aa
Simplify guide reuse logic
jeanconn Jan 13, 2026
f6d845a
Remove the immutable stuff and update comments
jeanconn Jan 13, 2026
842ef9a
Ruff again
jeanconn Jan 13, 2026
cafb4e6
Pop off fids and mons kwargs explicitly
jeanconn Jan 13, 2026
5bcd1ea
Change import order
jeanconn Jan 13, 2026
918e905
Remove unhelpful comment
jeanconn Jan 14, 2026
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
117 changes: 101 additions & 16 deletions proseco/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

from . import __version__ as VERSION
from . import characteristics as ACA
from . import characteristics_acq as ACQ
from . import get_aca_catalog as _get_aca_catalog_package
from .acq import AcqTable, get_acq_catalog, get_maxmag
from .core import (
Expand All @@ -22,7 +21,7 @@
get_kwargs_from_starcheck_text,
)
from .fid import FidTable, get_fid_catalog
from .guide import GuideTable, get_guide_catalog
from .guide import GuideTable, get_guide_candidates, get_guide_catalog, get_t_ccds_bonus
from .monitor import BadMonitorError, get_mon_catalog

# Colnames and types for final ACA catalog
Expand Down Expand Up @@ -133,15 +132,21 @@ def _get_aca_catalog(**kwargs):
if hasattr(aca.acqs, "dark_date"):
aca.dark_date = aca.acqs.dark_date

# Get initial guide candidates
guides = get_guide_candidates(stars=aca.acqs.stars, **kwargs)
initial_guide_cands = guides.cand_guides

# Note that aca.acqs.stars is a filtered version of aca.stars and includes
# only stars that are in or near ACA FOV. Use this for fids and guides stars.
aca.log("Starting get_fid_catalog")
aca.fids = get_fid_catalog(stars=aca.acqs.stars, acqs=aca.acqs, **kwargs)
aca.fids = get_fid_catalog(
stars=aca.acqs.stars, acqs=aca.acqs, guide_cands=initial_guide_cands, **kwargs
)
aca.acqs.fids = aca.fids

if aca.optimize:
aca.log("Starting optimize_acqs_fids")
aca.optimize_acqs_fids()
aca.optimize_acqs_fids(initial_guide_cands=initial_guide_cands, **kwargs)

aca.acqs.fid_set = aca.fids["id"]

Expand All @@ -153,6 +158,7 @@ def _get_aca_catalog(**kwargs):
stars=aca.acqs.stars,
fids=aca.fids,
mons=aca.mons,
initial_guide_cands=initial_guide_cands,
img_size=img_size_guide,
**kwargs,
)
Expand Down Expand Up @@ -409,15 +415,18 @@ def make_report(self, rootdir="."):
self.acqs.make_report(rootdir=rootdir)
self.guides.make_report(rootdir=rootdir)

def optimize_acqs_fids(self):
def optimize_acqs_fids(self, initial_guide_cands=None, **kwargs):
"""
Concurrently optimize acqs and fids in the case where there is not
already a good (no spoilers) fid set available.

This updates the acqs and fids tables in place.
This updates the acqs and fids tables in place. The optimization considers
both acquisition star probability (P2) and guide star count to find the
best fid set.

:param acqs: AcqTable object
:param fids: FidTable object
:param initial_guide_cands: Table of initial guide candidates (typically from
get_guide_candidates()). If None, guide candidates are generated internally.
:param **kwargs: additional keyword arguments passed to get_guide_catalog()
"""
acqs = self.acqs
fids = self.fids
Expand All @@ -433,10 +442,26 @@ def optimize_acqs_fids(self):
or len(self.fids.cand_fids) == 0
or len(self.fids.cand_fid_sets) == 0
):
self.log("No acq-fid optimization required")
return

from chandra_aca.star_probs import guide_count

no_fid_guides = get_guide_catalog(
stars=acqs.stars,
initial_guide_cands=initial_guide_cands,
**kwargs,
)

# Start with the no-fids optimum catalog and save required info to restore
opt_P2 = -acqs.get_log_p_2_or_fewer()
t_ccd_applied = get_t_ccds_bonus(
no_fid_guides["mag"],
no_fid_guides.t_ccd,
no_fid_guides.dyn_bgd_n_faint,
no_fid_guides.dyn_bgd_dt_ccd,
)
opt_guide_count = guide_count(no_fid_guides["mag"], t_ccd_applied)
orig_acq_idxs = acqs["idx"].tolist()
orig_acq_halfws = acqs["halfw"].tolist()

Expand All @@ -461,6 +486,7 @@ def optimize_acqs_fids(self):
# ranking metric P2 and the acq catalog info halfws and star ids.
fid_sets = Table(rows=rows, names=("fid_ids", "spoiler_score"))
fid_sets["P2"] = -99.0 # Marker for unfilled values
fid_sets["guide_count"] = -99.0
fid_sets["acq_halfws"] = None
fid_sets["acq_idxs"] = None

Expand All @@ -470,11 +496,32 @@ def optimize_acqs_fids(self):

# Iterate through each spoiler_score group and then within that iterate
# over each fid set.
n_tries = 0
for fid_set_group in fid_sets.groups:
spoiler_score = fid_set_group["spoiler_score"][0]
self.log(f"Checking fid sets with spoiler_score={spoiler_score}", level=1)

for fid_set in fid_set_group:
# get the rows of candidate fids that correspond to the fid_ids
# in the current fid_set.
n_tries += 1
fid_mask = [fid_id in fid_set["fid_ids"] for fid_id in cand_fids["id"]]
fids_for_set = cand_fids[fid_mask]
local_guides = get_guide_catalog(
stars=acqs.stars,
fids=fids_for_set,
initial_guide_cands=initial_guide_cands,
**kwargs,
)
local_t_ccd_applied = get_t_ccds_bonus(
local_guides["mag"],
local_guides.t_ccd,
local_guides.dyn_bgd_n_faint,
local_guides.dyn_bgd_dt_ccd,
)
local_guide_count = guide_count(
local_guides["mag"], local_t_ccd_applied
)
# Set the internal acqs fid set. This does validation of the set
# and also calls update_p_acq_column().
acqs.fid_set = fid_set["fid_ids"]
Expand All @@ -487,10 +534,12 @@ def optimize_acqs_fids(self):
# sets. Only optimize if P2 decreased by at least 0.001,
# remembering P2 is the -log10(prob).
fid_set_P2 = -acqs.get_log_p_2_or_fewer()
found_good_set = fid_set_P2 - opt_P2 > -0.001
found_good_set = (fid_set_P2 - opt_P2 > -0.001) & (
local_guide_count >= (opt_guide_count - 0.05)
)
if found_good_set:
self.log(
f"No change in P2 for fid set {acqs.fid_set}, "
f"No change in P2 or guide_count for fid set {acqs.fid_set}, "
f"skipping optimization"
)
else:
Expand All @@ -500,11 +549,13 @@ def optimize_acqs_fids(self):

# Store optimization results
fid_set["P2"] = -acqs.get_log_p_2_or_fewer()
fid_set["guide_count"] = local_guide_count
fid_set["acq_idxs"] = acqs["idx"].tolist()
fid_set["acq_halfws"] = acqs["halfw"].tolist()

self.log(
f"Fid set {fid_set['fid_ids']}: P2={fid_set['P2']:.2f} "
f" guide_count={fid_set['guide_count']:.2f} "
f"acq_idxs={fid_set['acq_idxs']} halfws={fid_set['acq_halfws']}",
level=2,
)
Expand All @@ -518,22 +569,55 @@ def optimize_acqs_fids(self):

# Get the best fid set / acq catalog configuration so far. Fid sets not
# yet considered have P2 = -99.
best_idx = np.argmax(fid_sets["P2"])
best_P2 = fid_sets["P2"][best_idx]

# Are there sets with P2 >= 2 and guide_count >= 4?
# P2 == 2 is considered the minimum operationally acceptable level
# and fractional guide_count == 4 is similarly considered the minimum
# acceptable level. In general, we have not put these tolerances into
# proseco, but have checked the catalogs with sparkles. However, here
# in optimization if there are cases that satisfy these minimums then
# we prefer those cases to ones that just maximize P2.
MIN_ACCEPTABLE_P2 = 2.0
MIN_ACCEPTABLE_GUIDE_COUNT = 4.0
passable = (fid_sets["P2"] >= MIN_ACCEPTABLE_P2) & (
fid_sets["guide_count"] >= MIN_ACCEPTABLE_GUIDE_COUNT
)
if np.any(passable):
fid_sets_passable = fid_sets[passable]
best_idx = np.argmax(fid_sets_passable["P2"])
best_P2 = fid_sets_passable["P2"][best_idx]
best_idx = np.where(passable)[0][best_idx]

# If none passable then just get the best P2
else:
best_idx = np.argmax(fid_sets["P2"])
best_P2 = fid_sets["P2"][best_idx]

# Get the row of the fid / acq stages table to determine the required minimum
# P2 given the fid spoiler score.
from . import characteristics_acq as ACQ

# For spoiler scores >= 12 (e.g., fid trap or 3 red spoilers), treat as
# unacceptable and skip this group. The fid_acq_stages table has score=12
# as the maximum with all min_P2 values of -1.0, effectively rejecting it.
if spoiler_score >= 12:
self.log(
f"Spoiler score {spoiler_score} >= 12, skipping this fid set group",
level=1,
)
stage_min_P2 = -1.0 # Set to mark as unacceptable
continue

stage = ACQ.fid_acq_stages.loc[spoiler_score]
stage_min_P2 = stage["min_P2"](opt_P2)

self.log(
f"Best P2={best_P2:.2f} at idx={best_idx} vs. "
"stage_min_P2={stage_min_P2:.2f}",
f"stage_min_P2={stage_min_P2:.2f}",
level=1,
)

# If we have a winner then use that.
if best_P2 >= stage_min_P2:
if (best_P2 >= stage_min_P2) and np.any(passable):
break

# Set the acqs table to the best catalog
Expand All @@ -547,7 +631,8 @@ def optimize_acqs_fids(self):

self.log(
f"Best acq-fid set: P2={best_P2:.2f} "
f"acq_idxs={best_acq_idxs} halfws={best_acq_halfws} fid_ids={acqs.fid_set}"
f"acq_idxs={best_acq_idxs} halfws={best_acq_halfws} "
f"fid_ids={acqs.fid_set} N opt runs={n_tries}"
)

if best_P2 < stage_min_P2:
Expand Down
Loading