diff --git a/README.md b/README.md index bf22516..d58cd91 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,15 @@ Missions adapt MMTC to their mission by accomplishing the following two steps: ## Quick Start -MMTC requires Red Hat Enterprise Linux (RHEL) 8 or 9 and Java 8. After cloning the repository and running `./gradlew build`, two MMTC installation options are available: +Build requirements: +- Red Hat Enterprise Linux (RHEL) 8 or 9 on an x86-64 host +- Java 17 + +Runtime requirements: +- Red Hat Enterprise Linux (RHEL) 8 or 9 on an x86-64 host +- Java 8 for all components except the web application, which requires Java 17 + +After cloning the repository and running `./gradlew build`, two MMTC installation options are available: ### Demo @@ -45,7 +53,6 @@ For users who wish to experiment with MMTC’s behavior and functionality withou See the "Quick Start Guide" section of the User Guide for complete instructions. - ### Installation To create a traditional clean installation of MMTC that is ready for configuration and adaptation: @@ -59,6 +66,6 @@ For further information, please see the User Guide at `docs/User_Guide.adoc`, wh ## Copyright -© 2024 The Johns Hopkins University Applied Physics Laboratory LLC +© 2025 The Johns Hopkins University Applied Physics Laboratory LLC This work was performed for the Jet Propulsion Laboratory, California Institute of Technology, sponsored by the United States Government under the Prime Contract 80NM0018D00004 between the Caltech and NASA under subcontract number 1658085. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 4ed61b9..1984cce 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -69,13 +69,33 @@ val createDistDir = tasks.register("createDistDir") { doLast { exec { - commandLine("bash", "create-dist.sh", project.version) + commandLine("bash", "create-dist.sh", project.version, "cli") } } outputs.dir("build/mmtc-dist-tmp") } +val createWebAppDistDir = tasks.register("createWebappDistDir") { + inputs.files("mmtc-core/bin/mmtc") + inputs.files("mmtc-core/build/libs/mmtc-core-" + project.version + "-app.jar") + inputs.files("mmtc-plugin-ampcs/build/libs/mmtc-plugin-ampcs-" + project.version + ".jar") + inputs.files("mmtc-webapp/build/libs/mmtc-webapp-" + project.version + ".jar") + + dependsOn("mmtc-core:uberJar") + dependsOn("mmtc-plugin-ampcs:jar") + dependsOn(":userGuidePdf") + dependsOn("mmtc-webapp:jar") + + doLast { + exec { + commandLine("bash", "create-dist.sh", project.version, "webapp") + } + } + + outputs.dir("build/mmtc-webapp-dist-tmp") +} + val mmtcEl8Rpm = tasks.register("mmtcEl8Rpm") { dependsOn(tasks.build) @@ -141,6 +161,73 @@ tasks.getByName("mmtcEl9Rpm") { dependsOn(tasks.getByName("cleanMmtcEl9Rpm")) } +val mmtcWebAppEl8Rpm = tasks.register("mmtcWebAppEl8Rpm") { + dependsOn(tasks.build) + + packageName = "mmtc-webapp" + distribution = "el8" + release = "1.$distribution" + archStr = "x86_64" + os = org.redline_rpm.header.Os.LINUX + + preInstall(file(projectDir.toPath().resolve("rpm-scripts/pre-install.sh"))) + postInstall(file(projectDir.toPath().resolve("rpm-scripts/post-install.sh"))) + + user(this, "mmtc") + permissionGroup(this, "mmtc") + + //make the RPM relocatable using prefix(). however, also set the default location using into(); + //otherwise, if you install without specifying a custom "--prefix", it gets installed in /usr + //(at least that's what happened in my local testing). + prefix("/opt/local/mmtc") + into("/opt/local/mmtc") + + //copy the distribution folder, preserving permissions since they're already correct. + //NOTE: this doesn't seem to set *directory* permissions, so we use post-install.sh for that. + from(projectDir.toPath().resolve("build/mmtc-webapp-dist-tmp")) + + //NOTE: create-dist.sh updates bin/mmtc to point to the correct (newly installed) jar file, + //so we no longer create a symlink at lib/mmtc.jar +} + +val mmtcWebAppEl9Rpm = tasks.register("mmtcWebAppEl9Rpm") { + dependsOn(tasks.build) + + packageName = "mmtc-webapp" + distribution = "el9" + release = "1.$distribution" + archStr = "x86_64" + os = org.redline_rpm.header.Os.LINUX + + preInstall(file(projectDir.toPath().resolve("rpm-scripts/pre-install.sh"))) + postInstall(file(projectDir.toPath().resolve("rpm-scripts/post-install.sh"))) + + user(this, "mmtc") + permissionGroup(this, "mmtc") + + //make the RPM relocatable using prefix(). however, also set the default location using into(); + //otherwise, if you install without specifying a custom "--prefix", it gets installed in /usr + //(at least that's what happened in my local testing). + prefix("/opt/local/mmtc") + into("/opt/local/mmtc") + + //copy the distribution folder, preserving permissions since they're already correct. + //NOTE: this doesn't seem to set *directory* permissions, so we use post-install.sh for that. + from(projectDir.toPath().resolve("build/mmtc-webapp-dist-tmp")) + + //NOTE: create-dist.sh updates bin/mmtc to point to the correct (newly installed) jar file, + //so we no longer create a symlink at lib/mmtc.jar +} + +// fix for Rpm task setting incorrect digests in RPM metadata +tasks.getByName("mmtcWebAppEl8Rpm") { + dependsOn(tasks.getByName("cleanMmtcWebAppEl8Rpm")) +} + +tasks.getByName("mmtcWebAppEl9Rpm") { + dependsOn(tasks.getByName("cleanMmtcWebAppEl9Rpm")) +} + distributions { main { distributionBaseName.set(project.name) @@ -150,6 +237,15 @@ distributions { } } +distributions { + create("mmtcWebApp") { + distributionBaseName.set("mmtc-webapp") + contents { + from("build/mmtc-webapp-dist-tmp") + } + } +} + tasks.distZip { dependsOn(createDistDir) } @@ -164,6 +260,20 @@ tasks.installDist { dependsOn(createDistDir) } +tasks.getByName("mmtcWebAppDistZip") { + dependsOn(createWebAppDistDir) +} + +tasks.getByName("mmtcWebAppDistTar") { + dependsOn(createWebAppDistDir) + compression = Compression.GZIP + archiveExtension.set("tar.gz") +} + +tasks.getByName("installMmtcWebAppDist") { + dependsOn(createWebAppDistDir) +} + val demoZip = tasks.register("demoZip") { dependsOn(tasks.build) dependsOn(createDistDir) diff --git a/create-dist.sh b/create-dist.sh index c65419b..e1ea17d 100644 --- a/create-dist.sh +++ b/create-dist.sh @@ -1,10 +1,16 @@ #!/bin/bash -# only arg provided to this script must be project version string +# two args provided to this script: +# - project version string +# - "cli" or "webapp" set -e -DIST_DIR=build/mmtc-dist-tmp +if [ "${2}" = "webapp" ]; then + DIST_DIR=build/mmtc-webapp-dist-tmp +else + DIST_DIR=build/mmtc-dist-tmp +fi if [[ -d $DIST_DIR ]]; then rm -r $DIST_DIR @@ -53,3 +59,17 @@ touch $DIST_DIR/log/.keep mkdir $DIST_DIR/output/ touch $DIST_DIR/output/.keep + +if [ "${2}" = "webapp" ]; then + cp mmtc-webapp/bin/mmtc-webapp $DIST_DIR/bin + + if [[ "$OSTYPE" == "darwin"* ]]; then + # BSD sed + sed -i '' "s|@VERSION@|${1}|" $DIST_DIR/bin/mmtc-webapp + else + # assume Linux (and GNU sed) + sed -i "s/@VERSION@/${1}/" $DIST_DIR/bin/mmtc-webapp + fi + + cp mmtc-webapp/build/libs/mmtc-webapp-$1.jar $DIST_DIR/lib/ +fi diff --git a/mmtc-core/bin/mmtc b/mmtc-core/bin/mmtc index 5e82d9b..3896f23 100755 --- a/mmtc-core/bin/mmtc +++ b/mmtc-core/bin/mmtc @@ -29,8 +29,13 @@ fi echo +if [[ -z "${MMTC_JAR+x}" ]] +then + export MMTC_JAR=mmtc-core-@VERSION@-app.jar +fi + exec ${JAVA_HOME}/bin/java \ -jar \ -Djava.library.path=${MMTC_HOME}/lib/naif/JNISpice/lib \ -Dlog4j2.configurationFile=${MMTC_HOME}/conf/log4j2.xml \ - ${MMTC_HOME}/lib/mmtc-core-@VERSION@-app.jar "$@" + ${MMTC_HOME}/lib/${MMTC_JAR} "$@" diff --git a/mmtc-core/build.gradle.kts b/mmtc-core/build.gradle.kts index 2a74a3c..d1937d5 100644 --- a/mmtc-core/build.gradle.kts +++ b/mmtc-core/build.gradle.kts @@ -22,6 +22,10 @@ dependencies { "configuration" to "precompiledClasses" ))) + // provides javax.xml.bind classes + implementation("jakarta.xml.bind:jakarta.xml.bind-api:4.0.2") + implementation("com.sun.xml.bind:jaxb-impl:4.0.2") + implementation("commons-beanutils:commons-beanutils:1.11.0") implementation("org.apache.commons:commons-configuration2:2.12.0") implementation("com.google.guava:guava:33.4.8-jre") diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/MmtcCli.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/MmtcCli.java index 81d2e2d..5f8dd70 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/MmtcCli.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/MmtcCli.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; public class MmtcCli { public static final Marker USER_NOTICE = MarkerManager.getMarker("USER_NOTICE"); @@ -115,7 +116,7 @@ public static void main(String[] args) { } case ROLLBACK: { try { - new TimeCorrelationRollback(appInvoc.args).rollback(); + new TimeCorrelationRollback(appInvoc.args).rollback(Optional.empty()); } catch (Exception e) { logger.fatal("Rollback failed.", e); System.exit(1); diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationAncillaryOperations.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationAncillaryOperations.java index e516842..d8f4b93 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationAncillaryOperations.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationAncillaryOperations.java @@ -1,6 +1,6 @@ package edu.jhuapl.sd.sig.mmtc.app; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.correlation.TimeCorrelationContext; import edu.jhuapl.sd.sig.mmtc.products.model.SclkKernel; import edu.jhuapl.sd.sig.mmtc.products.model.TextProductException; @@ -77,6 +77,7 @@ private double computeTdtPredictionErrorMs() throws MmtcException { double estimatedEtUsingPriorCorrelation = CSPICE.sct2e(ctx.config.getNaifSpacecraftId(), actualEncSclk); double estimatedTdtUsingPriorCorrelation = CSPICE.unitim(estimatedEtUsingPriorCorrelation, "ET", "TDT"); return (estimatedTdtUsingPriorCorrelation - actualTdt) * TimeConvert.MSEC_PER_SECOND; + // experimental == estimated - actual/accepted } catch (SpiceErrorException ex) { throw new MmtcException("Unable to compute TDT error: " + ex.getMessage(), ex); } @@ -115,7 +116,7 @@ private void retrieveAndComputeGncParams() throws MmtcException, TimeConvertExce logger.info("Retrieving GNC parameter values and calculating related metrics"); // create local refs for brevity throughout the rest of this method - final TimeCorrelationAppConfig config = ctx.config; + final TimeCorrelationRunConfig config = ctx.config; final TelemetrySource tlmSource = ctx.telemetrySource; // final FrameSample targetSample = ctx.correlation.target.get().getTargetSample(); final TimeCorrelationTarget tcTarget = ctx.correlation.target.get(); @@ -252,7 +253,7 @@ private double computeTdtSErrorMs(TelemetrySource.GncParms gncParms, Double curr * @throws MmtcException if the state vector could not be computed */ private double[] computeTargetState(String target, String observer) throws TimeConvertException, MmtcException { - final TimeCorrelationAppConfig config = ctx.config; + final TimeCorrelationRunConfig config = ctx.config; final FrameSample targetSample = ctx.correlation.target.get().getTargetSample(); final String corr = "NONE"; // Aberration correction diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationApp.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationApp.java index 6e7d301..fa0314e 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationApp.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationApp.java @@ -2,12 +2,12 @@ import java.math.BigInteger; import java.math.RoundingMode; -import java.nio.file.Files; -import java.nio.file.Paths; +import java.time.OffsetDateTime; import java.util.*; import java.math.BigDecimal; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationCliInputConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.correlation.TimeCorrelationContext; import edu.jhuapl.sd.sig.mmtc.filter.ContactFilter; import edu.jhuapl.sd.sig.mmtc.filter.TimeCorrelationFilter; @@ -27,7 +27,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.appender.RollingFileAppender; import static edu.jhuapl.sd.sig.mmtc.app.MmtcCli.USER_NOTICE; @@ -42,7 +41,7 @@ public class TimeCorrelationApp { private static final Logger logger = LogManager.getLogger(); - private final TimeCorrelationAppConfig config; + private final TimeCorrelationRunConfig config; private final TimeCorrelationContext ctx; private RunHistoryFile runHistoryFile; @@ -58,7 +57,17 @@ public class TimeCorrelationApp { public TimeCorrelationApp(String... args) throws Exception { try { - this.config = new TimeCorrelationAppConfig(args); + this.config = new TimeCorrelationRunConfig(new TimeCorrelationCliInputConfig(args)); + this.ctx = new TimeCorrelationContext(config); + init(); + } catch (Exception e) { + throw new MmtcException("MMTC correlation initialization failed.", e); + } + } + + public TimeCorrelationApp(TimeCorrelationRunConfig config) throws MmtcException { + try { + this.config = config; this.ctx = new TimeCorrelationContext(config); init(); } catch (Exception e) { @@ -117,10 +126,16 @@ private TimeCorrelationTarget selectSampleSetAndTimeCorrelationTarget() throws M final TelemetrySource tlmSource = config.getTelemetrySource(); final TelemetrySelectionStrategy tlmSelecStrat; - switch(config.getSampleSetBuildingStrategy()) { - case SEPARATE_CONSECUTIVE_WINDOWS: tlmSelecStrat = WindowingTelemetrySelectionStrategy.forSeparateConsecutiveWindows(config, tlmSource, tk_sclk_fine_tick_modulus); break; - case SLIDING_WINDOW: tlmSelecStrat = WindowingTelemetrySelectionStrategy.forSlidingWindow(config, tlmSource, tk_sclk_fine_tick_modulus); break; - case SAMPLING: tlmSelecStrat = new SamplingTelemetrySelectionStrategy(config, tlmSource, tk_sclk_fine_tick_modulus); break; + switch (config.getSampleSetBuildingStrategy()) { + case SEPARATE_CONSECUTIVE_WINDOWS: + tlmSelecStrat = WindowingTelemetrySelectionStrategy.forSeparateConsecutiveWindows(config, tlmSource, tk_sclk_fine_tick_modulus); + break; + case SLIDING_WINDOW: + tlmSelecStrat = WindowingTelemetrySelectionStrategy.forSlidingWindow(config, tlmSource, tk_sclk_fine_tick_modulus); + break; + case SAMPLING: + tlmSelecStrat = new SamplingTelemetrySelectionStrategy(config, tlmSource, tk_sclk_fine_tick_modulus); + break; default: throw new IllegalStateException("No such sample set building strategy: " + config.getSampleSetBuildingStrategy()); } @@ -217,7 +232,7 @@ private void recordRunHistoryFilePreRunValues() throws MmtcException { newRunHistoryFileRecord.setValue(RunHistoryFile.MMTC_BUILT_IN_OUTPUT_PRODUCT_VERSION, mmtcVersion); newRunHistoryFileRecord.setValue(RunHistoryFile.ROLLEDBACK, "false"); newRunHistoryFileRecord.setValue(RunHistoryFile.RUN_USER, System.getProperty("user.name")); - newRunHistoryFileRecord.setValue(RunHistoryFile.CLI_ARGS, String.join(" ", config.getCliArgs())); + newRunHistoryFileRecord.setValue(RunHistoryFile.INVOC_ARGS, config.getInvocationStringRepresentation()); // todo pick up here with restoring invoc args and rerunning NH test to ensure // Output products for (OutputProductDefinition prodDef : config.getAllOutputProductDefs()) { @@ -286,6 +301,44 @@ private static Double computeClkChgRate(int sclk0, double tdt_g0, int sclk1, dou return clkChgRate; } + private String[] getLookbackRecordForPredictedClkChgRate(Double tdtGOfNewTriplet) throws MmtcException, TextProductException, TimeConvertException { + Optional desiredPriorCorrelationTdt = config.getPriorCorrelationTdt(); + + // this is returned in order of most recent to oldest + final List priorRecsWithinLookbackWindow = ctx.currentSclkKernel.get().getPriorRecs( + tdtGOfNewTriplet, + config.getPredictedClkRateLookBackHours(), + config.getMaxPredictedClkRateLookBackHours(), + runHistoryFile.getSmoothingTripletTdtGValsToIgnoreDuringLookback() + ); + + for (String[] record : priorRecsWithinLookbackWindow) { + if (desiredPriorCorrelationTdt.isPresent()) { + if (TimeConvert.tdtCalStrToTdt(record[SclkKernel.TRIPLET_TDTG_FIELD_INDEX].substring(1)).equals(desiredPriorCorrelationTdt.get())) { + return record; + } + } else { + return record; + } + } + + if (desiredPriorCorrelationTdt.isPresent()) { + throw new MmtcException("Could not find the specified triplet to use as the lookback record with TDT: " + config.getPriorCorrelationTdt().get()); + } else { + final String[] mostRecentLookbackRecMeetingMinimumOnly = ctx.currentSclkKernel.get().getPriorRec(tdtGOfNewTriplet, config.getPredictedClkRateLookBackHours(), runHistoryFile.getSmoothingTripletTdtGValsToIgnoreDuringLookback()); + Double lookbackRecTdtG = TimeConvert.tdtCalStrToTdt(mostRecentLookbackRecMeetingMinimumOnly[SclkKernel.TRIPLET_TDTG_FIELD_INDEX].substring(1)); + final double deltaTdt = tdtGOfNewTriplet - lookbackRecTdtG; + + String errorMsg = "Insufficient earlier data in the input SCLK Kernel to compute the Predicted CLKRATE. "; + errorMsg += "The most recent lookback record in the input SCLK Kernel is at TDT(G) = " + mostRecentLookbackRecMeetingMinimumOnly[SclkKernel.TRIPLET_TDTG_FIELD_INDEX].substring(1) + ","; + errorMsg += "which is " + deltaTdt/3600 + " hours older than the new record being generated. "; + errorMsg += "The most recent lookback record is determined using a combination of the lookback and max lookback configuration values, and may not necessarily be the most recent entry in the latest SCLK Kernel. "; + errorMsg += String.format("However, the maximum allowable difference specified by the compute.tdtG.rate.predicted.maxLookBackDays configuration option is %f hours. ", config.getMaxPredictedClkRateLookBackHours()); + errorMsg += "Please consider rerunning MMTC with either the --clkchgrate-assign or --clkchgrate-nodrift mode selected, or rerun within a different time period."; + + throw new MmtcException(errorMsg); + } + } /** * Compute the predicted clock change rate. This method compares the newly @@ -301,7 +354,7 @@ private static Double computeClkChgRate(int sclk0, double tdt_g0, int sclk1, dou * @throws MmtcException if the new SCLK or TDT values overlap a previous time correlation */ private Double computePredictedClkChgRate(Integer sclk, Double tdt_g) throws TextProductException, TimeConvertException, MmtcException { - String[] lookBackRec = ctx.currentSclkKernel.get().getPriorRec(tdt_g, config.getPredictedClkRateLookBackDays()*24., runHistoryFile.getSmoothingTripletTdtGValsToIgnoreDuringLookback()); + String[] lookBackRec = getLookbackRecordForPredictedClkChgRate(tdt_g); logger.debug("computePredictedClkChgRate(): lookBackRec from SCLK = " + lookBackRec[SclkKernel.TRIPLET_ENCSCLK_FIELD_INDEX] + " " + lookBackRec[SclkKernel.TRIPLET_TDTG_FIELD_INDEX] + " " @@ -315,28 +368,6 @@ private Double computePredictedClkChgRate(Integer sclk, Double tdt_g) throws Tex Double tdt_g0 = TimeConvert.tdtCalStrToTdt(lookBackRec[SclkKernel.TRIPLET_TDTG_FIELD_INDEX].substring(1)); logger.debug("computePredictedClkChgRate(): sclk0 = " + sclk0 + ", tdt_g0 = " + tdt_g0 + ", sclk = " + sclk + ", tdt_g = " + tdt_g); - - // Check that the selected look back record from the input SCLK Kernel is not older than the time of the current - // sample minus the maximum number of look back hours as specified in the configuration parameters. If it is, - // throw an exception. Processing should not continue. The user should check that the input SCLK Kernel is - // correct for the run or rerun in Assign (--clkchgrate-assign) or No-Drift (--clkchgrate-nodrift) modes to - // compute the CLKRATE. - final double deltaTDT = tdt_g - tdt_g0; - logger.debug("computePredictedClkChgRate(): The look back record from the SCLK Kernel used to compute the " + - " Predicted CLKRATE is " + lookBackRec[SclkKernel.TRIPLET_TDTG_FIELD_INDEX] + ",\nwhich is " + deltaTDT/3600 + " hours earlier than the current TDT(G) of " + - tdtGStr + ". Processing continues."); - - if (deltaTDT > (config.getMaxPredictedClkRateLookBackHours()) * 3600.) { - String errorMsg = "Insufficient earlier data in the input SCLK Kernel to compute the Predicted CLKRATE. "; - errorMsg += "The most recent lookback record in the input SCLK Kernel is at TDT(G) = " + lookBackRec[SclkKernel.TRIPLET_TDTG_FIELD_INDEX].substring(1) + ","; - errorMsg += "which is " + deltaTDT/3600 + " hours older than the new record being generated. "; - errorMsg += "The most recent lookback record is determined using a combination of the lookback and max lookback configuration values, and may not necessarily be the most recent entry in the latest SCLK Kernel. "; - errorMsg += String.format("However, the maximum allowable difference specified by the compute.tdtG.rate.predicted.maxLookBackDays configuration option is %f hours. ", config.getMaxPredictedClkRateLookBackHours()); - errorMsg += "Please consider rerunning MMTC with either the --clkchgrate-assign or --clkchgrate-nodrift mode selected, or rerun within a different time period."; - - throw new MmtcException(errorMsg); - } - return computeClkChgRate(sclk0, tdt_g0, sclk, tdt_g); } @@ -388,15 +419,34 @@ private Double computeInterpolatedClkChgRate(Integer sclk, Double tdt_g) throws * * @throws Exception if time correlation cannot be successfully completed */ - public void run() throws Exception { - logger.info(USER_NOTICE, String.format("Running time correlation between %s and %s", config.getStartTime().toString(), config.getStopTime().toString())); + public TimeCorrelationContext run() throws Exception { + if (config.getTargetSampleInputErtMode().equals(TimeCorrelationRunConfig.TargetSampleInputErtMode.RANGE)) { + logger.info(USER_NOTICE, String.format("Running time correlation between %s and %s", + config.getResolvedTargetSampleRange().get().getStart().toString(), + config.getResolvedTargetSampleRange().get().getStop().toString() + )); + } else if (config.getTargetSampleInputErtMode().equals(TimeCorrelationRunConfig.TargetSampleInputErtMode.EXACT)){ + logger.info(USER_NOTICE, String.format("Running time correlation on a target sample with ERT %s", + config.getResolvedTargetSampleExactErt().get() + )); + } + if (config.isTestMode()) { logger.warn(String.format("Test mode is enabled! One-way light time will be set to the provided value %f and ancillary positional and velocity calculations will be skipped.", config.getTestModeOwlt())); } - if (config.isDryRun()) { - logger.warn("Dry run mode is enabled! No data products from this run will be kept and will instead be printed to the console and recorded in the log file according to log4j2.xml"); - } + switch(config.getDryRunConfig().mode) { + case NOT_DRY_RUN: break; + case DRY_RUN_RETAIN_NO_PRODUCTS: { + logger.warn("Dry run mode is enabled! No data products from this run will be kept and will instead be printed to the console and recorded in the log file"); + break; + } + case DRY_RUN_GENERATE_SEPARATE_SCLK_ONLY: { + logger.warn("Dry run mode is enabled! Only the SCLK kernel will be retained at a separate location; no data products from this run will be kept and will instead be printed to the console and recorded in the log file"); + break; + } + default: throw new IllegalStateException("Unexpected dry run config: " + config.getDryRunConfig().mode); + } // Select telemetry for this new time correlation run final TimeCorrelationTarget tcTarget = selectSampleSetAndTimeCorrelationTarget(); @@ -457,8 +507,8 @@ public void run() throws Exception { final double curr_tdt_g = tcTarget.getTargetSampleTdtG(); final double predictedClockChangeRate; - final TimeCorrelationAppConfig.ClockChangeRateMode actualClockChangeRateMode; - if (config.getClockChangeRateMode().equals(TimeCorrelationAppConfig.ClockChangeRateMode.COMPUTE_INTERPOLATE) && ctx.currentSclkKernel.get().getSourceProductDataRecCount() == 1) { + final TimeCorrelationRunConfig.ClockChangeRateMode actualClockChangeRateMode; + if (config.getClockChangeRateMode().equals(TimeCorrelationRunConfig.ClockChangeRateMode.COMPUTE_INTERPOLATE) && ctx.currentSclkKernel.get().getSourceProductDataRecCount() == 1) { /* * If this is the very first run of the application for a mission, the input SCLK Kernel is assumed to be the seed kernel. * In this case and ONLY in this case, only compute the predicted clock change rate value, so as @@ -466,7 +516,7 @@ public void run() throws Exception { * CLKRATE method anyway for the first few runs. */ logger.warn("Not computing interpolated rate for prior SCLK kernel record so as not to overwrite seed kernel entry; switching clock change rate mode to compute-predicted"); - actualClockChangeRateMode = TimeCorrelationAppConfig.ClockChangeRateMode.COMPUTE_PREDICT; + actualClockChangeRateMode = TimeCorrelationRunConfig.ClockChangeRateMode.COMPUTE_PREDICT; } else { actualClockChangeRateMode = config.getClockChangeRateMode(); } @@ -500,7 +550,7 @@ public void run() throws Exception { // Compute 'smoothing' record, if enabled if (config.getAdditionalSmoothingRecordConfig().enabled) { computeAdditionalSmoothingRecord(ctx); - newRunHistoryFileRecord.setValue(RunHistoryFile.SMOOTHING_TRIPLET_TDT, ctx.correlation.smoothingTriplet.get().tdtStr); + newRunHistoryFileRecord.setValue(RunHistoryFile.SMOOTHING_TRIPLET_TDT, ctx.correlation.newSmoothingTriplet.get().tdtStr); } // Perform all ancillary post-correlation operations @@ -511,14 +561,28 @@ public void run() throws Exception { for (OutputProductDefinition prodDef : config.getAllOutputProductDefs()) { final String postRunColProdColName = RunHistoryFile.getPostRunProductColNameFor(prodDef); - if (prodDef.shouldBeWritten(ctx) && !ctx.config.isDryRun()) { - final ProductWriteResult res = prodDef.write(ctx); - newRunHistoryFileRecord.setValue(postRunColProdColName, res.newVersion); - - } else if (prodDef.shouldBeWritten(ctx) && ctx.config.isDryRun()) { - // Log/print output products instead of writing them to files - final String productPrintout = prodDef.getDryRunPrintout(ctx); - logger.info(USER_NOTICE, productPrintout); + if (prodDef.shouldBeWritten(ctx)) { + switch(ctx.config.getDryRunConfig().mode) { + case NOT_DRY_RUN: { + final ProductWriteResult res = prodDef.write(ctx); + newRunHistoryFileRecord.setValue(postRunColProdColName, res.newVersion); + break; + } + case DRY_RUN_RETAIN_NO_PRODUCTS: { + // Log/print output products instead of writing them to files + final String productPrintout = prodDef.getDryRunPrintout(ctx); + logger.info(USER_NOTICE, productPrintout); + break; + } + case DRY_RUN_GENERATE_SEPARATE_SCLK_ONLY: { + // Intentionally skip all processing for other output products, and only write the SCLK kernel to a special path + if (prodDef.getName().equals(SclkKernelProductDefinition.PRODUCT_NAME)) { + SclkKernelProductDefinition sclkKernelProdDef = (SclkKernelProductDefinition) prodDef; + sclkKernelProdDef.writeToAlternatePath(ctx, ctx.config.getDryRunConfig().sclkKernelOutputPath); + } + break; + } + } } else { newRunHistoryFileRecord.setValue(postRunColProdColName, runHistoryFile.getLatestNonEmptyValueOfCol(postRunColProdColName, RunHistoryFile.RollbackEntryOption.IGNORE_ROLLBACKS).orElse("-")); } @@ -532,6 +596,8 @@ public void run() throws Exception { } logger.info(USER_NOTICE, "MMTC completed successfully."); + + return ctx; } private static void computeAdditionalSmoothingRecord(TimeCorrelationContext ctx) throws MmtcException { @@ -609,7 +675,7 @@ private static void computeAdditionalSmoothingRecord(TimeCorrelationContext ctx) smoothingRecordClkChgRate ); - ctx.correlation.smoothingTriplet.set(newSmoothingTriplet); + ctx.correlation.newSmoothingTriplet.set(newSmoothingTriplet); } catch (TextProductException | TimeConvertException e) { throw new MmtcException("Could not calculate additional smoothing record", e); } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationTarget.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationTarget.java index 54396ce..87cc96c 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationTarget.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/app/TimeCorrelationTarget.java @@ -1,6 +1,8 @@ package edu.jhuapl.sd.sig.mmtc.app; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.MmtcConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationMetricsConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.tlm.FrameSample; import edu.jhuapl.sd.sig.mmtc.util.Owlt; import edu.jhuapl.sd.sig.mmtc.util.TimeConvert; @@ -14,7 +16,7 @@ public class TimeCorrelationTarget { // input values private final List sampleSet; private final FrameSample targetSample; - private final TimeCorrelationAppConfig config; + private final TimeCorrelationMetricsConfig config; private final int tk_sclk_fine_tick_modulus; // computed values, assigned in `computeCorrelationValues` below @@ -27,7 +29,7 @@ public class TimeCorrelationTarget { private String ertGCalcLogStatement; private Double tdtG; - public TimeCorrelationTarget(List sampleSet, TimeCorrelationAppConfig config, int tk_sclk_fine_tick_modulus) throws MmtcException { + public TimeCorrelationTarget(List sampleSet, TimeCorrelationMetricsConfig config, int tk_sclk_fine_tick_modulus) throws MmtcException { this.sampleSet = sampleSet; this.targetSample = sampleSet.get(sampleSet.size() / 2); this.tk_sclk_fine_tick_modulus = tk_sclk_fine_tick_modulus; diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/CorrelationCommandLineConfig.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/CorrelationCommandLineConfig.java index 709cda3..7997708 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/CorrelationCommandLineConfig.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/CorrelationCommandLineConfig.java @@ -1,6 +1,5 @@ package edu.jhuapl.sd.sig.mmtc.cfg; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig.ClockChangeRateMode; import edu.jhuapl.sd.sig.mmtc.util.TimeConvert; import org.apache.commons.cli.*; import org.apache.commons.lang3.StringUtils; @@ -28,13 +27,13 @@ public class CorrelationCommandLineConfig implements IConfiguration { private OffsetDateTime startTime; private OffsetDateTime stopTime; - private ClockChangeRateMode clockChangeRateMode; + private TimeCorrelationRunConfig.ClockChangeRateMode clockChangeRateMode; private double clockChangeRateAssignedValue; private String clockChangeRateAssignedKey; private boolean isClockChangeRateModeExplicitlySet; private boolean isInsertAdditionalSmoothingRecordExplicitlySet; - private TimeCorrelationAppConfig.AdditionalSmoothingRecordConfig additionalSmoothingRecordConfig; + private TimeCorrelationRunConfig.AdditionalSmoothingRecordConfig additionalSmoothingRecordConfig; private static final String ClockChangeRateOptionCompute = "clkchgrate-compute"; private static final String ClockChangeRateOptionNoDrift = "clkchgrate-nodrift"; @@ -224,7 +223,7 @@ boolean hasClockChangeRateMode() { return isClockChangeRateModeExplicitlySet; } - ClockChangeRateMode getClockChangeRateMode() { + TimeCorrelationRunConfig.ClockChangeRateMode getClockChangeRateMode() { return clockChangeRateMode; } @@ -239,27 +238,27 @@ private void setClockChangeRateMode() throws ParseException { switch (method) { case "i": - clockChangeRateMode = ClockChangeRateMode.COMPUTE_INTERPOLATE; + clockChangeRateMode = MmtcConfig.ClockChangeRateMode.COMPUTE_INTERPOLATE; break; case "p": - clockChangeRateMode = ClockChangeRateMode.COMPUTE_PREDICT; + clockChangeRateMode = MmtcConfig.ClockChangeRateMode.COMPUTE_PREDICT; break; default: throw new ParseException("Invalid clock change rate compute method: " + method); } } else if (cmdLine.hasOption(ClockChangeRateOptionNoDrift)) { - clockChangeRateMode = ClockChangeRateMode.NO_DRIFT; + clockChangeRateMode = MmtcConfig.ClockChangeRateMode.NO_DRIFT; } else if (cmdLine.hasOption(ClockChangeRateOptionAssign)) { String assignValue = cmdLine.getOptionValue(ClockChangeRateOptionAssign); - clockChangeRateMode = ClockChangeRateMode.ASSIGN; + clockChangeRateMode = MmtcConfig.ClockChangeRateMode.ASSIGN; try { clockChangeRateAssignedValue = Double.parseDouble(assignValue); } catch (NumberFormatException ex) { logger.debug(String.format("Assigned clock change rate %s appears to reference a named preset.", assignValue)); - clockChangeRateMode = ClockChangeRateMode.ASSIGN_KEY; + clockChangeRateMode = MmtcConfig.ClockChangeRateMode.ASSIGN_KEY; clockChangeRateAssignedKey = assignValue; } } @@ -291,13 +290,13 @@ private void setAdditionalSmoothingRecord() { this.isInsertAdditionalSmoothingRecordExplicitlySet = true; if (isInsertAdditionalSmoothingRecord) { - this.additionalSmoothingRecordConfig = new TimeCorrelationAppConfig.AdditionalSmoothingRecordConfig(true, Integer.parseInt(cmdLine.getOptionValue("s"))); + this.additionalSmoothingRecordConfig = new TimeCorrelationRunConfig.AdditionalSmoothingRecordConfig(true, Integer.parseInt(cmdLine.getOptionValue("s"))); } else { - this.additionalSmoothingRecordConfig = new TimeCorrelationAppConfig.AdditionalSmoothingRecordConfig(false, 0); + this.additionalSmoothingRecordConfig = new TimeCorrelationRunConfig.AdditionalSmoothingRecordConfig(false, 0); } } - public Optional getAdditionalSmoothingRecordInsertionOverride() { + public Optional getAdditionalSmoothingRecordInsertionOverride() { if (isInsertAdditionalSmoothingRecordExplicitlySet) { return Optional.of(this.additionalSmoothingRecordConfig); } else { @@ -305,7 +304,7 @@ public Optional getAdd } } - public String getOptionValue(char shortOpt) { + public String getOptionValue(String shortOpt) { return cmdLine.getOptionValue(shortOpt); } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/MmtcConfig.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/MmtcConfig.java index 57bac2a..9d041bc 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/MmtcConfig.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/MmtcConfig.java @@ -36,6 +36,8 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import static edu.jhuapl.sd.sig.mmtc.app.MmtcCli.USER_NOTICE; + /** * A class assisting with loading and providing access to values in file-based configuration. These include the * parameters read from the TimeCorrelationConfigProperties.xml file, Ground Station Map associations, @@ -49,6 +51,9 @@ public abstract class MmtcConfig { private static final Set BUILT_IN_TLM_SOURCES = new HashSet<>(Collections.singletonList("rawTlmTable")); + public static final List CLOCK_CHANGE_RATE_ASSIGN_MODES = Arrays.asList(ClockChangeRateMode.ASSIGN, ClockChangeRateMode.ASSIGN_KEY); + public static final ClockChangeRateMode DEFAULT_CLOCK_CHANGE_RATE_MODE = ClockChangeRateMode.COMPUTE_INTERPOLATE; + protected final Path mmtcHome; protected final TimeCorrelationConfig timeCorrelationConfig; protected final List> allProductDefs; @@ -83,10 +88,51 @@ public MmtcConfig() throws Exception { this.allProductDefs = Collections.unmodifiableList(constructAllOutputProductDefinitions()); } + public MmtcConfig(MmtcConfig config) { + this.mmtcHome = config.mmtcHome; + this.timeCorrelationConfig = config.timeCorrelationConfig; + this.groundStationMap = config.groundStationMap; + this.sclkPartitionMap = config.sclkPartitionMap; + this.allProductDefs = config.allProductDefs; + } + + public Path getConfigFilepath() { + return this.timeCorrelationConfig.getPath(); + } + + public Path getMmtcHome() { + return mmtcHome; + } + public List> getAllOutputProductDefs() { return this.allProductDefs; } + public OutputProductDefinition getOutputProductDefByName(String name) throws MmtcException { + return this.allProductDefs.stream() + .filter(def -> def.getName().equals(name)) + .findFirst() + .orElseThrow(() -> new MmtcException("No such product found: " + name)); + } + + public EntireFileOutputProductDefinition getEntireFileProductDefByName(String name) throws MmtcException { + Optional> maybeDef = this.allProductDefs.stream() + .filter(def -> def instanceof EntireFileOutputProductDefinition) + .filter(def -> def.getName().equals(name)) + .findFirst(); + + return (EntireFileOutputProductDefinition) maybeDef.orElseThrow(() -> new MmtcException("No such product found: " + name)); + } + + public AppendedFileOutputProductDefinition getAppendedOutputProductDefByName(String name) throws MmtcException { + Optional> maybeDef = this.allProductDefs.stream() + .filter(def -> def instanceof AppendedFileOutputProductDefinition) + .filter(def -> def.getName().equals(name)) + .findFirst(); + + return (AppendedFileOutputProductDefinition) maybeDef.orElseThrow(() -> new MmtcException("No such product found: " + name)); + } + private List> constructAllOutputProductDefinitions() throws MmtcException, IOException { final List> productDefs = new ArrayList<>(); @@ -848,8 +894,8 @@ public String getSclkScetFileSuffix() { * * @return the number of days to look back */ - public Double getPredictedClkRateLookBackDays() { - return timeCorrelationConfig.getConfig().getDouble("compute.tdtG.rate.predicted.lookBackDays"); + public Double getPredictedClkRateLookBackHours() { + return timeCorrelationConfig.getConfig().getDouble("compute.tdtG.rate.predicted.lookBackDays") * 24.0; } /** @@ -1023,6 +1069,10 @@ public String getProducerId() { return timeCorrelationConfig.getConfig().getString("product.sclkScetFile.producerId"); } + public boolean isCreateUplinkCmdFile() { + return timeCorrelationConfig.getConfig().getBoolean("product.uplinkCmdFile.create", false); + } + /** * Gets the directory to which the optional Uplink Command File is to be written. * @return the Uplink Command File output directory @@ -1199,6 +1249,30 @@ public int getSamplingSampleSetBuildingStrategySamplingRateMinutes() { return timeCorrelationConfig.getConfig().getInt("telemetry.sampleSetBuildingStrategy.sampling.samplingRateMinutes"); } + public enum ClockChangeRateMode { + COMPUTE_INTERPOLATE, + COMPUTE_PREDICT, + ASSIGN, + ASSIGN_KEY, + NO_DRIFT + } + + public ClockChangeRateMode getConfiguredClockChangeRateMode() throws MmtcException { + if (timeCorrelationConfig.getConfig().containsKey("compute.clkchgrate.mode")) { + final String modeFromConfig = timeCorrelationConfig.getConfig().getString("compute.clkchgrate.mode"); + + if (modeFromConfig.equalsIgnoreCase("compute-predict")) { + return ClockChangeRateMode.COMPUTE_PREDICT; + } else if (modeFromConfig.equalsIgnoreCase("compute-interpolate")) { + return ClockChangeRateMode.COMPUTE_INTERPOLATE; + } else { + throw new MmtcException(String.format("The clock change rate mode in configuration must be either 'compute-predict' or 'compute-interpolate', but it was '%s'", modeFromConfig)); + } + } + + return DEFAULT_CLOCK_CHANGE_RATE_MODE; + } + /** * Method used for up-front validation of the presence of all required keys in TimeCorrelationAppConfig.xml. This is * intended to be used with the standard base config found at {$TK_CONFIG_PATH}/examples/TimeCorrelationConfigProperties-base.xml, @@ -1219,7 +1293,7 @@ public void validate() throws MmtcException { } // Parse TimeCorrelationConfigProperties-base.xml - ClassLoader classLoader = TimeCorrelationAppConfig.class.getClassLoader(); + ClassLoader classLoader = MmtcConfig.class.getClassLoader(); try (InputStream stream = classLoader.getResourceAsStream(BASE_CONFIG_FILENAME)) { Document document = builder.parse(stream); document.getDocumentElement().normalize(); diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/MmtcConfigWithTlmSource.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/MmtcConfigWithTlmSource.java new file mode 100644 index 0000000..ab99737 --- /dev/null +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/MmtcConfigWithTlmSource.java @@ -0,0 +1,36 @@ +package edu.jhuapl.sd.sig.mmtc.cfg; + +import edu.jhuapl.sd.sig.mmtc.tlm.TelemetrySource; + +import java.util.HashMap; +import java.util.Map; + +public abstract class MmtcConfigWithTlmSource extends MmtcConfig { + protected final TelemetrySource telemetrySource; + protected final Map additionalTlmSrcOptionsByName; + + public MmtcConfigWithTlmSource() throws Exception { + super(); + this.telemetrySource = this.initTlmSource(); + + this.additionalTlmSrcOptionsByName = new HashMap<>(); + + telemetrySource.getAdditionalOptions().forEach(additionalOption -> { + additionalTlmSrcOptionsByName.put(additionalOption.name, additionalOption); + }); + + this.telemetrySource.applyConfiguration(this); + } + + public MmtcConfigWithTlmSource(MmtcConfigWithTlmSource config) { + super(config); + this.telemetrySource = config.telemetrySource; + this.additionalTlmSrcOptionsByName = config.additionalTlmSrcOptionsByName; + } + + public TelemetrySource getTelemetrySource() { + return telemetrySource; + } + + public abstract String getAdditionalOptionValue(String optionName); +} diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationAppConfig.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationAppConfig.java deleted file mode 100644 index 2b40fd1..0000000 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationAppConfig.java +++ /dev/null @@ -1,362 +0,0 @@ -package edu.jhuapl.sd.sig.mmtc.cfg; - -import edu.jhuapl.sd.sig.mmtc.app.MmtcException; -import edu.jhuapl.sd.sig.mmtc.filter.TimeCorrelationFilter; -import edu.jhuapl.sd.sig.mmtc.tlm.TelemetrySource; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.time.OffsetDateTime; -import java.util.*; - -import static edu.jhuapl.sd.sig.mmtc.app.MmtcCli.USER_NOTICE; - -public class TimeCorrelationAppConfig extends MmtcConfig { - public static final String CONTACT_FILTER = "contact"; - public static final String MIN_DATARATE_FILTER = "minDataRate"; - public static final String MAX_DATARATE_FILTER = "maxDataRate"; - public static final String ERT_FILTER = "ert"; - public static final String GROUND_STATION_FILTER = "groundStation"; - public static final String SCLK_FILTER = "sclk"; - public static final String VALID_FILTER = "validFlag"; - public static final String CONSEC_FRAMES_FILTER = "consecutiveFrames"; - public static final String VCID_FILTER = "vcid"; - public static final String CONSEC_MC_FRAME_FILTER = "consecutiveMasterChannelFrames"; - - private static final List CLOCK_CHANGE_RATE_ASSIGN_MODES = Arrays.asList(ClockChangeRateMode.ASSIGN, ClockChangeRateMode.ASSIGN_KEY); - private static final ClockChangeRateMode DEFAULT_CLOCK_CHANGE_RATE_MODE = ClockChangeRateMode.COMPUTE_INTERPOLATE; - - private static final Logger logger = LogManager.getLogger(); - - private final CorrelationCommandLineConfig cmdLineConfig; - - private final TelemetrySource telemetrySource; - - private ClockChangeRateMode clockChangeRateMode; - private double clockChangeRateAssignedValue; - private AdditionalSmoothingRecordConfig additionalSmoothingRecordConfig; - - public String getCmdLineOptionValue(char shortOpt) { - return cmdLineConfig.getOptionValue(shortOpt); - } - - public boolean cmdLineHasOption(char shortOpt) { - return cmdLineConfig.hasOption(shortOpt); - } - - public String[] getCliArgs() { - return this.cmdLineConfig.getArgs(); - } - - public enum ClockChangeRateMode { - COMPUTE_INTERPOLATE, - COMPUTE_PREDICT, - ASSIGN, - ASSIGN_KEY, - NO_DRIFT - } - - public static class AdditionalSmoothingRecordConfig { - public final boolean enabled; - public final int coarseSclkTickDuration; - - public AdditionalSmoothingRecordConfig(boolean enabled, int coarseSclkTickDuration) { - this.enabled = enabled; - this.coarseSclkTickDuration = coarseSclkTickDuration; - } - } - - public TimeCorrelationAppConfig(String... args) throws Exception { - super(); - - this.telemetrySource = this.initTlmSource(); - this.cmdLineConfig = new CorrelationCommandLineConfig(args, this.telemetrySource.getAdditionalCliArguments()); - - if (! cmdLineConfig.load()) { - throw new MmtcException("Error parsing command line arguments."); - } - - setClockChangeRateConfiguration(); - setInsertAdditionalSmoothingRecordConfiguration(); - - if (additionalSmoothingRecordConfig.enabled && clockChangeRateMode == ClockChangeRateMode.COMPUTE_INTERPOLATE) { - throw new MmtcException("Cannot insert 'smoothing' correlation records into products with --clkchgrate-compute i"); - } - - // todo would be ideal if config was immutable - this.telemetrySource.applyConfiguration(this); - - logger.debug(toString()); - } - - public TelemetrySource getTelemetrySource() { - return telemetrySource; - } - - /** - * Gets the method to be used to compute the clock change rate. - * - * @return the clock change rate compute method - */ - public ClockChangeRateMode getClockChangeRateMode() { - return clockChangeRateMode; - } - - /** - * Records how the clock change rate is to be computed (either predicted, - * interpolated, assigned, or set to the identity rate (no drift)). This is determined by the - * command line options. If no relevant command line options are passed in, then - * this is determined by the configuration file. If no relevant option is found - * in the configuration file, then this falls back to a default value. - * - * @throws MmtcException - */ - private void setClockChangeRateConfiguration() throws MmtcException { - if (cmdLineConfig.hasClockChangeRateMode()) { - clockChangeRateMode = cmdLineConfig.getClockChangeRateMode(); - - // the 'assign' and 'nodrift' modes can only be set on the CLI; check for these first - if (isClockChangeRateModeAssign()) { - setAssignedClockChangeRateConfiguration(); - logger.info(USER_NOTICE, String.format("Clock change rate mode is specified by command line argument: %s with rate %f", clockChangeRateMode, getClockChangeRateAssignedValue())); - } else { - // nothing else to do here - logger.info(USER_NOTICE, String.format("Clock change rate mode is specified by command line argument: %s", clockChangeRateMode)); - } - } else if (timeCorrelationConfig.getConfig().containsKey("compute.clkchgrate.mode")) { - final String modeFromConfig = timeCorrelationConfig.getConfig().getString("compute.clkchgrate.mode"); - if (modeFromConfig.equalsIgnoreCase("compute-predict")) { - clockChangeRateMode = ClockChangeRateMode.COMPUTE_PREDICT; - } else if (modeFromConfig.equalsIgnoreCase("compute-interpolate")) { - clockChangeRateMode = ClockChangeRateMode.COMPUTE_INTERPOLATE; - } else { - throw new MmtcException(String.format("The clock change rate mode in configuration must be either 'compute-predict' or 'compute-interpolate', but it was '%s'", modeFromConfig)); - } - - logger.info(USER_NOTICE, String.format("Clock change rate mode specified in configuration: %s", clockChangeRateMode)); - } else { - clockChangeRateMode = DEFAULT_CLOCK_CHANGE_RATE_MODE; - logger.info(USER_NOTICE, String.format("Clock change rate mode is set to default: %s", DEFAULT_CLOCK_CHANGE_RATE_MODE)); - } - } - - /** - * Records the fixed value of the clock change rate to be written to the time - * correlation records. This value is taken from the command line if available, - * or else from the configuration file if specified there, or else from a - * hardcoded default. This method should only be used when the user has opted to - * assign the clock change rate rather than compute it. This is typically done - * in test venues or in anomalous circumstances, or when a new SCLK clock - * partition is defined, or when there is an oscillator switch. - */ - private void setAssignedClockChangeRateConfiguration() throws MmtcException { - if (! isClockChangeRateModeAssign()) { - throw new MmtcException("Clock change rate mode is not one of the 'assign' modes"); - } - - if (clockChangeRateMode == ClockChangeRateMode.ASSIGN) { - clockChangeRateAssignedValue = cmdLineConfig.getClockChangeRateAssignedValue(); - } else if (clockChangeRateMode == ClockChangeRateMode.ASSIGN_KEY) { - String clockChangeRatePresetKey = cmdLineConfig.getClockChangeRateAssignedKey(); - try { - logger.debug(String.format("Reading assigned clock change rate from config key compute.clkchgrate.assignedValuePresets.%s", clockChangeRatePresetKey)); - clockChangeRateAssignedValue = timeCorrelationConfig.getConfig().getDouble("compute.clkchgrate.assignedValuePresets." + clockChangeRatePresetKey); - } catch(NoSuchElementException e) { - throw new MmtcException("Could not find a corresponding clkchgrate preset in the config with name compute.clkchgrate.assignedValuePresets." + clockChangeRatePresetKey); - } - } - } - - public boolean isClockChangeRateModeAssign() { - return CLOCK_CHANGE_RATE_ASSIGN_MODES.contains(getClockChangeRateMode()); - } - - /** - * Gets the assigned clock change rate. Calls to this method will fail unless the clock change rate mode is actually - * ClockChangeRateMode.ASSIGN. - * - * @return the clock change rate value - * @throws MmtcException if called before the clock change rate mode has been set to 'assign', or if it is not 'assign' - */ - public double getClockChangeRateAssignedValue() throws MmtcException { - if (getClockChangeRateMode() == null) { - throw new MmtcException("Clock change rate mode has not been set yet"); - } - - if (! (getClockChangeRateMode().equals(ClockChangeRateMode.ASSIGN) || getClockChangeRateMode().equals(ClockChangeRateMode.ASSIGN_KEY))) { - throw new MmtcException("Attempted to use the clock change rate value in clock change rate mode: " + getClockChangeRateMode()); - } - - return clockChangeRateAssignedValue; - } - - private void setInsertAdditionalSmoothingRecordConfiguration() throws MmtcException { - final Optional additionalSmoothingRecordInsertionOverride = cmdLineConfig.getAdditionalSmoothingRecordInsertionOverride(); - - if (additionalSmoothingRecordInsertionOverride.isPresent()) { - this.additionalSmoothingRecordConfig = additionalSmoothingRecordInsertionOverride.get(); - return; - } - - this.additionalSmoothingRecordConfig = new AdditionalSmoothingRecordConfig( - isAdditionalSmoothingCorrelationRecordInsertionEnabled(), - getAdditionalSmoothingCorrelationRecordInsertionCoarseSclkTickDuration() - ); - } - - public AdditionalSmoothingRecordConfig getAdditionalSmoothingRecordConfig() { - return this.additionalSmoothingRecordConfig; - } - - /** - * The start time of the telemetry interval from which to query data for time correlation as supplied by - * the user in the command line arguments. - * - * @return the sampling interval start time supplied by the user - */ - public OffsetDateTime getStartTime() { - return cmdLineConfig.getStartTime(); - } - - /** - * The stop time of the telemetry interval from which to query data for time correlation as supplied by - * the user in the command line arguments. - * - * @return the sampling interval stop time supplied by the user - */ - public OffsetDateTime getStopTime() { - return cmdLineConfig.getStopTime(); - } - - /** - * Indicates if test mode is set. - * - * @return true if test mode is enabled - */ - public boolean isTestMode() { - return cmdLineConfig.isTestMode(); - } - - /** - * Gets the one-way light travel time (OWLT) value supplied by the user in the command line options in a test venue. - * - * @return the OWLT to be used for time correlation - */ - public double getTestModeOwlt() { return cmdLineConfig.getTestModeOwlt(); } - - /** - * As is the case with the other filters, the contact filter can be enabled or - * disabled from the parameter file, which is the preferred way to do it. - * However, it can also be disabled from the command line with the - * --disable-contact-filter or -F command line option. The command line option - * overrides the configuration filter.contact.enabled paramter setting. - * - * @return true if the contact filter is DISABLED, false if enabled - * @throws MmtcException if contact filter is not disabled from the - * command line and is also missing from - * application configuration - */ - public boolean isContactFilterDisabled() throws MmtcException { - boolean isDisabled = true; - if (!cmdLineConfig.isContactFilterDisabled()) { - String filterName = TimeCorrelationAppConfig.CONTACT_FILTER; - try { - if (timeCorrelationConfig.getConfig().getBoolean(String.format("filter.%s.enabled", filterName))) { - logger.info("Filter " + filterName + " is enabled."); - isDisabled = false; - } - else { - logger.info("Filter " + filterName + " is disabled."); - } - } - catch (NoSuchElementException ex) { - throw new MmtcException("Expected contact filter " + filterName + - " is is not indicated configuration file", ex); - } - } - return isDisabled; - } - - /** - * Creates a container map that holds the filters that are to be applied in this run, excluding the - * contact filter. - * @return a map containing names and TimeCorrelationFilter instances; note this does not include the - * contact filter - * @throws MmtcException if the filter entries in the configuration parameters are incomplete - */ - public Map getFilters() throws MmtcException { - Map filters = new LinkedHashMap<>(); - - // purposefully disincludes the Contact Filter, as this is handled as a special case elsewhere - String[] expectedFilterNames = { - TimeCorrelationAppConfig.MIN_DATARATE_FILTER, - TimeCorrelationAppConfig.MAX_DATARATE_FILTER, - TimeCorrelationAppConfig.ERT_FILTER, - TimeCorrelationAppConfig.GROUND_STATION_FILTER, - TimeCorrelationAppConfig.SCLK_FILTER, - TimeCorrelationAppConfig.VALID_FILTER, - TimeCorrelationAppConfig.CONSEC_FRAMES_FILTER, - TimeCorrelationAppConfig.VCID_FILTER, - TimeCorrelationAppConfig.CONSEC_MC_FRAME_FILTER, - }; - - for (String filterName : expectedFilterNames) { - try { - if (timeCorrelationConfig.getConfig().getBoolean(String.format("filter.%s.enabled", filterName))) { - TimeCorrelationFilter filter = TimeCorrelationFilter.createFilterInstanceByName(filterName); - filters.put(filterName, filter); - logger.info("Filter " + filterName + " is enabled."); - } - else { - logger.info("Filter " + filterName + " is disabled."); - } - } catch (NoSuchElementException ex) { - throw new MmtcException("Expected filter " + filterName + " is missing from configuration file"); - } - } - - return filters; - } - - /** - * Indicates if this is a dry run - * - * @return true if -D or --dry-run CLI options are invoked - */ - public boolean isDryRun() { - return cmdLineConfig.isDryRun(); - } - - /** - * Indicates if an optional Uplink Command File is to be created. This can be specified in either the command line - * or in configuration parameters. Command line overrides the configuration parameter. - * - * @return true if an Uplink Command File is to be created - */ - public boolean isCreateUplinkCmdFile() { - if (cmdLineConfig.isGenerateCmdFile()) { - return true; - } else { - return timeCorrelationConfig.getConfig().getBoolean("product.uplinkCmdFile.create", false); - } - } - - /** - * Validate that the configuration is complete: all required keys are present, and for any optional products that - * are enabled, that they have their required config keys populated - * - * @throws MmtcException if the MMTC configuration is incomplete - */ - public void validate() throws MmtcException { - // Validate that all required config keys are present - super.validate(); - - if (createSclkScetFile()) { - ensureValidSclkScetConfiguration(); - } - - if (isCreateUplinkCmdFile()) { - ensureValidUplinkCmdFileConfiguration(); - } - } -} diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationCliInputConfig.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationCliInputConfig.java new file mode 100644 index 0000000..3e322e3 --- /dev/null +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationCliInputConfig.java @@ -0,0 +1,69 @@ +package edu.jhuapl.sd.sig.mmtc.cfg; + +import edu.jhuapl.sd.sig.mmtc.app.MmtcException; +import edu.jhuapl.sd.sig.mmtc.tlm.TelemetrySource; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; +import java.util.stream.Collectors; + +import static edu.jhuapl.sd.sig.mmtc.cfg.MmtcConfig.ClockChangeRateMode.ASSIGN; +import static edu.jhuapl.sd.sig.mmtc.cfg.MmtcConfig.ClockChangeRateMode.ASSIGN_KEY; + +public class TimeCorrelationCliInputConfig implements TimeCorrelationRunConfigInputSupplier { + private static final Logger logger = LogManager.getLogger(); + + private final String[] args; + + public TimeCorrelationCliInputConfig(String... args) throws Exception { + this.args = args; + } + + @Override + public TimeCorrelationRunConfig.TimeCorrelationRunConfigInputs getRunConfigInputs(Map additionalTlmSrcOptionsByName) throws Exception { + logger.info("Command line arguments: " + Arrays.asList(args)); + + final CorrelationCommandLineConfig cmdLineConfig = new CorrelationCommandLineConfig( + args, + additionalTlmSrcOptionsByName.values().stream().map(additionalOption -> additionalOption.cliOption).collect(Collectors.toList()) + ); + + if (! cmdLineConfig.load()) { + throw new MmtcException("Error parsing command line arguments."); + } + + + final Optional testModeOwltSec = cmdLineConfig.isTestMode() ? Optional.of(cmdLineConfig.getTestModeOwlt()) : Optional.empty(); + + Optional assignVal = Optional.empty(); + Optional assignValKey = Optional.empty(); + + if (cmdLineConfig.hasClockChangeRateMode()) { + if (cmdLineConfig.getClockChangeRateMode().equals(ASSIGN)) { + assignVal = Optional.of(cmdLineConfig.getClockChangeRateAssignedValue()); + } else if (cmdLineConfig.getClockChangeRateMode().equals(ASSIGN_KEY)) { + assignValKey = Optional.of(cmdLineConfig.getClockChangeRateAssignedKey()); + } + } + + final TimeCorrelationRunConfig.DryRunConfig dryRunConfig = cmdLineConfig.isDryRun() ? new TimeCorrelationRunConfig.DryRunConfig(TimeCorrelationRunConfig.DryRunMode.DRY_RUN_RETAIN_NO_PRODUCTS, null) : new TimeCorrelationRunConfig.DryRunConfig(TimeCorrelationRunConfig.DryRunMode.NOT_DRY_RUN, null); + + return new TimeCorrelationRunConfig.TimeCorrelationRunConfigInputs( + TimeCorrelationRunConfig.TargetSampleInputErtMode.RANGE, + Optional.of(cmdLineConfig.getStartTime()), + Optional.of(cmdLineConfig.getStopTime()), + Optional.empty(), // todo enable this on the cmd line + Optional.empty(), // todo enable this on the cmd line + cmdLineConfig.isTestMode(), + testModeOwltSec, + assignVal, + assignValKey, + cmdLineConfig.hasClockChangeRateMode() ? Optional.of(cmdLineConfig.getClockChangeRateMode()) : Optional.empty(), + cmdLineConfig.getAdditionalSmoothingRecordInsertionOverride(), + cmdLineConfig.isContactFilterDisabled(), + cmdLineConfig.isGenerateCmdFile(), + dryRunConfig + ); + } +} diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationMetricsConfig.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationMetricsConfig.java new file mode 100644 index 0000000..d37a1fb --- /dev/null +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationMetricsConfig.java @@ -0,0 +1,21 @@ +package edu.jhuapl.sd.sig.mmtc.cfg; + +import edu.jhuapl.sd.sig.mmtc.app.MmtcException; +import edu.jhuapl.sd.sig.mmtc.util.TimeConvertException; + +import java.time.OffsetDateTime; + +public interface TimeCorrelationMetricsConfig { + double getFrameErtBitOffsetError(); + Integer getTkSclkFineTickModulus() throws TimeConvertException; + int getNaifSpacecraftId(); + + boolean isTestMode(); + double getTestModeOwlt(); + + String getStationId(int pathId) throws MmtcException; + + int getSclkPartition(OffsetDateTime groundReceiptTime); + + double getSpacecraftTimeDelaySec(); +} diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationRunConfig.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationRunConfig.java new file mode 100644 index 0000000..eb3d08f --- /dev/null +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationRunConfig.java @@ -0,0 +1,510 @@ +package edu.jhuapl.sd.sig.mmtc.cfg; + +import edu.jhuapl.sd.sig.mmtc.app.MmtcException; +import edu.jhuapl.sd.sig.mmtc.filter.TimeCorrelationFilter; +import edu.jhuapl.sd.sig.mmtc.tlm.persistence.cache.OffsetDateTimeRange; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.nio.file.Path; +import java.time.OffsetDateTime; +import java.util.*; + +import static edu.jhuapl.sd.sig.mmtc.app.MmtcCli.USER_NOTICE; + +// integrates the aspects of configuration that are inputs liable to change per-run, atop file-based configuration +public class TimeCorrelationRunConfig extends MmtcConfigWithTlmSource implements TimeCorrelationMetricsConfig { + private static final Logger logger = LogManager.getLogger(); + + public static final String CONTACT_FILTER = "contact"; + public static final String MIN_DATARATE_FILTER = "minDataRate"; + public static final String MAX_DATARATE_FILTER = "maxDataRate"; + public static final String ERT_FILTER = "ert"; + public static final String GROUND_STATION_FILTER = "groundStation"; + public static final String SCLK_FILTER = "sclk"; + public static final String VALID_FILTER = "validFlag"; + public static final String CONSEC_FRAMES_FILTER = "consecutiveFrames"; + public static final String VCID_FILTER = "vcid"; + public static final String CONSEC_MC_FRAME_FILTER = "consecutiveMasterChannelFrames"; + + private final TimeCorrelationRunConfigInputs runConfigInputs; + + // resolved attributes + private ClockChangeRateMode resolvedClockChangeRateMode; + private double resolvedClockChangeRateAssignedValue; + private AdditionalSmoothingRecordConfig resolvedAdditionalSmoothingRecordConfig; + private Optional resolvedTargetSampleRange; + private Optional resolvedTargetSampleExactErt; + + public enum TargetSampleInputErtMode { + RANGE, + EXACT + } + + public static class AdditionalSmoothingRecordConfig { + public boolean enabled; + public int coarseSclkTickDuration; + + public AdditionalSmoothingRecordConfig( ) { } + + public AdditionalSmoothingRecordConfig(boolean enabled, int coarseSclkTickDuration) { + this.enabled = enabled; + this.coarseSclkTickDuration = coarseSclkTickDuration; + } + + public String toLogString() { + if (this.enabled) { + return "Additional smoothing record insertion = enabled at " + this.coarseSclkTickDuration; + } else { + return "Additional smoothing record insertion = disabled"; + } + } + } + + public enum DryRunMode { + NOT_DRY_RUN, + DRY_RUN_RETAIN_NO_PRODUCTS, + DRY_RUN_GENERATE_SEPARATE_SCLK_ONLY + } + + public static class DryRunConfig { + public DryRunMode mode; + public Path sclkKernelOutputPath; + + public DryRunConfig() { } + + public DryRunConfig(DryRunMode mode, Path sclkKernelOutputPath) { + this.mode = mode; + this.sclkKernelOutputPath = sclkKernelOutputPath; + } + } + + + + // ideas for encoding this into the run history file + // - still encode this into a 'run args' column, implying a means to convert the data in an instance of this class into its CLI equivalent + // - maybe consider a separate column for the start & stop / exact ert input time this was run with + + public static class TimeCorrelationRunConfigInputs { + // inputs that must be provided with each run, which are not contained in configuration files at all + final TargetSampleInputErtMode targetSampleInputErtMode; + final Optional targetSampleRangeStartErt; + final Optional targetSampleRangeStopErt; + final Optional targetSampleExactErt; + final Optional priorCorrelationExactTdt; + final boolean testModeOwltEnabled; + final Optional testModeOwltSec; + final Optional clockChangeRateAssignedValue; + final Optional clockChangeRateAssignedKey; + final DryRunConfig dryRunConfig; + + // inputs that can override those specified in configuration files + final Optional clockChangeRateModeOverride; + final Optional additionalSmoothingRecordConfigOverride; + final boolean isDisableContactFilter; + final boolean isCreateUplinkCmdFile; + + public TimeCorrelationRunConfigInputs( + TargetSampleInputErtMode targetSampleInputErtMode, + Optional targetSampleRangeStartErt, + Optional targetSampleRangeStopErt, + Optional targetSampleExactErt, + Optional priorCorrelationExactTdt, + boolean testModeOwltEnabled, + Optional testModeOwltSec, + Optional clockChangeRateAssignedValue, + Optional clockChangeRateAssignedKey, + Optional clockChangeRateModeOverride, + Optional additionalSmoothingRecordConfigOverride, + boolean isDisableContactFilter, + boolean isCreateUplinkCmdFile, + DryRunConfig dryRunConfig + ) { + this.targetSampleInputErtMode = targetSampleInputErtMode; + this.targetSampleRangeStartErt = targetSampleRangeStartErt; + this.targetSampleRangeStopErt = targetSampleRangeStopErt; + this.targetSampleExactErt = targetSampleExactErt; + this.priorCorrelationExactTdt = priorCorrelationExactTdt; + this.testModeOwltEnabled = testModeOwltEnabled; + this.testModeOwltSec = testModeOwltSec; + this.clockChangeRateAssignedValue = clockChangeRateAssignedValue; + this.clockChangeRateAssignedKey = clockChangeRateAssignedKey; + this.clockChangeRateModeOverride = clockChangeRateModeOverride; + this.additionalSmoothingRecordConfigOverride = additionalSmoothingRecordConfigOverride; + this.isDisableContactFilter = isDisableContactFilter; + this.isCreateUplinkCmdFile = isCreateUplinkCmdFile; + this.dryRunConfig = dryRunConfig; + } + } + + public TimeCorrelationRunConfig(TimeCorrelationRunConfigInputSupplier runConfigInputSupplier) throws Exception { + super(); + this.runConfigInputs = runConfigInputSupplier.getRunConfigInputs(this.additionalTlmSrcOptionsByName); + resolveCalculatedRunConfigInputs(); + } + + public TimeCorrelationRunConfig(TimeCorrelationRunConfigInputSupplier runConfigInputSupplier, MmtcConfigWithTlmSource config) throws Exception { + super(config); + this.runConfigInputs = runConfigInputSupplier.getRunConfigInputs(this.additionalTlmSrcOptionsByName); + resolveCalculatedRunConfigInputs(); + } + + private void resolveCalculatedRunConfigInputs() throws MmtcException { + setTargetAndBasisSampleInputs(); + setClockChangeRateConfiguration(); + setInsertAdditionalSmoothingRecordConfiguration(); + + // ensure the given path for a dry run SCLK kernel file is NOT the same directory as the configured output directory + if (getDryRunConfig().mode.equals(DryRunMode.DRY_RUN_GENERATE_SEPARATE_SCLK_ONLY)) { + Path sclkKernelDryRunOutputPath = getDryRunConfig().sclkKernelOutputPath; + if (getSclkKernelOutputDir().toAbsolutePath().startsWith(sclkKernelDryRunOutputPath)) { + throw new IllegalStateException("The output SCLK kernel directory must be distinct from the configured SCLK kernel output directory: " + getSclkKernelOutputDir().toAbsolutePath()); + } + } + + if (resolvedAdditionalSmoothingRecordConfig.enabled && resolvedClockChangeRateMode == ClockChangeRateMode.COMPUTE_INTERPOLATE) { + throw new MmtcException("Cannot insert 'smoothing' correlation records into products with --clkchgrate-compute i"); + } + + this.telemetrySource.checkCorrelationConfiguration(this); + } + + private void setTargetAndBasisSampleInputs() { + if (this.runConfigInputs.targetSampleInputErtMode.equals(TargetSampleInputErtMode.RANGE)) { + this.resolvedTargetSampleRange = Optional.of(new OffsetDateTimeRange(this.runConfigInputs.targetSampleRangeStartErt.get(), this.runConfigInputs.targetSampleRangeStopErt.get())); + this.resolvedTargetSampleExactErt = Optional.empty(); + } else { + this.resolvedTargetSampleExactErt = this.runConfigInputs.targetSampleExactErt; + this.resolvedTargetSampleRange = Optional.empty(); + } + } + + /** + * Records how the clock change rate is to be computed (either predicted, + * interpolated, assigned, or set to the identity rate (no drift)). This is determined by the + * command line options. If no relevant command line options are passed in, then + * this is determined by the configuration file. If no relevant option is found + * in the configuration file, then this falls back to a default value. + * + * @throws MmtcException + */ + private void setClockChangeRateConfiguration() throws MmtcException { + if (runConfigInputs.clockChangeRateModeOverride.isPresent()) { + resolvedClockChangeRateMode = runConfigInputs.clockChangeRateModeOverride.get(); + + // the 'assign' and 'nodrift' modes can only be set via runtime user input; check for these first + if (isClockChangeRateModeAssign()) { + setAssignedClockChangeRateConfiguration(); + logger.info(USER_NOTICE, String.format("Clock change rate mode is specified by input: %s with rate %f", resolvedClockChangeRateMode, getClockChangeRateAssignedValue())); + } else { + // nothing else to do here for the other user-specified modes + logger.info(USER_NOTICE, String.format("Clock change rate mode is specified by input: %s", resolvedClockChangeRateMode)); + } + + return; + } + + resolvedClockChangeRateMode = getConfiguredClockChangeRateMode(); + logger.info(USER_NOTICE, String.format("Clock change rate mode: %s", resolvedClockChangeRateMode)); + } + + /** + * Records the fixed value of the clock change rate to be written to the time + * correlation records. This value is taken from the command line if available, + * or else from the configuration file if specified there, or else from a + * hardcoded default. This method should only be used when the user has opted to + * assign the clock change rate rather than compute it. This is typically done + * in test venues or in anomalous circumstances, or when a new SCLK clock + * partition is defined, or when there is an oscillator switch. + */ + private void setAssignedClockChangeRateConfiguration() throws MmtcException { + if (! isClockChangeRateModeAssign()) { + throw new MmtcException("Clock change rate mode is not one of the 'assign' modes"); + } + + if (resolvedClockChangeRateMode == ClockChangeRateMode.ASSIGN) { + resolvedClockChangeRateAssignedValue = this.runConfigInputs.clockChangeRateAssignedValue.get(); + } else if (resolvedClockChangeRateMode == ClockChangeRateMode.ASSIGN_KEY) { + String clockChangeRatePresetKey = this.runConfigInputs.clockChangeRateAssignedKey.get(); + try { + logger.debug(String.format("Reading assigned clock change rate from config key compute.clkchgrate.assignedValuePresets.%s", clockChangeRatePresetKey)); + resolvedClockChangeRateAssignedValue = timeCorrelationConfig.getConfig().getDouble("compute.clkchgrate.assignedValuePresets." + clockChangeRatePresetKey); + } catch (NoSuchElementException e) { + throw new MmtcException("Could not find a corresponding clkchgrate preset in the config with name compute.clkchgrate.assignedValuePresets." + clockChangeRatePresetKey); + } + } + } + + public boolean isClockChangeRateModeAssign() { + return CLOCK_CHANGE_RATE_ASSIGN_MODES.contains(resolvedClockChangeRateMode); + } + + + /** + * Gets the assigned clock change rate. Calls to this method will fail unless the clock change rate mode is actually + * ClockChangeRateMode.ASSIGN. + * + * @return the clock change rate value + * @throws MmtcException if called before the clock change rate mode has been set to 'assign', or if it is not 'assign' + */ + public double getClockChangeRateAssignedValue() throws MmtcException { + if (getClockChangeRateMode() == null) { + throw new MmtcException("Clock change rate mode has not been set yet"); + } + + if (! (getClockChangeRateMode().equals(ClockChangeRateMode.ASSIGN) || getClockChangeRateMode().equals(ClockChangeRateMode.ASSIGN_KEY))) { + throw new MmtcException("Attempted to use the clock change rate value in clock change rate mode: " + getClockChangeRateMode()); + } + + return resolvedClockChangeRateAssignedValue; + } + + private void setInsertAdditionalSmoothingRecordConfiguration() throws MmtcException { + if (runConfigInputs.additionalSmoothingRecordConfigOverride.isPresent()) { + this.resolvedAdditionalSmoothingRecordConfig = runConfigInputs.additionalSmoothingRecordConfigOverride.get(); + return; + } + + this.resolvedAdditionalSmoothingRecordConfig = new AdditionalSmoothingRecordConfig( + isAdditionalSmoothingCorrelationRecordInsertionEnabled(), + getAdditionalSmoothingCorrelationRecordInsertionCoarseSclkTickDuration() + ); + } + + public AdditionalSmoothingRecordConfig getAdditionalSmoothingRecordConfig() { + return this.resolvedAdditionalSmoothingRecordConfig; + } + + /** + * Indicates if test mode is set. + * + * @return true if test mode is enabled + */ + public boolean isTestMode() { + return runConfigInputs.testModeOwltEnabled; + } + + /** + * Gets the one-way light travel time (OWLT) value supplied by the user in the command line options in a test venue. + * + * @return the OWLT to be used for time correlation + */ + public double getTestModeOwlt() { return runConfigInputs.testModeOwltSec.get(); } + + public Optional getPriorCorrelationTdt() { + return runConfigInputs.priorCorrelationExactTdt; + } + + /** + * As is the case with the other filters, the contact filter can be enabled or + * disabled from the parameter file, which is the preferred way to do it. + * However, it can also be disabled from the command line with the + * --disable-contact-filter or -F command line option. The command line option + * overrides the configuration filter.contact.enabled paramter setting. + * + * @return true if the contact filter is DISABLED, false if enabled + * @throws MmtcException if contact filter is not disabled from the + * command line and is also missing from + * application configuration + */ + public boolean isContactFilterDisabled() throws MmtcException { + boolean isDisabled = true; + if (! runConfigInputs.isDisableContactFilter) { + String filterName = CONTACT_FILTER; + try { + if (timeCorrelationConfig.getConfig().getBoolean(String.format("filter.%s.enabled", filterName))) { + logger.info("Filter " + filterName + " is enabled."); + isDisabled = false; + } else { + logger.info("Filter " + filterName + " is disabled."); + } + } catch (NoSuchElementException ex) { + throw new MmtcException("Expected contact filter " + filterName + + " is is not indicated configuration file", ex); + } + } + return isDisabled; + } + + /** + * Creates a container map that holds the filters that are to be applied in this run, excluding the + * contact filter. + * @return a map containing names and TimeCorrelationFilter instances; note this does not include the + * contact filter + * @throws MmtcException if the filter entries in the configuration parameters are incomplete + */ + public Map getFilters() throws MmtcException { + Map filters = new LinkedHashMap<>(); + + // purposefully disincludes the Contact Filter, as this is handled as a special case elsewhere + String[] expectedFilterNames = { + MIN_DATARATE_FILTER, + MAX_DATARATE_FILTER, + ERT_FILTER, + GROUND_STATION_FILTER, + SCLK_FILTER, + VALID_FILTER, + CONSEC_FRAMES_FILTER, + VCID_FILTER, + CONSEC_MC_FRAME_FILTER, + }; + + for (String filterName : expectedFilterNames) { + try { + if (timeCorrelationConfig.getConfig().getBoolean(String.format("filter.%s.enabled", filterName))) { + TimeCorrelationFilter filter = TimeCorrelationFilter.createFilterInstanceByName(filterName); + filters.put(filterName, filter); + logger.info("Filter " + filterName + " is enabled."); + } + else { + logger.info("Filter " + filterName + " is disabled."); + } + } catch (NoSuchElementException ex) { + throw new MmtcException("Expected filter " + filterName + " is missing from configuration file"); + } + } + + return filters; + } + + public boolean isCreateUplinkCmdFile() { + if (this.runConfigInputs.isCreateUplinkCmdFile) { + return true; + } + + return super.isCreateUplinkCmdFile(); + } + + /** + * Validate that the configuration is complete: all required keys are present, and for any optional products that + * are enabled, that they have their required config keys populated + * + * @throws MmtcException if the MMTC configuration is incomplete + */ + public void validate() throws MmtcException { + super.validate(); + + if (createSclkScetFile()) { + ensureValidSclkScetConfiguration(); + } + + if (isCreateUplinkCmdFile()) { + ensureValidUplinkCmdFileConfiguration(); + } + } + + public boolean isDryRun() { + return ! this.runConfigInputs.dryRunConfig.mode.equals(DryRunMode.NOT_DRY_RUN); + } + + public DryRunConfig getDryRunConfig() { + return this.runConfigInputs.dryRunConfig; + } + + public ClockChangeRateMode getClockChangeRateMode() { + return resolvedClockChangeRateMode; + } + + @Override + public String getAdditionalOptionValue(String optionName) { + return this.additionalTlmSrcOptionsByName.get(optionName).cliOption.getValue(); + } + + public Optional getResolvedTargetSampleRange() { + return resolvedTargetSampleRange; + } + + public Optional getResolvedTargetSampleExactErt() { + return resolvedTargetSampleExactErt; + } + + public TargetSampleInputErtMode getTargetSampleInputErtMode() { + if (resolvedTargetSampleRange.isPresent()) { + return TargetSampleInputErtMode.RANGE; + } + + return TargetSampleInputErtMode.EXACT; + } + + + /* + final Optional targetSampleRangeStartErt; + final Optional targetSampleRangeStopErt; + final Optional targetSampleExactErt; + final Optional priorCorrelationExactTdt; + final boolean testModeOwltEnabled; + final Optional testModeOwltSec; + final Optional clockChangeRateAssignedValue; + final Optional clockChangeRateAssignedKey; + private final boolean isDryRun; + + // inputs that can override those specified in configuration files + final Optional clockChangeRateModeOverride; + final Optional additionalSmoothingRecordConfigOverride; + final boolean isDisableContactFilter; + final boolean isCreateUplinkCmdFile; + */ + public String getInvocationStringRepresentation() throws MmtcException { + // return runConfigInputs.toLoggableString(); + List elts = new ArrayList<>(); + + if (getTargetSampleInputErtMode().equals(TargetSampleInputErtMode.RANGE)) { + OffsetDateTimeRange resolvedRange = getResolvedTargetSampleRange().get(); + elts.add(String.format("Input ERT range = %s to %s", resolvedRange.getStart(), resolvedRange.getStop())); + } else if (getTargetSampleInputErtMode().equals(TargetSampleInputErtMode.EXACT)) { + elts.add(String.format("Input ERT = %s", getResolvedTargetSampleExactErt().get())); + } else { + throw new IllegalStateException("Unknown mode: " + getTargetSampleInputErtMode()); + } + + if (getPriorCorrelationTdt().isPresent()) { + elts.add(String.format("Prior corr TDT = %s", getPriorCorrelationTdt().get())); + } + + if (isTestMode()) { + elts.add(String.format("Test Mode OWLT = %f", getTestModeOwlt())); + } + + if (isDryRun()) { + elts.add("Dry run = true"); + } + + if (runConfigInputs.clockChangeRateModeOverride.isPresent()) { + elts.add(String.format("Clock change rate mode = %s", runConfigInputs.clockChangeRateModeOverride.get())); + + ClockChangeRateMode overrideMode = runConfigInputs.clockChangeRateModeOverride.get(); + if (overrideMode.equals(ClockChangeRateMode.ASSIGN_KEY)) { + elts.add(String.format("Clock change rate assign key = %s", runConfigInputs.clockChangeRateAssignedKey.get())); + } else if (overrideMode.equals(ClockChangeRateMode.ASSIGN)) { + elts.add(String.format("Clock change rate assign = %s", runConfigInputs.clockChangeRateAssignedValue.get())); + } + } + + if (runConfigInputs.additionalSmoothingRecordConfigOverride.isPresent()) { + elts.add(runConfigInputs.additionalSmoothingRecordConfigOverride.get().toLogString()); + } + + // todo add additional CLI options here? + if (runConfigInputs.isDisableContactFilter) { + elts.add("Contact filter = disabled"); + } + + if (runConfigInputs.isCreateUplinkCmdFile) { + elts.add("Uplink cmd file creation = enabled"); + } + + return String.join(" | ", elts); + } + + public static class ClockChangeRateConfig { + public TimeCorrelationRunConfig.ClockChangeRateMode clockChangeRateModeOverride; + public double specifiedClockChangeRateToAssign; + + public ClockChangeRateConfig() { } + + public ClockChangeRateConfig(ClockChangeRateMode clockChangeRateModeOverride, double specifiedClockChangeRateToAssign) { + this.clockChangeRateModeOverride = clockChangeRateModeOverride; + this.specifiedClockChangeRateToAssign = specifiedClockChangeRateToAssign; + } + } +} diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationRunConfigInputSupplier.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationRunConfigInputSupplier.java new file mode 100644 index 0000000..d0f8b5f --- /dev/null +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationRunConfigInputSupplier.java @@ -0,0 +1,10 @@ +package edu.jhuapl.sd.sig.mmtc.cfg; + +import edu.jhuapl.sd.sig.mmtc.tlm.TelemetrySource; + +import java.util.Map; + +@FunctionalInterface +public interface TimeCorrelationRunConfigInputSupplier { + TimeCorrelationRunConfig.TimeCorrelationRunConfigInputs getRunConfigInputs(Map additionalTlmSrcOptionsByName) throws Exception; +} diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationXmlPropertiesConfig.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationXmlPropertiesConfig.java index f2b9eea..17f2291 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationXmlPropertiesConfig.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/cfg/TimeCorrelationXmlPropertiesConfig.java @@ -55,9 +55,9 @@ public boolean load() { final boolean success = config != null; if (success) { - String urlString = config.getURLString(); - logger.info("Loaded configuration from: " + urlString); - setPath(Paths.get(urlString)); + String path = config.getURLString().replaceFirst("file:", ""); + logger.info("Loaded configuration from: " + path); + setPath(Paths.get(path)); } return success; diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/CorrelationInfo.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/CorrelationInfo.java index 9a68317..7f5b121 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/CorrelationInfo.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/CorrelationInfo.java @@ -1,7 +1,7 @@ package edu.jhuapl.sd.sig.mmtc.correlation; import edu.jhuapl.sd.sig.mmtc.app.TimeCorrelationTarget; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.products.model.SclkKernel; import edu.jhuapl.sd.sig.mmtc.util.Settable; @@ -16,11 +16,13 @@ public class CorrelationInfo { // time correlation target information, including the sample set, the chosen FrameSample, and some computed information public final Settable target = new Settable<>(); - public final Settable actual_clock_change_rate_mode = new Settable<>(); + public final Settable actual_clock_change_rate_mode = new Settable<>(); public final Settable predicted_clock_change_rate = new Settable<>(); public final Settable interpolated_clock_change_rate = new Settable<>(); - public Settable smoothingTriplet = new Settable<>(); + public Settable newPredictedTriplet = new Settable<>(); + public Settable updatedInterpolatedTriplet = new Settable<>(); + public Settable newSmoothingTriplet = new Settable<>(); // other computed information public final Settable sclk_drift_ms_per_day = new Settable<>(); diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/CorrelationMetrics.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/CorrelationMetrics.java index dd6a6c9..758a21b 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/CorrelationMetrics.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/CorrelationMetrics.java @@ -3,8 +3,6 @@ import edu.jhuapl.sd.sig.mmtc.util.Settable; public class CorrelationMetrics { - public final Settable sclkDrift = new Settable<>(); - // length of time since the prior correlation, in days public final Settable dt = new Settable<>(); diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/TimeCorrelationContext.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/TimeCorrelationContext.java index a2e628f..3ad0481 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/TimeCorrelationContext.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/correlation/TimeCorrelationContext.java @@ -1,6 +1,6 @@ package edu.jhuapl.sd.sig.mmtc.correlation; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.products.model.SclkKernel; import edu.jhuapl.sd.sig.mmtc.tlm.TelemetrySource; import edu.jhuapl.sd.sig.mmtc.util.Settable; @@ -17,7 +17,7 @@ public class TimeCorrelationContext { public final GeometryInfo geometry = new GeometryInfo(); public final AncillaryInfo ancillary = new AncillaryInfo(); - public final TimeCorrelationAppConfig config; + public final TimeCorrelationRunConfig config; public final TelemetrySource telemetrySource; public final OffsetDateTime appRunTime; public final Settable runId = new Settable<>(); @@ -31,7 +31,7 @@ public class TimeCorrelationContext { private final List warnings; - public TimeCorrelationContext(final TimeCorrelationAppConfig config) { + public TimeCorrelationContext(final TimeCorrelationRunConfig config) { this.config = config; this.telemetrySource = config.getTelemetrySource(); this.warnings = new ArrayList<>(); diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ConsecutiveFrameFilter.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ConsecutiveFrameFilter.java index f4fa144..f675fe3 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ConsecutiveFrameFilter.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ConsecutiveFrameFilter.java @@ -1,7 +1,7 @@ package edu.jhuapl.sd.sig.mmtc.filter; import edu.jhuapl.sd.sig.mmtc.app.MmtcException; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.tlm.FrameSample; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -54,7 +54,7 @@ public class ConsecutiveFrameFilter implements TimeCorrelationFilter { * @return true if the VCID is consistent among all samples, false otherwise */ @Override - public boolean process(List samples, TimeCorrelationAppConfig config) throws MmtcException { + public boolean process(List samples, TimeCorrelationRunConfig config) throws MmtcException { if (samples.isEmpty()) { logger.warn("Consecutive Frame Filter Failed: Attempted to filter an empty sample set."); return false; diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ConsecutiveMasterChannelFrameFilter.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ConsecutiveMasterChannelFrameFilter.java index 5991a9b..04307b5 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ConsecutiveMasterChannelFrameFilter.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ConsecutiveMasterChannelFrameFilter.java @@ -1,7 +1,7 @@ package edu.jhuapl.sd.sig.mmtc.filter; import edu.jhuapl.sd.sig.mmtc.app.MmtcException; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.tlm.FrameSample; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -12,7 +12,7 @@ public class ConsecutiveMasterChannelFrameFilter implements TimeCorrelationFilte private static final Logger logger = LogManager.getLogger(); @Override - public boolean process(List samples, TimeCorrelationAppConfig config) throws MmtcException { + public boolean process(List samples, TimeCorrelationRunConfig config) throws MmtcException { if (! (config.getMcfcMaxValue() > 0)) { throw new MmtcException("When using the ConsecutiveMasterChannelFrameFilter, the MCFC maximum value must be set in configuration to a positive integer."); } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ContactFilter.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ContactFilter.java index a825a0b..19651e4 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ContactFilter.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ContactFilter.java @@ -5,7 +5,7 @@ import java.text.DecimalFormat; import edu.jhuapl.sd.sig.mmtc.app.MmtcException; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.util.TimeConvert; import edu.jhuapl.sd.sig.mmtc.util.TimeConvertException; import org.apache.logging.log4j.LogManager; @@ -79,7 +79,7 @@ public void setTdt_g_current(double tdt_g0) { * configurable threshold * @throws MmtcException when any conversions fail */ - public boolean process(FrameSample targetSample, TimeCorrelationAppConfig config, Integer sclk_kernel_fine_tick_modulus) throws MmtcException { + public boolean process(FrameSample targetSample, TimeCorrelationRunConfig config, Integer sclk_kernel_fine_tick_modulus) throws MmtcException { try { boolean passesFilter = true; diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ErtFilter.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ErtFilter.java index 33518c9..d37a4d8 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ErtFilter.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ErtFilter.java @@ -1,7 +1,7 @@ package edu.jhuapl.sd.sig.mmtc.filter; import edu.jhuapl.sd.sig.mmtc.app.MmtcException; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.tlm.FrameSample; import edu.jhuapl.sd.sig.mmtc.util.CdsTimeCode; @@ -33,7 +33,7 @@ public class ErtFilter implements TimeCorrelationFilter { * @throws MmtcException if propagated from a called function */ @Override - public boolean process(List samples, TimeCorrelationAppConfig config) throws MmtcException { + public boolean process(List samples, TimeCorrelationRunConfig config) throws MmtcException { if (samples.isEmpty()) { logger.warn("Attempted to filter an empty sample set"); return false; diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/GroundStationFilter.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/GroundStationFilter.java index 13732db..280ff5a 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/GroundStationFilter.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/GroundStationFilter.java @@ -1,6 +1,6 @@ package edu.jhuapl.sd.sig.mmtc.filter; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.tlm.FrameSample; import org.apache.logging.log4j.LogManager; @@ -26,7 +26,7 @@ public class GroundStationFilter implements TimeCorrelationFilter { * @return true if both conditions are met, false otherwise */ @Override - public boolean process(List samples, TimeCorrelationAppConfig config) { + public boolean process(List samples, TimeCorrelationRunConfig config) { if (samples.isEmpty()) { logger.warn("Ground Station Filter Failed: Attempted to filter an empty sample set"); return false; diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/MaxDataRateFilter.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/MaxDataRateFilter.java index fa03327..6c5e916 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/MaxDataRateFilter.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/MaxDataRateFilter.java @@ -1,6 +1,6 @@ package edu.jhuapl.sd.sig.mmtc.filter; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.tlm.FrameSample; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -24,7 +24,7 @@ public class MaxDataRateFilter implements TimeCorrelationFilter { * @return true if all samples have a data rate no higher than the maximum */ @Override - public boolean process(List samples, TimeCorrelationAppConfig config) { + public boolean process(List samples, TimeCorrelationRunConfig config) { if (samples.isEmpty()) { logger.warn("Data Rate Filter failed: Attempted to filter an empty sample set"); return false; diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/MinDataRateFilter.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/MinDataRateFilter.java index 6223a9e..c9cf1a7 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/MinDataRateFilter.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/MinDataRateFilter.java @@ -1,6 +1,6 @@ package edu.jhuapl.sd.sig.mmtc.filter; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.tlm.FrameSample; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -24,7 +24,7 @@ public class MinDataRateFilter implements TimeCorrelationFilter { * @return true if all samples have a data rate no lower than the minimum */ @Override - public boolean process(List samples, TimeCorrelationAppConfig config) { + public boolean process(List samples, TimeCorrelationRunConfig config) { if (samples.isEmpty()) { logger.warn("Data Rate Filter failed: Attempted to filter an empty sample set"); return false; diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/SclkFilter.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/SclkFilter.java index 4f9012b..8953dee 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/SclkFilter.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/SclkFilter.java @@ -1,7 +1,7 @@ package edu.jhuapl.sd.sig.mmtc.filter; import edu.jhuapl.sd.sig.mmtc.app.MmtcException; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.tlm.FrameSample; import edu.jhuapl.sd.sig.mmtc.util.TimeConvertException; @@ -30,7 +30,7 @@ public class SclkFilter implements TimeCorrelationFilter { * @return true if the set of SCLK values are consistent, false otherwise */ @Override - public boolean process(List samples, TimeCorrelationAppConfig config) throws MmtcException { + public boolean process(List samples, TimeCorrelationRunConfig config) throws MmtcException { if (samples.isEmpty()) { logger.warn("Attempted to filter an empty sample set"); return false; diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/TimeCorrelationFilter.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/TimeCorrelationFilter.java index 07bbda3..7c3f9b9 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/TimeCorrelationFilter.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/TimeCorrelationFilter.java @@ -1,7 +1,7 @@ package edu.jhuapl.sd.sig.mmtc.filter; import edu.jhuapl.sd.sig.mmtc.app.MmtcException; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.tlm.FrameSample; import java.util.List; @@ -12,28 +12,28 @@ public interface TimeCorrelationFilter { static TimeCorrelationFilter createFilterInstanceByName(String name) throws MmtcException { switch (name) { - case TimeCorrelationAppConfig.MIN_DATARATE_FILTER: + case TimeCorrelationRunConfig.MIN_DATARATE_FILTER: return new MinDataRateFilter(); - case TimeCorrelationAppConfig.MAX_DATARATE_FILTER: + case TimeCorrelationRunConfig.MAX_DATARATE_FILTER: return new MaxDataRateFilter(); - case TimeCorrelationAppConfig.ERT_FILTER: + case TimeCorrelationRunConfig.ERT_FILTER: return new ErtFilter(); - case TimeCorrelationAppConfig.GROUND_STATION_FILTER: + case TimeCorrelationRunConfig.GROUND_STATION_FILTER: return new GroundStationFilter(); - case TimeCorrelationAppConfig.SCLK_FILTER: + case TimeCorrelationRunConfig.SCLK_FILTER: return new SclkFilter(); - case TimeCorrelationAppConfig.VALID_FILTER: + case TimeCorrelationRunConfig.VALID_FILTER: return new ValidFilter(); - case TimeCorrelationAppConfig.CONSEC_FRAMES_FILTER: + case TimeCorrelationRunConfig.CONSEC_FRAMES_FILTER: return new ConsecutiveFrameFilter(); - case TimeCorrelationAppConfig.VCID_FILTER: + case TimeCorrelationRunConfig.VCID_FILTER: return new VcidFilter(); - case TimeCorrelationAppConfig.CONSEC_MC_FRAME_FILTER: + case TimeCorrelationRunConfig.CONSEC_MC_FRAME_FILTER: return new ConsecutiveMasterChannelFrameFilter(); default: throw new MmtcException("No such filter type: " + name); } } - boolean process(List samples, TimeCorrelationAppConfig config) throws MmtcException; + boolean process(List samples, TimeCorrelationRunConfig config) throws MmtcException; } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ValidFilter.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ValidFilter.java index 8edcf46..b106759 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ValidFilter.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/ValidFilter.java @@ -2,11 +2,11 @@ import java.util.List; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import edu.jhuapl.sd.sig.mmtc.app.MmtcException; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; import edu.jhuapl.sd.sig.mmtc.tlm.FrameSample; /** @@ -26,7 +26,7 @@ public class ValidFilter implements TimeCorrelationFilter { * @return true if all samples are flagged as valid, false otherwise */ @Override - public boolean process(List samples, TimeCorrelationAppConfig config) throws MmtcException { + public boolean process(List samples, TimeCorrelationRunConfig config) throws MmtcException { if (samples.isEmpty()) { logger.warn("Sample Validity Filter failed: attempted to filter an empty sample set."); return false; diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/VcidFilter.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/VcidFilter.java index 3592c04..d17a182 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/VcidFilter.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/filter/VcidFilter.java @@ -4,11 +4,11 @@ import java.util.stream.Collectors; import edu.jhuapl.sd.sig.mmtc.app.MmtcException; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.util.CollectionUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; import edu.jhuapl.sd.sig.mmtc.tlm.FrameSample; public class VcidFilter implements TimeCorrelationFilter { @@ -26,7 +26,7 @@ public class VcidFilter implements TimeCorrelationFilter { * @return true if all samples have valid VCIDs (and TK VCIDs) according to the given configuration; false otherwise */ @Override - public boolean process(List samples, TimeCorrelationAppConfig config) throws MmtcException { + public boolean process(List samples, TimeCorrelationRunConfig config) throws MmtcException { if (samples.isEmpty()) { logger.warn("VCID Filter failed: Attempted to filter an empty sample set"); return false; diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/OutputProductDefinition.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/OutputProductDefinition.java index 83a2da4..2ecd215 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/OutputProductDefinition.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/OutputProductDefinition.java @@ -132,4 +132,9 @@ public Set resolveAllExistingPaths(MmtcConfig config) throws MmtcException throw new IllegalStateException("Unrecognized resolved location: " + resolvedLocation); } } + + /** + * @return the display name of the instance of the output product definition; must be unique at MMTC runtime + */ + public abstract String getDisplayName(); } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/RawTlmTableProductDefinition.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/RawTlmTableProductDefinition.java index f5b36dd..6848b89 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/RawTlmTableProductDefinition.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/RawTlmTableProductDefinition.java @@ -56,4 +56,9 @@ public Map getSandboxConfigUpdates(MmtcConfig originalConfig, Pa confUpdates.put("table.rawTelemetryTable.path", newProductOutputPath.toString()); return confUpdates; } + + @Override + public String getDisplayName() { + return "Raw Telemetry Table"; + } } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/SclkKernelProductDefinition.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/SclkKernelProductDefinition.java index 5af1a3b..a394cf3 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/SclkKernelProductDefinition.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/SclkKernelProductDefinition.java @@ -16,8 +16,10 @@ * A single SCLK kernel is modeled by {@link SclkKernel}. */ public class SclkKernelProductDefinition extends EntireFileOutputProductDefinition { + public static final String PRODUCT_NAME = "SCLK Kernel"; + public SclkKernelProductDefinition() { - super("SCLK Kernel"); + super(PRODUCT_NAME); } @Override @@ -78,4 +80,13 @@ public Map getSandboxConfigUpdates(MmtcConfig originalConfig, Pa confUpdates.put("spice.kernel.sclk.kerneldir", newProductOutputDir.toAbsolutePath().toString()); return confUpdates; } + + @Override + public String getDisplayName() { + return "SCLK Kernel"; + } + + public ProductWriteResult writeToAlternatePath(TimeCorrelationContext ctx, Path sclkKernelOutputPath) throws MmtcException { + return SclkKernel.writeNewProduct(ctx, sclkKernelOutputPath); + } } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/SclkScetProductDefinition.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/SclkScetProductDefinition.java index a17b2b7..9fbcd05 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/SclkScetProductDefinition.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/SclkScetProductDefinition.java @@ -83,4 +83,9 @@ public Map getSandboxConfigUpdates(MmtcConfig originalConfig, Pa confUpdates.put("product.sclkScetFile.dir", newProductOutputDir.toAbsolutePath().toString()); return confUpdates; } + + @Override + public String getDisplayName() { + return "SCLK-SCET File"; + } } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/TimeHistoryFileProductDefinition.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/TimeHistoryFileProductDefinition.java index f5998a1..be081c7 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/TimeHistoryFileProductDefinition.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/TimeHistoryFileProductDefinition.java @@ -63,4 +63,9 @@ public Map getSandboxConfigUpdates(MmtcConfig originalConfig, Pa confUpdates.put("table.timeHistoryFile.path", newProductOutputPath.toString()); return confUpdates; } + + @Override + public String getDisplayName() { + return "Time History File"; + } } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/UplinkCommandFileProductDefinition.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/UplinkCommandFileProductDefinition.java index 3803328..7f583b3 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/UplinkCommandFileProductDefinition.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/UplinkCommandFileProductDefinition.java @@ -70,4 +70,9 @@ public Map getSandboxConfigUpdates(MmtcConfig originalConfig, Pa confUpdates.put("product.uplinkCmdFile.outputDir", newProductOutputDir.toAbsolutePath().toString()); return confUpdates; } + + @Override + public String getDisplayName() { + return "Uplink Command File"; + } } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/util/ResolvedProductDirPrefixSuffix.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/util/ResolvedProductDirPrefixSuffix.java index 7792e94..1d8e3dc 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/util/ResolvedProductDirPrefixSuffix.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/definition/util/ResolvedProductDirPrefixSuffix.java @@ -23,4 +23,11 @@ public List findAllMatching() throws IOException { .filter(p -> p.getFileName().toString().endsWith(filenameSuffix)) .collect(Collectors.toList()); } + + public Path findMatchingFilename(String filename) throws IOException { + return findAllMatching().stream() + .filter(p -> p.getFileName().toString().equals(filename)) + .findFirst() + .orElseThrow(() -> new IOException("No such product with filename: " + filename)); + } } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/RunHistoryFile.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/RunHistoryFile.java index 0d3cee9..330849e 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/RunHistoryFile.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/RunHistoryFile.java @@ -26,7 +26,7 @@ public class RunHistoryFile extends AbstractTimeCorrelationTable { public static final String MMTC_BUILT_IN_OUTPUT_PRODUCT_VERSION = "Built-In Output Product Version"; public static final String ROLLEDBACK = "Rolled Back?"; public static final String RUN_USER = "Run User"; - public static final String CLI_ARGS = "MMTC Invocation Args Used"; + public static final String INVOC_ARGS = "MMTC Invocation Args Used"; public static final String SMOOTHING_TRIPLET_TDT = "Smoothing Triplet TDT"; private final List headers; @@ -96,7 +96,7 @@ public RunHistoryFile(Path path, List> allOutputProdD MMTC_BUILT_IN_OUTPUT_PRODUCT_VERSION, ROLLEDBACK, RUN_USER, - CLI_ARGS, + INVOC_ARGS, SMOOTHING_TRIPLET_TDT )); diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/SclkKernel.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/SclkKernel.java index 7b908f3..ee8cd4f 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/SclkKernel.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/SclkKernel.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Optional; +import java.util.List; /** * The SclkKernel class creates the SCLK kernel file and its contents. The SCLK Kernel is the primary product of MMTC. @@ -97,6 +98,15 @@ public void setNewTriplet(Double encSclk, String tdtStr, Double clockChgRate) { this.newTriplet = Optional.of(new CorrelationTriplet(encSclk, tdtStr, clockChgRate)); } + /** + * Sets the values of the new time correlation record. + * + * @param newTriplet newTriplet + */ + public void setNewTriplet(CorrelationTriplet newTriplet) { + this.newTriplet = Optional.of(newTriplet); + } + public boolean hasSmoothingRecordSet() { return smoothingTriplet.isPresent(); } @@ -191,7 +201,6 @@ public void createNewProduct() throws TextProductException { * @throws TextProductException if the time correlation record is invalid or cannot be updated. */ private String replaceChgRate() throws TextProductException { - String record = sourceProductLines.get(endDataNum); String[] tripletFields = parseRecord(record, NUM_FIELDS_IN_TRIPLET); @@ -204,7 +213,6 @@ private String replaceChgRate() throws TextProductException { return record1; } - /** * Formats a clock change rate into a string form of 11 decimal places. * @@ -284,6 +292,57 @@ public String[] getPriorRec(Double fromTdt, Double lookBackHours, Collection getPriorRecs(Double fromTdt, Double minLookbackHours, Double maxLookbackHours, Collection smoothingRecordTdtStringsToIgnore) throws TextProductException { + final double minLookbackSeconds = minLookbackHours * 3600.; + final double maxLookbackSeconds = maxLookbackHours * 3600.; + + List results = new ArrayList<>(); + + try { + for (int i = endDataNum; i > 0; i--) { + String record = sourceProductLines.get(i); + + if (! isDataRecord(record)) { + continue; + } + + final String[] recTripletFields = parseRecord(record, NUM_FIELDS_IN_TRIPLET); + final String recTdtStr = recTripletFields[TRIPLET_TDTG_FIELD_INDEX].substring(1); + final double recTdtSec = TimeConvert.tdtCalStrToTdt(recTdtStr); + + if (smoothingRecordTdtStringsToIgnore.contains(recTdtStr)) { + logger.debug(String.format("getPriorRec: skipping record at TDT %s due to it being a smoothing record", recTdtStr)); + continue; + } + + final double recDeltaTdt = fromTdt - recTdtSec; + if ((recDeltaTdt < minLookbackSeconds) || recDeltaTdt > maxLookbackSeconds) { + logger.debug(String.format("getPriorRec: skipping record at TDT %s due to not meeting lookback constraints", recTdtStr)); + continue; + } + + results.add(recTripletFields); + } + } catch (TimeConvertException e) { + throw new TextProductException("Unable to convert TDT string to numeric TDT seconds.", e); + } + + if (results.isEmpty()) { + throw new TextProductException("Look back time invalid for the specified SCLK kernel."); + } + + return results; + } /** * Creates a new time correlation record from the encoded SCLK, TDT, and clock change rate triplet @@ -391,6 +450,23 @@ public String[] getLastXRecords(int numRecords) { return records; } + public List getParsedRecords() throws TextProductException { + List dataRecords = new ArrayList<>(); + + for (int i = 0; i < sourceProductLines.size(); i++) { + String sclkKernelRecord = sourceProductLines.get(i).trim(); + if (isDataRecord(sclkKernelRecord)) { + String[] parsedVals = parseRecord(sclkKernelRecord, 3); + if (parsedVals[TRIPLET_TDTG_FIELD_INDEX].startsWith("@")) { + parsedVals[TRIPLET_TDTG_FIELD_INDEX] = parsedVals[TRIPLET_TDTG_FIELD_INDEX].replaceFirst("@", ""); + } + dataRecords.add(parsedVals); + } + } + + return dataRecords; + } + @Override public void readSourceProduct() throws IOException, TextProductException { super.readSourceProduct(); @@ -420,18 +496,23 @@ public static void calculateNewProduct(TimeCorrelationContext ctx) throws TimeCo newSclkKernel.setProductCreationTime(ctx.appRunTime); newSclkKernel.setDir(ctx.config.getSclkKernelOutputDir().toString()); newSclkKernel.setName(ctx.config.getSclkKernelBasename() + ctx.config.getSclkKernelSeparator() + ctx.newSclkVersionString.get() + ".tsc"); - newSclkKernel.setNewTriplet( + + final CorrelationTriplet newPredictedTriplet = new CorrelationTriplet( ctx.correlation.target.get().getTargetSampleEncSclk(), TimeConvert.tdtToTdtCalStr(ctx.correlation.target.get().getTargetSampleTdtG()), ctx.correlation.predicted_clock_change_rate.get() ); + newSclkKernel.setNewTriplet(newPredictedTriplet); + + ctx.correlation.newPredictedTriplet.set(newPredictedTriplet); if (ctx.correlation.interpolated_clock_change_rate.isSet()) { newSclkKernel.setReplacementClockChgRate(ctx.correlation.interpolated_clock_change_rate.get()); + // ctx.correlation.updatedInterpolatedTriplet.set(newSclkKernel.getUpdatedPriorCorrelationRecord()); } - if (ctx.correlation.smoothingTriplet.isSet()) { - newSclkKernel.setSmoothingTriplet(ctx.correlation.smoothingTriplet.get()); + if (ctx.correlation.newSmoothingTriplet.isSet()) { + newSclkKernel.setSmoothingTriplet(ctx.correlation.newSmoothingTriplet.get()); } ctx.newSclkKernel.set(newSclkKernel); @@ -445,18 +526,44 @@ public static void calculateNewProduct(TimeCorrelationContext ctx) throws TimeCo * @return the ProductWriteResult describing the newly-written product */ public static ProductWriteResult writeNewProduct(TimeCorrelationContext ctx) throws MmtcException { + return writeNewProduct(ctx, null); + } + + /** + * Writes a new SCLK Kernel + * @param ctx the current time correlation context from which to pull information for the output product + * + * @throws MmtcException if the SCLK Kernel cannot be written + * @return the ProductWriteResult describing the newly-written product + */ + public static ProductWriteResult writeNewProduct(TimeCorrelationContext ctx, Path overridingPath) throws MmtcException { try { calculateNewProduct(ctx); + final Path path; + if (overridingPath == null) { + path = Paths.get(ctx.newSclkKernel.get().getPath()); + } else { + path = overridingPath; + } + // If this is a dry run, write the new kernel to the temp output path for subsequent deletion. If not, // write it to the usual location. - if(ctx.config.isDryRun()) { - ctx.newSclkKernel.get().createFile("/tmp"); - } else { - ctx.newSclkKernel.get().createFile(); + switch(ctx.config.getDryRunConfig().mode) { + case NOT_DRY_RUN: { + ctx.newSclkKernel.get().createFile(); + break; + } + case DRY_RUN_RETAIN_NO_PRODUCTS: { + ctx.newSclkKernel.get().createFile("/tmp"); + break; + } + case DRY_RUN_GENERATE_SEPARATE_SCLK_ONLY: { + ctx.newSclkKernel.get().createFile(path); + break; + } } - final Path path = Paths.get(ctx.newSclkKernel.get().getPath()); ctx.newSclkKernelPath.set(path); return new ProductWriteResult( diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/SclkScetFile.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/SclkScetFile.java index ac6ecc6..31c9654 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/SclkScetFile.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/SclkScetFile.java @@ -1,7 +1,8 @@ package edu.jhuapl.sd.sig.mmtc.products.model; import edu.jhuapl.sd.sig.mmtc.app.MmtcException; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.MmtcConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.correlation.TimeCorrelationContext; import edu.jhuapl.sd.sig.mmtc.products.definition.util.ProductWriteResult; import edu.jhuapl.sd.sig.mmtc.util.TimeConvert; @@ -61,7 +62,7 @@ public class SclkScetFile extends TextProduct { /* Specifies the SCET of the last data record in the new SCLK/SCET file. */ private OffsetDateTime endSclkScetTime; - private final TimeCorrelationAppConfig.SclkScetFileLeapSecondSclkRate leapSecondSclkRateMode; + private final MmtcConfig.SclkScetFileLeapSecondSclkRate leapSecondSclkRateMode; /** * Metadata parameters for the SCLK/SCET file header if not read-in from an existing SCLK/SCET file. @@ -88,7 +89,7 @@ public class SclkScetFile extends TextProduct { * @param filename IN the new SCLK/SCET filename * @param product_version_id IN the version of this product */ - public SclkScetFile(TimeCorrelationAppConfig config, String filename, String product_version_id) { + public SclkScetFile(TimeCorrelationRunConfig config, String filename, String product_version_id) { super(); setDir(config.getSclkScetOutputDir().toString()); setName(filename); @@ -631,7 +632,7 @@ public Path createNewSclkScetFile(String originalFilespec) throws TextProductExc } public static SclkScetFile calculateNewProduct(TimeCorrelationContext ctx) { - final TimeCorrelationAppConfig conf = ctx.config; + final TimeCorrelationRunConfig conf = ctx.config; final String newSclkScetFilename = conf.getSclkScetFileBasename() + conf.getSclkScetFileSeparator() + ctx.newSclkVersionString.get() + diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TableRecord.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TableRecord.java index ad4a92a..d4916d0 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TableRecord.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TableRecord.java @@ -43,6 +43,10 @@ public List getValues() { return new ArrayList<>(data.values()); } + public Map getColsAndVals() { + return Collections.unmodifiableMap(data); + } + /** * Get a particular value. * diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TextProduct.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TextProduct.java index 7c5d6ce..af7b180 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TextProduct.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TextProduct.java @@ -188,10 +188,13 @@ public Path createFile() throws TextProductException, TimeConvertException { * @throws TimeConvertException if an error occurred in a computation * @return the Path representing the location where the new file was written */ - public Path createFile(String tempPath) throws TextProductException, TimeConvertException { - return createFile(sourceFilespec, tempPath, filename); + public Path createFile(String path) throws TextProductException, TimeConvertException { + return createFile(sourceFilespec, path, filename); } + public Path createFile(Path path) throws TextProductException, TimeConvertException { + return createFile(sourceFilespec, path.getParent().toString(), path.getFileName().toString()); + } /** * Creates a new product file from an existing source file and writes it to the directory diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TimeHistoryFile.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TimeHistoryFile.java index cafabb7..74cd7f7 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TimeHistoryFile.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/model/TimeHistoryFile.java @@ -192,7 +192,7 @@ public static void generateNewTimeHistRec(TimeCorrelationContext ctx, TimeHistor newThfRec.setValue(TimeHistoryFile.RF_ENCODING, targetSample.getTkRfEncoding()); newThfRec.setValue(TimeHistoryFile.STATION_ID, ctx.config.getStationId(targetSample.getPathId())); newThfRec.setValue(TimeHistoryFile.DATA_RATE_BPS, targetSample.getTkDataRateBpsAsRoundedString()); - newThfRec.setValue(TimeHistoryFile.CLK_CHANGE_RATE_INTERVAL_DAYS, String.format("%.2f", ctx.config.getPredictedClkRateLookBackDays())); + newThfRec.setValue(TimeHistoryFile.CLK_CHANGE_RATE_INTERVAL_DAYS, String.format("%.2f", ctx.config.getPredictedClkRateLookBackHours() / 24.0)); newThfRec.setValue(TimeHistoryFile.SPACECRAFT_TIME_DELAY, String.valueOf(ctx.config.getSpacecraftTimeDelaySec())); newThfRec.setValue(TimeHistoryFile.BITRATE_TIME_ERROR, String.valueOf(targetSample.getDerivedTdBe())); newThfRec.setValue(TimeHistoryFile.TF_OFFSET, String.valueOf(ctx.correlation.target.get().getTargetSampleTfOffset())); diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/util/GenericCsv.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/util/GenericCsv.java index d69ddb6..090f37c 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/util/GenericCsv.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/products/util/GenericCsv.java @@ -10,10 +10,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; public class GenericCsv { @@ -46,6 +43,17 @@ public GenericCsv(Path csvPath) throws MmtcException { } } + public List getHeaders() { + return Collections.unmodifiableList(this.headers); + } + + public List> getRows() { + return this.rows + .stream() + .map(Collections::unmodifiableMap) + .collect(Collectors.toList()); + } + public boolean hasColumn(String name) { return headers.contains(name); } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/rollback/TimeCorrelationRollback.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/rollback/TimeCorrelationRollback.java index 4ad4283..9cfa694 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/rollback/TimeCorrelationRollback.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/rollback/TimeCorrelationRollback.java @@ -21,7 +21,6 @@ import java.io.FilenameFilter; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -52,17 +51,17 @@ public TimeCorrelationRollback(String... args) throws Exception { } /** - * The primary method for rolling back MMTC's output products to a previous state. All rollback processes happen before + * The entry point for rolling back MMTC's output products to a previous state. All rollback processes happen before * TimeCorrelationApp is instantiated in main() and consequently use a unique empty config constructor. * Rollback is initiated by passing "rollback" as the first arg when invoking MMTC. * @throws MmtcRollbackException if rollback is unsuccessful for any reason * (this includes products being modified between rollback being initiated and deletion confirmation being given by the user) */ - public void rollback() throws MmtcException { - new BuiltInOutputProductMigrationManager(config).assertExistingProductsDoNotRequireMigration(); + public void rollback(Optional runId) throws MmtcRollbackException, MmtcException { + new BuiltInOutputProductMigrationManager(config).assertExistingProductsDoNotRequireMigration(); try { - prepareRollback(); + prepareRollback(runId); } catch (Exception e) { throw new MmtcRollbackException("Rollback aborted; no changes were made. Please see error details and retry.", e); } @@ -80,8 +79,9 @@ public void rollback() throws MmtcException { } } - private void prepareRollback() throws MmtcException { - Scanner scanner = new Scanner(System.in); + private void prepareRollback(Optional preselectedRunId) throws MmtcException { + final boolean isInteractive = ! preselectedRunId.isPresent(); + rawRunHistoryRecords = runHistoryFile.readRecords(RunHistoryFile.RollbackEntryOption.INCLUDE_ROLLBACKS); // Will be used to rewrite complete RunHistoryFile List runHistoryRecords = runHistoryFile.readRecords(RunHistoryFile.RollbackEntryOption.IGNORE_ROLLBACKS); // Will be used for rollback and shouldn't include rolled back entries @@ -103,16 +103,24 @@ private void prepareRollback() throws MmtcException { final TableRecord latestRun = runHistoryRecords.get(runHistoryRecords.size() - 1); - System.out.println("\nPlease choose a run to become the new latest run (including leading zeros), or enter '0' to roll back to MMTC's initial state (with no output products): "); - String selectedRunId = scanner.nextLine(); + Scanner scanner = new Scanner(System.in); + final String selectedRunId; + if (isInteractive) { + System.out.println("\nPlease choose a run to become the new latest run (including leading zeros), or enter '0' to roll back to MMTC's initial state (with no output products): "); + selectedRunId = scanner.nextLine(); + } else { + selectedRunId = preselectedRunId.get(); + } logger.info(String.format("Run ID %s selected by user.", selectedRunId)); if (Integer.parseInt(selectedRunId) == 0) { - System.out.printf(String.format("Rolling back to the initial state will revert all output products from the latest run (%s, SCLK Kernel %s) " + - "to a nonexistent state, except for the seed kernel.%n", - latestRun.getValue("Run ID"), - latestRun.getValue("Latest SCLK Kernel Post-run")) - ); + if (isInteractive) { + System.out.printf(String.format("Rolling back to the initial state will revert all output products from the latest run (%s, SCLK Kernel %s) " + + "to a nonexistent state, except for the seed kernel.%n", + latestRun.getValue("Run ID"), + latestRun.getValue("Latest SCLK Kernel Post-run")) + ); + } rawRecordsSelectedRunIndex = -1; rollbackOps = RollbackOperations.toInitialState(runHistoryFile, config); @@ -151,12 +159,14 @@ private void prepareRollback() throws MmtcException { throw new MmtcRollbackException("The selected run is also the latest run and rollback would not change any output products, aborting."); } - System.out.printf("Rolling back will revert all output products from the latest run (%s, SCLK Kernel %s) " + - "to the state they were in after run %s at %s with the following effects: %n", - latestRun.getValue("Run ID"), - latestRun.getValue("Latest SCLK Kernel Post-run"), - selectedRun.getValue("Run ID"), - selectedRun.getValue("Run Time")); + if (isInteractive) { + System.out.printf("Rolling back will revert all output products from the latest run (%s, SCLK Kernel %s) " + + "to the state they were in after run %s at %s with the following effects: %n", + latestRun.getValue("Run ID"), + latestRun.getValue("Latest SCLK Kernel Post-run"), + selectedRun.getValue("Run ID"), + selectedRun.getValue("Run Time")); + } rollbackOps = RollbackOperations.fromRunHistoryFile(runHistoryFile, config, selectedRunId); @@ -167,24 +177,26 @@ private void prepareRollback() throws MmtcException { ); } - System.out.println("- The following files will be deleted: "); - for (String description : rollbackOps.getDescriptionOfFilesToDelete()) { - System.out.printf(" - %s\n", description); - } + if (isInteractive) { + System.out.println("- The following files will be deleted: "); + for (String description : rollbackOps.getDescriptionOfFilesToDelete()) { + System.out.printf(" - %s\n", description); + } - System.out.println("- The following files will be truncated: "); - for (String description : rollbackOps.getDescriptionOfFilesToTruncate()) { - System.out.printf(" - %s\n", description); - } + System.out.println("- The following files will be truncated: "); + for (String description : rollbackOps.getDescriptionOfFilesToTruncate()) { + System.out.printf(" - %s\n", description); + } - System.out.println("\033[0;1mIt is recommended to save copies of all these files for your anomaly reporting system before continuing, as these deletions cannot be undone!"); + System.out.println("\033[0;1mIt is recommended to save copies of all these files for your anomaly reporting system before continuing, as these deletions cannot be undone!"); - System.out.println("Continue? [y/N]: "); - String confirmRollback = scanner.nextLine(); - scanner.close(); + System.out.println("Continue? [y/N]: "); + String confirmRollback = scanner.nextLine(); + scanner.close(); - if (!confirmRollback.equalsIgnoreCase("y")) { - throw new MmtcRollbackException("Rollback aborted due to user input, no changes made."); + if (!confirmRollback.equalsIgnoreCase("y")) { + throw new MmtcRollbackException("Rollback aborted due to user input, no changes made."); + } } } diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/sandbox/MmtcSandboxCreator.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/sandbox/MmtcSandboxCreator.java index 319229d..9351ddb 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/sandbox/MmtcSandboxCreator.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/sandbox/MmtcSandboxCreator.java @@ -275,11 +275,7 @@ private void copyProductsToSandbox(EntireFileOutputProductDefinition def) throws } Files.createDirectories(newProductOutputDir); - List filePathsToCopy = Files.list(originalProductOutputDir) - .filter(p -> p.getFileName().toString().startsWith(resolvedDirAndPrefix.filenamePrefix)) - .filter(p -> p.getFileName().toString().endsWith(resolvedDirAndPrefix.filenameSuffix)) - .collect(Collectors.toList()); - for (Path file : filePathsToCopy) { + for (Path file : resolvedDirAndPrefix.findAllMatching()) { Files.copy( file, newProductOutputDir.resolve(file.getFileName()) diff --git a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/tlm/CachingTelemetrySource.java b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/tlm/CachingTelemetrySource.java index ceea79b..49c9009 100644 --- a/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/tlm/CachingTelemetrySource.java +++ b/mmtc-core/src/main/java/edu/jhuapl/sd/sig/mmtc/tlm/CachingTelemetrySource.java @@ -2,9 +2,9 @@ import edu.jhuapl.sd.sig.mmtc.app.MmtcException; import edu.jhuapl.sd.sig.mmtc.cfg.MmtcConfig; -import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationAppConfig; +import edu.jhuapl.sd.sig.mmtc.cfg.MmtcConfigWithTlmSource; +import edu.jhuapl.sd.sig.mmtc.cfg.TimeCorrelationRunConfig; import edu.jhuapl.sd.sig.mmtc.tlm.persistence.cache.TelemetryCache; -import org.apache.commons.cli.Option; import java.io.IOException; import java.nio.file.Path; @@ -32,12 +32,17 @@ public String getName() { } @Override - public Collection