Skip to content

Ryan dev specular#33

Open
ryanketzner wants to merge 6 commits intomainfrom
ryan_dev_specular
Open

Ryan dev specular#33
ryanketzner wants to merge 6 commits intomainfrom
ryan_dev_specular

Conversation

@ryanketzner
Copy link
Collaborator

Code related to specular coverage calculation. Fixed a bug in RCG factor computation causing negative outputs. The get_specular_trajectory function was expanded to compute and return RCG factors alongside specular point trajectories. SpecularCoverage now supports multiple FOV geometries (conical, rectangular, and spherical polygon). Added function to select the highest-RCG (top or top-k) specular trajectories and coverage over time, and validated against CYGNSS data. Added new and updated examples.

Added

  • examples/specular_rcg_example.py
    Computes and plots the RCG factor for a single transmitter/receiver pair.

  • examples/specular_validation.py
    Validates the specular point switching algorithm against CYGNSS data.

  • examples/cygnss_data/
    CYGNSS datasets used for specular validation.

  • orbitpy/specular.py

    • Added get_best_trajectory for selecting the highest-RCG specular point trajectory.
    • Added get_topk_trajectories for retrieving the top-K specular trajectories by RCG.
  • orbitpy/coveragecalculator.py

    • Added get_best_coverage to return coverage associated with the highest-RCG specular point at each time step.

Modified

  • orbitpy/coveragecalculator.py

    • Fixed RCG factor memory handling so values persist over the entire simulation interval.
    • Ensured RCG factor outputs are always positive (LOS available) or NaN (no LOS).
    • Extended SpecularCoverage to support rectangular and spherical polygon FOVs in addition to conical FOVs.
  • orbitpy/specular.py

    • Renamed inputs transmitter and receiver to transmitter_state_itrf and receiver_state_itrf for clarity.
    • Updated API to compute and return the RCG factor along with specular point locations.
  • orbitpy/coverage.py

    • Added __getitem__ and __setitem__ methods to DiscreteCoverageTP.
  • examples/specular_example.py

    • Updated to use the new specular API.
    • Extended to plot RCG factor results.
  • examples/specular_coverage/specular_coverage_example.py

    • Extended to demonstrate usage of get_best_coverage.

Modified:

	orbitpy/coveragecalculator.py: Modified a single line to ensure that the RCG factor output is kept in memory
		over the entire simulation interval. Previously, it was set to retain only the buff size in memory, so that
		accesses at the end of the simulation to return the rcg factor to the user were out of bounds are returning
		garbage values. After this update, RCG factor should always be positive (LOS available) or NaN (no LOS).
…xplicit that the input is a stateseries in ITRF.

Modified:

	orbitpy/specular.py: Changed input names `transmitter` and `receiver` to `transmitter_state_itrf` and `receiver_state_itrf`.

	examples/specular_example.py: Updated example for new interface.
…ecularCoverage class.

Modified:

	orbitpy/coveragecalculator.py: Modified SpecularCoverage class to support rectangular and
		spherical polygon FOVs in addition to the previously supported conical FOVs. Follows
		same logic as the PointCoverage class. May be helpful in the future to consolodate some
		of this using function calls.
…. Modified specular.py to return RCG factor also.

    New:

            examples/specular_rcg_example.py: Computes and plots RCG factor for single transmitter/receiver pair.

    Modified:

            orbitpy/specular.py: Modified to compute and return RCG factor in addition to specular point location.

            examples/specular_example.py: Modified to use new specular.py API and also to plot RCG factor.
…sing RCG factor. Validated with CYGNSS data.

Modified:

	orbitpy/specular.py: Added get_best_trajectory function and get_topk_trajectories function.

	orbitpy/coverage.py: Added _getitem_ and _setitem_ functions to DiscreteCoverageTP.

	orbitpy/coveragecalculator.py: Added get_best_coverage function which returns the
		coverage associated with the highest-rcg specular point at each time point.

	examples/specular_coverage/specular_coverage_example.py: Added a few lines at the end to
		demonstrate use of the get_best_coverage function.

