motor selection enhancements
authorplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Fri, 30 Jul 2010 21:06:51 +0000 (21:06 +0000)
committerplaa <plaa@180e2498-e6e9-4542-8430-84ac67f01cd8>
Fri, 30 Jul 2010 21:06:51 +0000 (21:06 +0000)
git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/trunk@62 180e2498-e6e9-4542-8430-84ac67f01cd8

14 files changed:
ChangeLog
doc/properties.txt
src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java
src/net/sf/openrocket/file/openrocket/OpenRocketSaver.java
src/net/sf/openrocket/file/openrocket/PreferredMotorDigests.java [new file with mode: 0644]
src/net/sf/openrocket/file/openrocket/savers/RocketComponentSaver.java
src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorComparator.java [new file with mode: 0644]
src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorPlotDialog.java
src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorSelectionPanel.java
src/net/sf/openrocket/motor/ThrustCurveMotor.java
src/net/sf/openrocket/startup/Application.java
src/net/sf/openrocket/util/Prefs.java
src/net/sf/openrocket/utils/MotorCorrelation.java [new file with mode: 0644]
src/net/sf/openrocket/utils/MotorDigester.java [new file with mode: 0644]

index f958fe54bfdaddc68175bee9b828901c13c4313c..5223bc3879d96f5b097b18aca3ab9db7bad77977 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2010-07-30  Sampo Niskanen
+
+       * [BUG] Fixed motor statistic computation
+       * Finalized enhanced motor selection dialog
+
+2010-07-22  Doug Pedrick
+
+       * [BUG] Fixed RockSim fin loading
+
 2010-07-21  Sampo Niskanen
 
        * Implemented enhanced motor selection dialog
index cf2982564494104da43e2021e724243ece9d3385..ed0690416a760e28b6be8575e62a9a5d7ae05d09 100644 (file)
@@ -37,6 +37,9 @@ openrocket.debug.bugurl
 openrocket.debug.updateurl
        URL used for retrieving update notifications.
 
+openrocket.debug.motordigest
+       If defined the motor digest will be displayed in the selection dialog.
+
 openrocket.debug.coordinatecount
        If defined, the number of instantiations of the Coordinate class are counted and reported
        every 1M instantiations, or as often as defined by this parameter.
index db0ca84e5236812c2a2bd369bf0b45f6f49e1955..47e76a4f636e00ad9de4d35036d7d4692b6d7579 100644 (file)
@@ -21,8 +21,10 @@ import net.sf.openrocket.file.RocketLoader;
 import net.sf.openrocket.file.simplesax.ElementHandler;
 import net.sf.openrocket.file.simplesax.PlainTextHandler;
 import net.sf.openrocket.file.simplesax.SimpleSAX;
+import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.MotorDigest;
 import net.sf.openrocket.motor.ThrustCurveMotor;
 import net.sf.openrocket.rocketcomponent.BodyComponent;
 import net.sf.openrocket.rocketcomponent.BodyTube;
@@ -89,12 +91,15 @@ import org.xml.sax.SAXException;
  * 
  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
  */
