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/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..13def9e
--- /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.getImaginary().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.getImaginary().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..b8215e7
--- /dev/null
+++ b/src/test/java/edu/iris/dmc/station/conditions/Condition417Test.java
@@ -0,0 +1,73 @@
+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() };
+
+ ComplexConjugateCondition condition = new ComplexConjugateCondition(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() };
+
+ 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
new file mode 100644
index 0000000..882c15b
--- /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
+ 359
+ 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
+
+
+
+
+
+
+