Added:

	examples/specular_validation.py: Script to validate specular point switching algorithm against
		CYGNSS data.

	examples/cygnss_data/: Data used in specular_validation.
@vinay0000 vinay0000 requested a review from Copilot March 13, 2026 00:00
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands specular-point tooling to compute/use range-corrected gain (RCG) factors, adds utilities to select best/top‑k specular trajectories/coverage over time, and extends specular coverage to additional FOV geometries.

Changes:

  • get_specular_trajectory now computes/returns RCG alongside specular point trajectories; adds trajectory ranking helpers (get_best_trajectory, get_topk_trajectories).
  • SpecularCoverage adds get_best_coverage and extends FOV handling to rectangular and spherical polygon shapes.
  • Adds/updates example scripts for RCG plotting and CYGNSS validation.

Reviewed changes

Copilot reviewed 7 out of 44 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
orbitpy/specular.py Adds best/top‑k trajectory selection utilities and returns RCG from specular trajectory computation.
orbitpy/coveragecalculator.py Adds “best coverage” selection by RCG and supports rectangular/polygon FOV paths.
orbitpy/coverage.py Adds indexing helpers for DiscreteCoverageTP.
examples/specular_validation.py New CYGNSS validation example using top‑k by RCG.
examples/specular_rcg_example.py New example to compute/plot RCG for one Tx/Rx pair.
examples/specular_example.py Updates example to new specular API and plots RCG.
examples/specular_coverage/specular_coverage_example.py Demonstrates get_best_coverage usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 113 to 118
def get_specular_trajectory(
transmitter: StateSeries,
receiver: StateSeries,
transmitter_states_itrf: StateSeries,
receiver_states_itrf: StateSeries,
times: AbsoluteDateArray,
surface: SurfaceType = SurfaceType.WGS84,
) -> PositionSeries:
Comment on lines 223 to +227
result = PositionSeries(
data=specular_positions, time=times, frame=transmitter.frame
data=specular_positions, time=times, frame=transmitter_states_itrf.frame
)

return result
return result, rcg_factor
Comment on lines +23 to +25
def get_best_trajectory(
traj_list: List[Tuple[PositionSeries, List[float]]],
) -> Tuple[PositionSeries, List[float]]:
Comment on lines +40 to +54
output_traj, output_rcg = copy.deepcopy(traj_list[0])
num_timepoints = len(output_traj.time)
num_lists = len(traj_list)

for i in range(num_timepoints):
max_rcg = -np.inf
max_rcg_idx = 0
for j in range(0, num_lists):
rcg_j = traj_list[j][1][i] # rcg at timepoint i for transmitter j
if np.isfinite(rcg_j) and rcg_j > max_rcg:
max_rcg = rcg_j
max_rcg_idx = j

output_traj.data[0][i] = traj_list[max_rcg_idx][0].data[0][i]
output_rcg[i] = max_rcg
Comment on lines +40 to +54
output_traj, output_rcg = copy.deepcopy(traj_list[0])
num_timepoints = len(output_traj.time)
num_lists = len(traj_list)

for i in range(num_timepoints):
max_rcg = -np.inf
max_rcg_idx = 0
for j in range(0, num_lists):
rcg_j = traj_list[j][1][i] # rcg at timepoint i for transmitter j
if np.isfinite(rcg_j) and rcg_j > max_rcg:
max_rcg = rcg_j
max_rcg_idx = j