-
 public class OpenRocketLoader extends RocketLoader {
+       private static final LogHelper log = Application.getLogger();
+       
        
        @Override
        public OpenRocketDocument loadFromStream(InputStream source) throws RocketLoadException,
                        IOException {
+               log.info("Loading .ork file");
+               
                InputSource xmlSource = new InputSource(source);
                OpenRocketHandler handler = new OpenRocketHandler();
                
@@ -102,6 +107,7 @@ public class OpenRocketLoader extends RocketLoader {
                try {
                        SimpleSAX.readXML(xmlSource, handler, warnings);
                } catch (SAXException e) {
+                       log.warn("Malformed XML in input");
                        throw new RocketLoadException("Malformed XML in input.", e);
                }
                
@@ -141,6 +147,7 @@ public class OpenRocketLoader extends RocketLoader {
                doc.getDefaultStorageOptions().setExplicitlySet(false);
                
                doc.clearUndo();
+               log.info("Loading done");
                return doc;
        }
        
@@ -151,7 +158,7 @@ public class OpenRocketLoader extends RocketLoader {
 class DocumentConfig {
        
        /* Remember to update OpenRocketSaver as well! */
-       public static final String[] SUPPORTED_VERSIONS = { "0.9", "1.0", "1.1" };
+       public static final String[] SUPPORTED_VERSIONS = { "0.9", "1.0", "1.1", "1.2" };
        
 
        ////////  Component constructors
@@ -971,6 +978,7 @@ class MotorHandler extends ElementHandler {
        private Motor.Type type = null;
        private String manufacturer = null;
        private String designation = null;
+       private String digest = null;
        private double diameter = Double.NaN;
        private double length = Double.NaN;
        private double delay = Double.NaN;
@@ -990,25 +998,68 @@ class MotorHandler extends ElementHandler {
                        warnings.add(Warning.fromString("No motor specified, ignoring."));
                        return null;
                }
+               
                List<ThrustCurveMotor> motors = Application.getMotorSetDatabase().findMotors(type, manufacturer,
                                designation, diameter, length);
+               
+               // No motors
                if (motors.size() == 0) {
                        String str = "No motor with designation '" + designation + "'";
                        if (manufacturer != null)
                                str += " for manufacturer '" + manufacturer + "'";
-                       warnings.add(Warning.fromString(str + " found."));
+                       str += " found.";
+                       warnings.add(str);
                        return null;
                }
-               if (motors.size() > 1) {
+               
+               // One motor
+               if (motors.size() == 1) {
+                       ThrustCurveMotor m = motors.get(0);
+                       if (digest != null && !MotorDigest.digestMotor(m).equals(digest)) {
+                               String str = "Motor with designation '" + designation + "'";
+                               if (manufacturer != null)
+                                       str += " for manufacturer '" + manufacturer + "'";
+                               str += " has differing thrust curve than the original.";
+                               warnings.add(str);
+                       }
+                       return m;
+               }
+               
+               // Multiple motors, check digest for which one to use
+               if (digest != null) {
+                       
+                       // Check for motor with correct digest
+                       for (ThrustCurveMotor m : motors) {
+                               if (MotorDigest.digestMotor(m).equals(digest)) {
+                                       return m;
+                               }
+                       }
+                       String str = "Motor with designation '" + designation + "'";
+                       if (manufacturer != null)
+                               str += " for manufacturer '" + manufacturer + "'";
+                       str += " has differing thrust curve than the original.";
+                       warnings.add(str);
+                       
+               } else {
+                       
+                       // No digest, check for preferred digest (OpenRocket <= 1.1.0)
+                       // TODO: MEDIUM: This should only be done for document versions 1.1 and below
+                       for (ThrustCurveMotor m : motors) {
+                               if (PreferredMotorDigests.DIGESTS.contains(MotorDigest.digestMotor(m))) {
+                                       return m;
+                               }
+                       }
+                       
                        String str = "Multiple motors with designation '" + designation + "'";
                        if (manufacturer != null)
                                str += " for manufacturer '" + manufacturer + "'";
-                       warnings.add(Warning.fromString(str + " found, one chosen arbitrarily."));
+                       str += " found, one chosen arbitrarily.";
+                       warnings.add(str);
+                       
                }
                return motors.get(0);
        }
        
-       
        /**
         * Return the delay to use for the motor.
         */
@@ -1032,7 +1083,7 @@ class MotorHandler extends ElementHandler {
                        // Motor type
                        type = null;
                        for (Motor.Type t : Motor.Type.values()) {
-                               if (t.name().toLowerCase().equals(content)) {
+                               if (t.name().toLowerCase().equals(content.trim())) {
                                        type = t;
                                        break;
                                }
@@ -1044,19 +1095,24 @@ class MotorHandler extends ElementHandler {
                } else if (element.equals("manufacturer")) {
                        
                        // Manufacturer
-                       manufacturer = content;
+                       manufacturer = content.trim();
                        
                } else if (element.equals("designation")) {
                        
                        // Designation
-                       designation = content;
+                       designation = content.trim();
+                       
+               } else if (element.equals("digest")) {
+                       
+                       // Digest
+                       digest = content.trim();
                        
                } else if (element.equals("diameter")) {
                        
                        // Diameter
                        diameter = Double.NaN;
                        try {
-                               diameter = Double.parseDouble(content);
+                               diameter = Double.parseDouble(content.trim());
                        } catch (NumberFormatException e) {
                                // Ignore
                        }
@@ -1069,7 +1125,7 @@ class MotorHandler extends ElementHandler {
                        // Length
                        length = Double.NaN;
                        try {
-                               length = Double.parseDouble(content);
+                               length = Double.parseDouble(content.trim());
                        } catch (NumberFormatException ignore) {
                        }
                        
@@ -1085,7 +1141,7 @@ class MotorHandler extends ElementHandler {
                                delay = Motor.PLUGGED;
                        } else {
                                try {
-                                       delay = Double.parseDouble(content);
+                                       delay = Double.parseDouble(content.trim());
                                } catch (NumberFormatException ignore) {
                                }
                                
index 294fff7f96f6c5b4910625c01bc666c1ecfc9953..7cadcf4bcf2a8043b9f6c30fb7eaab31f895d36c 100644 (file)
@@ -15,7 +15,9 @@ import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.document.StorageOptions;
 import net.sf.openrocket.file.RocketSaver;
+import net.sf.openrocket.logging.LogHelper;
 import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.Rocket;
 import net.sf.openrocket.rocketcomponent.RocketComponent;
 import net.sf.openrocket.rocketcomponent.TubeCoupler;
@@ -24,6 +26,7 @@ import net.sf.openrocket.simulation.FlightDataBranch;
 import net.sf.openrocket.simulation.FlightDataType;
 import net.sf.openrocket.simulation.FlightEvent;
 import net.sf.openrocket.simulation.GUISimulationConditions;
+import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.MathUtil;
 import net.sf.openrocket.util.Prefs;
@@ -31,7 +34,9 @@ import net.sf.openrocket.util.Reflection;
 import net.sf.openrocket.util.TextUtil;
 
 public class OpenRocketSaver extends RocketSaver {
+       private static final LogHelper log = Application.getLogger();
        
+
        /**
         * Divisor used in converting an integer version to the point-represented version.
         * The integer version divided by this value is the major version and the remainder is
@@ -63,21 +68,25 @@ public class OpenRocketSaver extends RocketSaver {
        public void save(OutputStream output, OpenRocketDocument document, StorageOptions options)
                        throws IOException {
                
+               log.info("Saving .ork file");
+               
                if (options.isCompressionEnabled()) {
+                       log.debug("Enabling compression");
                        output = new GZIPOutputStream(output);
                }
                
                dest = new BufferedWriter(new OutputStreamWriter(output, OPENROCKET_CHARSET));
                
+               // Select file version number
                final int fileVersion = calculateNecessaryFileVersion(document, options);
                final String fileVersionString =
                                (fileVersion / FILE_VERSION_DIVISOR) + "." + (fileVersion % FILE_VERSION_DIVISOR);
+               log.debug("Storing file version " + fileVersionString);
                
 
                this.indent = 0;
                
-               System.out.println("Writing...");
-               
+
                writeln("<?xml version='1.0' encoding='utf-8'?>");
                writeln("<openrocket version=\"" + fileVersionString + "\" creator=\"OpenRocket "
                                + Prefs.getVersion() + "\">");
@@ -104,6 +113,7 @@ public class OpenRocketSaver extends RocketSaver {
                indent--;
                writeln("</openrocket>");
                
+               log.debug("Writing complete, flushing buffers");
                dest.flush();
                if (options.isCompressionEnabled()) {
                        ((GZIPOutputStream) output).finish();
@@ -173,6 +183,9 @@ public class OpenRocketSaver extends RocketSaver {
         */
        private int calculateNecessaryFileVersion(OpenRocketDocument document, StorageOptions opts) {
                /*
+                * File version 1.2 is required for:
+                *  - saving motor data
+                * 
                 * File version 1.1 is required for:
                 *  - fin tabs
                 *  - components attached to tube coupler
@@ -180,8 +193,23 @@ public class OpenRocketSaver extends RocketSaver {
                 * Otherwise use version 1.0.
                 */
 
-               // Check for fin tabs (version 1.1)
+               // Check for motor definitions (version 1.2)
                Iterator<RocketComponent> iterator = document.getRocket().deepIterator();
+               while (iterator.hasNext()) {
+                       RocketComponent c = iterator.next();
+                       if (!(c instanceof MotorMount))
+                               continue;
+                       
+                       MotorMount mount = (MotorMount) c;
+                       for (String id : document.getRocket().getMotorConfigurationIDs()) {
+                               if (mount.getMotor(id) != null) {
+                                       return FILE_VERSION_DIVISOR + 2;
+                               }
+                       }
+               }
+               
+               // Check for fin tabs (version 1.1)
+               iterator = document.getRocket().deepIterator();
                while (iterator.hasNext()) {
                        RocketComponent c = iterator.next();
                        
@@ -211,6 +239,8 @@ public class OpenRocketSaver extends RocketSaver {
        @SuppressWarnings("unchecked")
        private void saveComponent(RocketComponent component) throws IOException {
                
+               log.debug("Saving component " + component.getComponentName());
+               
                Reflection.Method m = Reflection.findMethod(METHOD_PACKAGE, component, METHOD_SUFFIX,
                                "getElements", RocketComponent.class);
                if (m == null) {
@@ -502,22 +532,8 @@ public class OpenRocketSaver extends RocketSaver {
        }
        
        
-       public static void main(String[] arg) {
-               double d = -0.000000123456789123;
-               
 
-               for (int i = 0; i < 20; i++) {
-                       String str = TextUtil.doubleToString(d);
-                       System.out.println(str + "   ->   " + Double.parseDouble(str));
-                       d *= 10;
-               }
-               
 
-               System.out.println("Value: " + Double.parseDouble("1.2345e9"));
-               
-       }
-       
-       
        /**
         * Return the XML equivalent of an enum name.
         * 
diff --git a/src/net/sf/openrocket/file/openrocket/PreferredMotorDigests.java b/src/net/sf/openrocket/file/openrocket/PreferredMotorDigests.java
new file mode 100644 (file)
index 0000000..d05cccb
--- /dev/null
@@ -0,0 +1,885 @@
+package net.sf.openrocket.file.openrocket;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class contains the motor digests of motors included in OpenRocket versions prior to 1.1.1.
+ * Before this the motor digest was not included in the design files, and therefore if the motor
+ * digest is missing when loading a file, the loader should prefer the motors with a digest defined
+ * in this class.
+ * <p>
+ * This is not a requirement for supporting the OpenRocket format, but allows opening older OpenRocket
+ * design files accurately without warnings of multiple motors. 
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+final class PreferredMotorDigests {
+       
+       /**
+        * A set containing the preferred motor digests.
+        */
+       public static final Set<String> DIGESTS;
+       static {
+               /*
+                * The list contains 845 digests, set initial parameters suitably to
+                * prevent any rehashing operations and to minimize size (power of two).
+                *    845/1024 = 0.825
+                */
+               Set<String> set = new HashSet<String>(1024, 0.85f);
+               
+               set.add("000ffb4c8e49ae47b2ab9a659da9e59b");
+               set.add("0039ed088e61360d934d9bd8503fad92");
+               set.add("003eeba358de7ebf9293b0e4c4ca9e66");
+               set.add("00e1a0576a93101d458c1c3d68d3eee0");
+               set.add("0111b89926277a6ea3f6075052343105");
+               set.add("0142c270a670ffff41c43268b0f129b9");
+               set.add("01be1f9100e05fb15df4c13395f7181c");
+               set.add("026f5924c48693077f2b11cdcdeb7452");
+               set.add("029082f7acda395568ca7f7df40764e1");
+               set.add("02dd1b3e2df7daf48b763f5ace35345e");
+               set.add("036e124dce42859ff08efa79e1f202e8");
+               set.add("03b88e64af521b03803247922801c996");
+               set.add("0468d7dc3dca25ac073dac1bd674e271");
+               set.add("048cfb7c2477c6e957d501c5ed3bc252");
+               set.add("049dda2ad1a709321734f393dc8a115b");
+               set.add("056d61b6a268283411e9dc9731dbb5e6");
+               set.add("05b85612f288726b02cdc47af7026aac");
+               set.add("05e205dc5dbd95db25305aa5c77b1192");
+               set.add("0601c6944d02e8736c09c2a8bb7cba49");
+               set.add("0622884d0a0954b1df6694ead24868bf");
+               set.add("063e7748d9a96508a70b1a2a1887aa3d");
+               set.add("06634321a8c5d533eb5efcbb40143257");
+               set.add("069a54372ed2776286160384ca0cac4f");
+               set.add("075539867b13c2afcc5198e00d7f4b5c");
+               set.add("076d9374af5fb0f2469083f9b57b7b96");
+               set.add("07c44b615a67060bca83c6faed56c0c6");
+               set.add("0825628215a980eed5fb4bed4eaec3b8");
+               set.add("082bad018f6d1e5622c371c1fe3148d6");
+               set.add("0837c3014078c8c8e79961b939be83cb");
+               set.add("08abceec22c5f6be5e9864be38df8ad5");
+               set.add("08c3b40a4bcf7a33256e5543e484f995");
+               set.add("08ca5be1a598772a8683016db619de36");
+               set.add("0a80cecafb53ae0ac73e6aec1a4567dd");
+               set.add("0add7ca688bcd32df8c3367a094e7847");
+               set.add("0b175b4beb0057db1b169d61061208a7");
+               set.add("0b955870dc2007c2b5f07eea57609420");
+               set.add("0c60a51d36ee5118fe29173aff2f6e49");
+               set.add("0c96cd95432d8e2ce6a6463ebf50beb1");
+               set.add("0d06d195c29a7f6fde6d002171922f2e");
+               set.add("0d642d7cb1544d19ec471124db97b92e");
+               set.add("0dd49968e2b1c4b1077e3c7ade056a79");
+               set.add("0e0d93ee28216440a5fa9452c9082351");
+               set.add("0e6774068b61579e20b89771b8a8f273");
+               set.add("0eac15679d3ae2fbd41083492b356b03");
+               set.add("0eca4c015dd1de561c2bbc47eaa4daf6");
+               set.add("0f0e1d09c7dec3a05b870b399ddbf6ee");
+               set.add("0f3c31b26b5768b3202f02f9d6bcc71c");
+               set.add("0f47293601d59fbad2076012090665dc");
+               set.add("0f5a1b31c333b722e4a72acbeba3a189");
+               set.add("0f6a55aca8a317f4d3d3236e4944343d");
+               set.add("0ffaa291ee52495d7dfec03b3a845636");
+               set.add("1092f5c5b48c2dcd4ac0bb16fa5383a8");
+               set.add("10a1689703dc533d435bef7265dd9ac0");
+               set.add("11bcc433b82e714f59809d76eed0ba05");
+               set.add("11ce2ec12b216f3e8d71fd9c53782b23");
+               set.add("11d11cdff93f18d10a1286cc3485c8c7");
+               set.add("11eac86852e12b9c3a2d0a4b183c3b79");
+               set.add("120eab6dd03b9bee7f5eb717e4e9d491");
+               set.add("1272d1a6979ea20a2efee3eb04657915");
+               set.add("12f6c5360c83656356c902ece3c0ff1b");
+               set.add("138a603a483dcf4127f1dcf208843e67");
+               set.add("140276d009fde1357ecdcb5d9ddc8a80");
+               set.add("1491fae1c7ce940915dd8296c74320f3");
+               set.add("14955ccec83043f3b1ef92f8524b3e67");
+               set.add("150b1279bc8b7f509a030274ee8e3f35");
+               set.add("153374d45687af1e96d5b8b1b03a2515");
+               set.add("1536a1389a9cd4ecf5bfaac9f4333852");
+               set.add("1539231d9952fdbe0533df405c46356c");
+               set.add("15d6a88bb656579740291df01297aa5c");
+               set.add("15fbf68a7c02161beb6cad00325752c3");
+               set.add("161cd37f60e13b9850e881bac61c840f");
+               set.add("161ed36663b694184f7f4131d1d5f3df");
+               set.add("167df7bf13809a19da8ff90a27f4b522");
+               set.add("170e81af0371550ea20c827669fbf0fd");
+               set.add("177c0df08cb85a4e13bf7412dacf2699");
+               set.add("179b9694bca64255ce9c0b06a08f46e6");
+               set.add("17d55e2cd3df50ef07aff9be6b160915");
+               set.add("1835337dfceafa20029fe6e472e7c7f0");
+               set.add("185820cacfb62e34c1f6c2e1feb42d27");
+               set.add("18981fde366efeca850bdf490253f0ec");
+               set.add("18b7f1dce04bc7838f3d2b234923de27");
+               set.add("18c2d213b8de15fc11ef66f7a7ad04a4");
+               set.add("1914ab609416b8559eeefda814867b9b");
+               set.add("19ae231357c49b6bf9427fa178dc58ab");
+               set.add("19b0b447800ba36f2d4ce76264009e2d");
+               set.add("19c9120e2fb7bbf6d21d71659f77439c");
+               set.add("19c9753bd99d7f0328792a434625f8a5");
+               set.add("1a508ce5b461be1998750bdad26764a3");
+               set.add("1a77681a4646cd21461df84c49074fe3");
+               set.add("1aa169a73004fc66a932576ac2732b15");
+               set.add("1aa1f3cc21a0f6a6eadb6166d468284c");
+               set.add("1ac8dac1b547a064a306bf42e568b5bc");
+               set.add("1af11d2e99f06b69ab5103731592ae8e");
+               set.add("1af30f73640ac1f9f3c8ef32fd04bfb8");
+               set.add("1b337a115a491abfc3abcd62399704d2");
+               set.add("1bb9c002f22ccd24bfcec36957ac0367");
+               set.add("1cbb12c9b58adc33642e1165b77c2e58");
+               set.add("1d30457aa2af0f212a26b9d2c203a216");
+               set.add("1d390d2ede88fb2f77ad7e7432155466");
+               set.add("1d920d4ee2bef0c7ffb28a91b9e325f6");
+               set.add("1e09cd01462e6d4728efafc4a550a5a6");
+               set.add("1e26c7969adb5bfc507da22802f17053");
+               set.add("1e5378337317821ffa4f53e9ecf47fbd");
+               set.add("1e68b1ce7eb224dc65b39546d0892299");
+               set.add("1e757827e2e03a781905e9f983c89933");
+               set.add("1f2564b3f0d78751b4e1d5836d54f7b1");
+               set.add("210bd4536d1c1872d213995420cf9513");
+               set.add("21bdc48d9411ffc8e811e32c45640f58");
+               set.add("21d4e53c3308cf3a1e916ef6cafff873");
+               set.add("21db7fea27e33cbab6fa2984017c241c");
+               set.add("221ab691a72a6f8b65792233b7bdf884");
+               set.add("222b7613b7a9a85d45051fb263b511bf");
+               set.add("224c062a8213f22058c0479c91ce470a");
+               set.add("22777fde378d9610258e4223fb5563f5");
+               set.add("22929b4849129644531a722397786513");
+               set.add("22c31705c3948c39721ced4ca04b2e65");
+               set.add("22e355a9e573b7f6f86c7e0791647ba7");
+               set.add("2320f4b15fb78448ce16a5a625f6f8f2");
+               set.add("234467bcf00a15e7377ceca46b7302f8");
+               set.add("23e140b2126af53487781f63082615e5");
+               set.add("245d147c568faf00dfb47d9c9080871c");
+               set.add("24a5102957c91107a092704f4f166e77");
+               set.add("24b7b0f55cea9329f981f00d922cfe61");
+               set.add("24d9308fa5d88f89365760a6e54f557f");
+               set.add("24fe3f1922a6d16b21f57b9925558296");
+               set.add("2571d40a353d275cdd8a4ba6e80a32fd");
+               set.add("259a0325a52acf54184fd439d1b2521d");
+               set.add("259d90773b3387c58aecb648c2c3812e");
+               set.add("25fd0f44fbbadfb70cee0467f9b53d3e");
+               set.add("26331fa38f2af84b18df5dd1db0244f0");
+               set.add("26a5e7834018943090396d419ca64662");
+               set.add("271f29d0b199d0d3f036e8f99ce76975");
+               set.add("2762f40ffacbc78b4c949cd38101a02a");
+               set.add("2769033a0acfff04e1f427027643c03a");
+               set.add("27b1601bb3a33c7cd2745caa651f0705");
+               set.add("27e522bd25f54343584ae89e90e64ee3");
+               set.add("2815e68ed1683663820682c8e00fd795");
+               set.add("285e598a676de414661a022f72967d29");
+               set.add("2886ee93f5dd4f02b331089928520e4f");
+               set.add("28f53f74ab45da2ab83072143f0d01d0");
+               set.add("2967cd7a160b396ef96f09695429d8e9");
+               set.add("29e99fbfab8c9771f4b5a86195db0c46");
+               set.add("2a1f5f5a829badfd64e2c20cd17bd38b");
+               set.add("2a941643d418880e0e7337aaaa00c555");
+               set.add("2a9d2a64b4601046774c9d27202de593");
+               set.add("2ad8de03de84415f1397cb2d4c77fb84");
+               set.add("2af7bcae566ada617d8888f34a5f70a3");
+               set.add("2bb2cea5465ab43f9b7e83cb44851223");
+               set.add("2bc22736450a8d0efb8d898bdcf52d43");
+               set.add("2c19c0cd4c005877798821dd65a2ff2e");
+               set.add("2c39985a5a49fa07759dc880e3722203");
+               set.add("2c58d5382b8d5fdbe1800e013f200f38");
+               set.add("2c8f6954ba9842ad9fc9bb367d14cf72");
+               set.add("2d13c151bbf6e1d7d7378c86d191d2d8");
+               set.add("2df4ee3f8a2c3611b267936e47bd3d3f");
+               set.add("2e6c8ecf50ee9ea82f407a8b0acd4f85");
+               set.add("2e97a2f015b1247b01b5e022bf6109cc");
+               set.add("2eae476e3eb97e2a1ad54c5b8fa48208");
+               set.add("2f44b9347e986c91ab886dc2e508885f");
+               set.add("2f478d2efa82571d5c3e49fde16c936e");
+               set.add("2f7460de6e7581a6775f739f894d86c6");
+               set.add("2fa429a16950f6c3f19a051b3417aac7");
+               set.add("2fa4545430dae966dce186984c98d0b7");
+               set.add("3027d63763f7aca58b41d52689f38dbd");
+               set.add("302b34ea5ec261fd74a4901d041d3f82");
+               set.add("30b5952157345beb00d753425a728757");
+               set.add("3136fef31b6d0e1d9a0dbbbdac05b0a3");
+               set.add("321377ccf346be4efa1fb8658032298a");
+               set.add("325e3898dc252f6c936301412be06505");
+               set.add("32fe6eecb5e97a6ff9c4f1c005857435");
+               set.add("33197b8e7194af401da6150c68004d7b");
+               set.add("3393a92e46a045c4eaf6b9e18f7044e3");
+               set.add("33a89133876e91fccc4058627b34d617");
+               set.add("3466c5940034ddb1371c4f86dabce964");
+               set.add("348abf304c63a702e4a229db28feee16");
+               set.add("349260e7bc0291ba2e4c26d4db00bee9");
+               set.add("3507c7d2b11a693620235ea3872dce66");
+               set.add("353236b8cb07eef73d80f25e240acddb");
+               set.add("35aeed248d254fbc3542b5cd3aa9842d");
+               set.add("36218bbb51e99aed53ea822ebaa2c873");
+               set.add("3666b06f839465adc5d36a6e75066a47");
+               set.add("36fb9fb79c253ee61e498e459f0cf395");
+               set.add("3703dd15904a118a05d771e7ee6e3f11");
+               set.add("370b98cc77577db1a07021e46c21cd3b");
+               set.add("3719475cc57cf3b5312f21b1efd228ef");
+               set.add("3738564e4327367bc2f359cdbb442304");
+               set.add("37bf1e76b05f333eefc0495e4f725838");
+               set.add("38715f11bf91b5ce06494e1ddd94c444");
+               set.add("387eea945f83c9567fa42c6e150b7ba9");
+               set.add("389687548b9f05e6c99d93a2ecf76307");
+               set.add("38b1e93cc1910ecc5301502fbb9bd8a3");
+               set.add("3a0b2ffd2d4593581c52bdc1094d92d8");
+               set.add("3a99a5318497e7108995a08675fa70d5");
+               set.add("3b4573f1f11db1ffedd14e10d539aad3");
+               set.add("3bc526028cf0be42fcbb75936810d41c");
+               set.add("3bc5834ec0726b10465b67f17b77044e");
+               set.add("3bf858e6c91e0292259a886b8bf793c3");
+               set.add("3c4eea57e65806dc59dd4c206bef79e1");
+               set.add("3c7b9e1836fe07f7a4ffaea90e7f33fc");
+               set.add("3c8aee818229c48b9a882caa6de58c18");
+               set.add("3cf831629486be08e747671a14d113f5");
+               set.add("3d6b990aaee7dff0be939343711dfa74");
+               set.add("3e2d355d7fd39e54ceead835d14df7e9");
+               set.add("3e8697fe46496d41528387e2d37d734a");
+               set.add("3ea538f54677ecaffbed1ae5f2e12d28");
+               set.add("3f654d824783b4392396b34ad2b44974");
+               set.add("3fc4889ea40aea23fedc994704ba4708");
+               set.add("41145e8204991f0b644b831cd859c4e2");
+               set.add("415fecbed32f8de16ffbab8e97edb4cb");
+               set.add("41633604778611785d7453c23823b0b3");
+               set.add("41d37971a99bb08d0f5f4fdcfcd87e8d");
+               set.add("428c0aeb520fe9e77d3b464543620716");
+               set.add("42cc2865a6fc399e689d2d569c58de2a");
+               set.add("43a6db581840e3645459ce51953ca9a5");
+               set.add("43a72eab1f3f874da7d68092e83357ec");
+               set.add("44255564acd68eca32ffab8e6130c5cc");
+               set.add("4448ff245bfd8d2606b418f33797571f");
+               set.add("44a4e02e520657221706cd6d69bcfb13");
+               set.add("44b12361fee8a2385a9b90e44fd079f3");
+               set.add("44b7c1c17e8e4728fadeecb6ba797af0");
+               set.add("44d734a18b45937c3035a047f9063dfd");
+               set.add("44edf41dd7624a6e2259d8e451622527");
+               set.add("4528bda7194c6dfafada95d68c2faa3a");
+               set.add("45a8a995a3614f823c04f3c157effe97");
+               set.add("45d2f014e70681483d6bc5864cf94b20");
+               set.add("46232174087bfb178ad7cc35bfb387a8");
+               set.add("46401106d96b729d330375a63e655374");
+               set.add("46ac2356b12ed7519ae2dd5f199b7c10");
+               set.add("4790684e33d48e3dfe99b6ff7712be8a");
+               set.add("479a2848353fef692063ec37e7d556dc");
+               set.add("47a649cae70a90e7d1ae2b2ab10465f0");
+               set.add("47bc150e2585e61cf9380ed540de4465");
+               set.add("4863872b48ecad3005e7b60d114c0fde");
+               set.add("487c3163ebf25cd3b4479e13e30cba5b");
+               set.add("48c5d84e56a982689f4268ed9b50cded");
+               set.add("493a84bde424f5946290238998d64873");
+               set.add("499e8c7c38dd4d8068eefc4eb58d4cf5");
+               set.add("4a03d963b887b4ade3d574e87d111e9d");
+               set.add("4a5509929d6991507c6e136178942a2d");
+               set.add("4a933f8824eba082555515e69d3bfe43");
+               set.add("4abc93cb926e33fbb97aa0d2ffe7885a");
+               set.add("4ad536d6aee9fffe1e84c9e75698f5cf");
+               set.add("4af14f94870a2e3d47dbd78cc05e58a8");
+               set.add("4b0a7961ee650f518533f03c38ca8320");
+               set.add("4b166cec69dc9ace3a9f598674c35b3c");
+               set.add("4b5a632e55f4dbea5435e151145337a7");
+               set.add("4b797a7d23faae4daf8b2946d6cb24dd");
+               set.add("4b9e8ea91d6bd67b22be67dd40b871a7");
+               set.add("4bd7e46dd429e45ddee5736f86d494cc");
+               set.add("4beec7064114b6e49cc76cc2f71594ec");
+               set.add("4c3f47c263ea5b933ac5184466573f6d");
+               set.add("4c9b11094fa43b8a9aaf1a1568bf60c2");
+               set.add("4ca44906c21909f396774827017e007e");
+               set.add("4ca7dd633f56f76f794faf958416f4c1");
+               set.add("4d6956c8d3db98528dfbdafa4a3316b6");
+               set.add("4d84b18416836b7297b990a408a6eda3");
+               set.add("4e13b8d5d4a77393b2fbfbaebe9ea1ca");
+               set.add("4e3b029d124b898f1d11a8d15d2a6688");
+               set.add("4e9723863a06235d9339cd00912871ed");
+               set.add("4efdf67cd98424e7cb008dd09b169942");
+               set.add("4f25dd1fcb4aedb512f24758961d87f9");
+               set.add("4f86907e557c00d13b42a2393b834d8d");
+               set.add("4fdb3ba6ebc9b3e9ab133c15312f995a");
+               set.add("504bbb5991ad94728e6b73c6ddc6c476");
+               set.add("515f449c1e9fd279dbdadf3cc38fd008");
+               set.add("51d9a0c78486de462af6a490acea1fcb");
+               set.add("52032bb8c1acb8bf7320aa73babd2e50");
+               set.add("5203feb9b0d422a38052d9df4103e3ab");
+               set.add("5222c37a7e8618d4cb43ce8c4a188129");
+               set.add("52731882ea73ad5b1d39c25e2969d9aa");
+               set.add("536af35745c20e4ee25486a31c2fb57c");
+               set.add("5379086fb93464cbdad4459101ed4d07");
+               set.add("542f3505366c2d6575e73064aacf536a");
+               set.add("54350b63fafc31af32bdf49cf0bbfda2");
+               set.add("5498ead583ab6cd6900a533b1cb69df8");
+               set.add("553eb9e396b2f304f963b717bb3a9059");
+               set.add("55c5181d0e1b170cfd05c3a9271b3bc6");
+               set.add("566ff170b185c5cfd893030c97080451");
+               set.add("568c906117202c4d4451dfb3be697921");
+               set.add("56a9926b91222c8943640da0b642d617");
+               set.add("56fcddb2fc61ab068e1ce98a226fd34d");
+               set.add("573f9b1aa16e771da95df44fe3a62167");
+               set.add("5805ae3e1c5fa9f7a604152c40c9d06d");
+               set.add("5844ffd995e179e21476fe41a72c7e85");
+               set.add("5866a0ca3348c1b406e4a4a869b183ae");
+               set.add("5922a04c19e52d4a3844b61b559a89d4");
+               set.add("5957b399b3380e097b70cfc09bae1bd3");
+               set.add("59785d3feccf91b7a9fcd96fe5e686de");
+               set.add("59cc15fde8f2bab7beac6a9542662df3");
+               set.add("59ef8fd572ad56b7c00092f185512c0a");
+               set.add("5a26e5d6effb9634941bbdaecf1cc4ce");
+               set.add("5a94fedb054c29331d22c4442ad619a6");
+               set.add("5b1a41ab325cdfb925f500868f77e058");
+               set.add("5b20fd5088ed40d65a52c38fbe314373");
+               set.add("5b3510c0aa53405e1fbd7a67b3af34fd");
+               set.add("5b96ce711afb37fb511e70ac12cb717f");
+               set.add("5bb8c694f0d7e39aceaa7fe7a885a6e1");
+               set.add("5bc7dae98ed248bc85f4782783c7a383");
+               set.add("5c1e091a898470db28aaddc968071a00");
+               set.add("5c603c37c8ae3b7441a49bfdd93a2426");
+               set.add("5ca4eac1f0b43f03288c19c42e1ccb2b");
+               set.add("5ced682df2330890f2239e8da8d33061");
+               set.add("5d437ac21a6da33b77c980abef5af0ac");
+               set.add("5d4f136bcd4f5f71e0402819f2f666ea");
+               set.add("5d9d43530d78a07475063de4f6b82578");
+               set.add("5e8973f53dfe0e36537e7d88ac84cfaa");
+               set.add("5e8b973df438112549cbd73b38240094");
+               set.add("5ec17176ac8ca3ffe5c7707d4b33aba0");
+               set.add("5ecdf016b2374b2029c58dce498548cf");
+               set.add("5f4d8576e9299aecd4ece33f8b4ffb3d");
+               set.add("5f5bc13ecb72cde7c4c6e89279e836f0");
+               set.add("5fc7b23ca79086fde585ac92b8ddfa61");
+               set.add("5fc8dfad0c6503b16fcbdaf2140f5bd6");
+               set.add("610b21fa92e08d26b0ebbd27ac406558");
+               set.add("618c82b1f690b74209a68062f0b7f50e");
+               set.add("6214548d7b510154960ca3d23da4f38d");
+               set.add("6244a9533207c9f3d89bd48d2c946d72");
+               set.add("628475d3f98ce21920983b2335d29771");
+               set.add("62b5f08d8f9087672b45485f5177b688");
+               set.add("62c0ca2c1be43447418c673a27f69a53");
+               set.add("62dd2d23b56d1991111028754f7d5718");
+               set.add("62fe634a6ec154d4b675a8944ab98a7b");
+               set.add("638e84fef470490263300ed27293aca9");
+               set.add("643181e6ca3418a86b5dac6858805839");
+               set.add("6431d2ee351952b0ca1c8e24aee89d9a");
+               set.add("64cf9d529b625818f06f910fd5a51ebc");
+               set.add("64f5901476b159bd9c4f5ed9aa2b4cc7");
+               set.add("651c5d94aa8b742ea6bf89eb4760d88b");
+               set.add("6535664e59493ee6581d3ec49d666a05");
+               set.add("659bd0331a1348d14e9cd72006008d5b");
+               set.add("659d8f3f58c60862ec21306475d5b86c");
+               set.add("65ed980ed9e129e301e3c219be11999c");
+               set.add("661c50f934f8b021101df91c063c2662");
+               set.add("66289df1c8d41414638696d9847188a7");
+               set.add("667f3707995e365e210a1bb9e1926455");
+               set.add("66ff6174d6a5b1c8a40637c8a3a8a7b9");
+               set.add("673d52c40a3153d07e7a81ad3bf2027c");
+               set.add("67c803799e8e1d877eb3f713dc775444");
+               set.add("680708ce5f383f0c7511eb3d7b7209d9");
+               set.add("68ee06fe147e143b6e1486d292fbc9b4");
+               set.add("690da28e475ebd2dec079e7de4c60719");
+               set.add("693db94b6ffb0c1afa7b82499d17b25f");
+               set.add("6961f9a08f066d0318669a9c9c94017d");
+               set.add("69a38fb26f994ccc04e84e66441e0287");
+               set.add("69f5b82d6acf05cee8615ff5d05f7921");
+               set.add("6a1e040ce59176bcbe4c47654dcf83a7");
+               set.add("6a26a773a6223c79697e12838582f122");
+               set.add("6a6e0e4350ef8d4a3489aa4398bd674b");
+               set.add("6a8abe4a6fe236bf93d9b85681db2c0e");
+               set.add("6aaddb50ae45f1006852479932dfbd54");
+               set.add("6adb0778b8930a8e6a2f1e99de2ef074");
+               set.add("6b54ec7203070bb29e514eb7d684e380");
+               set.add("6b598530a066271059bc94c1fa0cd7a1");
+               set.add("6be4f1c5af0ff30131481d009e87133b");
+               set.add("6be8cb8a938f1ecef2b36c823e8e6ade");
+               set.add("6bfe9b78813cfa4014e630454f7a06a5");
+               set.add("6cb4d52135f005a2c7ba8ccc3b8781e3");
+               set.add("6cd5f8dd36648fcafcfecc8d5b990e9b");
+               set.add("6cfdb07efc0d3c1f36b2d992263253f9");
+               set.add("6d95c9c12fe5498af055b013bf7ceb7d");
+               set.add("6e8f160f1b2b54c3c89b81c4f9a98283");
+               set.add("6eadec5ff4cb05c8ef1a64d2c94d627b");
+               set.add("6eceba3c0a19666f5a9adbc13ceb1ae7");
+               set.add("6f47bff8d62f5bd51cee156f78e8cfcb");
+               set.add("6f484725ba3abcadfe8fbfb4e6e83db6");
+               set.add("7031b89c62590b6a41e7ad524bb0a308");
+               set.add("7058fc792efe7aaddf8a0323bf998f72");
+               set.add("706e502b5a6db25407c2565457944633");
+               set.add("70cfd491765d3e4e6a4e4b1ccaf9c343");
+               set.add("711e7a11c4be36563cae1b71234dc414");
+               set.add("71794c9ad0e60b6d0dcd44b51c3704f0");
+               set.add("7193a4c6f286f7b86b18cc7908498d06");
+               set.add("71b17eeb05fd473e42aa5c4e51e47a15");
+               set.add("71e0014aeaebda1113a12cecb51fd20c");
+               set.add("71e2d06eaa0ab3ae59d0f7b7ef20fc31");
+               set.add("71fe7c7f2a54587c2278b3e630faee56");
+               set.add("729ba9dde60740e6d5e8140fae54f4c6");
+               set.add("72b5be92417a4a6a09f5e01c106cf96a");
+               set.add("72c0c6f5a653bb57a1aba44e7edb202b");
+               set.add("72f6580c0aa3b101acffce59adf7809b");
+               set.add("730ac94082f25931179a313af186b335");
+               set.add("73243d82b8157406558562b5eb70818b");
+               set.add("73371ae751751a5998c3bc8de577b83e");
+               set.add("733c6d4df3b6781473ba0a3a169ca74a");
+               set.add("7376d2568492e6f6c0fadab86e22416b");
+               set.add("737b791d27930ccba061fa36c4046208");
+               set.add("73a47e531e9c0ddf5a334c40508f6361");
+               set.add("73b2859aedfe1bf317841bbc150e0491");
+               set.add("7413c1de6d5f286055e8244101da306c");
+               set.add("741bfaabd99301c5503fd36d17912627");
+               set.add("7423f1b74c8367863a1071bcd0864d42");
+               set.add("747ddec4bc30cbde2ebefac7b8df466c");
+               set.add("7494df6c5102bbfb6e48c9b30281325b");
+               set.add("74c5cb4f540e2c9b27ae60dcc8247eae");
+               set.add("74f6a218f8877fb76f74eacc9df32fc6");
+               set.add("751ff3c7f3e77c8d3ba24c568fd11845");
+               set.add("757c05f3194d6d4b848b83c0e3e5f1a3");
+               set.add("75f724e20c3f2e26d2de13927fbf78f1");
+               set.add("761c6d190a411231fccfeef67f08eacf");
+               set.add("763eddbcb1f6787b3350f65c64b44ba4");
+               set.add("765a1a5d8f09ddffec749d3a6920c4a7");
+               set.add("76acd0d7e4d38152771480bedacba209");
+               set.add("76dc5d4b4f65aacb1dfc1a5a8f61b023");
+               set.add("771cc4503662f2fc2c5f15f46e7e58b6");
+               set.add("772a3193a3cf3e18fd2058c3f45c35f8");
+               set.add("7823311e8d60374d4b37c50d927507c8");
+               set.add("78282abebd03a40f2dd21f1505a5f789");
+               set.add("782e12a60bbef943f13f2fa1af1e39f1");
+               set.add("782e6df5b10a60b26117e0648e76c6c4");
+               set.add("7875a865fbaf33ba617cdb7c7f0f7140");
+               set.add("78ee37b2f7acb3d892e54f0e9d2e0c14");
+               set.add("791f7d21ea119ccd49df31b2f614a0d6");
+               set.add("795036eafd006f62ee5a68ba1c309865");
+               set.add("795fcaf2d6d9e1c37a4c283876f26cec");
+               set.add("79688fa15c424b73454d9cd0a070472f");
+               set.add("796c759040e72b8efd4630754bd3f30b");
+               set.add("798ad9ae186a9d89e6f69e065bc22a86");
+               set.add("7a2a604d923e5bd87846902e47acc438");
+               set.add("7a39ea82b6b2bb75f9f6a7b817dab9cb");
+               set.add("7a62872422cf100af636b434f4a0b307");
+               set.add("7acda66f5d077fa708de7d153882b97c");
+               set.add("7b1aca3caab3a61147d4ebf5f7971d42");
+               set.add("7b5bc0bfd0de5126b4d278aa5775abd7");
+               set.add("7bd0735d3b5d579f0c97e11309a12451");
+               set.add("7be2fb055d29d5c8e42c559295ee8113");
+               set.add("7c14e11e0126b310217db2488f898127");
+               set.add("7c4ab23d9b1db15ea1f49fe017edf346");
+               set.add("7c6080928783078289d9a473efecc134");
+               set.add("7ccde35451846731eff4ae16e40f661f");
+               set.add("7cce66eec1313c11f5b9005db8f2823d");
+               set.add("7d40723bc0200f13675626309559ce6d");
+               set.add("7da7fa494528cd0836f9988f3e7ada96");
+               set.add("7e3bc2bc33f34ad791573e94021381d5");
+               set.add("7fb1e485fa41841779a0a1f95a2b7cd8");
+               set.add("809b63d7a153ee60272ffc224766fd72");
+               set.add("80fc9ff72737286ad64fe7de1524c799");
+               set.add("82b602bacfe681cee58d5530ac9e8b99");
+               set.add("82f69b66499f2bc375467ee933fe4577");
+               set.add("83243e87941f4ec7f8841571dd90b3b2");
+               set.add("836481fe9bfd7612506e0545bdcf279d");
+               set.add("83a498353a59dea68538962eb2642ba8");
+               set.add("83eafb190276935630f43cddf3d78143");
+               set.add("845c54809778f5b838e32443a7d44072");
+               set.add("849b5885cbf965447675610ee1d0dca2");
+               set.add("84a895acdcd487244b6803082036fad7");
+               set.add("84bdf63a67691194e37082e3f7f6d712");
+               set.add("84c99be383e4ada00f4e6bd335774655");
+               set.add("84ed2fb163b5b2525a9a731056ffd144");
+               set.add("8517e14d6f657f1244c0344d4f1a828b");
+               set.add("8541aca6dd06f2dc6984d5e1b664900c");
+               set.add("85cc38b178bd721bf7225144dd301b0f");
+               set.add("85d00ae1ce88ace2bc6918750a97502f");
+               set.add("868af0eab556081846fdbff18df40b28");
+               set.add("871f7fe309f61ec7e45e4b29833349d9");
+               set.add("878e7848ab58bf9271fc04766e969c8f");
+               set.add("87b872efe9433147c61d5d2c3dcca14f");
+               set.add("87cd3518578a2ef76341b33f9c95198f");
+               set.add("87cd3a0a86f398ba1026cdb969e55090");
+               set.add("87cdeb3fcaa050209091a1600ce1df11");
+               set.add("88008ed2e9b600fa2e453390688aaa7e");
+               set.add("8833c25743e0f9725ca22dbc6e54d1bf");
+               set.add("88693556ff80aacd354c1948675c0674");
+               set.add("888664c26a3296f9571d561723b44255");
+               set.add("88ed07b96422ec99767fb35bf6a51966");
+               set.add("88ed43ef6f483b9a7e34c34b46335dea");
+               set.add("8a2e4445364c3c9151dcf4622c6add51");
+               set.add("8a73ce2e18dacf250a961dac49338407");
+               set.add("8ba75b207cc0bee8ec624e9f33019161");
+               set.add("8bc592cc7aaa336637b9c82c43cbb081");
+               set.add("8c1bdef25d6a6df69c303be204748da9");
+               set.add("8c8b182ec0845de4a5fed3245e5601ea");
+               set.add("8c8d724fba729940b1234e58e64125b8");
+               set.add("8ce47ac01efd8c0ab75ae5371ff0f7ba");
+               set.add("8e1600a04363c86199d660ccb7085735");
+               set.add("8eb548ee8bf98a6426f0b5a11e32c88a");
+               set.add("8ec54a8bd1ab30f324eb0f03ef79d097");
+               set.add("8ede1653debc5617deae6a7632c18502");
+               set.add("903594c774fd5be01132f559c00778b4");
+               set.add("9079d8f7488bca4504d58be0bc76deea");
+               set.add("909a1f7458c8f1f1138dff9ce019fb6c");
+               set.add("90b8dd2817509c2374b20a1975ca1a54");
+               set.add("90d0f3d40769a6219608964628d40e55");
+               set.add("9104737f888d85480d0cc9aef8587e77");
+               set.add("9118a19b2abc5d1d624b10f2bceb18bb");
+               set.add("912e499f9a4a55f11133e01b33542ad1");
+               set.add("915fcc373ba5d8a13814f236c1a9e4e5");
+               set.add("918ca652867678984ae1149a3b5467bd");
+               set.add("91fbebd600bbd88370994b739ae8e1f8");
+               set.add("92fc949a982c897ca4a17750e2ee4afd");
+               set.add("93c0446ee508efe75a176035986627cc");
+               set.add("93d4329e22ed50d3502b2d0bc117baa6");
+               set.add("93f33bcfa6201057376a3fe53eb29959");
+               set.add("944b74b5ff9c617312ca2b0868e8cbc2");
+               set.add("94bacf4caccc653a74a68698be0da4bc");
+               set.add("9572f2ed73f01766b0ede9ec3d52715a");
+               set.add("965e3d6087eec8e991175aada15a550a");
+               set.add("967119411833b80c9ea55f0e64dacad6");
+               set.add("968c5025a59e782d96682b09c4e94746");
+               set.add("97824aa7746b63a32ea6d0dedb3e3f84");
+               set.add("97aa914f28281f67ae3ac5222566c2a0");
+               set.add("97f5a198489144a2f306456c1a346f9b");
+               set.add("98a7e979d454d7f46ceb4a4283794d3c");
+               set.add("98ff8ee9107e864d7c52d631070fff3b");
+               set.add("993739fad4a47f34eb65e3ee48d15c09");
+               set.add("99bb411f89eb34ebfa59900de36581fc");
+               set.add("9a13940746bcf4cbe210150833b2a58b");
+               set.add("9a3d7af6ccb7d277e3ed582d8932b5db");
+               set.add("9a76e86b4275099983c5ede78626e0dd");
+               set.add("9a9caad4a9c674daf41b5cb616092501");
+               set.add("9ae6b0ad5010301ea610f49e008adf8c");
+               set.add("9b6033bd4470408ecf2949d88531d6a1");
+               set.add("9bfc7853ff00c7ea0e2f8972dc2595d4");
+               set.add("9c8bdd485912f9d9eaaba3d5872be726");
+               set.add("9cba07b76b4e79c0265deda5df2e2381");
+               set.add("9e082b9bb6c1361965c0f0e06f051acb");
+               set.add("9e24dbadcadc67447af65d571ffaee55");
+               set.add("9e6a5f03a8b524ffa3264a3f32818e1c");
+               set.add("9ead837b9e4f8c922f74ddbff0d2b88a");
+               set.add("9fb7aa659c0475d5dc72bb35567247c9");
+               set.add("a0006978c9a542518b425c0caa67042b");
+               set.add("a01bdd6575c3cad9f9a4cb8aac9c716a");
+               set.add("a02500e28eeb7e56e343607a822e2a7e");
+               set.add("a05c1799e061712459e6c46f533263a6");
+               set.add("a0799831bfb3f9b77b63c03fad39cce0");
+               set.add("a0d4911294ccb20c0920a3cc6705f326");
+               set.add("a11dfa1b02b1671d42b1abc858de2f2e");
+               set.add("a11e237bd6d3c4a4ee8a7ee791444ad3");
+               set.add("a148d83d50cf0852f6c08ceacbea0296");
+               set.add("a1d8b81c03860585fb40613e828c1b2e");
+               set.add("a20c867fdbb93bbe1d1231d9a8ea76c5");
+               set.add("a21e0795fe0977d50a4496ba60e685e1");
+               set.add("a260bb11468a2252a8dedff81f5672fd");
+               set.add("a2b01bf43bc1d403e6ab3a9b71f32484");
+               set.add("a2c15ded3e16d9aa12b9c07f3383c098");
+               set.add("a360659a62e2e34f4edc89ce5e8b8a0c");
+               set.add("a3a985e0ae5a9c96c74b8ee647101439");
+               set.add("a3bf05e31288667a43b4c39cc0604c97");
+               set.add("a427397e35b28706c5b8aa50e8f92a1c");
+               set.add("a432e1b27b7d9814368d8f4071cf2dd0");
+               set.add("a4b4800082feb6fcaf1cd56dda3a28c6");
+               set.add("a4b83742cb45f1dd9130250cd72c460e");
+               set.add("a5a8b20a222bd51b707e50303fdae33a");
+               set.add("a5cf16d12d611ddc5ae1b010083690ad");
+               set.add("a67b1720a7f123bb943c3f1ee74b8f00");
+               set.add("a6b31c2e971254f85e231653abdc3a06");
+               set.add("a6f9fe8c867cbef07730db4d1d461960");
+               set.add("a706de20cf1a31e379d98807d1cb2060");
+               set.add("a7b5467023706646a6b8ce2602bba226");
+               set.add("a7bb7f7f68b538fb778856d4fbda50b7");
+               set.add("a7fee39f2151056370c57d383e486348");
+               set.add("a84a5f90f1083075081f135c74314bff");
+               set.add("a8a6b73342c6a92638e41b86e6838958");
+               set.add("a8f1c8b28c017186778e3074637e52ef");
+               set.add("a90e513d9b2d0f55b875c3229e2d9816");
+               set.add("a9e697026e08d1a8765497a9569b04e6");
+               set.add("aa3218984177ce38bfdf06e38fbaa64b");
+               set.add("aaa0291aa11c304b3a2300d4025db74d");
+               set.add("aad63a3685d9f906a7c6c8716d626c0b");
+               set.add("aafee591c7a3ae5f3c4f44f2d0f8a70f");
+               set.add("ab85503c9acb730fcb9ed2a4dd74e2d7");
+               set.add("ab8ad454409604808d1b444b068e602d");
+               set.add("ac4c8af4d29676c8c79ac9ef180fc5df");
+               set.add("ac4cd34387b04786cc5315b464006ec8");
+               set.add("ac9c443698ac77bcb3a73a959f6ca0f0");
+               set.add("acde934989eba2c7fef7cce095ce85c7");
+               set.add("ad053830e5d0bb7e736ab98a8f3f1612");
+               set.add("ad08d0d2d84298deb08b4c4a1cf62f39");
+               set.add("ad0a1f2424a1b831f9777e638e8f829a");
+               set.add("add039636134cb123908df5053304f3e");
+               set.add("adf89cbcb01a2ec6d4afb24914790a67");
+               set.add("ae1ae7c31f46325ce6a28104fa7070e6");
+               set.add("af8c83664fd6eec8089ef1992aec463f");
+               set.add("afcd59e32572ecb7ebe2d9c993d5fa9d");
+               set.add("b012115b4276791c5049dace174933f7");
+               set.add("b218489d2d4d7ddbfee2f073e238ff30");
+               set.add("b251290e1d8690953d9cc0c1ea3bac6f");
+               set.add("b2843a551894de318b80f790565dcfe3");
+               set.add("b2a414aeb8800edfa8945863ffa5fbc9");
+               set.add("b2d68ad2619bbb15a827b7baca6677b0");
+               set.add("b2fe203ee319ae28b9ccdad26a8f21de");
+               set.add("b33afd95fbd9aae903bbe7cb853cbbf3");
+               set.add("b385f0f86168fea4f5f456b7700a8ffe");
+               set.add("b3bd462a51847d58ed348f17c8718dca");
+               set.add("b3d1befe2272f354b85f0ca5a3162dc8");
+               set.add("b3f50d0da11772487101b44ae8aeb4ac");
+               set.add("b42625f51295803ae1f99daf241c0bd0");
+               set.add("b49cdae29a3394a25058e94b4eb5573c");
+               set.add("b4ce8f683ec172aecf22cf8e516cce05");
+               set.add("b4ffd04e41c1b8757149777a894f33f2");
+               set.add("b5a1510fcf6dd596e87f497bfd5317bb");
+               set.add("b5a75d8c18db0a96a3423e06554122c8");
+               set.add("b5d312d32267bd15ee43f36077feefe9");
+               set.add("b6645bb07f58754b8838d54e24562c06");
+               set.add("b69831350ae6a3dfc18c0c05db0c25a8");
+               set.add("b6b70e569be8de2fdecf285730319735");
+               set.add("b6ee0ea7d82d3d7e0ab8bc62057c0385");
+               set.add("b707a076a44ca9b3e6b8dc1dcde7d877");
+               set.add("b77df6081bbeb4da02c075befb4beb9b");
+               set.add("b7bdcedd416cccc742643e8e244f6282");
+               set.add("b7ea4565381c6dc534cf0af8305f27ac");
+               set.add("b7f3fb01d8c41525b103fc5faba23362");
+               set.add("b80bf674f28284a3614596800ec02b3a");
+               set.add("b81ab08e53854aba9462ebbaee1ff248");
+               set.add("b87e12381d354360f7945299ad92a4d2");
+               set.add("b8bd5737f81fddbaf120ebe43a0010e4");
+               set.add("b92f1e45fdb62c1fd6d7f3e774b4b610");
+               set.add("b9769bfc0d93a570d94fa130de750b1f");
+               set.add("b980c7a501ce63ebb6e20b04348298b7");
+               set.add("b9e4b006db3e1597b21fb7aba13f79c2");
+               set.add("ba031cf2febc03ddbff278200dca45a0");
+               set.add("bb0f54037f5ab70e96c7d8ba7f57ca4b");
+               set.add("bb2eb6b3f7383d7ef521409fa7710385");
+               set.add("bb3eb6a5dbe0582b31f35e5dc2b457a7");
+               set.add("bbc22cc7f6851e06fadfac40a6225419");
+               set.add("bc4d886813fe2eba9ccd7bef0b4d05ca");
+               set.add("bc8162e261658ece13f8bc61aa43ab74");
+               set.add("bc89ec14f4629c3afe1e286b07e588f6");
+               set.add("bccdb576cb50b44ae34806c2a2781c52");
+               set.add("bd10772f1554ccd6e245d6869d84efe8");
+               set.add("bd969e90ff4b1362e2f24553124a66cc");
+               set.add("bde145f138ed13399b8a747da20c1826");
+               set.add("be11a726b56813c4b1aea0574c8302b2");
+               set.add("be1cfa6a82eb4fbf7186efd6ddbb0161");
+               set.add("be5f3dcf0badef84475741cc47e2ddc0");
+               set.add("bf316e6ad7e72f7dc4033207dd033c76");
+               set.add("bfadbf9c4cde6071e54e43a5da64aca9");
+               set.add("c029503ea85e7a7742d826bc184d65ce");
+               set.add("c049499ca03fd69115352e5d4be72de7");
+               set.add("c0524ddd036991b4718f6ab0ab4e4f42");
+               set.add("c056cf25df6751f7bb8a94bc4f64750f");
+               set.add("c0a9c2fd4f0ed2b8f2bdc7f2c0a7a7ce");
+               set.add("c165e480073bcdccb3fad1c5e507159f");
+               set.add("c24ac1ab205eb3fbd1c64841f0f288d6");
+               set.add("c26987c1c7e95810bbb6f2e284861494");
+               set.add("c295b3b2493aff880daac1532a063b72");
+               set.add("c2b18390691697037d5698b683eee361");
+               set.add("c2cd680e3032ce3a70d3bffdb7d0582f");
+               set.add("c2defcfb93d217c4be28aa27ec62978b");
+               set.add("c332adf9d689dcbbb38fead7098781b3");
+               set.add("c4d8f1baafe99501b0d80e8a9c8c3086");
+               set.add("c4e5ca3e96b219539e3e61c3c4fbe5a9");
+               set.add("c5e1448d1fb24ebcef161ee65f21a725");
+               set.add("c60db0ccfc2a22a152f7470505eef8d3");
+               set.add("c65e2561352e75a66b5738268b1d126a");
+               set.add("c69c01ed9b781941561c3a9dcfacf7ca");
+               set.add("c76bb0011d2519fc9e3af85de593e8a9");
+               set.add("c7a946bb164a3f642e4c5f1b7af337f1");
+               set.add("c833820441cbbf28a25d1ea7934ad6f8");
+               set.add("c8762972b9325b7ec040c782aa9414d0");
+               set.add("c8b1563c45f4fd4dc8ba5fafd5c566d2");
+               set.add("c8f2a5a0533de5eae8d1d01da8fcfc1c");
+               set.add("c94045226f625ab9a507552f64892fbe");
+               set.add("ca365baface31f6167328e65a0aec03b");
+               set.add("ca3dc74a6eb57042ea911afa05b1021b");
+               set.add("ca5b57fca35c5bfa4281802b13381d0c");
+               set.add("cab05efb1584bddbc5e4f064c1628a13");
+               set.add("cad2db4a8a73a867a6cdacceec4044ac");
+               set.add("cb6b65a06bbb9ba5536936188a08d836");
+               set.add("cba49b7c8d1982635866a32956861df3");
+               set.add("cbd94e882bdb84ec79ea2bebc1eb4aed");
+               set.add("cc49ecf163d383488b44dbb17dd5b4d9");
+               set.add("cc4bcc37de4d7bf87acea95ac914997e");
+               set.add("cc9300ecd7f2799c750ca3efcde7ce20");
+               set.add("ccccf43f691ed8bb94ac27d3eab98659");
+               set.add("cd0d8952d3e5742f4bf62195e4b385ec");
+               set.add("cd57964f0a86f3c9afbcca44733081d2");
+               set.add("cd80d4f5366cd31ae31e377c636a773a");
+               set.add("cd987a30667f7ff04a5852fd3f15fe3b");
+               set.add("ce16d46a26c771b1adbff14cc7272bf2");
+               set.add("ce43a9cf2d81a1e09237ed2279ca04be");
+               set.add("ce7a500ffd8f5925bea7a48401fae95e");
+               set.add("cf3894401e317e2545d0ae284802291f");
+               set.add("cf779cafecefb6bae5137bb77582e7e2");
+               set.add("cfdbc0be3829a6d55a5403ad92c77bcf");
+               set.add("cff24b5ef480cd58324e927e4ba0ed37");
+               set.add("d05a83622e817871306a3878a9d867e9");
+               set.add("d060e6662cda17c2c523c74963d2d556");
+               set.add("d0b058971d8a20e840de043c68c140b1");
+               set.add("d0c122a8a62cb5728f1423816b26c49f");
+               set.add("d141ed3ad5b33d6f92c976ad8d518b3b");
+               set.add("d15c7bdb5fc7b9316d1cc60a85acdc64");
+               set.add("d1c8ba5392f01f13dfef28e4ecd11cc2");
+               set.add("d1e661c0bfe1c23ca4e91cfa0a40a9d3");
+               set.add("d25a8aef0d42d7a31783e3da52dd4ee8");
+               set.add("d26ef38d4ea39ba85171f444a8947443");
+               set.add("d30b7d8663c7852f2be21d886b79a6eb");
+               set.add("d315c862f290b99466e3d406d3104413");
+               set.add("d4007ee1fd6ede4a148bdca7ab778dd3");
+               set.add("d4805374b5f1ece75c0dd51d6283a0f6");
+               set.add("d55b77d76f6ece8bbd33cb2afdbd040f");
+               set.add("d570cb3cbfe88dfdf086bb1ab9ef76f8");
+               set.add("d587ebc9902ba2620d52a610441470cc");
+               set.add("d6196bfd55d758dd5a4899ce84cea85b");
+               set.add("d6815ec0b3e12fea0f470c01c0da3888");
+               set.add("d68971ffd271de2ddde6ac10c2817451");
+               set.add("d68ff175d25d083eee4f31bf0701a0d8");
+               set.add("d7fca16ff4e4ed99b72046f99533eef3");
+               set.add("d815d7a8dd0446ba21ddbc37d3733f36");
+               set.add("d8d40312d0751951b4c45f2100379950");
+               set.add("d97e0d0fbea2a015d5e7ea229b99a6c3");
+               set.add("d98c6a23fafafb0f9981f2123b1a1938");
+               set.add("d99d6bfaede491ceae109a644cf54098");
+               set.add("da0f523e815ce990a3a5f5b5574cec4a");
+               set.add("da686b7f2a26a36051b273bbabdd7ecc");
+               set.add("dadb54d4a8ba762a8d2a2fad5fcd7582");
+               set.add("db03b5399e1a324660934ad81036d987");
+               set.add("db29f48ac45b41ad389b1b4e8e30f553");
+               set.add("db98a87e4be1e5b59f589335e1509996");
+               set.add("db9be69a1dd929be69406f9e75533fd3");
+               set.add("dba45dfda4d06aebf3adc194ca4ec35d");
+               set.add("dc339714def04ec3e5e280faec8445e5");
+               set.add("dc3fb757715c96f7c629b7712d87ca61");
+               set.add("dc5dabf20b3fbee0d3a0b1d800c74d4f");
+               set.add("dcc7b2c56f358641ea9a73c6a81479f5");
+               set.add("dd922d98ecf5096482946d8671b647e3");
+               set.add("dda8214d7b53392f8ed9fbe4178e90b9");
+               set.add("ddd37af5af0a69bed06bc50cc0a6f4c2");
+               set.add("de8d217fad9883d9bfdee5af01472971");
+               set.add("def34fc0fd41527b300d3181a70cdecf");
+               set.add("df00d9361332c48cba23bfcd41e799d4");
+               set.add("df35772f10769bc28701c488d33e89b2");
+               set.add("df769f9dc2477135b0c4320e7e7b4c2f");
+               set.add("df95377a3f69b8fbe5dcdfa041491717");
+               set.add("df98c3766238aa84f9a9dd92cd73fe72");
+               set.add("dfd019d69302047a67434458d0fa8952");
+               set.add("e037a9e26a8b319437ab7c962714dc56");
+               set.add("e0d2f02d29a965fafd85a4ae6ad37246");
+               set.add("e10e651cd85e41be3402f51885bbf107");
+               set.add("e162d7b2e436ae6d369f4fbaf53af4b4");
+               set.add("e176177a2b64669a6bcd1cf8beb35df2");
+               set.add("e194252a63a3433b5a5278f68678b7dc");
+               set.add("e19d16546c555d073454ea56ece1cbd6");
+               set.add("e1cb375938189d4090b000ab41c77a06");
+               set.add("e1ce2428389f0c2356881e626f161ccc");
+               set.add("e27c22419443eb612af1620f4c8be007");
+               set.add("e2a5adcdd7b01611736b6b74f8c506ee");
+               set.add("e3084721ba7ae53996337e82c5f469ab");
+               set.add("e35f4cccfe57bdd7338dadeba55609f1");
+               set.add("e39a2ef2eaaaf7ba74623f14c917ee1d");
+               set.add("e3e11cf57dc3f1c6ca59acb06370698f");
+               set.add("e4557f7733332200116b753408cdb966");
+               set.add("e48c96d6025b38addad2278f24c963ef");
+               set.add("e48db7db130af48cccb2d830d3cbaa14");
+               set.add("e4d169990e34bfeab0c6a780d6a49d58");
+               set.add("e4ea1f6b01c9cdcf9e550792ed336384");
+               set.add("e4ee8ada1fbbe886fb25a7f484609690");
+               set.add("e54846d325334547923d8b64da70f529");
+               set.add("e56ccf6eca77d62dde88c895abfc1c1a");
+               set.add("e591790779db1c866b179d6f85b09dda");
+               set.add("e5bec4799ceef43816054f92de9652b5");
+               set.add("e5db5832d59e14d6999144fa8cd10e3f");
+               set.add("e611fb9e857f9bee391056e1f971a0aa");
+               set.add("e6321fdd099d70352883b45f6c2a20a9");
+               set.add("e682fc42ee7ecdbf595116293cbe8a6b");
+               set.add("e6ae48418d10883fe9657075b476274d");
+               set.add("e6fa2a139e5c56f8f483aaeeee0b7fbb");
+               set.add("e77d3c78240ec60f7f4dd67a2e71085a");
+               set.add("e78ad15fb1fd450f9221147e458b1abd");
+               set.add("e7a1dc89a6cab821776ea61fe6ba10f4");
+               set.add("e7df074666f6caa44b798342bdab6230");
+               set.add("e7f25163d78c2c658300cd0f9a8a3b04");
+               set.add("e80f22347419025053de7da1f07912ec");
+               set.add("e828123fa3cdf86dc0fe1b5c86d7c87d");
+               set.add("e8764c00097a0a1254f43a16c98a1d7f");
+               set.add("e89df60deddf270cbc2232bbe26420d1");
+               set.add("e8d289f3c1aa961cf4ac8d164e286dde");
+               set.add("e9051eac7829dc1a95987230fb21d2d9");
+               set.add("e90846d2c3e16de5ed5dff4c21356edd");
+               set.add("e954907cdfba1cf07f19f64af5cf45b1");
+               set.add("e96e004e988b8e36b2ab9ed1b0f65649");
+               set.add("e984d3924451d3498a3efccd845a77fe");
+               set.add("e98b4097ddb057755e561c33a0f3428d");
+               set.add("e9a2ba17cc4b93063d61813359fd6798");
+               set.add("ea90b42f6ada6e0ac5d179af4129022d");
+               set.add("eab4231af5b3ffab13f9a04b8aef0fad");
+               set.add("eac17a7d30d24e60e6b2ce10059b37a0");
+               set.add("eaf3af4d0b61df60ee8fe3a808de6ffd");
+               set.add("eb3178d36a578fd8b430ea82429ee145");
+               set.add("eb4fbf9835a3584e6015260f0dff2174");
+               set.add("ec3a9437b1481db4d8cbc3b4fc0888a1");
+               set.add("ec47d133c23dba3d015ae730a1b9659f");
+               set.add("ec6d321581a133fee9766eedff4db9d6");
+               set.add("eca16f6d986bd893af3c4a97b99df623");
+               set.add("ecf09182c51e5110d636828364ba3ee6");
+               set.add("ecfbf6f7017f0981ba6d30331c9dc870");
+               set.add("ed1eaef061f0d30503a64f27d8ea2825");
+               set.add("ed2e4441ad7dcbe0a5d3e62bc31aa9bc");
+               set.add("ed6304572c0f1283fd06f9c09ef32b61");
+               set.add("ed7d650fc0f5de8c9da107e53025f220");
+               set.add("ef405c5b0de638c01cf57c379aaff45b");
+               set.add("ef5ec03860cd32e910a3ddb2d8740300");
+               set.add("efdc8c21ee843f98a0dc08ef694b6db7");
+               set.add("f0111af67e822944e1dc1ab24e5b8458");
+               set.add("f0e8026289bc1f9109b25f4599556aaf");
+               set.add("f0ff5a7aa6f4e8785fa406636618c01d");
+               set.add("f19a809facb46a7a094c17039126eb3e");
+               set.add("f1c7524d454c25cdd837662a80709030");
+               set.add("f202d26f911074235ac8e4a5c1ed4dad");
+               set.add("f2250dd8736aa007a7e2530dca1c6081");
+               set.add("f2e9d36561ed03eb23d38005919625d2");
+               set.add("f303e8a2a96635d2f44c414229c349bb");
+               set.add("f35b8eac398bae58ba622ef643f64aa2");
+               set.add("f3a37dbd51e59af2801272fffe457d64");
+               set.add("f3c4afc965427977685da607f8a6edca");
+               set.add("f468c204661ab47379613a1d03f56571");
+               set.add("f4ff8d1667c95377ac96e870170bfe64");
+               set.add("f585dccae7fae67affbf500ecf9a3839");
+               set.add("f59a4193ec432bd26f780a6b74b8be38");
+               set.add("f5d44e9d1523c3e6834108d9c76b2da9");
+               set.add("f69f58acf2b80f5dc29682c64de8da7f");
+               set.add("f6adafaf98b92438e1ad916e51a65366");
+               set.add("f6f175a7910c5039d0fa51393c920df8");
+               set.add("f71a00225b1abf1dddfcace79d0345a2");
+               set.add("f7446eb342242f011c118bb3439335a0");
+               set.add("f76e6e86c9b0d84638c1c26c55b16cc4");
+               set.add("f775463704e3d13817abd9674d62f468");
+               set.add("f80df9b85c1219fd2030ada584fbfc35");
+               set.add("f843fa1d0cd00122fcbcfd7caf1cb8ca");
+               set.add("f88e05d8303a6e5dfbd60ceed3988d78");
+               set.add("f92dbcd91aac65e0770f5fe947fc5a80");
+               set.add("f9512c5cc198adeff74fed3d4b0f4509");
+               set.add("f9e1ffe33f3676d0e39bc24e63cf3a99");
+               set.add("fa492225fbf03ad58ee88b6d84914583");
+               set.add("fa6279cc58de3fe6c617559684afec4f");
+               set.add("fb2a4db1a1a68dae79653dd2e32ade50");
+               set.add("fb2bc93f011d62ac03aed40d92b26ba2");
+               set.add("fb9b6d2d2d5e3c219e0163092181e014");
+               set.add("fbdc7fc274e5c71226372606beedb706");
+               set.add("fbe25dc54e2761c2c5e9f1f3a98d7f0f");
+               set.add("fbec5f910872b333be655c5143b1cb37");
+               set.add("fc372707722b210b257ef9e2f731edc3");
+               set.add("fcedc7e1d4fc17c7c4f2c6f6c7a820e0");
+               set.add("fcff88f351f2535dcbab726dec9060ee");
+               set.add("fd15d45e5f876ac3ff10cef478077e8b");
+               set.add("fd21ff84af0fe74de102f1985d068dee");
+               set.add("fd30f89057cd8ad151d612def38afb41");
+               set.add("fdab6eed0ecadf924ae128f25e8b1a10");
+               set.add("fdced723077daed39d0e4da044f75d64");
+               set.add("fddbc361461ae318e147e420a805a428");
+               set.add("fdee78ddeb6f567a636b9850f942256f");
+               set.add("fe858217631f3aaf02d8aaf849c7b2c9");
+               set.add("fec4bbfe3563397790d48ce6f5b48260");
+               set.add("ff73d7804897f4e1624a3b5571e48fbb");
+               set.add("ff78c3b27889d5365a03b3a3fd3a4c1e");
+               set.add("ffac65c383eb053e54b267fe4dfd2141");
+               
+               DIGESTS = Collections.unmodifiableSet(set);
+       }
+       
+       private PreferredMotorDigests() {
+               // Prevent instantiation
+       }
+       
+}
index fdc830aed056e78014868b46113c78ac91230c7c..ab0a00380354816c3bf577f1d1cd726ef65c01b1 100644 (file)
@@ -8,6 +8,7 @@ import java.util.List;
 import net.sf.openrocket.file.RocketSaver;
 import net.sf.openrocket.material.Material;
 import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.MotorDigest;
 import net.sf.openrocket.motor.ThrustCurveMotor;
 import net.sf.openrocket.rocketcomponent.ComponentAssembly;
 import net.sf.openrocket.rocketcomponent.MotorMount;
@@ -124,7 +125,10 @@ public class RocketComponentSaver {
                                elements.add("    <type>" + motor.getMotorType().name().toLowerCase() + "</type>");
                        }
                        if (motor instanceof ThrustCurveMotor) {
-                               elements.add("    <manufacturer>" + RocketSaver.escapeXML(((ThrustCurveMotor) motor).getManufacturer().getSimpleName()) + "</manufacturer>");
+                               ThrustCurveMotor m = (ThrustCurveMotor) motor;
+                               elements.add("    <manufacturer>" + RocketSaver.escapeXML(m.getManufacturer().getSimpleName()) +
+                                               "</manufacturer>");
+                               elements.add("    <digest>" + MotorDigest.digestMotor(m) + "</digest>");
                        }
                        elements.add("    <designation>" + RocketSaver.escapeXML(motor.getDesignation()) + "</designation>");
                        elements.add("    <diameter>" + motor.getDiameter() + "</diameter>");
diff --git a/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorComparator.java b/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/ThrustCurveMotorComparator.java
new file mode 100644 (file)
index 0000000..0096bf1
--- /dev/null
@@ -0,0 +1,30 @@
+package net.sf.openrocket.gui.dialogs.motor.thrustcurve;
+
+import java.util.Comparator;
+
+import net.sf.openrocket.motor.ThrustCurveMotor;
+
+/**
+ * Compares two ThrustCurveMotor objects for quality.
+ * 
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ThrustCurveMotorComparator implements Comparator<ThrustCurveMotor> {
+       
+
+       @Override
+       public int compare(ThrustCurveMotor o1, ThrustCurveMotor o2) {
+               return calculateGoodness(o2) - calculateGoodness(o1);
+       }
+       
+       
+       private int calculateGoodness(ThrustCurveMotor motor) {
+               /*
+                * 10 chars of comments correspond to one thrust point, max ten points.
+                */
+               int commentLength = Math.min(motor.getDescription().length(), 100);
+               return motor.getTimePoints().length * 10 + commentLength;
+       }
+       
+
+}
index da6fb51658911d55aedd36357bfc2dda5561d2f2..314219a3396bb751bdf1708702eaae0931bacde4 100644 (file)
@@ -13,7 +13,6 @@ import javax.swing.JDialog;
 import javax.swing.JPanel;
 
 import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.database.ThrustCurveMotorSet;
 import net.sf.openrocket.motor.ThrustCurveMotor;
 import net.sf.openrocket.unit.UnitGroup;
 import net.sf.openrocket.util.GUIUtil;
@@ -29,7 +28,7 @@ import org.jfree.data.xy.XYSeriesCollection;
 
 public class ThrustCurveMotorPlotDialog extends JDialog {
        
-       public ThrustCurveMotorPlotDialog(ThrustCurveMotorSet motorSet, ThrustCurveMotor selectedMotor, Window parent) {
+       public ThrustCurveMotorPlotDialog(List<ThrustCurveMotor> motors, int selected, Window parent) {
                super(parent, "Motor thrust curves", ModalityType.APPLICATION_MODAL);
                
                JPanel panel = new JPanel(new MigLayout("fill"));
@@ -73,21 +72,19 @@ public class ThrustCurveMotorPlotDialog extends JDialog {
 
                // Create the plot data set
                XYSeriesCollection dataset = new XYSeriesCollection();
-               List<ThrustCurveMotor> motors = motorSet.getMotors();
                
                // Selected thrust curve
-               int index = motors.indexOf(selectedMotor);
                int n = 0;
-               dataset.addSeries(generateSeries(selectedMotor));
-               renderer.setSeriesStroke(n, new BasicStroke(1.5f));
-               if (index >= 0) {
-                       renderer.setSeriesPaint(n, ThrustCurveMotorSelectionPanel.getColor(index));
+               if (selected >= 0) {
+                       dataset.addSeries(generateSeries(motors.get(selected)));
+                       renderer.setSeriesStroke(n, new BasicStroke(1.5f));
+                       renderer.setSeriesPaint(n, ThrustCurveMotorSelectionPanel.getColor(selected));
                }
                n++;
                
                // Other thrust curves
                for (int i = 0; i < motors.size(); i++) {
-                       if (i == index)
+                       if (i == selected)
                                continue;
                        
                        ThrustCurveMotor m = motors.get(i);
index 038359d780c6b9056a027c3e88675e05a4daa791..94f38a9758961f8410f4e917c3b97ba85ad9c7c2 100644 (file)
@@ -18,6 +18,7 @@ import java.util.prefs.Preferences;
 
 import javax.swing.BorderFactory;
 import javax.swing.DefaultComboBoxModel;
+import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
 import javax.swing.JComponent;
 import javax.swing.JLabel;
@@ -56,6 +57,7 @@ import net.sf.openrocket.util.BugException;
 import net.sf.openrocket.util.GUIUtil;
 import net.sf.openrocket.util.Icons;
 import net.sf.openrocket.util.Prefs;
+import net.sf.openrocket.utils.MotorCorrelation;
 
 import org.jfree.chart.ChartColor;
 import org.jfree.chart.ChartFactory;
@@ -71,6 +73,8 @@ import org.jfree.data.xy.XYSeriesCollection;
 public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelector {
        private static final LogHelper log = Application.getLogger();
        
+       private static final double MOTOR_SIMILARITY_THRESHOLD = 0.95;
+       
        private static final int SHOW_ALL = 0;
        private static final int SHOW_SMALLER = 1;
        private static final int SHOW_EXACT = 2;
@@ -89,6 +93,10 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
        private static final Color NO_COMMENT_COLOR = Color.GRAY;
        private static final Color WITH_COMMENT_COLOR = Color.BLACK;
        
+       private static final ThrustCurveMotorComparator MOTOR_COMPARATOR = new ThrustCurveMotorComparator();
+       
+
+
        private final List<ThrustCurveMotorSet> database;
        
        private final double diameter;
@@ -99,6 +107,8 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
        private final JTable table;
        private final TableRowSorter<TableModel> sorter;
        
+       private final JCheckBox hideSimilarBox;
+       
        private final JTextField searchField;
        private String[] searchTerms = new String[0];
        
@@ -114,6 +124,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
        private final JLabel launchMassLabel;
        private final JLabel emptyMassLabel;
        private final JLabel dataPointsLabel;
+       private final JLabel digestLabel;
        
        private final JTextArea comment;
        private final Font noCommentFont;
@@ -212,9 +223,21 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
                                                scrollSelectionVisible();
                                        }
                });
-               panel.add(filterComboBox, "spanx, growx, wrap para");
+               panel.add(filterComboBox, "spanx, growx, wrap rel");
                
 
+               hideSimilarBox = new JCheckBox("Hide very similar thrust curves");
+               GUIUtil.changeFontSize(hideSimilarBox, -1);
+               hideSimilarBox.setSelected(Prefs.getBoolean(Prefs.MOTOR_HIDE_SIMILAR, true));
+               hideSimilarBox.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               Prefs.putBoolean(Prefs.MOTOR_HIDE_SIMILAR, hideSimilarBox.isSelected());
+                               updateData();
+                       }
+               });
+               panel.add(hideSimilarBox, "gapleft para, spanx, growx, wrap para");
+               
 
                // Motor selection table
                model = new ThrustCurveMotorDatabaseModel(database);
@@ -402,6 +425,14 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
                dataPointsLabel = new JLabel();
                panel.add(dataPointsLabel, "wrap para");
                
+               if (System.getProperty("openrocket.debug.motordigest") != null) {
+                       panel.add(new JLabel("Digest:"));
+                       digestLabel = new JLabel();
+                       panel.add(digestLabel, "w :300:, wrap para");
+               } else {
+                       digestLabel = null;
+               }
+               
 
                comment = new JTextArea(5, 5);
                GUIUtil.changeFontSize(comment, -2);
@@ -461,7 +492,9 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
                                        return;
                                if (e.getButton() == MouseEvent.BUTTON1) {
                                        // Open plot dialog
-                                       ThrustCurveMotorPlotDialog plotDialog = new ThrustCurveMotorPlotDialog(selectedMotorSet, selectedMotor,
+                                       List<ThrustCurveMotor> motors = getFilteredCurves();
+                                       ThrustCurveMotorPlotDialog plotDialog = new ThrustCurveMotorPlotDialog(motors,
+                                                       motors.indexOf(selectedMotor),
                                                        SwingUtilities.getWindowAncestor(ThrustCurveMotorSelectionPanel.this));
                                        plotDialog.setVisible(true);
                                }
@@ -488,7 +521,7 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
 
 
                // Sets the filter:
-               int showMode = Prefs.getChoise("MotorDiameterMatch", SHOW_MAX, SHOW_EXACT);
+               int showMode = Prefs.getChoise(Prefs.MOTOR_DIAMETER_FILTER, SHOW_MAX, SHOW_EXACT);
                filterComboBox.setSelectedIndex(showMode);
                
 
@@ -500,7 +533,6 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
        }
        
        
-
        @Override
        public Motor getSelectedMotor() {
                return selectedMotor;
@@ -590,15 +622,21 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
                        launchMassLabel.setText("");
                        emptyMassLabel.setText("");
                        dataPointsLabel.setText("");
+                       if (digestLabel != null) {
+                               digestLabel.setText("");
+                       }
                        setComment("");
                        chart.getXYPlot().setDataset(new XYSeriesCollection());
                        return;
                }
                
 
-               List<ThrustCurveMotor> motors = selectedMotorSet.getMotors();
+               // Check which thrust curves to display
+               List<ThrustCurveMotor> motors = getFilteredCurves();
                final int index = motors.indexOf(selectedMotor);
                
+
+               // Update the thrust curve selection box
                curveSelectionModel.removeAllElements();
                for (int i = 0; i < motors.size(); i++) {
                        curveSelectionModel.addElement(new MotorHolder(motors.get(i), i));
@@ -613,6 +651,8 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
                        curveSelectionLabel.setEnabled(false);
                }
                
+
+               // Update thrust curve data
                totalImpulseLabel.setText(UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(
                                selectedMotor.getTotalImpulseEstimate()));
                avgThrustLabel.setText(UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(
@@ -626,6 +666,9 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
                emptyMassLabel.setText(UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(
                                selectedMotor.getEmptyCG().weight));
                dataPointsLabel.setText("" + (selectedMotor.getTimePoints().length - 1));
+               if (digestLabel != null) {
+                       digestLabel.setText(MotorDigest.digestMotor(selectedMotor));
+               }
                
                setComment(selectedMotor.getDescription());
                
@@ -656,6 +699,32 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
        }
        
        
+       private List<ThrustCurveMotor> getFilteredCurves() {
+               List<ThrustCurveMotor> motors = selectedMotorSet.getMotors();
+               if (hideSimilarBox.isSelected()) {
+                       List<ThrustCurveMotor> filtered = new ArrayList<ThrustCurveMotor>(motors.size());
+                       for (int i = 0; i < motors.size(); i++) {
+                               ThrustCurveMotor m = motors.get(i);
+                               if (m.equals(selectedMotor)) {
+                                       filtered.add(m);
+                                       continue;
+                               }
+                               
+                               double similarity = MotorCorrelation.similarity(selectedMotor, m);
+                               log.debug("Motor similarity: " + similarity);
+                               if (similarity < MOTOR_SIMILARITY_THRESHOLD) {
+                                       filtered.add(m);
+                               }
+                       }
+                       motors = filtered;
+               }
+               
+               Collections.sort(motors, MOTOR_COMPARATOR);
+               
+               return motors;
+       }
+       
+       
        private void setComment(String s) {
                s = s.trim();
                if (s.length() == 0) {
@@ -721,18 +790,20 @@ public class ThrustCurveMotorSelectionPanel extends JPanel implements MotorSelec
                        return set.getMotors().get(0);
                }
                
+
                // Find which motor has been used the most recently
+               List<ThrustCurveMotor> list = set.getMotors();
                Preferences prefs = Prefs.getNode(Prefs.PREFERRED_THRUST_CURVE_MOTOR_NODE);
-               for (ThrustCurveMotor m : set.getMotors()) {
+               for (ThrustCurveMotor m : list) {
                        String digest = MotorDigest.digestMotor(m);
                        if (prefs.getBoolean(digest, false)) {
                                return m;
                        }
                }
                
-               // No motor has been used, use heuristics to select motor
-               // TODO: CRITICAL: Heuristics
-               return set.getMotors().get(0);
+               // No motor has been used
+               Collections.sort(list, MOTOR_COMPARATOR);
+               return list.get(0);
        }
        
        
index ce3108dd048f35cc8e032f207eb3cd95179d97b1..2bbddd14ad054ff2763564f919964c8cd2929cb8 100644 (file)
@@ -280,12 +280,11 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> {
                        throw new BugException("Could not compute burn start time, maxThrust=" + maxThrust +
                                        " limit=" + thrustLimit + " thrust=" + Arrays.toString(thrust));
                }
-               if (MathUtil.equals(thrust[pos], thrust[pos + 1])) {
+               if (MathUtil.equals(thrust[pos - 1], thrust[pos])) {
                        // For safety
-                       burnStart = (time[pos] + time[pos + 1]) / 2;
+                       burnStart = (time[pos - 1] + time[pos]) / 2;
                } else {
-                       burnStart = MathUtil.map(thrustLimit, thrust[pos], thrust[pos + 1],
-                                       time[pos], time[pos + 1]);
+                       burnStart = MathUtil.map(thrustLimit, thrust[pos - 1], thrust[pos], time[pos - 1], time[pos]);
                }
                
 
@@ -319,9 +318,9 @@ public class ThrustCurveMotor implements Motor, Comparable<ThrustCurveMotor> {
                        double t0 = time[pos];
                        double t1 = time[pos + 1];
                        double f0 = thrust[pos];
-                       double f1 = thrust[pos];
+                       double f1 = thrust[pos + 1];
                        
-                       totalImpulse += (f0 + f1) / 2 * (t1 - t0);
+                       totalImpulse += (t1 - t0) * (f0 + f1) / 2;
                        
                        if (t0 < burnStart && t1 > burnStart) {
                                double fStart = MathUtil.map(burnStart, t0, t1, f0, f1);
index a00c9535dfa27b70d4e9c290e83dcd70f431f112..b8ef783490524740264849e1c99446d62a8c06d5 100644 (file)
@@ -18,12 +18,10 @@ public final class Application {
        
        private static ThrustCurveMotorSetDatabase motorSetDatabase;
        
+
        // Initialize the logger to something sane for testing without executing Startup
        static {
-               logger = new PrintStreamLogger();
-               for (LogLevel l : LogLevel.values()) {
-                       ((PrintStreamLogger) logger).setOutput(l, System.out);
-               }
+               setLogOutputLevel(LogLevel.DEBUG);
        }
        
        
@@ -64,7 +62,22 @@ public final class Application {
        }
        
        
-
+       /**
+        * Set the logging to output the specified log level and upwards to standard output.
+        * 
+        * @param level         the minimum logging level to output.
+        */
+       public static void setLogOutputLevel(LogLevel level) {
+               logger = new PrintStreamLogger();
+               for (LogLevel l : LogLevel.values()) {
+                       if (l.atLeast(level)) {
+                               ((PrintStreamLogger) logger).setOutput(l, System.out);
+                       }
+               }
+               
+       }
+       
+       
        /**
         * Return the database of all thrust curves loaded into the system.
         */
index 8f5a75ec50dc8e0addc6d49475e19fc1ec986ad9..53d9ff262a965bd0c957911eacca00826f106f89 100644 (file)
@@ -128,6 +128,9 @@ public class Prefs {
        private static final String CHECK_UPDATES = "CheckUpdates";
        public static final String LAST_UPDATE = "LastUpdateVersion";
        
+       public static final String MOTOR_DIAMETER_FILTER = "MotorDiameterMatch";
+       public static final String MOTOR_HIDE_SIMILAR = "MotorHideSimilar";
+       
 
        // Node names
        public static final String PREFERRED_THRUST_CURVE_MOTOR_NODE = "preferredThrustCurveMotors";
diff --git a/src/net/sf/openrocket/utils/MotorCorrelation.java b/src/net/sf/openrocket/utils/MotorCorrelation.java
new file mode 100644 (file)
index 0000000..aab9b17
--- /dev/null
@@ -0,0 +1,145 @@
+package net.sf.openrocket.utils;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.openrocket.file.GeneralMotorLoader;
+import net.sf.openrocket.file.MotorLoader;
+import net.sf.openrocket.logging.LogLevel;
+import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.MotorDigest;
+import net.sf.openrocket.motor.MotorInstance;
+import net.sf.openrocket.motor.ThrustCurveMotor;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.MathUtil;
+
+public class MotorCorrelation {
+       
+       /**
+        * Return a measure of motor similarity.  The measure is a value between 0.0 and 1.0.
+        * The larger the value, the more similar the motor thrust curves are, for value 1.0 they
+        * are identical.
+        * <p>
+        * This method takes into account the thrust curve shape, average thrust, burn time and
+        * total impulse of the motor.  The similarity is the minimum of all of these.
+        * 
+        * @param motor1        the first motor
+        * @param motor2        the second motor
+        * @return                      the similarity of the two motors
+        */
+       public static double similarity(Motor motor1, Motor motor2) {
+               double d;
+               
+               d = crossCorrelation(motor1, motor2);
+               d = Math.min(d, diff(motor1.getAverageThrustEstimate(), motor2.getAverageThrustEstimate()));
+               d = Math.min(d, 2 * diff(motor1.getBurnTimeEstimate(), motor2.getBurnTimeEstimate()));
+               d = Math.min(d, diff(motor1.getTotalImpulseEstimate(), motor2.getTotalImpulseEstimate()));
+               
+               return d;
+       }
+       
+       
+       private static double diff(double a, double b) {
+               double min = Math.min(a, b);
+               double max = Math.max(a, b);
+               
+               if (MathUtil.equals(max, 0))
+                       return 1.0;
+               return min / max;
+       }
+       
+       
+       /**
+        * Compute the cross-correlation of the thrust curves of the two motors.  The result is
+        * a double between 0 and 1 (inclusive).  The closer the return value is to one the more
+        * similar the thrust curves are.
+        * 
+        * @param motor1        the first motor.
+        * @param motor2        the second motor.
+        * @return                      the scaled cross-correlation of the two thrust curves.
+        */
+       public static double crossCorrelation(Motor motor1, Motor motor2) {
+               MotorInstance m1 = motor1.getInstance();
+               MotorInstance m2 = motor2.getInstance();
+               
+               AtmosphericConditions cond = new AtmosphericConditions();
+               
+               double t;
+               double auto1 = 0;
+               double auto2 = 0;
+               double cross = 0;
+               for (t = 0; t < 1000; t += 0.01) {
+                       m1.step(t, 0, cond);
+                       m2.step(t, 0, cond);
+                       
+                       double t1 = m1.getThrust();
+                       double t2 = m2.getThrust();
+                       
+                       if (t1 < 0 || t2 < 0) {
+                               throw new BugException("Negative thrust, t1=" + t1 + " t2=" + t2);
+                       }
+                       
+                       auto1 += t1 * t1;
+                       auto2 += t2 * t2;
+                       cross += t1 * t2;
+               }
+               
+               double auto = Math.max(auto1, auto2);
+               
+               if (MathUtil.equals(auto, 0)) {
+                       return 1.0;
+               }
+               
+               return cross / auto;
+       }
+       
+       
+
+
+       public static void main(String[] args) {
+               Application.setLogOutputLevel(LogLevel.WARN);
+               
+               MotorLoader loader = new GeneralMotorLoader();
+               List<Motor> motors = new ArrayList<Motor>();
+               List<String> files = new ArrayList<String>();
+               
+               // Load files
+               for (String file : args) {
+                       List<Motor> m = null;
+                       try {
+                               InputStream stream = new FileInputStream(file);
+                               m = loader.load(stream, file);
+                               stream.close();
+                       } catch (IOException e) {
+                               e.printStackTrace();
+                               System.exit(1);
+                       }
+                       if (m != null) {
+                               motors.addAll(m);
+                               for (int i = 0; i < m.size(); i++)
+                                       files.add(file);
+                       }
+               }
+               
+               // Output motor digests
+               final int count = motors.size();
+               for (int i = 0; i < count; i++) {
+                       System.out.println(files.get(i) + ": " + MotorDigest.digestMotor((ThrustCurveMotor) motors.get(i)));
+               }
+               
+               // Cross-correlate every pair
+               for (int i = 0; i < count; i++) {
+                       for (int j = i + 1; j < count; j++) {
+                               System.out.println(files.get(i) + " " + files.get(j) + " : " +
+                                               crossCorrelation(motors.get(i), motors.get(j)));
+                       }
+               }
+               
+       }
+       
+}
\ No newline at end of file
diff --git a/src/net/sf/openrocket/utils/MotorDigester.java b/src/net/sf/openrocket/utils/MotorDigester.java
new file mode 100644 (file)
index 0000000..d9e53a9
--- /dev/null
@@ -0,0 +1,60 @@
+package net.sf.openrocket.utils;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import net.sf.openrocket.file.GeneralMotorLoader;
+import net.sf.openrocket.file.MotorLoader;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.motor.MotorDigest;
+import net.sf.openrocket.motor.ThrustCurveMotor;
+
+public class MotorDigester {
+       
+       public static void main(String[] args) {
+               final MotorLoader loader = new GeneralMotorLoader();
+               final boolean printFileNames;
+               
+               if (args.length == 0) {
+                       System.err.println("Usage:  MotorDigester <files>");
+                       printFileNames = false;
+                       System.exit(1);
+               } else if (args.length == 1) {
+                       printFileNames = false;
+               } else {
+                       printFileNames = true;
+               }
+               
+
+               for (String file : args) {
+                       
+                       List<Motor> motors = null;
+                       try {
+                               InputStream stream = new FileInputStream(file);
+                               motors = loader.load(stream, file);
+                               stream.close();
+                       } catch (IOException e) {
+                               System.err.println("ERROR: " + e.getMessage());
+                               e.printStackTrace();
+                               continue;
+                       }
+                       
+                       for (Motor m : motors) {
+                               if (!(m instanceof ThrustCurveMotor)) {
+                                       System.err.println(file + ": Not ThrustCurveMotor: " + m);
+                                       continue;
+                               }
+                               
+                               String digest = MotorDigest.digestMotor((ThrustCurveMotor) m);
+                               if (printFileNames) {
+                                       System.out.print(file + ": ");
+                               }
+                               System.out.println(digest);
+                       }
+               }
+               
+       }
+       
+}