From 15ca929eae774903c582bd08390d5ce36836887e Mon Sep 17 00:00:00 2001 From: Aaron Kearns Date: Mon, 6 Jul 2026 13:28:01 -0600 Subject: [PATCH 1/2] Initial commit to add complex conjugate pair validation --- .../iris/dmc/station/RuleEngineRegistry.java | 13 +- .../conditions/ComplexConjugateCondition.java | 122 +++++++++++ .../station/conditions/Condition417Test.java | 72 +++++++ src/test/resources/F1_417.xml | 197 ++++++++++++++++++ 4 files changed, 398 insertions(+), 6 deletions(-) create mode 100644 src/main/java/edu/iris/dmc/station/conditions/ComplexConjugateCondition.java create mode 100644 src/test/java/edu/iris/dmc/station/conditions/Condition417Test.java create mode 100644 src/test/resources/F1_417.xml diff --git a/src/main/java/edu/iris/dmc/station/RuleEngineRegistry.java b/src/main/java/edu/iris/dmc/station/RuleEngineRegistry.java index bf2f620..a5a4a52 100644 --- a/src/main/java/edu/iris/dmc/station/RuleEngineRegistry.java +++ b/src/main/java/edu/iris/dmc/station/RuleEngineRegistry.java @@ -15,6 +15,7 @@ import edu.iris.dmc.station.conditions.AzimuthDipCondition; import edu.iris.dmc.station.conditions.CalibrationUnitCondition; import edu.iris.dmc.station.conditions.CodeCondition; +import edu.iris.dmc.station.conditions.ComplexConjugateCondition; import edu.iris.dmc.station.conditions.Condition; import edu.iris.dmc.station.conditions.DecimationAnalogFilterCondition; import edu.iris.dmc.station.conditions.DecimationCondition; @@ -37,7 +38,6 @@ import edu.iris.dmc.station.conditions.OrientationConditionE; import edu.iris.dmc.station.conditions.OrientationConditionZ; import edu.iris.dmc.station.conditions.PolesZerosCondition; -import edu.iris.dmc.station.conditions.PolesZerosSequenceCondition; import edu.iris.dmc.station.conditions.PolynomialCondition; import edu.iris.dmc.station.conditions.ResponseListCondition; import edu.iris.dmc.station.conditions.SampleRateCondition; @@ -276,11 +276,12 @@ private void defaultResponseRules(Set s) { "Response must include InstrumentSensitivity if no Polynomial stages are included.", new ChannelCodeRestriction(), new ChannelTypeRestriction()), Response.class); } - //if (!s.contains(417)) { - // add(417, new PolesZerosSequenceCondition(false, - // "If Stage[N]:PolesZeros contains Zeros and Poles then Zero:Number and Pole:Number must start at 0 and be sequential.", - // new ChannelCodeRestriction(), new ChannelTypeRestriction()), Response.class); - //} + if (!s.contains(417)) { + add(417, new ComplexConjugateCondition(false, + "If Stage[N] of type PolesZeros contains a Pole or Zero with nonzero Imaginary component then Stage[N]:PolesZeros:Poles or Stage[N]:PolesZeros:Zeros must also include its complex conjugate.", + new ChannelCodeRestriction(), new ChannelTypeRestriction(), new ResponsePolynomialRestriction()), + Response.class); + } if (!s.contains(420)) { add(420, new MissingDecimationCondition(true, diff --git a/src/main/java/edu/iris/dmc/station/conditions/ComplexConjugateCondition.java b/src/main/java/edu/iris/dmc/station/conditions/ComplexConjugateCondition.java new file mode 100644 index 0000000..d10d729 --- /dev/null +++ b/src/main/java/edu/iris/dmc/station/conditions/ComplexConjugateCondition.java @@ -0,0 +1,122 @@ +package edu.iris.dmc.station.conditions; + +import edu.iris.dmc.fdsn.station.model.*; +import edu.iris.dmc.station.restrictions.Restriction; +import edu.iris.dmc.station.rules.Message; +import edu.iris.dmc.station.rules.Result; + +import java.text.DecimalFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +public class ComplexConjugateCondition extends ChannelRestrictedCondition { + + private static final Logger LOGGER = Logger.getLogger(ComplexConjugateCondition.class.getName()); + + private final DecimalFormat df = new DecimalFormat("#.#####"); + + public ComplexConjugateCondition(boolean required, String description, Restriction... restrictions) { + super(required, description, restrictions); + } + + @Override + public Message evaluate(Network network) { + throw new IllegalArgumentException("method not supported!"); + } + + @Override + public Message evaluate(Station station) { + throw new IllegalArgumentException("method not supported!"); + } + + @Override + public Message evaluate(Channel channel) { + if (channel == null) { + return Result.success(); + } + return evaluate(channel, channel.getResponse()); + } + + @Override + public Message evaluate(Channel channel, Response response) { + + if (isRestricted(channel)) { + return Result.success(); + } + if (this.required) { + if (response == null) { + return Result.error("expected response but was null"); + } + } + if (response.getStage() != null && !response.getStage().isEmpty()) { + List stages = response.getStage(); + int stage = 1; + for (ResponseStage s : stages) { + if (s.getPolesZeros() != null) { + if (s.getPolesZeros().getZero() != null) { + Map> conjugatePairMap = new ConcurrentHashMap<>(); + // first, collect potential conjugate pairs + for (PoleZero z : s.getPolesZeros().getZero()) { + double realValue = z.getReal().getValue(); + double imaginaryValue = z.getReal().getValue(); + if (imaginaryValue == 0.) { + continue; + } + if (!conjugatePairMap.containsKey(z.getReal().getValue())) { + conjugatePairMap.put(realValue, new HashSet<>()); + } + conjugatePairMap.get(realValue).add(imaginaryValue); + } + // now, check existence of conjugate pairs + for (Double key : conjugatePairMap.keySet()) { + Set pairs = conjugatePairMap.get(key); + for (Double imaginaryValue : pairs) { + Double conjugate = -1 * imaginaryValue; + if (!pairs.contains(conjugate)){ + // stage 2 zero with real term -2.31400 and nonzero imaginary term -3.17000 has no conjugate pair + return Result.error("stage " + s.getNumber() + " zero with real term " + + df.format(key) + " and nonzero imaginary term " + + df.format(imaginaryValue) + " has no conjugate pair"); + } + } + } + } + if (s.getPolesZeros().getPole() != null) { + Map> conjugatePairMap = new ConcurrentHashMap<>(); + // first, collect potential conjugate pairs + for (PoleZero p : s.getPolesZeros().getPole()) { + double realValue = p.getReal().getValue(); + double imaginaryValue = p.getReal().getValue(); + if (imaginaryValue == 0.) { + continue; + } + if (!conjugatePairMap.containsKey(p.getReal().getValue())) { + conjugatePairMap.put(realValue, new HashSet<>()); + } + conjugatePairMap.get(realValue).add(imaginaryValue); + } + // now, check existence of conjugate pairs + for (Double key : conjugatePairMap.keySet()) { + Set pairs = conjugatePairMap.get(key); + for (Double imaginaryValue : pairs) { + Double conjugate = -1 * imaginaryValue; + if (!pairs.contains(conjugate)){ + // stage 2 pole with real term -2.31400 and nonzero imaginary term -3.17000 has no conjugate pair + return Result.error("stage " + s.getNumber() + " pole with real term " + + df.format(key) + " and nonzero imaginary term " + + df.format(imaginaryValue) + " has no conjugate pair"); + } + } + } + } + } + stage++; + } + + } + + return Result.success(); + } + +} diff --git a/src/test/java/edu/iris/dmc/station/conditions/Condition417Test.java b/src/test/java/edu/iris/dmc/station/conditions/Condition417Test.java new file mode 100644 index 0000000..6b01c03 --- /dev/null +++ b/src/test/java/edu/iris/dmc/station/conditions/Condition417Test.java @@ -0,0 +1,72 @@ +package edu.iris.dmc.station.conditions; + +import edu.iris.dmc.DocumentMarshaller; +import edu.iris.dmc.fdsn.station.model.Channel; +import edu.iris.dmc.fdsn.station.model.FDSNStationXML; +import edu.iris.dmc.fdsn.station.model.Network; +import edu.iris.dmc.fdsn.station.model.Response; +import edu.iris.dmc.station.RuleEngineServiceTest; +import edu.iris.dmc.station.restrictions.ChannelCodeRestriction; +import edu.iris.dmc.station.restrictions.ChannelTypeRestriction; +import edu.iris.dmc.station.restrictions.Restriction; +import edu.iris.dmc.station.rules.Message; +import edu.iris.dmc.station.rules.Success; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; + +public class Condition417Test { + + private FDSNStationXML theDocument; + + @Before + public void init() throws Exception { + + } + + @Test + public void fail() throws Exception { + // dummy file with zero value with unmatched imaginary value + try (InputStream is = RuleEngineServiceTest.class.getClassLoader().getResourceAsStream("F1_417.xml")) { + theDocument = DocumentMarshaller.unmarshal(is); + + Network iu = theDocument.getNetwork().get(0); + Channel bhz00 = iu.getStations().get(0).getChannels().get(0); + + Restriction[] restrictions = new Restriction[] { new ChannelCodeRestriction(), + new ChannelTypeRestriction() }; + + PolesZerosCondition condition = new PolesZerosCondition(true, "", restrictions); + + Response response = bhz00.getResponse(); + Message result = condition.evaluate(bhz00, response); + Assert.assertTrue(result instanceof edu.iris.dmc.station.rules.Error); + }catch(IOException e){ + e.printStackTrace(); + } + + } + + @Test + public void pass() throws Exception { + // this file includes complex conjugate values and should pass examination + try (InputStream is = RuleEngineServiceTest.class.getClassLoader().getResourceAsStream("AUMCQBHZ_stageNOSEQUENCE.xml")) { + theDocument = DocumentMarshaller.unmarshal(is); + + Network iu = theDocument.getNetwork().get(0); + Channel bhz00 = iu.getStations().get(0).getChannels().get(0); + + Restriction[] restrictions = new Restriction[] { new ChannelCodeRestriction(), + new ChannelTypeRestriction() }; + + PolesZerosCondition condition = new PolesZerosCondition(true, "", restrictions); + Response response = bhz00.getResponse(); + Message result = condition.evaluate(bhz00, response); + Assert.assertTrue(result instanceof Success); + } + + } +} diff --git a/src/test/resources/F1_417.xml b/src/test/resources/F1_417.xml new file mode 100644 index 0000000..63bc812 --- /dev/null +++ b/src/test/resources/F1_417.xml @@ -0,0 +1,197 @@ + + + + IRIS-DMC + IRIS-DMC + IRIS WEB SERVICE: fdsnws-station | version: 1.1.33 + http://service.iris.edu/fdsnws/station/1/query?net=XX&sta=VPASS&cha=BDF&starttime=2018-08-06T00:00:01&level=response&format=xml&includecomments=true&nodata=404 + 2018-08-06T20:17:16 + + [IRIS DMC] Station XML Validator Passing Test File + 1 + 1 + + 47.66157 + -122.31332 + 225.879999 + + Synthetic Test File, IRIS DMC, USA, 218 2018, Tim Ronan + + 2018-08-06T00:00:00 + 1 + 1 + + 47.66157 + -122.31332 + 225.879999 + 0 + 360 + 0 + CONTINUOUS + GEOPHYSICAL + 2E01 + 0E00 + + V + Volts + + + Response Developed for testing the station XML validator. + + + + 10.0 + 1.0 + + PA + No Abbreviation Referenced + + + COUNTS + Digital Counts + + + + + + PA + No Abbreviation Referenced + + + V + Volts + + LAPLACE (RADIANS/SECOND) + 314.1 + 1.00000 + + 0.00000 + 0.00000 + + + 0.00000 + 0.00000 + + + -0.170000 + 0.00000 + + + 0.00000 + 0.00000 + + + -314.000 + 0.00000 + + + -0.188000 + 0.00000 + + + -0.0440000 + -1.00000 + + + + 5.0 + 1.0 + + + + + 2.0 + 1.0 + + + + + + V + Volts + + + COUNTS + Digital Counts + + DIGITAL + 1.00000 + + + 80.0 + 2 + 0 + 0.0 + 0.0 + + + 1.0 + 1.0 + + + + + + COUNTS + Digital Counts + + + COUNTS + Digital Counts + + DIGITAL + 0.0312500 + 0.156250 + 0.312500 + 0.312500 + 0.156250 + 0.0312500 + + + 40.0 + 2 + 0 + 3.9063E-5 + 3.9063E-5 + + + 1.0 + 1.0 + + + + + + COUNTS + Digital Counts + + + COUNTS + Digital Counts + + DIGITAL + 0.0156250 + 0.0937500 + 0.234375 + 0.312500 + 0.234375 + 0.0937500 + 0.0156250 + + + 20.0 + 1 + 0 + 9.375E-5 + 9.375E-5 + + + 1.0 + 1.0 + + + + + + + From b5c05f4efe8b2948962097cca8bebf3b17379c0c Mon Sep 17 00:00:00 2001 From: Aaron Kearns Date: Wed, 22 Jul 2026 16:11:46 -0600 Subject: [PATCH 2/2] Fix dependency conflicts, fix broken tests --- pom.xml | 12 ++++++++++++ .../conditions/ComplexConjugateCondition.java | 4 ++-- .../dmc/station/conditions/Condition417Test.java | 5 +++-- src/test/resources/F1_417.xml | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index e10b31d..5666f32 100644 --- a/pom.xml +++ b/pom.xml @@ -197,6 +197,18 @@ edu.iris.dmc stationxml-seed-converter 2.1.3 + + + edu.iris.dmc + java-4-seed + + + + + edu.iris.dmc + java-4-seed + 1.1.0 + compile org.apache.commons diff --git a/src/main/java/edu/iris/dmc/station/conditions/ComplexConjugateCondition.java b/src/main/java/edu/iris/dmc/station/conditions/ComplexConjugateCondition.java index d10d729..13def9e 100644 --- a/src/main/java/edu/iris/dmc/station/conditions/ComplexConjugateCondition.java +++ b/src/main/java/edu/iris/dmc/station/conditions/ComplexConjugateCondition.java @@ -59,7 +59,7 @@ public Message evaluate(Channel channel, Response response) { // first, collect potential conjugate pairs for (PoleZero z : s.getPolesZeros().getZero()) { double realValue = z.getReal().getValue(); - double imaginaryValue = z.getReal().getValue(); + double imaginaryValue = z.getImaginary().getValue(); if (imaginaryValue == 0.) { continue; } @@ -87,7 +87,7 @@ public Message evaluate(Channel channel, Response response) { // first, collect potential conjugate pairs for (PoleZero p : s.getPolesZeros().getPole()) { double realValue = p.getReal().getValue(); - double imaginaryValue = p.getReal().getValue(); + double imaginaryValue = p.getImaginary().getValue(); if (imaginaryValue == 0.) { continue; } diff --git a/src/test/java/edu/iris/dmc/station/conditions/Condition417Test.java b/src/test/java/edu/iris/dmc/station/conditions/Condition417Test.java index 6b01c03..b8215e7 100644 --- a/src/test/java/edu/iris/dmc/station/conditions/Condition417Test.java +++ b/src/test/java/edu/iris/dmc/station/conditions/Condition417Test.java @@ -39,7 +39,7 @@ public void fail() throws Exception { Restriction[] restrictions = new Restriction[] { new ChannelCodeRestriction(), new ChannelTypeRestriction() }; - PolesZerosCondition condition = new PolesZerosCondition(true, "", restrictions); + ComplexConjugateCondition condition = new ComplexConjugateCondition(true, "", restrictions); Response response = bhz00.getResponse(); Message result = condition.evaluate(bhz00, response); @@ -62,9 +62,10 @@ public void pass() throws Exception { Restriction[] restrictions = new Restriction[] { new ChannelCodeRestriction(), new ChannelTypeRestriction() }; - PolesZerosCondition condition = new PolesZerosCondition(true, "", restrictions); + ComplexConjugateCondition condition = new ComplexConjugateCondition(true, "", restrictions); Response response = bhz00.getResponse(); Message result = condition.evaluate(bhz00, response); + System.out.println(result); Assert.assertTrue(result instanceof Success); } diff --git a/src/test/resources/F1_417.xml b/src/test/resources/F1_417.xml index 63bc812..882c15b 100644 --- a/src/test/resources/F1_417.xml +++ b/src/test/resources/F1_417.xml @@ -25,7 +25,7 @@ -122.31332 225.879999 0 - 360 + 359 0 CONTINUOUS GEOPHYSICAL