output_traj.data[0][i] = traj_list[max_rcg_idx][0].data[0][i]
output_rcg[i] = max_rcg
Comment on lines +125 to +127
def get_best_coverage(cls,
coverage_list: List[Tuple[DiscreteCoverageTP, List[float]]]
) -> Tuple[DiscreteCoverageTP, List[float]]:
Comment on lines +146 to +159
output_coverage, output_rcg = copy.deepcopy(coverage_list[0])
num_timepoints = len(coverage_list[0][0].time)
num_lists = len(coverage_list)
for i in range(num_timepoints):
max_rcg = 0.0
max_rcg_idx = 0
for j in range(0, num_lists):
rcg_j = coverage_list[j][1][i] # rcg at timepoint i for transmitter j
if rcg_j > max_rcg:
max_rcg = rcg_j
max_rcg_idx = j

output_coverage.coverage[i] = coverage_list[max_rcg_idx][0].coverage[i]
output_rcg[i] = max_rcg
@@ -0,0 +1,197 @@
"""
Example script demonstrating specular point computation and switching algorithm.
Uses saved data for CYGNSS position and GPS tramnsmitter positions, and computes the

best_cov, best_rcg = SpecularCoverage.get_best_coverage(coverage)

print(f"Total best coverage time summed accross target grid points is {best_cov.coverage_time()}")
import os
import matplotlib.pyplot as plt

from eosimutils.time import AbsoluteDateArray, AbsoluteDate, AbsoluteDateArray
from the entry with the r-th highest RCG at that time.

Also returns, for each timepoint, the k ids selected (in rank order: best -> k-th best).

Copy link
Contributor

@vinay0000 vinay0000 Mar 13, 2026

Choose a reason for hiding this comment

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

It would be good to include a small example with say just 5 time-points, number of input trajectories as 4, and k-2.

import kcl
import GeometricTools as gte

def get_best_trajectory(
Copy link
Contributor

Choose a reason for hiding this comment

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

Alternative to using this function, one could call get_topk_trajectories with k=1.
Mention it in the docstring.


Args:
coverage_list (List[Tuple[DiscreteCoverageTP, List[float]]]): A list of
tuples containing (1) the coverage for each GPS transmitter and (2) the radar's
Copy link
Contributor

Choose a reason for hiding this comment

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

replace "A list of tuples containing (1) the coverage" with "A list of tuples, with each tuple containing (1) the coverage..."

return cls()

@classmethod
def get_best_coverage(cls,
Copy link
Contributor

@vinay0000 vinay0000 Mar 13, 2026

Choose a reason for hiding this comment

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

The code within this function seems to be similar to the get_best_trajectory function in the specular module. Can you modify this to use the function inside the specular module, to avoid duplication of code.

Could you also add a get_topk_coverage which leverages the get_topk_trajectories in the specular module?

sp_posseries = get_specular_trajectory(
transmitter=tx_stateseries,
receiver=sc_stateseries,
sp_posseries, rcg_factor_ellipsoidal = get_specular_trajectory(
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename sp_posseries to sp_posseries_ellipsoidal

@@ -0,0 +1,197 @@
"""
Example script demonstrating specular point computation and switching algorithm.
Copy link
Contributor

Choose a reason for hiding this comment

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

Explain switching algorithm

Uses saved data for CYGNSS position and GPS tramnsmitter positions, and computes the
specular point generated by each GPS/CYGNSS pair.

Prints the percentage of time points where the top 4 GPS satellites selected by the switching
Copy link
Contributor

Choose a reason for hiding this comment

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

Clarify what happens in the cases when at a time-point there is partial match. For example if 3 out of 4 GPS satellites match, is that time-point treated as a complete failure in the overall metric calculation?

algorithm match the top 4 GPS satellites from the reference data. The top 4 are selected by taking
the specular point trajectories with the highest RCG factor at each time point.

Then, at each time point, selects the specular point with the lowest error norm vs the reference
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this part should be modified so that the GPS indices are matched before calculating the error norm.
The results will likely remain similar or identical. However, there is a possibility that the specular point from one GPS satellite (selected by the switching algorithm) could produce a lower error norm when compared with the specular point of a different GPS satellite in the reference dataset.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants