"""GFINDER CLI `compute` command."""
import click
from pathlib import Path
import spiceypy as spice
from .docstring import set_docstring
from gfinder.config import Config
from gfinder.datastore import DataStore
from gfinder.opportunity import Opportunity, TimeInputs, OpportunityDefinition
import gfinder.simulator as simulator
from gfinder.config import DEFAULT_N_STEPS
from ptr import agm_simulation
@click.command(name='compute', context_settings=dict(
ignore_unknown_options=True,
allow_extra_args=True,
))
@click.option('--odf-file', type=click.STRING, help='Input Opportunity Definition File.')
@click.option('--opportunity-id', type=click.STRING, help='Identifier of the opportunity to compute geometry data for.')
@click.option('--event-id', type=click.STRING, help='ID of the mission event to be used as input time window (based on Mission Fvents file).')
@click.option('--start-time', type=click.STRING, help='Start time of the input time window (UTC).')
@click.option('--stop-time', type=click.STRING, help='Stop time of the input time window (UTC).')
@click.option('--n-steps', type=click.INT, help=f'Number of computation steps (or repetitions) (default: {DEFAULT_N_STEPS}).', default=DEFAULT_N_STEPS)
@click.option('--time-step', type=click.FLOAT, help='Computation time step, in seconds (or repetition time)')
@click.option('--mission-scenario-id', type=click.STRING, help='Mission scenario ID (default: '+Config().default_mission_scenario_id+')', default=Config().default_mission_scenario_id)
@click.option('--target', type=click.STRING, help='Target name ODF overwrite.')
@click.option('--binning', type=click.INT, help='Detector binning: 1 (default), 2 or 4.', default=1)
@click.option('--ptr-file', type=click.STRING, help='Input PTR file, converted into CK file and loaded to SPICE kernel pool.')
@click.option('--ck-file', type=click.STRING, help='Input CK file loaded to SPICE kernel pool.')
@click.option('--sim-sc-att/--no-sim-sc-att', help='Simulate SC attitude based on input ODF pointing definition (default: False).', default='False')
@click.option('--sc-slew-angles', type=click.STRING, help='Overwrites ODF SC_Slew_Angles parameters (run `geometry` command to know allowed parameters).')
@click.option('--sim-scanner/--no-sim-scanner', help='Simulate MAJIS scanner pointing based on input ODF pointing definition (default: False).', default='False')
@click.option('--majis-scan-angle', type=click.STRING, help='Overwrites ODF MAJIS_Scan_Angle parameters (run `geometry` command to know allowed parameters).')
@click.option('--overwrite/--no-overwrite', help='Overwrite existing opportunity data files.', default='False')
@click.option('--suffix', type=click.STRING, help='Add suffix string to the computed opportunity ID.')
@click.pass_context
def compute_cmd(ctx, odf_file, opportunity_id, event_id, start_time, stop_time, n_steps, time_step, mission_scenario_id, target, binning, ptr_file, ck_file, sim_sc_att, sc_slew_angles, sim_scanner, majis_scan_angle, addendum_kernel, overwrite, suffix):
"""
Compute observation geometry.
This command takes as input an Opportunity Definition File (ODF) file, containing the geometry condition(s) and
geometry parameters corresponding to a user-defined observation opportunity.
Computation time window can be provided as timing inputs (using start-time, stop-time, event-id options),
or or as an 'opportunity' resulting from a previous search.
It produces a set of output geometry event (GeoEvt) files in JSON format, depending of the number of input time
intervals or opportunities, bundled in an 'opportunity' directory. It also produce companion GeoJSON files. The
identifier of an opportunity is automatically assigned. Geometry events are either: a sequence of observations,
or an observation:
- Sequence GeoEvt files contain the times and geometry of a sequence of observations, and the times and geometry
of each individual observation opportunity.
- Observation GeoEvt files contains the times and geometry of an observation opportunity, and the times and geometry
of each individual instant measurement.
The geometry of an observation is always derived from the geometry of all individual instant measurement (model
hypothesis). Timing information, such as the number of time steps, can be provided as an option.
Examples:
$ gfinder compute --odf-file=data/odf/highres_ganymede_mapping.json --event-id=GCO1
$ gfinder compute --odf-file=data/odf/jupiter_disk.json --event-id=JFB1
"""
# Retrieve extra arguments to be passed to the command function.
kwargs = []
for arg in ctx.args:
pair = arg[2:].split('=')
key = pair[0].replace('-', '_')
try:
val = float(pair[1])
except:
val = pair[1]
kwargs.append((key,val))
kwargs = dict(kwargs)
compute(odf_file=odf_file, opportunity_id=opportunity_id, event_id=event_id, start_time=start_time, stop_time=stop_time,
n_steps=n_steps, time_step=time_step, mission_scenario_id=mission_scenario_id, target=target, binning=binning,
ptr_file=ptr_file, ck_file=ck_file, sim_sc_att=sim_sc_att, sc_slew_angles=sc_slew_angles, sim_scanner=sim_scanner,
majis_scan_angle=majis_scan_angle, overwrite=overwrite, suffix=suffix, **kwargs)
[docs]@set_docstring(compute_cmd)
def compute(odf_file=None, opportunity_id=None, event_id=None, start_time=None, stop_time=None, n_steps=DEFAULT_N_STEPS,
time_step=None, mission_scenario_id=Config().default_mission_scenario_id, target=None, binning=1, ptr_file=None,
ck_file=None, sim_sc_att=False, sc_slew_angles=None, sim_scanner=False, majis_scan_angle=None, overwrite=False, suffix=None,
silent=False, no_save=False, **kwargs):
# Init data store
datastore = DataStore()
# Run computation from no previous search results (used for 'overview' and 'simulation')
if odf_file:
# Load SPICE kernels for the selected mission scenario
if not mission_scenario_id:
mission_scenario_id = Config().default_mission_scenario_id
mission_scenario = datastore.getMissionScenario(mission_scenario_id)
if not mission_scenario:
print('Input `{}` mission scenario does not exist, or is not correctly defined in scenario/mission-scenarios.json file.'.format(mission_scenario_id))
return
mission_scenario.loadKernels()
if ptr_file or (ptr_file and ck_file):
ptr_file = Path(ptr_file)
if not ptr_file.exists():
print(f'Input PTR file does not exist: {ptr_file}.')
return
ck_file = Path(ptr_file.absolute().parent, ptr_file.stem + '.bc')
metakernel_id = mission_scenario.agm_metakernel_id
endpoint = 'JUICE_API'
print(f'AGM simulation: {metakernel_id}, {endpoint}')
results = agm_simulation(metakernel_id, ptr_file, endpoint)
if results.success:
print(f'AGM simulation: VALID {ptr_file}')
results.ck.save(ck_file, overwrite=True)
print(f'AGM simulation: CK saved {ck_file}.')
else:
print(f'AGM simulation: INVALID {ptr_file}.')
print('\n' + repr(results.log) + '\n')
return
elif ck_file:
ck_file = Path(ck_file)
if not ck_file.exists():
print(f'Input CK file does not exist: {ck_file}.')
return
if ck_file:
# load CK file
spice.furnsh(str(ck_file))
print(f'Loaded CK to SPICE kernels pool: {ck_file}')
print()
# Set pointing simulator state from CLI user input
# TODO: to be moved within Opportunity class
if sim_sc_att:
simulator.turn_on(frame='JUICE_SPACECRAFT')
else:
simulator.turn_off(frame='JUICE_SPACECRAFT')
if sim_scanner:
simulator.turn_on(frame='JUICE_MAJIS_SCAN')
else:
simulator.turn_off(frame='JUICE_MAJIS_SCAN')
# Load input Opportunity Definition File (ODF) (geometry event definition)
odf_file_abspath = datastore.get_ODF_filename(odf_file)
if odf_file_abspath:
opportunity_definition = OpportunityDefinition(
odf_file_abspath,
target=target,
binning=binning,
majis_scan_angle=majis_scan_angle,
sc_slew_angles=sc_slew_angles,
datastore=datastore,
**kwargs
)
else:
print(f'Input <{odf_file}> ODF file not valid or was not found (neither in your user data nor the app data directories).')
return
if not opportunity_definition.valid:
print(f'Invalid opportunity definition based on input <{odf_file}> ODF file.')
return
# Set time inputs as object/time_inputs_dict
time_inputs_dict = {
'opportunity_id': opportunity_id,
'event_id': event_id,
'start_time': start_time,
'stop_time': stop_time,
'n_steps': n_steps,
'time_step': time_step
}
time_inputs = TimeInputs(time_inputs_dict, mission_scenario=mission_scenario)
# Instantiate opportunity (calculation)
opportunity = Opportunity(mission_scenario, opportunity_definition, time_inputs, binning=binning, sim_sc_att=sim_sc_att, sim_scanner=sim_scanner)
# Check if such an opportunity has been computed before (available in the data store),
# except if --overwrite option is used.
#
if not overwrite and not no_save: # default (--no-overwrite)
exist, opportunity_id = datastore.opportunityExists(opportunity, suffix=suffix)
if exist:
click.echo()
click.echo('Opportunity exists already: ' + opportunity_id)
click.echo('Use `gfinder opportunity {}` command to get more information.'.format(opportunity_id))
return
# Run computation of the geometry of a previously search opportunity.
elif opportunity_id:
# Load opportunity from data store if exist
opportunity_dict = datastore.getOpportunityDict(opportunity_id)
if not opportunity_dict:
click.echo()
click.echo('Unknown opportunity <{}>.'.format(opportunity_id))
return
opportunity = datastore.load_opportunity(opportunity_id)
# Load SPICE kernels for loaded opportunity mission scenario
opportunity.mission_scenario.loadKernels()
#force overwritting ...why?
overwrite = True
suffix = None
else:
click.echo()
click.echo('--odf-file or --opportunity-id option is required.')
return
# # Load addendum CK kernels if present in associated opportunity data directory
# # testing w/ compute --odf-file=jupiter_disk_slew.json --start-time="2029-10-07 17:00:00" --n-steps=600 --time-step=2 --addendum-kernel=nosimsc --suffix=nosimsc_ckadd --overwrite
# if addendum_kernel:
# this_opportunity_id = datastore.assignOpportunityID(opportunity) + '_' + addendum_kernel
# print('Looking for and loading addendum CK kernels in {}/ opportunity sub-directory:'.format(this_opportunity_id))
# kernels = datastore.getAddendumKernels(this_opportunity_id)
# for kernel in kernels:
# print(kernel)
# spice.furnsh(kernel)
# Compute
opportunity.compute()
# Report on computation results
if not silent:
opportunity.summary()
if no_save is False:
# Write output opportunity data files (GeoEvt,GeoJSON)
#
new_opportunity_id, opportunity_path = datastore.writeOpportunity(opportunity, overwrite=overwrite, suffix=suffix)
# or opportunity.save()
click.echo('Opportunity ID : '+ new_opportunity_id)
click.echo('All results in {} directory.'.format(opportunity_path))
# mission_scenario.unloadKernels()
# Export
# opportunity.export_to_geojson()
# opportunity.export_to_csv()
# opportunity.export_to_ptr()
# datastore.export_opportunity(opportunity, overwrite=overwrite, suffix=suffix)
return opportunity