1- import argparse
1+
22import logging
33import pathlib
44from typing import Literal , Optional , Union
@@ -535,6 +535,60 @@ def compute_observation_cadence(
535535 )
536536
537537
538+ class MaximumImpactProbabilityPriorToImpact (qv .Table ):
539+ orbit_id = qv .LargeStringColumn ()
540+ maximum_impact_probability = qv .Float64Column (nullable = True )
541+
542+
543+ def compute_maximum_impact_probability_5_years_prior_to_impact (
544+ impactor_orbits : ImpactorOrbits ,
545+ results : WindowResult ,
546+ ) -> MaximumImpactProbabilityPriorToImpact :
547+ """
548+ Compute the maximum impact probability 5 years prior to impact for each object.
549+ """
550+ maximum_impact_probability_prior_to_impact = (
551+ MaximumImpactProbabilityPriorToImpact .empty ()
552+ )
553+ unique_orbits = impactor_orbits .orbit_id .unique ()
554+ for orbit_id in unique_orbits :
555+ impactor_orbit = impactor_orbits .select ("orbit_id" , orbit_id )
556+ orbit_results = results .select ("orbit_id" , orbit_id )
557+ if len (orbit_results ) == 0 :
558+ maximum_impact_probability_prior_to_impact = qv .concatenate (
559+ [
560+ maximum_impact_probability_prior_to_impact ,
561+ MaximumImpactProbabilityPriorToImpact .from_kwargs (
562+ orbit_id = [orbit_id ], maximum_impact_probability = [None ]
563+ ),
564+ ]
565+ )
566+ continue
567+
568+ # Filter windows to those five years prior to impact
569+ five_years_prior_to_impact = impactor_orbit .impact_time .add_days (365 * - 5 )
570+ windows_five_years_prior_to_impact = orbit_results .apply_mask (
571+ pc .greater_equal (
572+ orbit_results .observation_end .mjd (), five_years_prior_to_impact .mjd ()[0 ]
573+ )
574+ )
575+
576+ maximum_impact_probability = pc .max (
577+ windows_five_years_prior_to_impact .impact_probability
578+ ).as_py ()
579+ maximum_impact_probability_prior_to_impact = qv .concatenate (
580+ [
581+ maximum_impact_probability_prior_to_impact ,
582+ MaximumImpactProbabilityPriorToImpact .from_kwargs (
583+ orbit_id = [orbit_id ],
584+ maximum_impact_probability = [maximum_impact_probability ],
585+ ),
586+ ]
587+ )
588+
589+ return maximum_impact_probability_prior_to_impact
590+
591+
538592class CompletenessByDiameter (qv .Table ):
539593 diameter = qv .Float64Column ()
540594 percentage_discovered = qv .Float64Column ()
@@ -628,14 +682,18 @@ def _orbit_complete_status(
628682 # No observations means an IP of 0.
629683 if len (observations ) == 0 :
630684 return "complete"
631-
685+
632686 # If there are no windows, the object is complete
633687 # This can occur if the observations occur over less than 3 unique nights
634688 if len (window_results ) == 0 :
635689 return "complete"
636690
637- assert len (observations .orbit_id .unique ().to_pylist ()) == 1 , "Observations must be for a single orbit"
638- assert len (window_results .orbit_id .unique ().to_pylist ()) == 1 , "Window results must be for a single orbit"
691+ assert (
692+ len (observations .orbit_id .unique ().to_pylist ()) == 1
693+ ), "Observations must be for a single orbit"
694+ assert (
695+ len (window_results .orbit_id .unique ().to_pylist ()) == 1
696+ ), "Window results must be for a single orbit"
639697
640698 # If any windows are incomplete, the object is not complete
641699 if pc .any (pc .equal (window_results .status , "incomplete" )).as_py ():
@@ -651,7 +709,9 @@ def _orbit_complete_status(
651709 # If any windows has an error that don't match the two above substrings, then mark
652710 # as incomplete
653711 matches_find_orb_failed = pc .equal (window_results .error , find_orb_failed )
654- matches_covariance_failed = pc .match_substring (window_results .error , covariance_failed )
712+ matches_covariance_failed = pc .match_substring (
713+ window_results .error , covariance_failed
714+ )
655715 combined_mask = pc .or_ (matches_find_orb_failed , matches_covariance_failed )
656716 if pc .any (pc .invert (combined_mask )).as_py ():
657717 return "incomplete"
@@ -715,6 +775,12 @@ def summarize_impact_study_object_results(
715775 orbit_id , orbit_window_results , orbit_discovery_dates
716776 )
717777
778+ maximum_impact_probability_prior_to_impact = (
779+ compute_maximum_impact_probability_5_years_prior_to_impact (
780+ impactor_orbit , completed_window_results
781+ )
782+ )
783+
718784 if len (orbit_observations ) == 0 :
719785 return ImpactorResultSummary .from_kwargs (
720786 orbit = impactor_orbit ,
@@ -736,12 +802,12 @@ def summarize_impact_study_object_results(
736802 ip_threshold_90_percent = Timestamp .nulls (1 , scale = "utc" ),
737803 ip_threshold_100_percent = Timestamp .nulls (1 , scale = "utc" ),
738804 maximum_impact_probability = [0 ],
805+ maximum_impact_probability_5_years_prior = [0 ],
739806 results_timing = orbit_results_timing ,
740807 error = [None ],
741808 status = [orbit_complete ],
742809 )
743810
744-
745811 # If the observations are not linked, we can return early
746812 if not pc .all (pc .equal (orbit_observations .linked , True )).as_py ():
747813 return ImpactorResultSummary .from_kwargs (
@@ -766,6 +832,7 @@ def summarize_impact_study_object_results(
766832 maximum_impact_probability = [
767833 pc .max (orbit_window_results .impact_probability )
768834 ],
835+ maximum_impact_probability_5_years_prior = maximum_impact_probability_prior_to_impact .maximum_impact_probability ,
769836 results_timing = orbit_results_timing ,
770837 status = [orbit_complete ],
771838 )
@@ -791,6 +858,7 @@ def summarize_impact_study_object_results(
791858 ip_threshold_90_percent = ip_threshold_90_percent .date ,
792859 ip_threshold_100_percent = ip_threshold_100_percent .date ,
793860 maximum_impact_probability = [0 ],
861+ maximum_impact_probability_5_years_prior = [0 ],
794862 results_timing = orbit_results_timing ,
795863 error = [None ],
796864 status = [orbit_complete ],
@@ -819,6 +887,7 @@ def summarize_impact_study_object_results(
819887 maximum_impact_probability = [
820888 pc .max (orbit_window_results .impact_probability )
821889 ],
890+ maximum_impact_probability_5_years_prior = maximum_impact_probability_prior_to_impact .maximum_impact_probability ,
822891 results_timing = orbit_results_timing ,
823892 error = ["Orbit has incomplete windows" ],
824893 status = [orbit_complete ],
@@ -850,6 +919,7 @@ def summarize_impact_study_object_results(
850919 ip_threshold_90_percent = ip_threshold_90_percent .date ,
851920 ip_threshold_100_percent = ip_threshold_100_percent .date ,
852921 maximum_impact_probability = [pc .max (orbit_window_results .impact_probability )],
922+ maximum_impact_probability_5_years_prior = maximum_impact_probability_prior_to_impact .maximum_impact_probability ,
853923 results_timing = orbit_results_timing ,
854924 status = [orbit_complete ],
855925 )
0 commit comments