Source code for gfinder.commands.compute

"""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