[med-svn] [beagle] 03/04: Imported Upstream version 4.1~160503-862+dfsg

Dylan Aïssi bob.dybian-guest at moszumanska.debian.org
Mon May 30 20:42:38 UTC 2016


This is an automated email from the git hooks/post-receive script.

bob.dybian-guest pushed a commit to branch master
in repository beagle.

commit ef336ca3d3f6ce56f78ab7440592f8d62e564a90
Author: Dylan Aïssi <bob.dybian at gmail.com>
Date:   Fri May 6 10:51:44 2016 +0200

    Imported Upstream version 4.1~160503-862+dfsg
---
 beagleutil/BasicIntInterval.java  |  296 ++++----
 blbutil/Const.java                |  186 ++---
 blbutil/IndexMap.java             |  390 +++++-----
 blbutil/IndexSet.java             |  304 ++++----
 blbutil/InputIt.java              |  640 ++++++++--------
 blbutil/StringUtil.java           |  562 +++++++-------
 blbutil/Utilities.java            |  416 +++++------
 dag/MergeableDagLevel.java        | 1460 ++++++++++++++++++-------------------
 gpl_license                       | 1348 +++++++++++++++++-----------------
 haplotype/RefHapPairs.java        |  378 +++++-----
 ibd/HaploidIbd.java               |  528 +++++++-------
 main/ConstrainedAlleleProbs.java  |   37 +-
 main/Main.java                    |   16 +-
 main/WindowWriter.java            |   24 +-
 sample/DuoBaumLevel.java          | 1282 ++++++++++++++++----------------
 sample/DuoNodes.java              |  676 ++++++++---------
 sample/HapNodes.java              |  466 ++++++------
 sample/RecombSingleBaumLevel.java |    8 +-
 sample/RecombSingleNodes.java     |  722 +++++++++---------
 sample/SingleBaumLevel.java       |    8 +-
 sample/SingleNodes.java           |  588 +++++++--------
 vcf/AllData.java                  |  710 +++++++++---------
 vcf/GprobsStatistics.java         |  106 +--
 vcf/Markers.java                  |  778 ++++++++++----------
 vcf/VcfWindow.java                |  642 ++++++++--------
 vcf/VcfWriter.java                |  140 +++-
 26 files changed, 6399 insertions(+), 6312 deletions(-)

diff --git a/beagleutil/BasicIntInterval.java b/beagleutil/BasicIntInterval.java
index ed1d786..b7814d4 100644
--- a/beagleutil/BasicIntInterval.java
+++ b/beagleutil/BasicIntInterval.java
@@ -1,148 +1,148 @@
-/*
- * Copyright 2014 Brian L. Browning
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package beagleutil;
-
-/**
- * <p>Class {@code BasicIntInterval} represents an interval of
- * consecutive integers.
- * </p>
- * Instances of class {@code BasicIntInterval} are immutable.
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
-
-} */
-public final class BasicIntInterval implements IntInterval,
-        Comparable<BasicIntInterval> {
-
-    private final int start;
-    private final int end;
-
-    /**
-     * Constructs an {@code SimpleIntInterval} instance.
-     * @param start the starting integer (inclusive).
-     * @param end the ending integer (inclusive).
-     * @throws IllegalArgumentException if {@code start>end}.
-     */
-    public BasicIntInterval(int start, int end) {
-        if (start > end) {
-            String s = "start=" + start + " end=" + end;
-            throw new IllegalArgumentException(s);
-        }
-        this.start = start;
-        this.end = end;
-    }
-
-    @Override
-    public int start() {
-        return start;
-    }
-
-    @Override
-    public int end() {
-        return end;
-    }
-
-    /**
-     * <p>Returns a hash code value for the object.
-     * </p>
-     * <p>The hash code is defined by the following calculation:
-     * </p>
-     * <pre>
-        int hash = 3;
-        hash += 59 * hash + this.start();
-        hash += 59 * hash + this.end();
-     * </pre>
-     * @return a hash code value for the object.
-     */
-    @Override
-    public int hashCode() {
-        int hash = 3;
-        hash = 59 * hash + this.start;
-        hash = 59 * hash + this.end;
-        return hash;
-    }
-
-    /**
-     * Returns {@code true} if the specified object is an
-     * {@code BasicIntInterval} instance and
-     * {@code this.start() == ((BasicIntInterval) obj).start()}, and
-     * {@code this.end() == ((BasicIntInterval) obj).end()},
-     * and returns {@code false} otherwise.
-     *
-     * @param obj the object to be compared with {@code this} for equality.
-     * @return {@code true} if the specified object is equal to
-     * {@code this}, and returns false otherwise.
-     */
-    @Override
-    public boolean equals(Object obj) {
-        if (this==obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final BasicIntInterval other = (BasicIntInterval) obj;
-        if (this.start != other.start) {
-            return false;
-        }
-        return this.end==other.end;
-    }
-
-    /**
-     * Compares the specified {@code BasicIntInterval} with this for order,
-     * and returns a negative integer, zero, or a positive integer as
-     * {@code this} is less than, equal to, or greater than the specified
-     * {@code BasicIntInterval} object.
-     * {@code BasicIntInterval} objects are
-     * ordered by their start and their end values in that order.
-     *
-     * @param o the {@code BasicIntInterval} to be compared with this.
-     *
-     * @return a negative integer, zero, or a positive integer as this
-     * object is less than, equal to, or greater than the specified object.
-     */
-    @Override
-    public int compareTo(BasicIntInterval o) {
-        if (this.start != o.start) {
-            return this.start < o.start ? -1 : 1;
-        }
-        else if (this.end != o.end) {
-            return this.end < o.end ? -1 : 1;
-        }
-        return 0;
-    }
-
-    /**
-     * Returns a string representation of {@code this}.  The exact
-     * details of the representation are unspecified and subject to change.
-     *
-     * @return a string representation of {@code this}.  The exact
-     * details of the representation are unspecified and subject to change.
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append('[');
-        sb.append(start);
-        sb.append(", ");
-        sb.append(end);
-        sb.append(']');
-        return sb.toString();
-    }
-}
+/*
+ * Copyright 2014 Brian L. Browning
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package beagleutil;
+
+/**
+ * <p>Class {@code BasicIntInterval} represents an interval of
+ * consecutive integers.
+ * </p>
+ * Instances of class {@code BasicIntInterval} are immutable.
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+
+} */
+public final class BasicIntInterval implements IntInterval,
+        Comparable<BasicIntInterval> {
+
+    private final int start;
+    private final int end;
+
+    /**
+     * Constructs an {@code SimpleIntInterval} instance.
+     * @param start the starting integer (inclusive).
+     * @param end the ending integer (inclusive).
+     * @throws IllegalArgumentException if {@code start>end}.
+     */
+    public BasicIntInterval(int start, int end) {
+        if (start > end) {
+            String s = "start=" + start + " end=" + end;
+            throw new IllegalArgumentException(s);
+        }
+        this.start = start;
+        this.end = end;
+    }
+
+    @Override
+    public int start() {
+        return start;
+    }
+
+    @Override
+    public int end() {
+        return end;
+    }
+
+    /**
+     * <p>Returns a hash code value for the object.
+     * </p>
+     * <p>The hash code is defined by the following calculation:
+     * </p>
+     * <pre>
+        int hash = 3;
+        hash += 59 * hash + this.start();
+        hash += 59 * hash + this.end();
+     * </pre>
+     * @return a hash code value for the object.
+     */
+    @Override
+    public int hashCode() {
+        int hash = 3;
+        hash = 59 * hash + this.start;
+        hash = 59 * hash + this.end;
+        return hash;
+    }
+
+    /**
+     * Returns {@code true} if the specified object is an
+     * {@code BasicIntInterval} instance and
+     * {@code this.start() == ((BasicIntInterval) obj).start()}, and
+     * {@code this.end() == ((BasicIntInterval) obj).end()},
+     * and returns {@code false} otherwise.
+     *
+     * @param obj the object to be compared with {@code this} for equality.
+     * @return {@code true} if the specified object is equal to
+     * {@code this}, and returns false otherwise.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this==obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final BasicIntInterval other = (BasicIntInterval) obj;
+        if (this.start != other.start) {
+            return false;
+        }
+        return this.end==other.end;
+    }
+
+    /**
+     * Compares the specified {@code BasicIntInterval} with this for order,
+     * and returns a negative integer, zero, or a positive integer as
+     * {@code this} is less than, equal to, or greater than the specified
+     * {@code BasicIntInterval} object.
+     * {@code BasicIntInterval} objects are
+     * ordered by their start and their end values in that order.
+     *
+     * @param o the {@code BasicIntInterval} to be compared with this.
+     *
+     * @return a negative integer, zero, or a positive integer as this
+     * object is less than, equal to, or greater than the specified object.
+     */
+    @Override
+    public int compareTo(BasicIntInterval o) {
+        if (this.start != o.start) {
+            return this.start < o.start ? -1 : 1;
+        }
+        else if (this.end != o.end) {
+            return this.end < o.end ? -1 : 1;
+        }
+        return 0;
+    }
+
+    /**
+     * Returns a string representation of {@code this}.  The exact
+     * details of the representation are unspecified and subject to change.
+     *
+     * @return a string representation of {@code this}.  The exact
+     * details of the representation are unspecified and subject to change.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append('[');
+        sb.append(start);
+        sb.append(", ");
+        sb.append(end);
+        sb.append(']');
+        return sb.toString();
+    }
+}
diff --git a/blbutil/Const.java b/blbutil/Const.java
index a7f4ccf..46d8ad2 100644
--- a/blbutil/Const.java
+++ b/blbutil/Const.java
@@ -1,93 +1,93 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package blbutil;
-
-/**
- * Class {@code Const} provides public static final fields with
- * string and character constants.
- *
- * @author Brian L Browning
- */
-public class Const {
-
-    private Const() {
-        // private constructor to prevent instantiation.
-    }
-
-    /**
-     * The system-dependent string representing a new line-line:
-     * {@code System.getProperty("line.separator")}
-     */
-    public static final String nl = System.getProperty("line.separator");
-
-    /**
-     * The VCF missing-data symbol as a string: {@code "."}
-     */
-    public static final String MISSING_DATA_STRING = ".";
-
-    /**
-     * The VCF missing-data symbol as a character: {@code '.'}
-     */
-    public static final char MISSING_DATA_CHAR = '.';
-
-    /**
-     * The colon character: {@code ':'}
-     */
-    public static final char colon = ':';
-
-    /**
-     * The hyphen character: {@code '-'}
-     */
-    public static final char hyphen = '-';
-
-    /**
-     * The tab character: {@code '\t'}
-     */
-    public static final char tab = '\t';
-
-    /**
-     * The semicolon character: {@code ';'}
-     */
-    public static final char semicolon = ';';
-
-    /**
-     * The comma character: {@code ','}
-     */
-    public static final char comma = ',';
-
-    /**
-     * The phased allele separator: {@code '|'}
-     */
-    public static final char phasedSep = '|';
-
-    /**
-     *  The unphased allele separator: {@code '/'}
-     */
-    public static final char unphasedSep = '/';
-
-    /**
-     * The value 1,000,000,000
-     */
-    public static final int giga = 1000000000;
-
-    /**
-     * The value 1,000,000
-     */
-    public static final int mega = 1000000;
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package blbutil;
+
+/**
+ * Class {@code Const} provides public static final fields with
+ * string and character constants.
+ *
+ * @author Brian L Browning
+ */
+public class Const {
+
+    private Const() {
+        // private constructor to prevent instantiation.
+    }
+
+    /**
+     * The system-dependent string representing a new line-line:
+     * {@code System.getProperty("line.separator")}
+     */
+    public static final String nl = System.getProperty("line.separator");
+
+    /**
+     * The VCF missing-data symbol as a string: {@code "."}
+     */
+    public static final String MISSING_DATA_STRING = ".";
+
+    /**
+     * The VCF missing-data symbol as a character: {@code '.'}
+     */
+    public static final char MISSING_DATA_CHAR = '.';
+
+    /**
+     * The colon character: {@code ':'}
+     */
+    public static final char colon = ':';
+
+    /**
+     * The hyphen character: {@code '-'}
+     */
+    public static final char hyphen = '-';
+
+    /**
+     * The tab character: {@code '\t'}
+     */
+    public static final char tab = '\t';
+
+    /**
+     * The semicolon character: {@code ';'}
+     */
+    public static final char semicolon = ';';
+
+    /**
+     * The comma character: {@code ','}
+     */
+    public static final char comma = ',';
+
+    /**
+     * The phased allele separator: {@code '|'}
+     */
+    public static final char phasedSep = '|';
+
+    /**
+     *  The unphased allele separator: {@code '/'}
+     */
+    public static final char unphasedSep = '/';
+
+    /**
+     * The value 1,000,000,000
+     */
+    public static final int giga = 1000000000;
+
+    /**
+     * The value 1,000,000
+     */
+    public static final int mega = 1000000;
+}
diff --git a/blbutil/IndexMap.java b/blbutil/IndexMap.java
index b15ea4a..0ca3fd8 100644
--- a/blbutil/IndexMap.java
+++ b/blbutil/IndexMap.java
@@ -1,195 +1,195 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package blbutil;
-
-import java.util.Arrays;
-
-/**
- * <p>Class {@code IndexMap} is a map whose keys are a bounded set of
- * non-negative integers and whose values are integers.
- * </p>
- * <p>Class {@code IndexMap} supports a {@code clear()} method, but it does not
- * support a {@code remove()} method.
- * </p>
- * <p>Class {@code IndexMap} is not thread-safe.
- * </p>
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class IndexMap {
-
-    private final int nil;
-    private final int[] values;
-    private final int[] keys;
-    private int size = 0;
-
-    /**
-     * Creates a new instance of {@code IndexMap} whose {@code nil()} method
-     * will return the specified {@code nil} value.
-     * @param maxKey the maximum key
-     * @param nil the value that will be returned by the instance's
-     * {@code get()} method if a key has no assigned value
-     * @throws IllegalArgumentException if {@code maxKey < 0}
-     */
-    public IndexMap(int maxKey, int nil) {
-        if (maxKey < 0) {
-            throw new IllegalArgumentException(String.valueOf(maxKey));
-        }
-        this.nil = nil;
-        this.values = new int[maxKey+1];
-        this.keys = new int[maxKey+1];
-        Arrays.fill(values, nil);
-    }
-
-    /**
-     * Returns the value that is returned by {@code this.get()} if
-     * a key has no assigned value.
-     * @return the value that is returned by {@code this.get()} if
-     * a key has no assigned value
-     */
-    public int nil() {
-        return nil;
-    }
-
-    /**
-     * Adds the specified key and value to the map. If the map
-     * contains a value for the specified key when the method is invoked,
-     * the old value is replaced by the specified value.
-     *
-     * @param key the key
-     * @param value the value
-     * @return the previous value associated with {@code key}, or
-     * {@code this.nil()} if no such previous value exists
-     *
-     * @throws IllegalArgumentException if {@code value == this.nil()}
-     * @throws IndexOutOfBoundsException if
-     * {@code key < 0 || key > this.maxKey()}
-     */
-    public int put(int key, int value) {
-        if (value==nil) {
-            throw new IllegalArgumentException("value==nil()");
-        }
-        int prevValue = values[key];
-        if (prevValue == nil) {
-            keys[size++] = key;
-        }
-        this.values[key] = value;
-        return prevValue;
-    }
-
-    /**
-     * Returns the value associated with the specified key, or
-     * {@code this.nil()} if the specified key is not contained in the map.
-     * @param key the key
-     * @return the value associated with the specified key, or
-     * {@code this.nil()} if the specified key is not contained in the map.
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code key < 0 || key > this.maxKey()}
-     */
-    public int get(int key) {
-        return values[key];
-    }
-
-    /**
-     * Returns the number of key-value pairs in the map.
-     *
-     * @return the number of key-value pairs in the map
-     */
-    public int size() {
-        return size;
-    }
-
-    /**
-     * Returns the maximum key.
-     *
-     * @return the maximum key
-     */
-    public int maxKey() {
-        return keys.length-1;
-    }
-
-    /**
-     * Removes all key-value pairs from the map.
-     */
-    public void clear() {
-        for (int j=0, n=size; j<n; ++j) {
-            values[keys[j]] = nil;
-        }
-        size = 0;
-    }
-
-    /**
-     * Returns the specified key in an enumeration of the keys in the map.
-     * @param index an index of an element in the enumeration
-     * @return the specified key in an enumeration of the keys-value
-     * pairs in the map
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public int enumeratedKey(int index) {
-        if (index>=size) {
-            throw new IndexOutOfBoundsException(String.valueOf(index));
-        }
-        return keys[index];
-    }
-
-    /**
-     * Returns the value associated with the specified key
-     * in an enumeration of the keys in the map.
-     * If {@code (index >= 0 && index < this.size())}, then the returned value
-     * will satisfy:
-     * {@code this.get(this.enumeratedKey(index)==this.enumeratedValue(index)}.
-     * @param index an index of an element in the enumeration
-     * @return the value associated with the specified key
-     * in an enumeration of the keys in the map
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public int enumeratedValue(int index) {
-        if (index>=size) {
-            throw new IndexOutOfBoundsException(String.valueOf(index));
-        }
-        return values[keys[index]];
-    }
-
-    /**
-     * Returns a string representation of {@code this}.  The exact
-     * details of the representation are unspecified and subject to change.
-     *
-     * @return a string representation of {@code this}.
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder(80);
-        sb.append("size=");
-        sb.append(size);
-        sb.append(" {");
-        for (int j=0; j<size; ++j) {
-            sb.append(enumeratedKey(j));
-            sb.append(" : ");
-            sb.append(enumeratedValue(j));
-            if (j+1 < size) {
-                sb.append(Const.comma);
-            }
-        }
-        sb.append("}");
-        return sb.toString();
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package blbutil;
+
+import java.util.Arrays;
+
+/**
+ * <p>Class {@code IndexMap} is a map whose keys are a bounded set of
+ * non-negative integers and whose values are integers.
+ * </p>
+ * <p>Class {@code IndexMap} supports a {@code clear()} method, but it does not
+ * support a {@code remove()} method.
+ * </p>
+ * <p>Class {@code IndexMap} is not thread-safe.
+ * </p>
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class IndexMap {
+
+    private final int nil;
+    private final int[] values;
+    private final int[] keys;
+    private int size = 0;
+
+    /**
+     * Creates a new instance of {@code IndexMap} whose {@code nil()} method
+     * will return the specified {@code nil} value.
+     * @param maxKey the maximum key
+     * @param nil the value that will be returned by the instance's
+     * {@code get()} method if a key has no assigned value
+     * @throws IllegalArgumentException if {@code maxKey < 0}
+     */
+    public IndexMap(int maxKey, int nil) {
+        if (maxKey < 0) {
+            throw new IllegalArgumentException(String.valueOf(maxKey));
+        }
+        this.nil = nil;
+        this.values = new int[maxKey+1];
+        this.keys = new int[maxKey+1];
+        Arrays.fill(values, nil);
+    }
+
+    /**
+     * Returns the value that is returned by {@code this.get()} if
+     * a key has no assigned value.
+     * @return the value that is returned by {@code this.get()} if
+     * a key has no assigned value
+     */
+    public int nil() {
+        return nil;
+    }
+
+    /**
+     * Adds the specified key and value to the map. If the map
+     * contains a value for the specified key when the method is invoked,
+     * the old value is replaced by the specified value.
+     *
+     * @param key the key
+     * @param value the value
+     * @return the previous value associated with {@code key}, or
+     * {@code this.nil()} if no such previous value exists
+     *
+     * @throws IllegalArgumentException if {@code value == this.nil()}
+     * @throws IndexOutOfBoundsException if
+     * {@code key < 0 || key > this.maxKey()}
+     */
+    public int put(int key, int value) {
+        if (value==nil) {
+            throw new IllegalArgumentException("value==nil()");
+        }
+        int prevValue = values[key];
+        if (prevValue == nil) {
+            keys[size++] = key;
+        }
+        this.values[key] = value;
+        return prevValue;
+    }
+
+    /**
+     * Returns the value associated with the specified key, or
+     * {@code this.nil()} if the specified key is not contained in the map.
+     * @param key the key
+     * @return the value associated with the specified key, or
+     * {@code this.nil()} if the specified key is not contained in the map.
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code key < 0 || key > this.maxKey()}
+     */
+    public int get(int key) {
+        return values[key];
+    }
+
+    /**
+     * Returns the number of key-value pairs in the map.
+     *
+     * @return the number of key-value pairs in the map
+     */
+    public int size() {
+        return size;
+    }
+
+    /**
+     * Returns the maximum key.
+     *
+     * @return the maximum key
+     */
+    public int maxKey() {
+        return keys.length-1;
+    }
+
+    /**
+     * Removes all key-value pairs from the map.
+     */
+    public void clear() {
+        for (int j=0, n=size; j<n; ++j) {
+            values[keys[j]] = nil;
+        }
+        size = 0;
+    }
+
+    /**
+     * Returns the specified key in an enumeration of the keys in the map.
+     * @param index an index of an element in the enumeration
+     * @return the specified key in an enumeration of the keys-value
+     * pairs in the map
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public int enumeratedKey(int index) {
+        if (index>=size) {
+            throw new IndexOutOfBoundsException(String.valueOf(index));
+        }
+        return keys[index];
+    }
+
+    /**
+     * Returns the value associated with the specified key
+     * in an enumeration of the keys in the map.
+     * If {@code (index >= 0 && index < this.size())}, then the returned value
+     * will satisfy:
+     * {@code this.get(this.enumeratedKey(index)==this.enumeratedValue(index)}.
+     * @param index an index of an element in the enumeration
+     * @return the value associated with the specified key
+     * in an enumeration of the keys in the map
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public int enumeratedValue(int index) {
+        if (index>=size) {
+            throw new IndexOutOfBoundsException(String.valueOf(index));
+        }
+        return values[keys[index]];
+    }
+
+    /**
+     * Returns a string representation of {@code this}.  The exact
+     * details of the representation are unspecified and subject to change.
+     *
+     * @return a string representation of {@code this}.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(80);
+        sb.append("size=");
+        sb.append(size);
+        sb.append(" {");
+        for (int j=0; j<size; ++j) {
+            sb.append(enumeratedKey(j));
+            sb.append(" : ");
+            sb.append(enumeratedValue(j));
+            if (j+1 < size) {
+                sb.append(Const.comma);
+            }
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+}
diff --git a/blbutil/IndexSet.java b/blbutil/IndexSet.java
index f2076e8..f1cebb9 100644
--- a/blbutil/IndexSet.java
+++ b/blbutil/IndexSet.java
@@ -1,152 +1,152 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package blbutil;
-
-import java.util.Arrays;
-
-/**
- * <p>Class {@code IndexSet} is a set that stores non-negative indices that are
- * less than or equal to a specified maximum value.
- * </p>
- * <p>Class {@code IndexSet} supports a {@code clear()} method, but it does not
- * support a {@code remove()} method.
- * </p>
- * <p>Class {@code IndexSet} is not thread-safe.
- * </p>
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class IndexSet {
-
-    private final boolean[] inSet;
-    private final int[] indices;
-    private int size = 0;
-
-    /**
-     * Creates a new instance of {@code IndexSet} that can contain
-     * non-negative integer indices that are less than or equal to the specified
-     * maximum value.
-     *
-     * @param max the maximum element that is permitted in the set.
-     * @throws IllegalArgumentException if {@code max < 0}
-     */
-    public IndexSet(int max) {
-        if (max < 0) {
-            throw new IllegalArgumentException(String.valueOf(max));
-        }
-        this.inSet = new boolean[max+1];
-        this.indices = new int[max+1];
-    }
-
-    /**
-     * Adds the specified element to the set.
-     *
-     * @param element an element to add to this set.
-     * @return {@code true} if the set was changed by the operation, and
-     * {@code false} otherwise.
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index > this.maxPermittedIndex()}
-     */
-    public boolean add(int element) {
-        if (inSet[element]==false) {
-            indices[size++] = element;
-            inSet[element]=true;
-            return true;
-        }
-        else {
-            return false;
-        }
-    }
-
-    /**
-     * Returns {@code true} if the set contains the specified element,
-     * and returns {@code false} otherwise.
-     * @param element an element
-     * @return {@code true} if the set contains the specified element
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index > this.maxPermittedIndex()}
-     */
-    public boolean contains(int element) {
-        return inSet[element];
-    }
-
-    /**
-     * Returns the number of elements in this set.
-     *
-     * @return the number of elements in this set
-     */
-    public int size() {
-        return size;
-    }
-
-    /**
-     * Returns the maximum permitted element in the set.
-     *
-     * @return the maximum permitted element in the set
-     */
-    public int maxPermittedElement() {
-        return indices.length-1;
-    }
-
-    /**
-     * Removes all elements from the set.
-     */
-    public void clear() {
-        for (int j=0, n=size; j<n; ++j) {
-            inSet[indices[j]] = false;
-        }
-        size = 0;
-    }
-
-    /**
-     * Returns the specified element in an enumeration of the elements in the
-     * set.
-     * @param enumIndex an index of an element in the enumeration
-     * @return the specified element in an enumeration of the elements in the
-     * set
-     * @throws IndexOutOfBoundsException if
-     * {@code enumIndex < 0 || enumIndex >= this.size()}
-     */
-    public int enumeratedValue(int enumIndex) {
-        if (enumIndex>=size) {
-            throw new IndexOutOfBoundsException(String.valueOf(enumIndex));
-        }
-        return indices[enumIndex];
-    }
-
-    /**
-     * Returns an array containing the elements in this set.
-     * @return an array containing the elements in this set
-     */
-    public int[] toArray() {
-        return Arrays.copyOf(indices, size);
-    }
-
-    /**
-     * Returns {@code java.util.Arrays.toString(this.toArray())}.
-     *
-     * @return {@code java.util.Arrays.toString(this.toArray())}
-     */
-    @Override
-    public String toString() {
-        return Arrays.toString(toArray());
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package blbutil;
+
+import java.util.Arrays;
+
+/**
+ * <p>Class {@code IndexSet} is a set that stores non-negative indices that are
+ * less than or equal to a specified maximum value.
+ * </p>
+ * <p>Class {@code IndexSet} supports a {@code clear()} method, but it does not
+ * support a {@code remove()} method.
+ * </p>
+ * <p>Class {@code IndexSet} is not thread-safe.
+ * </p>
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class IndexSet {
+
+    private final boolean[] inSet;
+    private final int[] indices;
+    private int size = 0;
+
+    /**
+     * Creates a new instance of {@code IndexSet} that can contain
+     * non-negative integer indices that are less than or equal to the specified
+     * maximum value.
+     *
+     * @param max the maximum element that is permitted in the set.
+     * @throws IllegalArgumentException if {@code max < 0}
+     */
+    public IndexSet(int max) {
+        if (max < 0) {
+            throw new IllegalArgumentException(String.valueOf(max));
+        }
+        this.inSet = new boolean[max+1];
+        this.indices = new int[max+1];
+    }
+
+    /**
+     * Adds the specified element to the set.
+     *
+     * @param element an element to add to this set.
+     * @return {@code true} if the set was changed by the operation, and
+     * {@code false} otherwise.
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index > this.maxPermittedIndex()}
+     */
+    public boolean add(int element) {
+        if (inSet[element]==false) {
+            indices[size++] = element;
+            inSet[element]=true;
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+
+    /**
+     * Returns {@code true} if the set contains the specified element,
+     * and returns {@code false} otherwise.
+     * @param element an element
+     * @return {@code true} if the set contains the specified element
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index > this.maxPermittedIndex()}
+     */
+    public boolean contains(int element) {
+        return inSet[element];
+    }
+
+    /**
+     * Returns the number of elements in this set.
+     *
+     * @return the number of elements in this set
+     */
+    public int size() {
+        return size;
+    }
+
+    /**
+     * Returns the maximum permitted element in the set.
+     *
+     * @return the maximum permitted element in the set
+     */
+    public int maxPermittedElement() {
+        return indices.length-1;
+    }
+
+    /**
+     * Removes all elements from the set.
+     */
+    public void clear() {
+        for (int j=0, n=size; j<n; ++j) {
+            inSet[indices[j]] = false;
+        }
+        size = 0;
+    }
+
+    /**
+     * Returns the specified element in an enumeration of the elements in the
+     * set.
+     * @param enumIndex an index of an element in the enumeration
+     * @return the specified element in an enumeration of the elements in the
+     * set
+     * @throws IndexOutOfBoundsException if
+     * {@code enumIndex < 0 || enumIndex >= this.size()}
+     */
+    public int enumeratedValue(int enumIndex) {
+        if (enumIndex>=size) {
+            throw new IndexOutOfBoundsException(String.valueOf(enumIndex));
+        }
+        return indices[enumIndex];
+    }
+
+    /**
+     * Returns an array containing the elements in this set.
+     * @return an array containing the elements in this set
+     */
+    public int[] toArray() {
+        return Arrays.copyOf(indices, size);
+    }
+
+    /**
+     * Returns {@code java.util.Arrays.toString(this.toArray())}.
+     *
+     * @return {@code java.util.Arrays.toString(this.toArray())}
+     */
+    @Override
+    public String toString() {
+        return Arrays.toString(toArray());
+    }
+}
diff --git a/blbutil/InputIt.java b/blbutil/InputIt.java
index ae2b633..eb34f31 100644
--- a/blbutil/InputIt.java
+++ b/blbutil/InputIt.java
@@ -1,320 +1,320 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package blbutil;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.NoSuchElementException;
-import java.util.zip.GZIPInputStream;
-import net.sf.samtools.util.BlockCompressedInputStream;
-
-/**
- * <p>Class {@code InputIt} is a buffered iterator whose {@code next()}
- * method returns lines of a text input stream.
- * </p>
- * <p>If an {@code IOException} is thrown when an {@code InputIt}
- * instance reads from the text input stream, the {@code IOException}
- * is trapped, an error message is written to standard out, and the
- * Java Virtual Machine is terminated.
- * </p>
- * Instances of class {@code InputIt} are not thread-safe.
- *
- * @see #DEFAULT_BUFFER_SIZE
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class InputIt implements FileIt<String> {
-
-    /**
-     * The default buffer size, which is 4,194,304 bytes.
-     */
-    public static final int DEFAULT_BUFFER_SIZE = 1<<22;
-
-    private final File file;
-    private final BufferedReader in;
-    private String next = null;
-
-    /**
-     * Constructs a new {@code InputStreamIterator} with default buffer
-     * size that will iterate through lines of the specified input stream.
-     *
-     * @param is input stream of text data
-     *
-     * @see #DEFAULT_BUFFER_SIZE
-     */
-    private InputIt(InputStream is, File file) {
-        this(is, file, DEFAULT_BUFFER_SIZE);
-    }
-
-    /**
-     * Constructs a new {@code InputStreamIterator} with default buffer size
-     * that will iterate through the lines of the specified input stream.
-     *
-     * @param is input stream of text data
-     * @param bufferSize the buffer size in bytes
-     *
-     * @throws IllegalArgumentException if {@code bufferSize < 0}
-     */
-    private InputIt(InputStream is, File file, int bufferSize) {
-        BufferedReader br = null;
-        try {
-            InputStreamReader isr = new InputStreamReader(is);
-            br = new BufferedReader(isr, bufferSize);
-            next = br.readLine();
-        }
-        catch(IOException e) {
-            Utilities.exit("Error reading " + is, e);
-        }
-        this.in = br;
-        this.file = file;
-    }
-
-    @Override
-    public File file() {
-        return file;
-    }
-
-    /**
-     * Returns {@code true} if the iteration has more elements.
-     * @return {@code true} if the iteration has more elements
-     */
-    @Override
-    public boolean hasNext() {
-        return (next != null);
-    }
-
-    /**
-     * Returns the next element in the iteration.
-     * @return the next element in the iteration
-     * @throws NoSuchElementException if the iteration has no more elements
-     */
-    @Override
-    public String next() {
-        if (!hasNext()) {
-            throw new NoSuchElementException();
-        }
-        String current = next;
-        try {
-            next = in.readLine();
-        }
-        catch (IOException e) {
-            Utilities.exit("Error reading " + in, e);
-        }
-        return current;
-    }
-
-    /**
-     * The {@code remove} method is not supported by this iterator.
-     * @throws UnsupportedOperationException if this method is invoked
-     */
-    @Override
-    public void remove() {
-        String s = this.getClass().toString() + ".remove()";
-        throw new UnsupportedOperationException(s);
-    }
-
-    @Override
-    public void close() {
-        try {
-            in.close();
-        }
-        catch (IOException e) {
-            Utilities.exit("Error closing " + in, e);
-        }
-        next=null;
-    }
-
-    /**
-     * Returns a string representation of this iterator.  The exact details
-     * of the representation are unspecified and subject to change.
-     * @return a string representation of this iterator
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder(200);
-        sb.append("[file= ");
-        sb.append(file);
-        sb.append("; next=\"");
-        sb.append(next);
-        sb.append("\"]");
-        return sb.toString();
-    }
-
-    /**
-     * Constructs and returns an {@code InputIt} instance with the default
-     * buffer size that iterates through lines of text read from standard input.
-     *
-     * @return a new {@code InputIt} instance that iterates
-     * through lines of text read from standard input
-     */
-    public static InputIt fromStdIn() {
-        File file = null;
-        return new InputIt(System.in, file);
-    }
-
-    /**
-     * Constructs and returns an {@code InputIt} instance with the specified
-     * buffer size that iterates through lines of text read from standard input.
-     *
-     * @param bufferSize the buffer size in bytes
-     *
-     * @return a new {@code InputIt} instance that iterates
-     * through lines of text read from standard input
-     *
-     * @throws IllegalArgumentException if {@code bufferSize < 0}
-     */
-    public static InputIt fromStdIn(int bufferSize) {
-        File file = null;
-        return new InputIt(System.in, file, bufferSize);
-    }
-
-    /**
-     * Constructs and returns an {@code InputIt} instance with the default
-     * buffer size that iterates through lines of the specified compressed
-     * or uncompressed text file. If the filename ends in ".gz", the file
-     * must be either BGZIP-compressed or GZIP-compressed.
-     *
-     * @param file a compressed or uncompressed text file
-     * @return  a new {@code InputIt} instance that iterates
-     * through lines of the specified text file
-     *
-     * @throws NullPointerException if {@code file == null}
-     */
-    public static InputIt fromGzipFile(File file) {
-        try {
-            InputStream is = new FileInputStream(file);
-            if (file.getName().endsWith(".gz")) {
-                if (isBGZipFile(file)) {
-                    return new InputIt(
-                            new BlockCompressedInputStream(is), file);
-                }
-                else {
-                    return new InputIt(new GZIPInputStream(is), file);
-                }
-            }
-            else {
-                return new InputIt(is, file);
-            }
-        }
-        catch(FileNotFoundException e) {
-            Utilities.exit("Error opening " + file, e);
-        }
-        catch(IOException e) {
-            Utilities.exit("Error reading " + file, e);
-        }
-        assert false;
-        return null;
-    }
-
-    /**
-     * Constructs and returns an {@code InputIt} instance with the specified
-     * buffer size that iterates through lines of the specified compressed
-     * or uncompressed text file. If the filename ends in ".gz", the file must
-     * be either BGZIP-compressed or GZIP-compressed.
-     *
-     * @param file a compressed or uncompressed text file
-     * @param bufferSize the buffer size in bytes
-     * @return  a new {@code InputIt} instance that iterates
-     * through lines of the specified text file
-     *
-     * @throws IllegalArgumentException if {@code bufferSize < 0}
-     * @throws NullPointerException if {@code file == null}
-     */
-    public static InputIt fromGzipFile(File file, int bufferSize) {
-        try {
-            InputStream is = new FileInputStream(file);
-            if (file.getName().endsWith(".gz")) {
-                if (isBGZipFile(file)) {
-                    return new InputIt(
-                            new BlockCompressedInputStream(is), file, bufferSize);
-                }
-                else {
-                    return new InputIt(new GZIPInputStream(is), file, bufferSize);
-                }
-            }
-            else {
-                return new InputIt(is, file);
-            }
-        }
-        catch(FileNotFoundException e) {
-            Utilities.exit("Error opening " + file, e);
-        }
-        catch(IOException e) {
-            Utilities.exit("Error reading " + file, e);
-        }
-        assert false;
-        return null;
-    }
-
-    private static boolean isBGZipFile(File file) throws IOException {
-        try (InputStream is=new BufferedInputStream(new FileInputStream(file))) {
-            return BlockCompressedInputStream.isValidFile(is);
-        }
-     }
-
-     /**
-     * Constructs and returns an {@code InputIt} instance with the default
-     * buffer size that iterates through lines of the specified text file.
-     *
-     * @param file a text file
-     * @return a new {@code InputIt} instance that iterates through
-     * lines of the specified text file
-     *
-     * @throws NullPointerException if {@code filename == null}
-     */
-    public static InputIt fromTextFile(File file) {
-        try {
-            return new InputIt(new FileInputStream(file), file);
-        }
-        catch(FileNotFoundException e) {
-            Utilities.exit("Error opening " + file, e);
-        }
-        assert false;
-        return null;
-    }
-
-     /**
-     * Constructs and returns an {@code InputIt} instance with the specified
-     * buffer size that iterates through lines of the specified text file.
-     *
-     * @param file a text file
-     * @param bufferSize the buffer size in bytes
-     * @return a new {@code InputIt} instance that iterates through
-     * lines of the specified text file
-     *
-     * @throws IllegalArgumentException if {@code bufferSize < 0}
-     * @throws NullPointerException if {@code filename == null}
-     */
-    public static InputIt fromTextFile(File file, int bufferSize) {
-        try {
-            return new InputIt(new FileInputStream(file), file, bufferSize);
-        }
-        catch(FileNotFoundException e) {
-            Utilities.exit("Error opening " + file, e);
-        }
-        assert false;
-        return null;
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package blbutil;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.NoSuchElementException;
+import java.util.zip.GZIPInputStream;
+import net.sf.samtools.util.BlockCompressedInputStream;
+
+/**
+ * <p>Class {@code InputIt} is a buffered iterator whose {@code next()}
+ * method returns lines of a text input stream.
+ * </p>
+ * <p>If an {@code IOException} is thrown when an {@code InputIt}
+ * instance reads from the text input stream, the {@code IOException}
+ * is trapped, an error message is written to standard out, and the
+ * Java Virtual Machine is terminated.
+ * </p>
+ * Instances of class {@code InputIt} are not thread-safe.
+ *
+ * @see #DEFAULT_BUFFER_SIZE
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class InputIt implements FileIt<String> {
+
+    /**
+     * The default buffer size, which is 4,194,304 bytes.
+     */
+    public static final int DEFAULT_BUFFER_SIZE = 1<<22;
+
+    private final File file;
+    private final BufferedReader in;
+    private String next = null;
+
+    /**
+     * Constructs a new {@code InputStreamIterator} with default buffer
+     * size that will iterate through lines of the specified input stream.
+     *
+     * @param is input stream of text data
+     *
+     * @see #DEFAULT_BUFFER_SIZE
+     */
+    private InputIt(InputStream is, File file) {
+        this(is, file, DEFAULT_BUFFER_SIZE);
+    }
+
+    /**
+     * Constructs a new {@code InputStreamIterator} with default buffer size
+     * that will iterate through the lines of the specified input stream.
+     *
+     * @param is input stream of text data
+     * @param bufferSize the buffer size in bytes
+     *
+     * @throws IllegalArgumentException if {@code bufferSize < 0}
+     */
+    private InputIt(InputStream is, File file, int bufferSize) {
+        BufferedReader br = null;
+        try {
+            InputStreamReader isr = new InputStreamReader(is);
+            br = new BufferedReader(isr, bufferSize);
+            next = br.readLine();
+        }
+        catch(IOException e) {
+            Utilities.exit("Error reading " + is, e);
+        }
+        this.in = br;
+        this.file = file;
+    }
+
+    @Override
+    public File file() {
+        return file;
+    }
+
+    /**
+     * Returns {@code true} if the iteration has more elements.
+     * @return {@code true} if the iteration has more elements
+     */
+    @Override
+    public boolean hasNext() {
+        return (next != null);
+    }
+
+    /**
+     * Returns the next element in the iteration.
+     * @return the next element in the iteration
+     * @throws NoSuchElementException if the iteration has no more elements
+     */
+    @Override
+    public String next() {
+        if (!hasNext()) {
+            throw new NoSuchElementException();
+        }
+        String current = next;
+        try {
+            next = in.readLine();
+        }
+        catch (IOException e) {
+            Utilities.exit("Error reading " + in, e);
+        }
+        return current;
+    }
+
+    /**
+     * The {@code remove} method is not supported by this iterator.
+     * @throws UnsupportedOperationException if this method is invoked
+     */
+    @Override
+    public void remove() {
+        String s = this.getClass().toString() + ".remove()";
+        throw new UnsupportedOperationException(s);
+    }
+
+    @Override
+    public void close() {
+        try {
+            in.close();
+        }
+        catch (IOException e) {
+            Utilities.exit("Error closing " + in, e);
+        }
+        next=null;
+    }
+
+    /**
+     * Returns a string representation of this iterator.  The exact details
+     * of the representation are unspecified and subject to change.
+     * @return a string representation of this iterator
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(200);
+        sb.append("[file= ");
+        sb.append(file);
+        sb.append("; next=\"");
+        sb.append(next);
+        sb.append("\"]");
+        return sb.toString();
+    }
+
+    /**
+     * Constructs and returns an {@code InputIt} instance with the default
+     * buffer size that iterates through lines of text read from standard input.
+     *
+     * @return a new {@code InputIt} instance that iterates
+     * through lines of text read from standard input
+     */
+    public static InputIt fromStdIn() {
+        File file = null;
+        return new InputIt(System.in, file);
+    }
+
+    /**
+     * Constructs and returns an {@code InputIt} instance with the specified
+     * buffer size that iterates through lines of text read from standard input.
+     *
+     * @param bufferSize the buffer size in bytes
+     *
+     * @return a new {@code InputIt} instance that iterates
+     * through lines of text read from standard input
+     *
+     * @throws IllegalArgumentException if {@code bufferSize < 0}
+     */
+    public static InputIt fromStdIn(int bufferSize) {
+        File file = null;
+        return new InputIt(System.in, file, bufferSize);
+    }
+
+    /**
+     * Constructs and returns an {@code InputIt} instance with the default
+     * buffer size that iterates through lines of the specified compressed
+     * or uncompressed text file. If the filename ends in ".gz", the file
+     * must be either BGZIP-compressed or GZIP-compressed.
+     *
+     * @param file a compressed or uncompressed text file
+     * @return  a new {@code InputIt} instance that iterates
+     * through lines of the specified text file
+     *
+     * @throws NullPointerException if {@code file == null}
+     */
+    public static InputIt fromGzipFile(File file) {
+        try {
+            InputStream is = new FileInputStream(file);
+            if (file.getName().endsWith(".gz")) {
+                if (isBGZipFile(file)) {
+                    return new InputIt(
+                            new BlockCompressedInputStream(is), file);
+                }
+                else {
+                    return new InputIt(new GZIPInputStream(is), file);
+                }
+            }
+            else {
+                return new InputIt(is, file);
+            }
+        }
+        catch(FileNotFoundException e) {
+            Utilities.exit("Error opening " + file, e);
+        }
+        catch(IOException e) {
+            Utilities.exit("Error reading " + file, e);
+        }
+        assert false;
+        return null;
+    }
+
+    /**
+     * Constructs and returns an {@code InputIt} instance with the specified
+     * buffer size that iterates through lines of the specified compressed
+     * or uncompressed text file. If the filename ends in ".gz", the file must
+     * be either BGZIP-compressed or GZIP-compressed.
+     *
+     * @param file a compressed or uncompressed text file
+     * @param bufferSize the buffer size in bytes
+     * @return  a new {@code InputIt} instance that iterates
+     * through lines of the specified text file
+     *
+     * @throws IllegalArgumentException if {@code bufferSize < 0}
+     * @throws NullPointerException if {@code file == null}
+     */
+    public static InputIt fromGzipFile(File file, int bufferSize) {
+        try {
+            InputStream is = new FileInputStream(file);
+            if (file.getName().endsWith(".gz")) {
+                if (isBGZipFile(file)) {
+                    return new InputIt(
+                            new BlockCompressedInputStream(is), file, bufferSize);
+                }
+                else {
+                    return new InputIt(new GZIPInputStream(is), file, bufferSize);
+                }
+            }
+            else {
+                return new InputIt(is, file);
+            }
+        }
+        catch(FileNotFoundException e) {
+            Utilities.exit("Error opening " + file, e);
+        }
+        catch(IOException e) {
+            Utilities.exit("Error reading " + file, e);
+        }
+        assert false;
+        return null;
+    }
+
+    private static boolean isBGZipFile(File file) throws IOException {
+        try (InputStream is=new BufferedInputStream(new FileInputStream(file))) {
+            return BlockCompressedInputStream.isValidFile(is);
+        }
+     }
+
+     /**
+     * Constructs and returns an {@code InputIt} instance with the default
+     * buffer size that iterates through lines of the specified text file.
+     *
+     * @param file a text file
+     * @return a new {@code InputIt} instance that iterates through
+     * lines of the specified text file
+     *
+     * @throws NullPointerException if {@code filename == null}
+     */
+    public static InputIt fromTextFile(File file) {
+        try {
+            return new InputIt(new FileInputStream(file), file);
+        }
+        catch(FileNotFoundException e) {
+            Utilities.exit("Error opening " + file, e);
+        }
+        assert false;
+        return null;
+    }
+
+     /**
+     * Constructs and returns an {@code InputIt} instance with the specified
+     * buffer size that iterates through lines of the specified text file.
+     *
+     * @param file a text file
+     * @param bufferSize the buffer size in bytes
+     * @return a new {@code InputIt} instance that iterates through
+     * lines of the specified text file
+     *
+     * @throws IllegalArgumentException if {@code bufferSize < 0}
+     * @throws NullPointerException if {@code filename == null}
+     */
+    public static InputIt fromTextFile(File file, int bufferSize) {
+        try {
+            return new InputIt(new FileInputStream(file), file, bufferSize);
+        }
+        catch(FileNotFoundException e) {
+            Utilities.exit("Error opening " + file, e);
+        }
+        assert false;
+        return null;
+    }
+}
diff --git a/blbutil/StringUtil.java b/blbutil/StringUtil.java
index 09632bd..7c81666 100644
--- a/blbutil/StringUtil.java
+++ b/blbutil/StringUtil.java
@@ -1,281 +1,281 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package blbutil;
-
-/**
- * Class {@code StringUtil} is a utility class with static methods
- * for counting and returning delimited fields in a string.
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class StringUtil {
-
-    /* Private constructor to prevent instantiation */
-    private StringUtil() {
-    }
-
-    /**
-     * Returns the number of delimited fields in the specified
-     * string.  Returns 0 if the specified string has length 0.
-     *
-     * @param s a string
-     * @param delimiter a delimiter character
-     * @return the number of delimited fields in the specified string
-     * @throws NullPointerException if {@code s == null}
-     */
-    public static int countFields(String s, char delimiter) {
-        int cnt = 0;
-        for (int j=0, n=s.length(); j<n; ++j) {
-            if (s.charAt(j)==delimiter) {
-                ++cnt;
-            }
-        }
-        return cnt + 1;
-    }
-
-    /**
-     * Returns {@code Math.min(countFields(s, delimiter), max)}.
-     *
-     * @param s a string with 0 or more {@code delimiter} characters
-     * @param delimiter the delimiter character
-     * @param max the maximum value that can be returned
-     *
-     * @return {@code Math.min(countFields(s, delimiter), max)}
-     *
-     * @throws NullPointerException if {@code s == null}
-     */
-    public static int countFields(String s, char delimiter, int max) {
-        int cnt = 0;
-        int maxCnt = max - 1;
-        for (int j=0, n=s.length(); j<n && cnt<maxCnt; ++j) {
-            if (s.charAt(j)==delimiter) {
-                ++cnt;
-            }
-        }
-        return Math.min(cnt + 1, max);
-    }
-
-    /**
-     * Returns an array obtained by splitting the specified string
-     * around the specified delimiter.
-     * The array returned by this method contains each substring of
-     * the string that does not contain the delimiter and that
-     * is preceded by the delimiter or the beginning of
-     * the string and that is terminated by the delimiter or the end
-     * of the string.  The substrings in the array are in
-     * the order in which they occur in the specified string.
-     * If there are no delimiters in the specified string then the method
-     * return an array of length one, whose single element is the specified
-     * string.
-     *
-     * @param s a string
-     * @param delimiter a delimiter character
-     *
-     * @return the array of strings obtained by splitting the specified string
-     * around the specified delimiter
-     *
-     * @throws NullPointerException if {@code s == null}
-     */
-    public static String[] getFields(String s, char delimiter) {
-        String[] fields = new String[countFields(s, delimiter)];
-        int start = 0;
-        for (int j=0; j<fields.length; ++j)  {
-            int end = s.indexOf(delimiter, start);
-            fields[j] = end>=0 ? s.substring(start,end) : s.substring(start);
-            start = end + 1;
-        }
-        return fields;
-    }
-
-    /**
-     * Returns an array obtained by splitting the specified string
-     * around the first {@code (limit - 1)} occurrences of the specified
-     * delimiter.  If the string contains fewer than {@code (limit - 1)}
-     * delimiter characters, the returned value will equal
-     * {@code StringUtil.getFields(s, delimiter)}
-     *
-     * @param s a string
-     * @param delimiter a delimiter character
-     * @param limit the maximum length of the returned array
-     *
-     * @return an array obtained by splitting the specified string
-     * around the specified delimiter
-     *
-     * @throws NullPointerException if {@code s == null}
-     * @throws IllegalArgumentException if {@code limit < 2 }
-     */
-    public static String[] getFields(String s, char delimiter, int limit) {
-        if (limit < 2) {
-            throw new IllegalArgumentException("limit: " + limit);
-        }
-        String[] fields = new String[countFields(s, delimiter, limit)];
-        if (fields.length > 0) {
-            int start = 0;
-            for (int j=0, n=fields.length-1; j<n; ++j)  {
-                int end = s.indexOf(delimiter, start);
-                fields[j] = s.substring(start, end);
-                start = end + 1;
-            }
-            fields[fields.length - 1] = s.substring(start);
-        }
-        return fields;
-    }
-
-    /**
-     * Returns the number of white-space delimited fields in the specified
-     * string.  A field is a maximal set of consecutive characters that are not
-     * white space characters.  White space is defined as any unicode
-     * characters less than or equal to '\u0020'.
-     *
-     * @param s a string
-     * @return the number of white-space delimited fields in the specified
-     * string
-     * @throws NullPointerException if {@code s == null}
-     */
-    public static int countFields(String s) {
-        int start = 0;
-        int end = s.length();
-        while (start<end && s.charAt(start)<=' ') {
-            ++start;
-        }
-        while (end>start && s.charAt(end-1)<=' ') {
-            --end;
-        }
-        int fieldCount = (start<end) ? 1 : 0;
-        while (++start<end && s.charAt(start)>' ') {
-        }
-        while (start<end) {
-            while (s.charAt(++start)<=' ') {
-            }
-            ++fieldCount;
-            while (++start<end && s.charAt(start)>' ') {
-            }
-        }
-        return fieldCount;
-    }
-
-    /**
-     * Returns an array obtained by trimming white-space from the
-     * beginning and end of the specified string, and splitting the resulting
-     * string around white space.
-     * White space is any maximal substring of unicode characters
-     * less than or equal to '\u0020'. White-space at the beginning and
-     * end of the string is ignored.  The substrings in the returned array
-     * are in the order in which they occur in this string.  If there is no
-     * white-space in the specified string, the method returns an array
-     * of length one whose single element is the trimmed string.  If the
-     * specified string contains only white-space a string array
-     * of length 0 is returned.
-     *
-     * @param s a string
-     * @return the array of strings obtained by splitting the specified string
-     * around white space
-     *
-     * @throws NullPointerException if {@code s == null}
-     */
-    public static String[] getFields(String s) {
-        s = s.trim();
-        int n = s.length();
-        String[] fields = new String[countFields(s)];
-        if (fields.length > 0) {
-            int index = 0;
-            int start = 0;
-            int j = -1;
-            while (++j<n && s.charAt(j)>' ')  {
-            }
-            fields[index++] = s.substring(start, j);
-            while (j<n) {
-                while (s.charAt(++j)<=' ') {
-                }
-                start = j;
-                while (++j<n && s.charAt(j)>' ') {
-                }
-                fields[index++] = s.substring(start, j);
-            }
-            assert index==fields.length;
-        }
-        return fields;
-    }
-
-    /**
-     * <p>Returns an array obtained by trimming white-space from the
-     * beginning and end of the specified string, and splitting the resulting
-     * string around the first {@code (limit-1)} white-space delimiters.
-     * A white-space delimiter is any maximal substring of unicode characters
-     * less than or equal to '\u0020'.  If the trimemed string contains
-     * fewer than {@code (limit - 1)} white space delimiters, the returned value
-     * will equal {@code StringUtil.getFields(s)}.  The substrings in the
-     * returned array are in the order in which they occur in this string.
-     * If there are no white-space delimiters in the specified string, the
-     * method returns an array of length one whose single element is the
-     * trimmed string. If the specified string contains only white-space,
-     * a string array of length 0 is returned.
-     *</p>
-     *
-     * @param s a string
-     * @param limit the maximum length of the returned array
-     *
-     * @return the array of strings obtained by splitting the specified string
-     * around white space
-     *
-     * @throws NullPointerException if {@code s == null}
-     * @throws IllegalArgumentException if {@code limit < 2}
-     */
-    public static String[] getFields(String s, int limit) {
-        if (limit<2) {
-            throw new IllegalArgumentException("limit: " + limit);
-        }
-        s = s.trim();
-        int n = s.length();
-        int j=-1;
-        while (++j<n && s.charAt(j)>' ') {
-        }
-        int fieldCount = (j>0) ? 1 : 0;
-        while (j<n && fieldCount<limit) {
-            while (s.charAt(++j)<=' ') {
-            }
-            ++fieldCount;
-            while (++j<n && s.charAt(j)>' ') {
-            }
-        }
-        String[] fields = new String[fieldCount];
-        if (fields.length>0) {
-            int index = 0;
-            int start = 0;
-            j = -1;
-            while (++j<n && s.charAt(j)>' ') {
-            }
-            fields[index++] = s.substring(start, j);
-            while (j<n && index<limit) {
-                while (s.charAt(++j)<=' ') {
-                }
-                start = j;
-                while (++j<n && s.charAt(j)>' ') {
-                }
-                if (index < limit-1) {
-                    fields[index++] = s.substring(start, j);
-                }
-                else {
-                    fields[index++] = s.substring(start);
-                }
-            }
-        }
-        return fields;
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package blbutil;
+
+/**
+ * Class {@code StringUtil} is a utility class with static methods
+ * for counting and returning delimited fields in a string.
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class StringUtil {
+
+    /* Private constructor to prevent instantiation */
+    private StringUtil() {
+    }
+
+    /**
+     * Returns the number of delimited fields in the specified
+     * string.  Returns 0 if the specified string has length 0.
+     *
+     * @param s a string
+     * @param delimiter a delimiter character
+     * @return the number of delimited fields in the specified string
+     * @throws NullPointerException if {@code s == null}
+     */
+    public static int countFields(String s, char delimiter) {
+        int cnt = 0;
+        for (int j=0, n=s.length(); j<n; ++j) {
+            if (s.charAt(j)==delimiter) {
+                ++cnt;
+            }
+        }
+        return cnt + 1;
+    }
+
+    /**
+     * Returns {@code Math.min(countFields(s, delimiter), max)}.
+     *
+     * @param s a string with 0 or more {@code delimiter} characters
+     * @param delimiter the delimiter character
+     * @param max the maximum value that can be returned
+     *
+     * @return {@code Math.min(countFields(s, delimiter), max)}
+     *
+     * @throws NullPointerException if {@code s == null}
+     */
+    public static int countFields(String s, char delimiter, int max) {
+        int cnt = 0;
+        int maxCnt = max - 1;
+        for (int j=0, n=s.length(); j<n && cnt<maxCnt; ++j) {
+            if (s.charAt(j)==delimiter) {
+                ++cnt;
+            }
+        }
+        return Math.min(cnt + 1, max);
+    }
+
+    /**
+     * Returns an array obtained by splitting the specified string
+     * around the specified delimiter.
+     * The array returned by this method contains each substring of
+     * the string that does not contain the delimiter and that
+     * is preceded by the delimiter or the beginning of
+     * the string and that is terminated by the delimiter or the end
+     * of the string.  The substrings in the array are in
+     * the order in which they occur in the specified string.
+     * If there are no delimiters in the specified string then the method
+     * return an array of length one, whose single element is the specified
+     * string.
+     *
+     * @param s a string
+     * @param delimiter a delimiter character
+     *
+     * @return the array of strings obtained by splitting the specified string
+     * around the specified delimiter
+     *
+     * @throws NullPointerException if {@code s == null}
+     */
+    public static String[] getFields(String s, char delimiter) {
+        String[] fields = new String[countFields(s, delimiter)];
+        int start = 0;
+        for (int j=0; j<fields.length; ++j)  {
+            int end = s.indexOf(delimiter, start);
+            fields[j] = end>=0 ? s.substring(start,end) : s.substring(start);
+            start = end + 1;
+        }
+        return fields;
+    }
+
+    /**
+     * Returns an array obtained by splitting the specified string
+     * around the first {@code (limit - 1)} occurrences of the specified
+     * delimiter.  If the string contains fewer than {@code (limit - 1)}
+     * delimiter characters, the returned value will equal
+     * {@code StringUtil.getFields(s, delimiter)}
+     *
+     * @param s a string
+     * @param delimiter a delimiter character
+     * @param limit the maximum length of the returned array
+     *
+     * @return an array obtained by splitting the specified string
+     * around the specified delimiter
+     *
+     * @throws NullPointerException if {@code s == null}
+     * @throws IllegalArgumentException if {@code limit < 2 }
+     */
+    public static String[] getFields(String s, char delimiter, int limit) {
+        if (limit < 2) {
+            throw new IllegalArgumentException("limit: " + limit);
+        }
+        String[] fields = new String[countFields(s, delimiter, limit)];
+        if (fields.length > 0) {
+            int start = 0;
+            for (int j=0, n=fields.length-1; j<n; ++j)  {
+                int end = s.indexOf(delimiter, start);
+                fields[j] = s.substring(start, end);
+                start = end + 1;
+            }
+            fields[fields.length - 1] = s.substring(start);
+        }
+        return fields;
+    }
+
+    /**
+     * Returns the number of white-space delimited fields in the specified
+     * string.  A field is a maximal set of consecutive characters that are not
+     * white space characters.  White space is defined as any unicode
+     * characters less than or equal to '\u0020'.
+     *
+     * @param s a string
+     * @return the number of white-space delimited fields in the specified
+     * string
+     * @throws NullPointerException if {@code s == null}
+     */
+    public static int countFields(String s) {
+        int start = 0;
+        int end = s.length();
+        while (start<end && s.charAt(start)<=' ') {
+            ++start;
+        }
+        while (end>start && s.charAt(end-1)<=' ') {
+            --end;
+        }
+        int fieldCount = (start<end) ? 1 : 0;
+        while (++start<end && s.charAt(start)>' ') {
+        }
+        while (start<end) {
+            while (s.charAt(++start)<=' ') {
+            }
+            ++fieldCount;
+            while (++start<end && s.charAt(start)>' ') {
+            }
+        }
+        return fieldCount;
+    }
+
+    /**
+     * Returns an array obtained by trimming white-space from the
+     * beginning and end of the specified string, and splitting the resulting
+     * string around white space.
+     * White space is any maximal substring of unicode characters
+     * less than or equal to '\u0020'. White-space at the beginning and
+     * end of the string is ignored.  The substrings in the returned array
+     * are in the order in which they occur in this string.  If there is no
+     * white-space in the specified string, the method returns an array
+     * of length one whose single element is the trimmed string.  If the
+     * specified string contains only white-space a string array
+     * of length 0 is returned.
+     *
+     * @param s a string
+     * @return the array of strings obtained by splitting the specified string
+     * around white space
+     *
+     * @throws NullPointerException if {@code s == null}
+     */
+    public static String[] getFields(String s) {
+        s = s.trim();
+        int n = s.length();
+        String[] fields = new String[countFields(s)];
+        if (fields.length > 0) {
+            int index = 0;
+            int start = 0;
+            int j = -1;
+            while (++j<n && s.charAt(j)>' ')  {
+            }
+            fields[index++] = s.substring(start, j);
+            while (j<n) {
+                while (s.charAt(++j)<=' ') {
+                }
+                start = j;
+                while (++j<n && s.charAt(j)>' ') {
+                }
+                fields[index++] = s.substring(start, j);
+            }
+            assert index==fields.length;
+        }
+        return fields;
+    }
+
+    /**
+     * <p>Returns an array obtained by trimming white-space from the
+     * beginning and end of the specified string, and splitting the resulting
+     * string around the first {@code (limit-1)} white-space delimiters.
+     * A white-space delimiter is any maximal substring of unicode characters
+     * less than or equal to '\u0020'.  If the trimemed string contains
+     * fewer than {@code (limit - 1)} white space delimiters, the returned value
+     * will equal {@code StringUtil.getFields(s)}.  The substrings in the
+     * returned array are in the order in which they occur in this string.
+     * If there are no white-space delimiters in the specified string, the
+     * method returns an array of length one whose single element is the
+     * trimmed string. If the specified string contains only white-space,
+     * a string array of length 0 is returned.
+     *</p>
+     *
+     * @param s a string
+     * @param limit the maximum length of the returned array
+     *
+     * @return the array of strings obtained by splitting the specified string
+     * around white space
+     *
+     * @throws NullPointerException if {@code s == null}
+     * @throws IllegalArgumentException if {@code limit < 2}
+     */
+    public static String[] getFields(String s, int limit) {
+        if (limit<2) {
+            throw new IllegalArgumentException("limit: " + limit);
+        }
+        s = s.trim();
+        int n = s.length();
+        int j=-1;
+        while (++j<n && s.charAt(j)>' ') {
+        }
+        int fieldCount = (j>0) ? 1 : 0;
+        while (j<n && fieldCount<limit) {
+            while (s.charAt(++j)<=' ') {
+            }
+            ++fieldCount;
+            while (++j<n && s.charAt(j)>' ') {
+            }
+        }
+        String[] fields = new String[fieldCount];
+        if (fields.length>0) {
+            int index = 0;
+            int start = 0;
+            j = -1;
+            while (++j<n && s.charAt(j)>' ') {
+            }
+            fields[index++] = s.substring(start, j);
+            while (j<n && index<limit) {
+                while (s.charAt(++j)<=' ') {
+                }
+                start = j;
+                while (++j<n && s.charAt(j)>' ') {
+                }
+                if (index < limit-1) {
+                    fields[index++] = s.substring(start, j);
+                }
+                else {
+                    fields[index++] = s.substring(start);
+                }
+            }
+        }
+        return fields;
+    }
+}
diff --git a/blbutil/Utilities.java b/blbutil/Utilities.java
index bee3ff3..b62a575 100644
--- a/blbutil/Utilities.java
+++ b/blbutil/Utilities.java
@@ -1,208 +1,208 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package blbutil;
-
-import java.io.File;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Class {@code Utilities} contains miscellaneous static utility methods.
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class Utilities {
-
-    private Utilities() {
-        // private constructor to prevent instantiation
-    }
-
-    /**
-     * Prints a summary of memory use at the time of method invocation
-     * to standard output.
-     * @param msg a string a message to be printed with the summary
-     * of memory use
-     */
-    public static void printMemoryUse(String msg) {
-        long Mb = 1024*1024;
-        Runtime rt = Runtime.getRuntime();
-        System.out.println(Const.nl + msg
-                + Const.tab + "maxMb=" + (rt.maxMemory()/Mb)
-                + Const.tab + "totalMb=" + (rt.totalMemory()/Mb)
-                + Const.tab + "freeMb=" + (rt.freeMemory()/Mb)
-                + Const.tab + "usedMb=" + ((rt.totalMemory() - rt.freeMemory())/Mb));
-    }
-
-    /**
-     * Returns the current local time as a string.  The
-     * exact details of the string representation
-     * are unspecified and subject to change.
-     *
-     * @return the current local time as a string.
-     */
-    public static String timeStamp() {
-        Date now = new Date();
-        SimpleDateFormat sdf =
-                new SimpleDateFormat("hh:mm a z 'on' dd MMM yyyy");
-        return sdf.format(now);
-    }
-
-    /**
-     * <p>Returns a set of identifiers found in a text file that has
-     * one identifier per line.  The empty set is returned if
-     * {@code file == null}. Blank lines are ignored, and white-space that
-     * begins or ends a line is ignored.
-     * </p>
-     * If an {@code IOException} is thrown, an error message is printed
-     * to standard error and the Java virtual machine is forced to terminate.
-     *
-     * @param file a text file with one identifier per line
-     * @return a set of identifiers
-     *
-     * @throws IllegalArgumentException if the specified file does not exist
-     * @throws IllegalArgumentException if the specified file is a directory
-     * @throws IllegalArgumentException if any line of the specified
-     * file contains two non-white-space characters separated by one or
-     * more white-space characters
-     */
-    public static Set<String> idSet(File file) {
-        if (file==null) {
-            return Collections.emptySet();
-        }
-        else {
-            if (file.exists()==false) {
-                String s = "file does not exist: " + file;
-                throw new IllegalArgumentException(s);
-            }
-            if (file.isDirectory()) {
-                String s = "file is a directory: " + file;
-                throw new IllegalArgumentException(s);
-            }
-            Set<String> idSet = new HashSet<>();
-            try (FileIt<String> it = InputIt.fromGzipFile(file)) {
-                while (it.hasNext()) {
-                    String line = it.next().trim();
-                    if (line.length() > 0) {
-                        if (StringUtil.countFields(line) > 1) {
-                            String s = "line has >1 white-space delimited fields: "
-                                    + line;
-                            throw new IllegalArgumentException(s);
-                        }
-                        idSet.add(line);
-                    }
-                }
-            }
-            return idSet;
-        }
-    }
-
-    /**
-     * Prints the specified string to the specified {@code PrintWriter} and
-     * to standard out.  The line separator string is not appended to the
-     * specified string before printing.
-     *
-     * @param out a print writer
-     * @param s a string to be printed
-     *
-     * @throws NullPointerException if {@code out == null}
-     */
-    public static void duoPrint(PrintWriter out, String s) {
-        System.out.print(s);
-        out.print(s);
-    }
-
-   /**
-     * Prints the specified string to the specified {@code PrintWriter} and
-     * to standard out.  The line separator string is appended to the
-     * specified string before printing.
-     *
-     * @param out a print writer
-     * @param s a string to be printed
-     *
-     * @throws NullPointerException if {@code out == null}
-     */
-    public static void duoPrintln(PrintWriter out, String s) {
-        System.out.println(s);
-        out.println(s);
-    }
-
-     /**
-     * Returns a string representation of the specified elapsed time
-     * in the format "H hours M minutes S seconds".
-     *
-     * @param nanoseconds the elapsed time in nanoseconds
-     *
-     * @return a string representation of the specified elapsed time
-     */
-    public static String elapsedNanos(long nanoseconds) {
-        long seconds = Math.round(nanoseconds /1000000000.0);
-        StringBuilder sb = new StringBuilder(80);
-        if (seconds >= 3600) {
-            long hours = seconds / 3600;
-            sb.append(hours);
-            sb.append(hours==1 ? " hour " : " hours ");
-            seconds %= 3600;
-
-        }
-        if (seconds >= 60) {
-            long minutes = seconds / 60;
-            sb.append(minutes);
-            sb.append(minutes==1 ? " minute " : " minutes ");
-            seconds %= 60;
-        }
-        sb.append(seconds);
-        sb.append(seconds==1 ? " second" : " seconds");
-        return sb.toString();
-    }
-
-    /**
-     * Prints the specified exception, its stack trace, and
-     * the specified string to standard out and then terminates the
-     * Java virtual machine.
-     *
-     * @param s a string to be printed to standard err
-     * @param e an exception or error to be printed to standard err
-     *
-     * @throws NullPointerException if {@code e == null}
-     */
-    public static void exit(String s, Throwable e) {
-        e.printStackTrace(System.out);
-        System.out.println(e);
-        System.out.println(s);
-        System.out.println("terminating program.");
-        System.exit(1);
-    }
-
-    /**
-     * Prints the specified string to standard out and then terminates the
-     * Java virtual machine.
-     *
-     * @param s a string to be written to standard output
-     */
-    public static void exit(String s) {
-        System.out.println(s);
-        System.out.flush();
-        System.exit(0);
-    }
-}
-
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package blbutil;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Class {@code Utilities} contains miscellaneous static utility methods.
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class Utilities {
+
+    private Utilities() {
+        // private constructor to prevent instantiation
+    }
+
+    /**
+     * Prints a summary of memory use at the time of method invocation
+     * to standard output.
+     * @param msg a string a message to be printed with the summary
+     * of memory use
+     */
+    public static void printMemoryUse(String msg) {
+        long Mb = 1024*1024;
+        Runtime rt = Runtime.getRuntime();
+        System.out.println(Const.nl + msg
+                + Const.tab + "maxMb=" + (rt.maxMemory()/Mb)
+                + Const.tab + "totalMb=" + (rt.totalMemory()/Mb)
+                + Const.tab + "freeMb=" + (rt.freeMemory()/Mb)
+                + Const.tab + "usedMb=" + ((rt.totalMemory() - rt.freeMemory())/Mb));
+    }
+
+    /**
+     * Returns the current local time as a string.  The
+     * exact details of the string representation
+     * are unspecified and subject to change.
+     *
+     * @return the current local time as a string.
+     */
+    public static String timeStamp() {
+        Date now = new Date();
+        SimpleDateFormat sdf =
+                new SimpleDateFormat("hh:mm a z 'on' dd MMM yyyy");
+        return sdf.format(now);
+    }
+
+    /**
+     * <p>Returns a set of identifiers found in a text file that has
+     * one identifier per line.  The empty set is returned if
+     * {@code file == null}. Blank lines are ignored, and white-space that
+     * begins or ends a line is ignored.
+     * </p>
+     * If an {@code IOException} is thrown, an error message is printed
+     * to standard error and the Java virtual machine is forced to terminate.
+     *
+     * @param file a text file with one identifier per line
+     * @return a set of identifiers
+     *
+     * @throws IllegalArgumentException if the specified file does not exist
+     * @throws IllegalArgumentException if the specified file is a directory
+     * @throws IllegalArgumentException if any line of the specified
+     * file contains two non-white-space characters separated by one or
+     * more white-space characters
+     */
+    public static Set<String> idSet(File file) {
+        if (file==null) {
+            return Collections.emptySet();
+        }
+        else {
+            if (file.exists()==false) {
+                String s = "file does not exist: " + file;
+                throw new IllegalArgumentException(s);
+            }
+            if (file.isDirectory()) {
+                String s = "file is a directory: " + file;
+                throw new IllegalArgumentException(s);
+            }
+            Set<String> idSet = new HashSet<>();
+            try (FileIt<String> it = InputIt.fromGzipFile(file)) {
+                while (it.hasNext()) {
+                    String line = it.next().trim();
+                    if (line.length() > 0) {
+                        if (StringUtil.countFields(line) > 1) {
+                            String s = "line has >1 white-space delimited fields: "
+                                    + line;
+                            throw new IllegalArgumentException(s);
+                        }
+                        idSet.add(line);
+                    }
+                }
+            }
+            return idSet;
+        }
+    }
+
+    /**
+     * Prints the specified string to the specified {@code PrintWriter} and
+     * to standard out.  The line separator string is not appended to the
+     * specified string before printing.
+     *
+     * @param out a print writer
+     * @param s a string to be printed
+     *
+     * @throws NullPointerException if {@code out == null}
+     */
+    public static void duoPrint(PrintWriter out, String s) {
+        System.out.print(s);
+        out.print(s);
+    }
+
+   /**
+     * Prints the specified string to the specified {@code PrintWriter} and
+     * to standard out.  The line separator string is appended to the
+     * specified string before printing.
+     *
+     * @param out a print writer
+     * @param s a string to be printed
+     *
+     * @throws NullPointerException if {@code out == null}
+     */
+    public static void duoPrintln(PrintWriter out, String s) {
+        System.out.println(s);
+        out.println(s);
+    }
+
+     /**
+     * Returns a string representation of the specified elapsed time
+     * in the format "H hours M minutes S seconds".
+     *
+     * @param nanoseconds the elapsed time in nanoseconds
+     *
+     * @return a string representation of the specified elapsed time
+     */
+    public static String elapsedNanos(long nanoseconds) {
+        long seconds = Math.round(nanoseconds /1000000000.0);
+        StringBuilder sb = new StringBuilder(80);
+        if (seconds >= 3600) {
+            long hours = seconds / 3600;
+            sb.append(hours);
+            sb.append(hours==1 ? " hour " : " hours ");
+            seconds %= 3600;
+
+        }
+        if (seconds >= 60) {
+            long minutes = seconds / 60;
+            sb.append(minutes);
+            sb.append(minutes==1 ? " minute " : " minutes ");
+            seconds %= 60;
+        }
+        sb.append(seconds);
+        sb.append(seconds==1 ? " second" : " seconds");
+        return sb.toString();
+    }
+
+    /**
+     * Prints the specified exception, its stack trace, and
+     * the specified string to standard out and then terminates the
+     * Java virtual machine.
+     *
+     * @param s a string to be printed to standard err
+     * @param e an exception or error to be printed to standard err
+     *
+     * @throws NullPointerException if {@code e == null}
+     */
+    public static void exit(String s, Throwable e) {
+        e.printStackTrace(System.out);
+        System.out.println(e);
+        System.out.println(s);
+        System.out.println("terminating program.");
+        System.exit(1);
+    }
+
+    /**
+     * Prints the specified string to standard out and then terminates the
+     * Java virtual machine.
+     *
+     * @param s a string to be written to standard output
+     */
+    public static void exit(String s) {
+        System.out.println(s);
+        System.out.flush();
+        System.exit(0);
+    }
+}
+
diff --git a/dag/MergeableDagLevel.java b/dag/MergeableDagLevel.java
index 27adef9..74c8f4e 100644
--- a/dag/MergeableDagLevel.java
+++ b/dag/MergeableDagLevel.java
@@ -1,731 +1,731 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package dag;
-
-import blbutil.Const;
-import vcf.HapsMarker;
-import java.util.Arrays;
-
-/**
- * <p>Class {@code MergeableDagLevel} represents a level of a leveled
- * directed acyclic graph (DAG). The class includes a public method for
- * merging parent nodes.
- * </p>
- * <p>
- * Instances of class {@code MergebleDagLevel} are not thread-safe.
- * </p>
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class MergeableDagLevel {
-
-    private MergeableDagLevel nextLevel = null;
-    private MergeableDagLevel prevLevel = null;
-
-    private final int levelIndex;
-    private final int nAlleles;
-    private final int nHaps;
-    private final float[] weights;
-
-    private int[][] outEdges;  // [allele][parent node]
-    private int[] child2FirstInEdge;
-    private int[] inEdge2NextInEdge;
-
-    private int[] parentNodes;    // edge -> parent node
-    private int[] childNodes;     // edge -> child node
-    private int[] symbols;        // edge -> symbol
-    private float[] counts;       // edge -> weight
-
-    private int[] child2FirstHap; // child node -> first hap index
-    private int[] hap2NextHap;    // current hap index -> next hap index
-
-    private static float[] defaultWeights(HapsMarker data) {
-        float[] fa = new float[data.nHaps()];
-        Arrays.fill(fa, 1f);
-        return fa;
-    }
-
-    /**
-     * Constructs a new {@code MergeableDagLevel} instance from the specified
-     * phased genotype data.  The {@code previous()} method of the
-     * constructed instance will return {@code null}. Each haplotype
-     * will be assigned a weight of 1.
-     * @param data the phased genotype data
-     * @throws NullPointerException if {@code data == null}
-     */
-    public MergeableDagLevel(HapsMarker data) {
-        this(data, defaultWeights(data));
-    }
-
-    /**
-     * Constructs a new {@code MergeableDagLevel} instance from the specified
-     * phased genotype data and haplotype weights.  The {@code previous()}
-     * method of the constructed instance will return {@code null}.
-     * @param data the phased genotype data
-     * @param weights an array mapping haplotype indices to non-negative
-     * weights
-     * @throws IllegalArgumentException if
-     * {@code weights.length != data.nHaps()}
-     * @throws NullPointerException if {@code data==null || weights==null}
-     */
-    public MergeableDagLevel(HapsMarker data, float[] weights) {
-        checkParameters(data, weights);
-        this.prevLevel = null;
-        this.nextLevel = null;
-        this.levelIndex = 0;
-        this.nAlleles = data.marker().nAlleles();
-        this.nHaps = data.nHaps();
-        this.weights = weights.clone();
-        allocateAndInitializeArrays(nAlleles, nHaps);
-        fillArrays(data, weights);
-    }
-
-    /**
-     * Constructs a new {@code MergeableDagLevel} instance with the
-     * specified previous {@code MergeableDagLevel} and the
-     * specified phased genotype data.  This constructor does not alter
-     * any field of the specified {@code prevLevel} object.
-     * @param prevLevel the previous {@code MergeableDagLevel}
-     * @param data the phased genotype data
-     *
-     * @throws IllegalArgumentException if
-     * {@code prevLevel.nextLevel() != null}
-     * @throws IllegalArgumentException if
-     * {@code parent.nHaps() != data.nHaps()}
-     * @throws NullPointerException if
-     * {@code parent == null || data == null}
-     */
-    public MergeableDagLevel(MergeableDagLevel prevLevel, HapsMarker data) {
-        checkParameters(prevLevel, data);
-        this.prevLevel = prevLevel;
-        this.nextLevel = null;
-        this.levelIndex = prevLevel.index() + 1;
-        this.nAlleles = data.marker().nAlleles();
-        this.nHaps = data.nHaps();
-        this.weights = prevLevel.weights;
-        allocateAndInitializeArrays(nAlleles, nHaps);
-        fillArrays(prevLevel, data, weights);
-    }
-
-    private void checkParameters(HapsMarker data, float[] weights) {
-        if (weights.length != data.nHaps()) {
-             String s = "data.nHaps()=" + data.nHaps()
-                    + " != weights.length=" + weights.length;
-             throw new IllegalArgumentException(s);
-        }
-    }
-
-    private void checkParameters(MergeableDagLevel parent, HapsMarker data) {
-        if (parent.nextLevel!=null) {
-            throw new IllegalArgumentException("parent.nextLevel!=null");
-        }
-        if (parent.nHaps()!=data.nHaps()) {
-            throw new IllegalArgumentException("inconsistent samples");
-        }
-        // NB: the sequences of sample ID indices are not checked
-    }
-
-    private void allocateAndInitializeArrays(int nAlleles, int nHaps) {
-        this.outEdges = new int[nAlleles][nHaps];
-        this.child2FirstInEdge = new int[nHaps];
-        this.inEdge2NextInEdge = new int[nHaps];
-        this.parentNodes = new int[nHaps];
-        this.childNodes = new int[nHaps];
-        this.symbols = new int[nHaps];
-        this.counts = new float[nHaps];
-        this.child2FirstHap = new int[nHaps];
-        this.hap2NextHap = new int[nHaps];
-
-        for (int[] oe : outEdges) {
-            Arrays.fill(oe, -1);
-        }
-        Arrays.fill(child2FirstInEdge, -1);
-        Arrays.fill(inEdge2NextInEdge, -1);
-        Arrays.fill(parentNodes, -1);
-        Arrays.fill(childNodes, -1);
-        Arrays.fill(symbols, -1);
-        Arrays.fill(child2FirstHap, -1);
-        Arrays.fill(hap2NextHap, -1);
-    }
-
-    private void fillArrays(HapsMarker data, float[] weights) {
-        int parentNode = 0;
-        for (int hap=0, n=data.nHaps(); hap<n; ++hap) {
-            int symbol = data.allele(hap);
-            float count = weights[hap];
-            int edge = this.outEdges[symbol][parentNode];
-            if (edge == -1) {
-                edge = symbol;
-                addEdge(parentNode, symbol, count, edge, hap);
-            }
-            else {
-                assert edge == symbol;
-                assert edge==childNodes[edge];
-                int child = childNodes[edge];
-                this.counts[edge] += count;
-                this.hap2NextHap[hap] = this.child2FirstHap[child];
-                this.child2FirstHap[child] = hap;
-            }
-        }
-    }
-
-    private void fillArrays(MergeableDagLevel prevLevel, HapsMarker data,
-            float[] weights) {
-        int nEdges = 0;
-        for (int node=0, n=prevLevel.child2FirstHap.length; node<n; ++node) {
-            if (prevLevel.child2FirstHap[node] >= 0) {
-                int hap = prevLevel.child2FirstHap[node];
-                while (hap != -1) {
-                    int symbol = data.allele(hap);
-                    float count = weights[hap];
-                    int edge = this.outEdges[symbol][node];
-                    if (edge == -1) {
-                        addEdge(node, symbol, count, nEdges++, hap);
-                    }
-                    else {
-                        assert edge==childNodes[edge];
-                        int child = childNodes[edge];
-                        this.counts[edge] += count;
-                        this.hap2NextHap[hap] = this.child2FirstHap[child];
-                        this.child2FirstHap[child] = hap;
-                    }
-                    hap = prevLevel.hap2NextHap[hap];
-                }
-            }
-        }
-        if (nEdges < 0.75*nHaps) {
-            reduceEdgeArrayLengths(nEdges);
-        }
-        prevLevel.removeHaplotypeIndices();
-    }
-
-    private void addEdge(int parentNode, int symbol, float weight,
-            int edge, int haplotype) {
-        int childNode = edge;
-        outEdges[symbol][parentNode] = edge;
-        child2FirstInEdge[childNode] = edge;
-        parentNodes[edge] = parentNode;
-        childNodes[edge] = childNode;
-        symbols[edge] = symbol;
-        counts[edge] = weight;
-        child2FirstHap[childNode] = haplotype;
-    }
-
-    private void reduceEdgeArrayLengths(int newLength) {
-        child2FirstInEdge = Arrays.copyOf(child2FirstInEdge, newLength);
-        inEdge2NextInEdge = Arrays.copyOf(inEdge2NextInEdge, newLength);
-        parentNodes = Arrays.copyOf(parentNodes, newLength);
-        childNodes = Arrays.copyOf(childNodes, newLength);
-        symbols = Arrays.copyOf(symbols, newLength);
-        counts = Arrays.copyOf(counts, newLength);
-    }
-
-    /**
-     * Removes haplotype index data from {@code this}.
-     */
-    private void removeHaplotypeIndices() {
-        this.child2FirstHap = null;
-        this.hap2NextHap = null;
-    }
-
-   /**
-     * Sets the previous DAG level to {@code null}, and returns
-     * the previous DAG level that existed immediately prior to the invocation
-     * of this method.
-     * @return the previous DAG level that existed immediately prior to the
-     * invocation of this method.
-     */
-    public MergeableDagLevel setPreviousToNull() {
-        MergeableDagLevel prev = this.prevLevel;
-        this.prevLevel = null;
-        return prev;
-    }
-
-    /**
-     * Sets the next level to the specified {@code MergeableDagLevel}.
-     * @param nextLevel the next level
-     * @throws IllegalArgumentException if
-     * {@code nextLevel.previousLevel() != this}
-     */
-    public void setNextLevel(MergeableDagLevel nextLevel) {
-        if (nextLevel.prevLevel != this) {
-            throw new IllegalArgumentException("nextLevel.previousLevel!=this");
-        }
-        this.nextLevel = nextLevel;
-    }
-
-    /**
-     * Returns the previous DAG level or {@code null} if no previous level
-     * is stored.
-     * @return the previous DAG level
-     */
-    public MergeableDagLevel previous() {
-        return prevLevel;
-    }
-
-    /**
-     * Returns the next DAG level or {@code null} if no next level is stored.
-     * @return the next DAG level
-     */
-    public MergeableDagLevel next() {
-        return nextLevel;
-    }
-
-    /**
-     * Returns {@code true} if the specified parent node has a
-     * sibling  and returns {@code false} otherwise.
-     * Two parent nodes are siblings if they are connected by an
-     * edge to the same parent node at the previous level of the DAG.
-     *
-     * @param parentNode a parent node index
-     * @return {@code true} if the specified parent node has a
-     * sibling
-     */
-    public boolean hasSibling(int parentNode) {
-        int edge = prevLevel.child2FirstInEdge[parentNode];
-        while (edge>=0) {
-            int pn = prevLevel.parentNodes[edge];
-            int cnt = 0;
-            for (int allele=0, n=prevLevel.nAlleles; allele<n; ++allele) {
-                if (prevLevel.outEdges[allele][pn]>=0) {
-                    ++cnt;
-                }
-            }
-            if (cnt>1) {
-                return true;
-            }
-            edge = prevLevel.inEdge2NextInEdge[edge];
-        }
-        return false;
-    }
-
-    /**
-     * Returns an immutable {@code DagLevel} corresponding to
-     * {@code this}. The parent node, edge, and child node indices
-     * in the returned {@code DagLevel} are the ranks of the
-     * parent node, edge, and child node indices for {@code this},
-     * with rank 0 corresponding to the smallest index.
-     * @return an immutable {@code DagLevel} corresponding to {@code this}
-     */
-    public DagLevel toDagLevel() {
-        float[] modCounts = DagUtil.removeValues(counts, 0f);
-        int[] modSymbols = DagUtil.removeValues(symbols, -1);
-        int[] modParentNodes = DagUtil.removeValues(parentNodes, -1);
-        int[] modChildNodes = DagUtil.removeValues(childNodes, -1);
-        if (modCounts.length<=Character.MAX_VALUE) {
-            char[] mod2Symbols = toCharArray(modSymbols);
-            char[] mod2ParentNodes = rankedCharValues(modParentNodes);
-            char[] mod2ChildNodes = rankedCharValues(modChildNodes);
-            return new LowCapacityDagLevel(mod2ParentNodes, mod2ChildNodes,
-                    mod2Symbols, modCounts);
-        }
-        else {
-            int[] mod2ParentNodes = rankedIntValues(modParentNodes);
-            int[] mod2ChildNodes = rankedIntValues(modChildNodes);
-            return new HighCapacityDagLevel(mod2ParentNodes, mod2ChildNodes,
-                    modSymbols, modCounts);
-        }
-    }
-
-    private static char[] toCharArray(int[] ia) {
-        char[] ca = new char[ia.length];
-        for (int j=0; j < ca.length; ++j) {
-            if (ia[j] < 0 || ia[j] > Character.MAX_VALUE) {
-                throw new IllegalArgumentException(String.valueOf(ia[j]));
-            }
-            ca[j] = (char) ia[j];
-        }
-        return ca;
-    }
-
-    /*
-     * Returns an array obtained by replacing each array value with it's
-     * rank when the set of array values is ordered: the smallest value
-     * is replaced by 0, the next smallest value is replaced by 1, etc.
-     *
-     * @throws IllegalArgumentException if {@code array.length == 0}
-     * @throws IllegalArgumentException if any element of the array
-     * is negative
-     * @throws IllegalArgumentException if the array has more than
-     * {@code Character.MAX_VALUE + 1} distinct values
-     * @throws NullPointerException if {@code array == null}
-     */
-    private static char[] rankedCharValues(int[] array) {
-        if (array.length==0) {
-            throw new IllegalArgumentException("array.length==0");
-        }
-        int[] sortedCopy = array.clone();
-        Arrays.sort(sortedCopy);
-        if (sortedCopy[0] < 0) {
-            throw new IllegalArgumentException(String.valueOf(sortedCopy[0]));
-        }
-        int n = sortedCopy[sortedCopy.length - 1] + 1;
-        int[] indexMap = new int[n];
-        int index = 0;
-        indexMap[sortedCopy[0]] = index++;
-        for (int j=1; j<sortedCopy.length; ++j) {
-            if (sortedCopy[j] != sortedCopy[j-1]) {
-                indexMap[sortedCopy[j]] = index++;
-            }
-        }
-        if ( (index - 1) >= Character.MAX_VALUE) {
-            String s = "Array has more than (Character.MAX_VALUE + 1) values";
-            throw new IllegalArgumentException(s);
-        }
-        char[] transformedArray = new char[array.length];
-        for (int j=0; j<transformedArray.length; ++j) {
-            transformedArray[j] = (char) indexMap[array[j]];
-        }
-        return transformedArray;
-    }
-
-    /*
-     * Returns an array obtained by replacing each array value with it's
-     * rank when the set of array values is ordered: the smallest value
-     * is replaced by 0, the next smallest value is replaced by 1, etc.
-     *
-     * @throws IllegalArgumentException if {@code array.length == 0}
-     * @throws IllegalArgumentException if any element of the array
-     * is negative
-     * @throws NullPointerException if {@code array == null}
-     */
-    private static int[] rankedIntValues(int[] array) {
-        if (array.length==0) {
-            throw new IllegalArgumentException("array.length==0");
-        }
-        int[] sortedCopy = array.clone();
-        Arrays.sort(sortedCopy);
-        if (sortedCopy[0] < 0) {
-            throw new IllegalArgumentException(String.valueOf(sortedCopy[0]));
-        }
-        int n = sortedCopy[sortedCopy.length - 1] + 1;
-        int[] indexMap = new int[n];
-        int index = 0;
-        indexMap[sortedCopy[0]] = index++;
-        for (int j=1; j<sortedCopy.length; ++j) {
-            if (sortedCopy[j] != sortedCopy[j-1]) {
-                indexMap[sortedCopy[j]] = index++;
-            }
-        }
-        int[] transformedArray = new int[array.length];
-        for (int j=0; j<transformedArray.length; ++j) {
-            transformedArray[j] = indexMap[array[j]];
-        }
-        return transformedArray;
-    }
-
-    /**
-     * Merges the two specified parent nodes and assigns the specified
-     * {@code retainedNode} index to the merged node.
-     *
-     * @param retainedNode a parent node which will receive ingoing and
-     * outgoing edges of {@code removedNode}
-     * @param removedNode a parent node that will be deleted after merging.
-     *
-     * @throws IllegalArgumentException if {@code retainedNode}
-     * or {@code returnedNode} is not a valid parent node index.
-     */
-    public void mergeParentNodes(int retainedNode, int removedNode) {
-        if (isParentNode(retainedNode)==false) {
-            String s = "invalid parent node: " + retainedNode;
-            throw new IllegalArgumentException(s);
-        }
-        if (isParentNode(removedNode)==false) {
-            String s = "invalid parent node: " + removedNode;
-            throw new IllegalArgumentException(s);
-        }
-        prevLevel.mergeChildNodes(retainedNode, removedNode);
-        mergeParentNodes2(retainedNode, removedNode);
-    }
-
-    private void mergeParentNodes2(int retainedNode, int removedNode) {
-        for (int j=0; j<nAlleles; ++j) {
-            int retainedEdge = outEdges[j][retainedNode];
-            int removedEdge = outEdges[j][removedNode];
-            if (removedEdge >= 0) {
-                if (retainedEdge == -1) {
-                    changeParent(removedEdge, retainedNode);
-                }
-                else {
-                    int retainedChild = childNode(retainedEdge);
-                    int removedChild = childNode(removedEdge);
-                    mergeEdges(retainedEdge, removedEdge);
-                    if (nextLevel != null) {
-                        nextLevel.mergeParentNodes2(retainedChild, removedChild);
-                    }
-                }
-            }
-        }
-    }
-
-    /*
-     * Merges the two specified child nodes and assigns the merged
-     * node to the specified {@code retainedNode} index.  Ingoing edges
-     * to {@code removedNode} are redirected to be ingoing edges
-     * to {@code retainedNode}.
-     *
-     * @param retainedNode a child node which will receive ingoing edges of
-     * {@code removedNode}
-     * @param removedNode a child node that will be deleted after merging
-     */
-    private void mergeChildNodes(int retainedNode, int removedNode) {
-        int lastEdge = -1;
-        int edge = child2FirstInEdge[removedNode];
-        while (edge != -1) {
-            assert childNodes[edge] == removedNode;
-            childNodes[edge] = retainedNode;
-            lastEdge = edge;
-            edge = inEdge2NextInEdge[edge];
-        }
-        if (lastEdge != -1) {
-            inEdge2NextInEdge[lastEdge] = child2FirstInEdge[retainedNode];
-            child2FirstInEdge[retainedNode] = child2FirstInEdge[removedNode];
-            child2FirstInEdge[removedNode] = -1;
-        }
-    }
-
-    private void changeParent(int edge, int newParent) {
-        int oldParent = parentNodes[edge];
-        int symbol = symbols[edge];
-        assert (outEdges[symbol][oldParent] == edge);
-        assert (outEdges[symbol][newParent] == -1);
-        outEdges[symbol][oldParent] = -1;
-        outEdges[symbol][newParent] = edge;
-        parentNodes[edge] = newParent;
-    }
-
-    private void mergeEdges(int retainedEdge, int removedEdge) {
-        assert symbols[retainedEdge] == symbols[removedEdge];
-        assert counts[removedEdge] > 0.0f;
-        counts[retainedEdge] += counts[removedEdge];
-        if (nextLevel==null) {
-            mergeHaplotypes(childNodes[retainedEdge], childNodes[removedEdge]);
-        }
-        int parentNode = parentNodes[removedEdge];
-        int childNode = childNodes[removedEdge];
-        int symbol = symbols[removedEdge];
-        assert inEdge2NextInEdge[child2FirstInEdge[childNode]] == -1;
-        outEdges[symbol][parentNode] = -1;
-        child2FirstInEdge[childNode] = -1;
-        counts[removedEdge] = 0.0f;
-        parentNodes[removedEdge] = -1;
-        childNodes[removedEdge] = -1;
-        symbols[removedEdge] = -1;
-    }
-
-    private void mergeHaplotypes(int retainedChild, int removedChild) {
-        int hap = child2FirstHap[removedChild];
-        while (hap2NextHap[hap] != -1) {
-            hap = hap2NextHap[hap];
-        }
-        hap2NextHap[hap] = child2FirstHap[retainedChild];
-        child2FirstHap[retainedChild] = child2FirstHap[removedChild];
-        child2FirstHap[removedChild] = -1;
-    }
-
-    /**
-     * Returns the marker index.
-     * @return the marker index
-     */
-    public int index() {
-        return this.levelIndex;
-    }
-
-    /**
-     * Returns the number of sequences used to construct the DAG.
-     * @return the number of sequences used to construct the DAG
-     */
-    public int nHaps() {
-        return this.nHaps;
-    }
-
-    /**
-     * Returns the number of alleles.
-     *
-     * @return the number of alleles
-     */
-    public int nAlleles() {
-        return this.nAlleles;
-    }
-
-   /**
-    * Returns the sum of weights for the sequences that pass
-    * through the specified edge or 0 if the edge does not exist.
-    *
-    * @param edge index of the edge
-    * @return sum of weights for the sequences that pass
-    * through the specified edge or 0 if the edge does not exist
-    *
-    * @throws IndexOutOfBoundsException if
-    * {@code edge < 0 || edge >= this.nHaps()}
-    */
-    public float edgeCount(int edge) {
-        return counts[edge];
-    }
-
-   /**
-    * Returns the sum of weights for the sequences that pass
-    * through the specified parent node or 0 if the parent node
-    * does not exist.
-    *
-    * @param parentNode index of the parent node
-    * @return sum of weights for the sequences that pass
-    * through the specified parent node or 0 if the parent node
-    * does not exist
-    *
-    * @throws IndexOutOfBoundsException if
-    * {@code parentNode < 0 || parentNode >= this.nHaps()}
-    */
-    public float nodeCount(int parentNode) {
-        float sum = 0.0f;
-        for (int symbol=0; symbol<nAlleles; ++symbol) {
-            if (outEdges[symbol][parentNode] >= 0) {
-                sum += edgeCount(outEdges[symbol][parentNode]);
-            }
-        }
-        return sum;
-    }
-
-    /**
-     * Returns an array of parent node indices.
-     * @return an array of parent node indices
-     */
-    public int[] parentNodeArray() {
-        int[] sortedReducedArray = DagUtil.removeValues(parentNodes, -1);
-        Arrays.sort(sortedReducedArray);
-        assert sortedReducedArray.length > 0;
-        int cnt = 1;
-        for (int j=1; j<sortedReducedArray.length; ++j) {
-            if (sortedReducedArray[j] != sortedReducedArray[j-1]) {
-                ++cnt;
-            }
-        }
-        int[] parentNodeArray = new int[cnt];
-        int index = 0;
-        parentNodeArray[index++] = sortedReducedArray[0];
-        for (int j=1; j<sortedReducedArray.length; ++j) {
-            if (sortedReducedArray[j] != sortedReducedArray[j-1]) {
-                parentNodeArray[index++] = sortedReducedArray[j];
-            }
-        }
-        assert index==parentNodeArray.length;
-        return parentNodeArray;
-    }
-
-   /**
-     * Returns the parent node of the specified edge or -1 if the edge does
-     * not exist.
-     *
-     * @param edge index of the edge
-     * @return the parent node of the specified edge or -1 if the edge does
-     * not exist
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code edge < 0 || edge >= this.nHaps()}
-     */
-    public int parentNode(int edge) {
-        return parentNodes[edge];
-    }
-
-    /**
-     * Returns the child node of the specified edge or -1 if the edge does
-     * not exist
-     *
-     * @param edge the edge
-     * @return the child node of the specified edge or -1 if the edge does
-     * not exist
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code edge < 0 || edge >= this.Haplotypes()}
-     */
-    public int childNode(int edge) {
-        return childNodes[edge];
-    }
-
-    /**
-     * Returns the edge that is the outgoing edge of the specified
-     * parent parent node having the specified symbol, or
-     * returns -1 if no such edge exists.
-     *
-     * @param parentNode the parent node
-     * @param symbol symbol labeling the outgoing edge
-     * @return the edge that is the outgoing edge of the specified
-     * parent parent node having the specified symbol, or
-     * -1 if no such edge exists.
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code parentNode < 0 || parentNode >= this.nHaps()}
-     * @throws IndexOutOfBoundsException if
-     * {@code symbol < 0 || symbol >= this.nAlleles()}
-     */
-    public int outEdge(int parentNode, int symbol) {
-        return outEdges[symbol][parentNode];
-    }
-
-    /**
-     * Returns a string representation of {@code this}.  The exact
-     * details of the representation are unspecified and subject to change.
-     *
-     * @return a string representation of {@code this}
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder(1000);
-        sb.append(Const.nl);
-        sb.append("[ MergeableDagLevel: marker=");
-        sb.append(levelIndex);
-        sb.append(Const.nl);
-        for (int j=0, n=nHaps(); j<n; ++j) {
-            if (parentNodes[j] != -1) {
-                sb.append("edge=");
-                sb.append(j);
-                sb.append(" parent=");
-                sb.append(parentNodes[j]);
-                sb.append(" child=");
-                sb.append(childNodes[j]);
-                sb.append(" symbol=");
-                sb.append(symbols[j]);
-                sb.append(" count=");
-                sb.append(counts[j]);
-                sb.append(Const.nl);
-            }
-        }
-        sb.append("previous=");
-        sb.append(prevLevel!=null);
-        sb.append(" next=");
-        sb.append(nextLevel!=null);
-        sb.append(Const.nl);
-        sb.append(" ]");
-        return sb.toString();
-    }
-
-    private boolean isParentNode(int node) {
-        if (prevLevel!=null) {
-            return prevLevel.child2FirstInEdge[node]>=0;
-        }
-        else {
-            for (int j=0; j<nAlleles; ++j) {
-                if (outEdges[j][node] != -1) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package dag;
+
+import blbutil.Const;
+import vcf.HapsMarker;
+import java.util.Arrays;
+
+/**
+ * <p>Class {@code MergeableDagLevel} represents a level of a leveled
+ * directed acyclic graph (DAG). The class includes a public method for
+ * merging parent nodes.
+ * </p>
+ * <p>
+ * Instances of class {@code MergebleDagLevel} are not thread-safe.
+ * </p>
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class MergeableDagLevel {
+
+    private MergeableDagLevel nextLevel = null;
+    private MergeableDagLevel prevLevel = null;
+
+    private final int levelIndex;
+    private final int nAlleles;
+    private final int nHaps;
+    private final float[] weights;
+
+    private int[][] outEdges;  // [allele][parent node]
+    private int[] child2FirstInEdge;
+    private int[] inEdge2NextInEdge;
+
+    private int[] parentNodes;    // edge -> parent node
+    private int[] childNodes;     // edge -> child node
+    private int[] symbols;        // edge -> symbol
+    private float[] counts;       // edge -> weight
+
+    private int[] child2FirstHap; // child node -> first hap index
+    private int[] hap2NextHap;    // current hap index -> next hap index
+
+    private static float[] defaultWeights(HapsMarker data) {
+        float[] fa = new float[data.nHaps()];
+        Arrays.fill(fa, 1f);
+        return fa;
+    }
+
+    /**
+     * Constructs a new {@code MergeableDagLevel} instance from the specified
+     * phased genotype data.  The {@code previous()} method of the
+     * constructed instance will return {@code null}. Each haplotype
+     * will be assigned a weight of 1.
+     * @param data the phased genotype data
+     * @throws NullPointerException if {@code data == null}
+     */
+    public MergeableDagLevel(HapsMarker data) {
+        this(data, defaultWeights(data));
+    }
+
+    /**
+     * Constructs a new {@code MergeableDagLevel} instance from the specified
+     * phased genotype data and haplotype weights.  The {@code previous()}
+     * method of the constructed instance will return {@code null}.
+     * @param data the phased genotype data
+     * @param weights an array mapping haplotype indices to non-negative
+     * weights
+     * @throws IllegalArgumentException if
+     * {@code weights.length != data.nHaps()}
+     * @throws NullPointerException if {@code data==null || weights==null}
+     */
+    public MergeableDagLevel(HapsMarker data, float[] weights) {
+        checkParameters(data, weights);
+        this.prevLevel = null;
+        this.nextLevel = null;
+        this.levelIndex = 0;
+        this.nAlleles = data.marker().nAlleles();
+        this.nHaps = data.nHaps();
+        this.weights = weights.clone();
+        allocateAndInitializeArrays(nAlleles, nHaps);
+        fillArrays(data, weights);
+    }
+
+    /**
+     * Constructs a new {@code MergeableDagLevel} instance with the
+     * specified previous {@code MergeableDagLevel} and the
+     * specified phased genotype data.  This constructor does not alter
+     * any field of the specified {@code prevLevel} object.
+     * @param prevLevel the previous {@code MergeableDagLevel}
+     * @param data the phased genotype data
+     *
+     * @throws IllegalArgumentException if
+     * {@code prevLevel.nextLevel() != null}
+     * @throws IllegalArgumentException if
+     * {@code parent.nHaps() != data.nHaps()}
+     * @throws NullPointerException if
+     * {@code parent == null || data == null}
+     */
+    public MergeableDagLevel(MergeableDagLevel prevLevel, HapsMarker data) {
+        checkParameters(prevLevel, data);
+        this.prevLevel = prevLevel;
+        this.nextLevel = null;
+        this.levelIndex = prevLevel.index() + 1;
+        this.nAlleles = data.marker().nAlleles();
+        this.nHaps = data.nHaps();
+        this.weights = prevLevel.weights;
+        allocateAndInitializeArrays(nAlleles, nHaps);
+        fillArrays(prevLevel, data, weights);
+    }
+
+    private void checkParameters(HapsMarker data, float[] weights) {
+        if (weights.length != data.nHaps()) {
+             String s = "data.nHaps()=" + data.nHaps()
+                    + " != weights.length=" + weights.length;
+             throw new IllegalArgumentException(s);
+        }
+    }
+
+    private void checkParameters(MergeableDagLevel parent, HapsMarker data) {
+        if (parent.nextLevel!=null) {
+            throw new IllegalArgumentException("parent.nextLevel!=null");
+        }
+        if (parent.nHaps()!=data.nHaps()) {
+            throw new IllegalArgumentException("inconsistent samples");
+        }
+        // NB: the sequences of sample ID indices are not checked
+    }
+
+    private void allocateAndInitializeArrays(int nAlleles, int nHaps) {
+        this.outEdges = new int[nAlleles][nHaps];
+        this.child2FirstInEdge = new int[nHaps];
+        this.inEdge2NextInEdge = new int[nHaps];
+        this.parentNodes = new int[nHaps];
+        this.childNodes = new int[nHaps];
+        this.symbols = new int[nHaps];
+        this.counts = new float[nHaps];
+        this.child2FirstHap = new int[nHaps];
+        this.hap2NextHap = new int[nHaps];
+
+        for (int[] oe : outEdges) {
+            Arrays.fill(oe, -1);
+        }
+        Arrays.fill(child2FirstInEdge, -1);
+        Arrays.fill(inEdge2NextInEdge, -1);
+        Arrays.fill(parentNodes, -1);
+        Arrays.fill(childNodes, -1);
+        Arrays.fill(symbols, -1);
+        Arrays.fill(child2FirstHap, -1);
+        Arrays.fill(hap2NextHap, -1);
+    }
+
+    private void fillArrays(HapsMarker data, float[] weights) {
+        int parentNode = 0;
+        for (int hap=0, n=data.nHaps(); hap<n; ++hap) {
+            int symbol = data.allele(hap);
+            float count = weights[hap];
+            int edge = this.outEdges[symbol][parentNode];
+            if (edge == -1) {
+                edge = symbol;
+                addEdge(parentNode, symbol, count, edge, hap);
+            }
+            else {
+                assert edge == symbol;
+                assert edge==childNodes[edge];
+                int child = childNodes[edge];
+                this.counts[edge] += count;
+                this.hap2NextHap[hap] = this.child2FirstHap[child];
+                this.child2FirstHap[child] = hap;
+            }
+        }
+    }
+
+    private void fillArrays(MergeableDagLevel prevLevel, HapsMarker data,
+            float[] weights) {
+        int nEdges = 0;
+        for (int node=0, n=prevLevel.child2FirstHap.length; node<n; ++node) {
+            if (prevLevel.child2FirstHap[node] >= 0) {
+                int hap = prevLevel.child2FirstHap[node];
+                while (hap != -1) {
+                    int symbol = data.allele(hap);
+                    float count = weights[hap];
+                    int edge = this.outEdges[symbol][node];
+                    if (edge == -1) {
+                        addEdge(node, symbol, count, nEdges++, hap);
+                    }
+                    else {
+                        assert edge==childNodes[edge];
+                        int child = childNodes[edge];
+                        this.counts[edge] += count;
+                        this.hap2NextHap[hap] = this.child2FirstHap[child];
+                        this.child2FirstHap[child] = hap;
+                    }
+                    hap = prevLevel.hap2NextHap[hap];
+                }
+            }
+        }
+        if (nEdges < 0.75*nHaps) {
+            reduceEdgeArrayLengths(nEdges);
+        }
+        prevLevel.removeHaplotypeIndices();
+    }
+
+    private void addEdge(int parentNode, int symbol, float weight,
+            int edge, int haplotype) {
+        int childNode = edge;
+        outEdges[symbol][parentNode] = edge;
+        child2FirstInEdge[childNode] = edge;
+        parentNodes[edge] = parentNode;
+        childNodes[edge] = childNode;
+        symbols[edge] = symbol;
+        counts[edge] = weight;
+        child2FirstHap[childNode] = haplotype;
+    }
+
+    private void reduceEdgeArrayLengths(int newLength) {
+        child2FirstInEdge = Arrays.copyOf(child2FirstInEdge, newLength);
+        inEdge2NextInEdge = Arrays.copyOf(inEdge2NextInEdge, newLength);
+        parentNodes = Arrays.copyOf(parentNodes, newLength);
+        childNodes = Arrays.copyOf(childNodes, newLength);
+        symbols = Arrays.copyOf(symbols, newLength);
+        counts = Arrays.copyOf(counts, newLength);
+    }
+
+    /**
+     * Removes haplotype index data from {@code this}.
+     */
+    private void removeHaplotypeIndices() {
+        this.child2FirstHap = null;
+        this.hap2NextHap = null;
+    }
+
+   /**
+     * Sets the previous DAG level to {@code null}, and returns
+     * the previous DAG level that existed immediately prior to the invocation
+     * of this method.
+     * @return the previous DAG level that existed immediately prior to the
+     * invocation of this method.
+     */
+    public MergeableDagLevel setPreviousToNull() {
+        MergeableDagLevel prev = this.prevLevel;
+        this.prevLevel = null;
+        return prev;
+    }
+
+    /**
+     * Sets the next level to the specified {@code MergeableDagLevel}.
+     * @param nextLevel the next level
+     * @throws IllegalArgumentException if
+     * {@code nextLevel.previousLevel() != this}
+     */
+    public void setNextLevel(MergeableDagLevel nextLevel) {
+        if (nextLevel.prevLevel != this) {
+            throw new IllegalArgumentException("nextLevel.previousLevel!=this");
+        }
+        this.nextLevel = nextLevel;
+    }
+
+    /**
+     * Returns the previous DAG level or {@code null} if no previous level
+     * is stored.
+     * @return the previous DAG level
+     */
+    public MergeableDagLevel previous() {
+        return prevLevel;
+    }
+
+    /**
+     * Returns the next DAG level or {@code null} if no next level is stored.
+     * @return the next DAG level
+     */
+    public MergeableDagLevel next() {
+        return nextLevel;
+    }
+
+    /**
+     * Returns {@code true} if the specified parent node has a
+     * sibling  and returns {@code false} otherwise.
+     * Two parent nodes are siblings if they are connected by an
+     * edge to the same parent node at the previous level of the DAG.
+     *
+     * @param parentNode a parent node index
+     * @return {@code true} if the specified parent node has a
+     * sibling
+     */
+    public boolean hasSibling(int parentNode) {
+        int edge = prevLevel.child2FirstInEdge[parentNode];
+        while (edge>=0) {
+            int pn = prevLevel.parentNodes[edge];
+            int cnt = 0;
+            for (int allele=0, n=prevLevel.nAlleles; allele<n; ++allele) {
+                if (prevLevel.outEdges[allele][pn]>=0) {
+                    ++cnt;
+                }
+            }
+            if (cnt>1) {
+                return true;
+            }
+            edge = prevLevel.inEdge2NextInEdge[edge];
+        }
+        return false;
+    }
+
+    /**
+     * Returns an immutable {@code DagLevel} corresponding to
+     * {@code this}. The parent node, edge, and child node indices
+     * in the returned {@code DagLevel} are the ranks of the
+     * parent node, edge, and child node indices for {@code this},
+     * with rank 0 corresponding to the smallest index.
+     * @return an immutable {@code DagLevel} corresponding to {@code this}
+     */
+    public DagLevel toDagLevel() {
+        float[] modCounts = DagUtil.removeValues(counts, 0f);
+        int[] modSymbols = DagUtil.removeValues(symbols, -1);
+        int[] modParentNodes = DagUtil.removeValues(parentNodes, -1);
+        int[] modChildNodes = DagUtil.removeValues(childNodes, -1);
+        if (modCounts.length<=Character.MAX_VALUE) {
+            char[] mod2Symbols = toCharArray(modSymbols);
+            char[] mod2ParentNodes = rankedCharValues(modParentNodes);
+            char[] mod2ChildNodes = rankedCharValues(modChildNodes);
+            return new LowCapacityDagLevel(mod2ParentNodes, mod2ChildNodes,
+                    mod2Symbols, modCounts);
+        }
+        else {
+            int[] mod2ParentNodes = rankedIntValues(modParentNodes);
+            int[] mod2ChildNodes = rankedIntValues(modChildNodes);
+            return new HighCapacityDagLevel(mod2ParentNodes, mod2ChildNodes,
+                    modSymbols, modCounts);
+        }
+    }
+
+    private static char[] toCharArray(int[] ia) {
+        char[] ca = new char[ia.length];
+        for (int j=0; j < ca.length; ++j) {
+            if (ia[j] < 0 || ia[j] > Character.MAX_VALUE) {
+                throw new IllegalArgumentException(String.valueOf(ia[j]));
+            }
+            ca[j] = (char) ia[j];
+        }
+        return ca;
+    }
+
+    /*
+     * Returns an array obtained by replacing each array value with it's
+     * rank when the set of array values is ordered: the smallest value
+     * is replaced by 0, the next smallest value is replaced by 1, etc.
+     *
+     * @throws IllegalArgumentException if {@code array.length == 0}
+     * @throws IllegalArgumentException if any element of the array
+     * is negative
+     * @throws IllegalArgumentException if the array has more than
+     * {@code Character.MAX_VALUE + 1} distinct values
+     * @throws NullPointerException if {@code array == null}
+     */
+    private static char[] rankedCharValues(int[] array) {
+        if (array.length==0) {
+            throw new IllegalArgumentException("array.length==0");
+        }
+        int[] sortedCopy = array.clone();
+        Arrays.sort(sortedCopy);
+        if (sortedCopy[0] < 0) {
+            throw new IllegalArgumentException(String.valueOf(sortedCopy[0]));
+        }
+        int n = sortedCopy[sortedCopy.length - 1] + 1;
+        int[] indexMap = new int[n];
+        int index = 0;
+        indexMap[sortedCopy[0]] = index++;
+        for (int j=1; j<sortedCopy.length; ++j) {
+            if (sortedCopy[j] != sortedCopy[j-1]) {
+                indexMap[sortedCopy[j]] = index++;
+            }
+        }
+        if ( (index - 1) >= Character.MAX_VALUE) {
+            String s = "Array has more than (Character.MAX_VALUE + 1) values";
+            throw new IllegalArgumentException(s);
+        }
+        char[] transformedArray = new char[array.length];
+        for (int j=0; j<transformedArray.length; ++j) {
+            transformedArray[j] = (char) indexMap[array[j]];
+        }
+        return transformedArray;
+    }
+
+    /*
+     * Returns an array obtained by replacing each array value with it's
+     * rank when the set of array values is ordered: the smallest value
+     * is replaced by 0, the next smallest value is replaced by 1, etc.
+     *
+     * @throws IllegalArgumentException if {@code array.length == 0}
+     * @throws IllegalArgumentException if any element of the array
+     * is negative
+     * @throws NullPointerException if {@code array == null}
+     */
+    private static int[] rankedIntValues(int[] array) {
+        if (array.length==0) {
+            throw new IllegalArgumentException("array.length==0");
+        }
+        int[] sortedCopy = array.clone();
+        Arrays.sort(sortedCopy);
+        if (sortedCopy[0] < 0) {
+            throw new IllegalArgumentException(String.valueOf(sortedCopy[0]));
+        }
+        int n = sortedCopy[sortedCopy.length - 1] + 1;
+        int[] indexMap = new int[n];
+        int index = 0;
+        indexMap[sortedCopy[0]] = index++;
+        for (int j=1; j<sortedCopy.length; ++j) {
+            if (sortedCopy[j] != sortedCopy[j-1]) {
+                indexMap[sortedCopy[j]] = index++;
+            }
+        }
+        int[] transformedArray = new int[array.length];
+        for (int j=0; j<transformedArray.length; ++j) {
+            transformedArray[j] = indexMap[array[j]];
+        }
+        return transformedArray;
+    }
+
+    /**
+     * Merges the two specified parent nodes and assigns the specified
+     * {@code retainedNode} index to the merged node.
+     *
+     * @param retainedNode a parent node which will receive ingoing and
+     * outgoing edges of {@code removedNode}
+     * @param removedNode a parent node that will be deleted after merging.
+     *
+     * @throws IllegalArgumentException if {@code retainedNode}
+     * or {@code returnedNode} is not a valid parent node index.
+     */
+    public void mergeParentNodes(int retainedNode, int removedNode) {
+        if (isParentNode(retainedNode)==false) {
+            String s = "invalid parent node: " + retainedNode;
+            throw new IllegalArgumentException(s);
+        }
+        if (isParentNode(removedNode)==false) {
+            String s = "invalid parent node: " + removedNode;
+            throw new IllegalArgumentException(s);
+        }
+        prevLevel.mergeChildNodes(retainedNode, removedNode);
+        mergeParentNodes2(retainedNode, removedNode);
+    }
+
+    private void mergeParentNodes2(int retainedNode, int removedNode) {
+        for (int j=0; j<nAlleles; ++j) {
+            int retainedEdge = outEdges[j][retainedNode];
+            int removedEdge = outEdges[j][removedNode];
+            if (removedEdge >= 0) {
+                if (retainedEdge == -1) {
+                    changeParent(removedEdge, retainedNode);
+                }
+                else {
+                    int retainedChild = childNode(retainedEdge);
+                    int removedChild = childNode(removedEdge);
+                    mergeEdges(retainedEdge, removedEdge);
+                    if (nextLevel != null) {
+                        nextLevel.mergeParentNodes2(retainedChild, removedChild);
+                    }
+                }
+            }
+        }
+    }
+
+    /*
+     * Merges the two specified child nodes and assigns the merged
+     * node to the specified {@code retainedNode} index.  Ingoing edges
+     * to {@code removedNode} are redirected to be ingoing edges
+     * to {@code retainedNode}.
+     *
+     * @param retainedNode a child node which will receive ingoing edges of
+     * {@code removedNode}
+     * @param removedNode a child node that will be deleted after merging
+     */
+    private void mergeChildNodes(int retainedNode, int removedNode) {
+        int lastEdge = -1;
+        int edge = child2FirstInEdge[removedNode];
+        while (edge != -1) {
+            assert childNodes[edge] == removedNode;
+            childNodes[edge] = retainedNode;
+            lastEdge = edge;
+            edge = inEdge2NextInEdge[edge];
+        }
+        if (lastEdge != -1) {
+            inEdge2NextInEdge[lastEdge] = child2FirstInEdge[retainedNode];
+            child2FirstInEdge[retainedNode] = child2FirstInEdge[removedNode];
+            child2FirstInEdge[removedNode] = -1;
+        }
+    }
+
+    private void changeParent(int edge, int newParent) {
+        int oldParent = parentNodes[edge];
+        int symbol = symbols[edge];
+        assert (outEdges[symbol][oldParent] == edge);
+        assert (outEdges[symbol][newParent] == -1);
+        outEdges[symbol][oldParent] = -1;
+        outEdges[symbol][newParent] = edge;
+        parentNodes[edge] = newParent;
+    }
+
+    private void mergeEdges(int retainedEdge, int removedEdge) {
+        assert symbols[retainedEdge] == symbols[removedEdge];
+        assert counts[removedEdge] > 0.0f;
+        counts[retainedEdge] += counts[removedEdge];
+        if (nextLevel==null) {
+            mergeHaplotypes(childNodes[retainedEdge], childNodes[removedEdge]);
+        }
+        int parentNode = parentNodes[removedEdge];
+        int childNode = childNodes[removedEdge];
+        int symbol = symbols[removedEdge];
+        assert inEdge2NextInEdge[child2FirstInEdge[childNode]] == -1;
+        outEdges[symbol][parentNode] = -1;
+        child2FirstInEdge[childNode] = -1;
+        counts[removedEdge] = 0.0f;
+        parentNodes[removedEdge] = -1;
+        childNodes[removedEdge] = -1;
+        symbols[removedEdge] = -1;
+    }
+
+    private void mergeHaplotypes(int retainedChild, int removedChild) {
+        int hap = child2FirstHap[removedChild];
+        while (hap2NextHap[hap] != -1) {
+            hap = hap2NextHap[hap];
+        }
+        hap2NextHap[hap] = child2FirstHap[retainedChild];
+        child2FirstHap[retainedChild] = child2FirstHap[removedChild];
+        child2FirstHap[removedChild] = -1;
+    }
+
+    /**
+     * Returns the marker index.
+     * @return the marker index
+     */
+    public int index() {
+        return this.levelIndex;
+    }
+
+    /**
+     * Returns the number of sequences used to construct the DAG.
+     * @return the number of sequences used to construct the DAG
+     */
+    public int nHaps() {
+        return this.nHaps;
+    }
+
+    /**
+     * Returns the number of alleles.
+     *
+     * @return the number of alleles
+     */
+    public int nAlleles() {
+        return this.nAlleles;
+    }
+
+   /**
+    * Returns the sum of weights for the sequences that pass
+    * through the specified edge or 0 if the edge does not exist.
+    *
+    * @param edge index of the edge
+    * @return sum of weights for the sequences that pass
+    * through the specified edge or 0 if the edge does not exist
+    *
+    * @throws IndexOutOfBoundsException if
+    * {@code edge < 0 || edge >= this.nHaps()}
+    */
+    public float edgeCount(int edge) {
+        return counts[edge];
+    }
+
+   /**
+    * Returns the sum of weights for the sequences that pass
+    * through the specified parent node or 0 if the parent node
+    * does not exist.
+    *
+    * @param parentNode index of the parent node
+    * @return sum of weights for the sequences that pass
+    * through the specified parent node or 0 if the parent node
+    * does not exist
+    *
+    * @throws IndexOutOfBoundsException if
+    * {@code parentNode < 0 || parentNode >= this.nHaps()}
+    */
+    public float nodeCount(int parentNode) {
+        float sum = 0.0f;
+        for (int symbol=0; symbol<nAlleles; ++symbol) {
+            if (outEdges[symbol][parentNode] >= 0) {
+                sum += edgeCount(outEdges[symbol][parentNode]);
+            }
+        }
+        return sum;
+    }
+
+    /**
+     * Returns an array of parent node indices.
+     * @return an array of parent node indices
+     */
+    public int[] parentNodeArray() {
+        int[] sortedReducedArray = DagUtil.removeValues(parentNodes, -1);
+        Arrays.sort(sortedReducedArray);
+        assert sortedReducedArray.length > 0;
+        int cnt = 1;
+        for (int j=1; j<sortedReducedArray.length; ++j) {
+            if (sortedReducedArray[j] != sortedReducedArray[j-1]) {
+                ++cnt;
+            }
+        }
+        int[] parentNodeArray = new int[cnt];
+        int index = 0;
+        parentNodeArray[index++] = sortedReducedArray[0];
+        for (int j=1; j<sortedReducedArray.length; ++j) {
+            if (sortedReducedArray[j] != sortedReducedArray[j-1]) {
+                parentNodeArray[index++] = sortedReducedArray[j];
+            }
+        }
+        assert index==parentNodeArray.length;
+        return parentNodeArray;
+    }
+
+   /**
+     * Returns the parent node of the specified edge or -1 if the edge does
+     * not exist.
+     *
+     * @param edge index of the edge
+     * @return the parent node of the specified edge or -1 if the edge does
+     * not exist
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code edge < 0 || edge >= this.nHaps()}
+     */
+    public int parentNode(int edge) {
+        return parentNodes[edge];
+    }
+
+    /**
+     * Returns the child node of the specified edge or -1 if the edge does
+     * not exist
+     *
+     * @param edge the edge
+     * @return the child node of the specified edge or -1 if the edge does
+     * not exist
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code edge < 0 || edge >= this.Haplotypes()}
+     */
+    public int childNode(int edge) {
+        return childNodes[edge];
+    }
+
+    /**
+     * Returns the edge that is the outgoing edge of the specified
+     * parent parent node having the specified symbol, or
+     * returns -1 if no such edge exists.
+     *
+     * @param parentNode the parent node
+     * @param symbol symbol labeling the outgoing edge
+     * @return the edge that is the outgoing edge of the specified
+     * parent parent node having the specified symbol, or
+     * -1 if no such edge exists.
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code parentNode < 0 || parentNode >= this.nHaps()}
+     * @throws IndexOutOfBoundsException if
+     * {@code symbol < 0 || symbol >= this.nAlleles()}
+     */
+    public int outEdge(int parentNode, int symbol) {
+        return outEdges[symbol][parentNode];
+    }
+
+    /**
+     * Returns a string representation of {@code this}.  The exact
+     * details of the representation are unspecified and subject to change.
+     *
+     * @return a string representation of {@code this}
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(1000);
+        sb.append(Const.nl);
+        sb.append("[ MergeableDagLevel: marker=");
+        sb.append(levelIndex);
+        sb.append(Const.nl);
+        for (int j=0, n=nHaps(); j<n; ++j) {
+            if (parentNodes[j] != -1) {
+                sb.append("edge=");
+                sb.append(j);
+                sb.append(" parent=");
+                sb.append(parentNodes[j]);
+                sb.append(" child=");
+                sb.append(childNodes[j]);
+                sb.append(" symbol=");
+                sb.append(symbols[j]);
+                sb.append(" count=");
+                sb.append(counts[j]);
+                sb.append(Const.nl);
+            }
+        }
+        sb.append("previous=");
+        sb.append(prevLevel!=null);
+        sb.append(" next=");
+        sb.append(nextLevel!=null);
+        sb.append(Const.nl);
+        sb.append(" ]");
+        return sb.toString();
+    }
+
+    private boolean isParentNode(int node) {
+        if (prevLevel!=null) {
+            return prevLevel.child2FirstInEdge[node]>=0;
+        }
+        else {
+            for (int j=0; j<nAlleles; ++j) {
+                if (outEdges[j][node] != -1) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
 }
\ No newline at end of file
diff --git a/gpl_license b/gpl_license
index 94a9ed0..818433e 100644
--- a/gpl_license
+++ b/gpl_license
@@ -1,674 +1,674 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
-  The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works.  By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users.  We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors.  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
-  To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights.  Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received.  You must make sure that they, too, receive
-or can get the source code.  And you must show them these terms so they
-know their rights.
-
-  Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
-  For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software.  For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
-  Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so.  This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software.  The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable.  Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products.  If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
-  Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary.  To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                       TERMS AND CONDITIONS
-
-  0. Definitions.
-
-  "This License" refers to version 3 of the GNU General Public License.
-
-  "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
-  "The Program" refers to any copyrightable work licensed under this
-License.  Each licensee is addressed as "you".  "Licensees" and
-"recipients" may be individuals or organizations.
-
-  To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy.  The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
-  A "covered work" means either the unmodified Program or a work based
-on the Program.
-
-  To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy.  Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
-  To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies.  Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
-  An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License.  If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
-  1. Source Code.
-
-  The "source code" for a work means the preferred form of the work
-for making modifications to it.  "Object code" means any non-source
-form of a work.
-
-  A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
-  The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form.  A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
-  The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities.  However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work.  For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
-  The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
-  The Corresponding Source for a work in source code form is that
-same work.
-
-  2. Basic Permissions.
-
-  All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met.  This License explicitly affirms your unlimited
-permission to run the unmodified Program.  The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work.  This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
-  You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force.  You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright.  Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
-  Conveying under any other circumstances is permitted solely under
-the conditions stated below.  Sublicensing is not allowed; section 10
-makes it unnecessary.
-
-  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
-  No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
-  When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
-  4. Conveying Verbatim Copies.
-
-  You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
-  You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
-  5. Conveying Modified Source Versions.
-
-  You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
-    a) The work must carry prominent notices stating that you modified
-    it, and giving a relevant date.
-
-    b) The work must carry prominent notices stating that it is
-    released under this License and any conditions added under section
-    7.  This requirement modifies the requirement in section 4 to
-    "keep intact all notices".
-
-    c) You must license the entire work, as a whole, under this
-    License to anyone who comes into possession of a copy.  This
-    License will therefore apply, along with any applicable section 7
-    additional terms, to the whole of the work, and all its parts,
-    regardless of how they are packaged.  This License gives no
-    permission to license the work in any other way, but it does not
-    invalidate such permission if you have separately received it.
-
-    d) If the work has interactive user interfaces, each must display
-    Appropriate Legal Notices; however, if the Program has interactive
-    interfaces that do not display Appropriate Legal Notices, your
-    work need not make them do so.
-
-  A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit.  Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
-  6. Conveying Non-Source Forms.
-
-  You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
-    a) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by the
-    Corresponding Source fixed on a durable physical medium
-    customarily used for software interchange.
-
-    b) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by a
-    written offer, valid for at least three years and valid for as
-    long as you offer spare parts or customer support for that product
-    model, to give anyone who possesses the object code either (1) a
-    copy of the Corresponding Source for all the software in the
-    product that is covered by this License, on a durable physical
-    medium customarily used for software interchange, for a price no
-    more than your reasonable cost of physically performing this
-    conveying of source, or (2) access to copy the
-    Corresponding Source from a network server at no charge.
-
-    c) Convey individual copies of the object code with a copy of the
-    written offer to provide the Corresponding Source.  This
-    alternative is allowed only occasionally and noncommercially, and
-    only if you received the object code with such an offer, in accord
-    with subsection 6b.
-
-    d) Convey the object code by offering access from a designated
-    place (gratis or for a charge), and offer equivalent access to the
-    Corresponding Source in the same way through the same place at no
-    further charge.  You need not require recipients to copy the
-    Corresponding Source along with the object code.  If the place to
-    copy the object code is a network server, the Corresponding Source
-    may be on a different server (operated by you or a third party)
-    that supports equivalent copying facilities, provided you maintain
-    clear directions next to the object code saying where to find the
-    Corresponding Source.  Regardless of what server hosts the
-    Corresponding Source, you remain obligated to ensure that it is
-    available for as long as needed to satisfy these requirements.
-
-    e) Convey the object code using peer-to-peer transmission, provided
-    you inform other peers where the object code and Corresponding
-    Source of the work are being offered to the general public at no
-    charge under subsection 6d.
-
-  A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
-  A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling.  In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage.  For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product.  A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
-  "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source.  The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
-  If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information.  But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
-  The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed.  Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
-  Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
-  7. Additional Terms.
-
-  "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law.  If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
-  When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it.  (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.)  You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
-  Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
-    a) Disclaiming warranty or limiting liability differently from the
-    terms of sections 15 and 16 of this License; or
-
-    b) Requiring preservation of specified reasonable legal notices or
-    author attributions in that material or in the Appropriate Legal
-    Notices displayed by works containing it; or
-
-    c) Prohibiting misrepresentation of the origin of that material, or
-    requiring that modified versions of such material be marked in
-    reasonable ways as different from the original version; or
-
-    d) Limiting the use for publicity purposes of names of licensors or
-    authors of the material; or
-
-    e) Declining to grant rights under trademark law for use of some
-    trade names, trademarks, or service marks; or
-
-    f) Requiring indemnification of licensors and authors of that
-    material by anyone who conveys the material (or modified versions of
-    it) with contractual assumptions of liability to the recipient, for
-    any liability that these contractual assumptions directly impose on
-    those licensors and authors.
-
-  All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10.  If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term.  If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
-  If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
-  Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
-  8. Termination.
-
-  You may not propagate or modify a covered work except as expressly
-provided under this License.  Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
-  However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
-  Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
-  Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License.  If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
-  9. Acceptance Not Required for Having Copies.
-
-  You are not required to accept this License in order to receive or
-run a copy of the Program.  Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance.  However,
-nothing other than this License grants you permission to propagate or
-modify any covered work.  These actions infringe copyright if you do
-not accept this License.  Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
-  10. Automatic Licensing of Downstream Recipients.
-
-  Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License.  You are not responsible
-for enforcing compliance by third parties with this License.
-
-  An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations.  If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
-  You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License.  For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
-  11. Patents.
-
-  A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based.  The
-work thus licensed is called the contributor's "contributor version".
-
-  A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version.  For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
-  Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
-  In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement).  To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
-  If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients.  "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
-  If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
-  A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License.  You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
-  Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
-  12. No Surrender of Others' Freedom.
-
-  If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all.  For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
-  13. Use with the GNU Affero General Public License.
-
-  Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work.  The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
-  14. Revised Versions of this License.
-
-  The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-  Each version is given a distinguishing version number.  If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation.  If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
-  If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
-  Later license versions may give you additional or different
-permissions.  However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
-  15. Disclaimer of Warranty.
-
-  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. Limitation of Liability.
-
-  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
-  17. Interpretation of Sections 15 and 16.
-
-  If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This program is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
-  If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
-    <program>  Copyright (C) <year>  <name of author>
-    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
-  You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-<http://www.gnu.org/licenses/>.
-
-  The GNU General Public License does not permit incorporating your program
-into proprietary programs.  If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library.  If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.  But first, please read
-<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/haplotype/RefHapPairs.java b/haplotype/RefHapPairs.java
index 14f0dc7..5014db4 100644
--- a/haplotype/RefHapPairs.java
+++ b/haplotype/RefHapPairs.java
@@ -1,189 +1,189 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package haplotype;
-
-import beagleutil.Samples;
-import vcf.Marker;
-import vcf.Markers;
-import vcf.VcfEmission;
-
-/**
- * <p>Class {@code RefHapPairs} stores a list of samples and a
- * haplotype pair for each sample.
- * </p>
- * <p>Instances of class {@code RefHapPairs} are immutable.<p>
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class RefHapPairs implements SampleHapPairs {
-
-    private final Markers markers;
-    private final Samples samples;
-    private final VcfEmission[] refVcfRecs;
-
-    /**
-     * Constructs a new {@code RefHapPairs} instance.
-     * @param markers the sequence of markers
-     * @param samples the sequence of samples
-     * @param refVcfRecs the sequence of per-marker genotype data
-     *
-     * @throws IllegalArgumentException if
-     * {@code markers.nMarkers() != refVcfRecs.length}
-     * @throws IllegalArgumentException if
-     * {@code refVcfRecs[k].samples().equals(samples) == false} for any
-     * {@code k} satisfying {@code 0 <= k  && k < refVcfRecs.length}
-     * @throws IllegalArgumentException if
-     * {@code refVcfRecs[k].marker().equals(markers.marker(k)) == false}
-     * for any {@code k} satisfying {@code 0 <= k && k < refVcfRecs.length}
-     * @throws IllegalArgumentException if
-     * {@code refVcfRecs[k].isRefData() == false} for any {@code k}
-     * satisfying {@code 0 <= k && k < refVcfRecs.length}
-     * @throws NullPointerException if
-     * {@code markers == null || samples == null || refVcfRecs == null
-     * || refVcfRecs[k] == null} for any {@code k} satisfying
-     * {@code 0 <= k && k <= refVcfRecs.length}
-     */
-    public RefHapPairs(Markers markers, Samples samples,
-            VcfEmission[] refVcfRecs) {
-        checkPhasedMarkers(markers, samples, refVcfRecs);
-        this.markers = markers;
-        this.samples = samples;
-        this.refVcfRecs = refVcfRecs.clone();
-    }
-
-    private static void checkPhasedMarkers(Markers markers, Samples samples,
-            VcfEmission[] refVcfRecs) {
-        if (markers.nMarkers()!=refVcfRecs.length) {
-            String s = "markers.nMarkers()=" + markers.nMarkers()
-                    + " refVcfRecs.length=" + refVcfRecs.length;
-            throw new IllegalArgumentException(s);
-        }
-        for (int j=0; j<refVcfRecs.length; ++j) {
-            if (refVcfRecs[j].samples().equals(samples)==false) {
-                String s = "sample inconsistency at index " + j;
-                throw new IllegalArgumentException(s);
-            }
-            if (refVcfRecs[j].marker().equals(markers.marker(j))==false) {
-                String s = "marker inconsistency at index " + j;
-                throw new IllegalArgumentException(s);
-            }
-            if (refVcfRecs[j].isRefData()==false) {
-                String s = "non-reference data at marker index " + j;
-                throw new IllegalArgumentException(s);
-            }
-        }
-    }
-
-    @Override
-    public int allele1(int marker, int hapPair) {
-        return refVcfRecs[marker].allele1(hapPair);
-    }
-
-    @Override
-    public int allele2(int marker, int hapPair) {
-        return refVcfRecs[marker].allele2(hapPair);
-    }
-
-    @Override
-    public int allele(int marker, int haplotype) {
-        int hapPair = haplotype/2;
-        if ((haplotype & 1)==0) {
-            return refVcfRecs[marker].allele1(hapPair);
-        }
-        else {
-            return refVcfRecs[marker].allele2(hapPair);
-        }
-    }
-
-    @Override
-    public int nMarkers() {
-       return markers.nMarkers();
-    }
-
-    @Override
-    public Markers markers() {
-        return markers;
-    }
-
-    @Override
-    public Marker marker(int marker) {
-        return markers.marker(marker);
-    }
-
-    @Override
-    public int nHaps() {
-        return 2*samples.nSamples();
-    }
-
-    @Override
-    public int nHapPairs() {
-        return samples.nSamples();
-    }
-
-    @Override
-    public int nSamples() {
-        return samples.nSamples();
-    }
-
-    @Override
-    public Samples samples() {
-        return samples;
-    }
-
-    @Override
-    public Samples samples(int hapPair) {
-        if (hapPair < 0 || hapPair >= samples.nSamples()) {
-            throw new IndexOutOfBoundsException(String.valueOf(hapPair));
-        }
-        return samples;
-    }
-
-    @Override
-    public int sampleIndex(int hapPair) {
-        if (hapPair < 0 || hapPair >= samples.nSamples()) {
-            throw new IndexOutOfBoundsException(String.valueOf(hapPair));
-        }
-        return hapPair;
-    }
-
-    @Override
-    public int nAlleles(int marker) {
-        return refVcfRecs[marker].nAlleles();
-    }
-
-    @Override
-    public boolean storesNonMajorIndices(int marker) {
-        return refVcfRecs[marker].storesNonMajorIndices();
-    }
-
-    @Override
-    public int majorAllele(int marker) {
-        return refVcfRecs[marker].majorAllele();
-    }
-
-    @Override
-    public int alleleCount(int marker, int allele) {
-        return refVcfRecs[marker].alleleCount(allele);
-    }
-
-    @Override
-    public int hapIndex(int marker, int allele, int copy) {
-        return refVcfRecs[marker].hapIndex(allele, copy);
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package haplotype;
+
+import beagleutil.Samples;
+import vcf.Marker;
+import vcf.Markers;
+import vcf.VcfEmission;
+
+/**
+ * <p>Class {@code RefHapPairs} stores a list of samples and a
+ * haplotype pair for each sample.
+ * </p>
+ * <p>Instances of class {@code RefHapPairs} are immutable.<p>
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class RefHapPairs implements SampleHapPairs {
+
+    private final Markers markers;
+    private final Samples samples;
+    private final VcfEmission[] refVcfRecs;
+
+    /**
+     * Constructs a new {@code RefHapPairs} instance.
+     * @param markers the sequence of markers
+     * @param samples the sequence of samples
+     * @param refVcfRecs the sequence of per-marker genotype data
+     *
+     * @throws IllegalArgumentException if
+     * {@code markers.nMarkers() != refVcfRecs.length}
+     * @throws IllegalArgumentException if
+     * {@code refVcfRecs[k].samples().equals(samples) == false} for any
+     * {@code k} satisfying {@code 0 <= k  && k < refVcfRecs.length}
+     * @throws IllegalArgumentException if
+     * {@code refVcfRecs[k].marker().equals(markers.marker(k)) == false}
+     * for any {@code k} satisfying {@code 0 <= k && k < refVcfRecs.length}
+     * @throws IllegalArgumentException if
+     * {@code refVcfRecs[k].isRefData() == false} for any {@code k}
+     * satisfying {@code 0 <= k && k < refVcfRecs.length}
+     * @throws NullPointerException if
+     * {@code markers == null || samples == null || refVcfRecs == null
+     * || refVcfRecs[k] == null} for any {@code k} satisfying
+     * {@code 0 <= k && k <= refVcfRecs.length}
+     */
+    public RefHapPairs(Markers markers, Samples samples,
+            VcfEmission[] refVcfRecs) {
+        checkPhasedMarkers(markers, samples, refVcfRecs);
+        this.markers = markers;
+        this.samples = samples;
+        this.refVcfRecs = refVcfRecs.clone();
+    }
+
+    private static void checkPhasedMarkers(Markers markers, Samples samples,
+            VcfEmission[] refVcfRecs) {
+        if (markers.nMarkers()!=refVcfRecs.length) {
+            String s = "markers.nMarkers()=" + markers.nMarkers()
+                    + " refVcfRecs.length=" + refVcfRecs.length;
+            throw new IllegalArgumentException(s);
+        }
+        for (int j=0; j<refVcfRecs.length; ++j) {
+            if (refVcfRecs[j].samples().equals(samples)==false) {
+                String s = "sample inconsistency at index " + j;
+                throw new IllegalArgumentException(s);
+            }
+            if (refVcfRecs[j].marker().equals(markers.marker(j))==false) {
+                String s = "marker inconsistency at index " + j;
+                throw new IllegalArgumentException(s);
+            }
+            if (refVcfRecs[j].isRefData()==false) {
+                String s = "non-reference data at marker index " + j;
+                throw new IllegalArgumentException(s);
+            }
+        }
+    }
+
+    @Override
+    public int allele1(int marker, int hapPair) {
+        return refVcfRecs[marker].allele1(hapPair);
+    }
+
+    @Override
+    public int allele2(int marker, int hapPair) {
+        return refVcfRecs[marker].allele2(hapPair);
+    }
+
+    @Override
+    public int allele(int marker, int haplotype) {
+        int hapPair = haplotype/2;
+        if ((haplotype & 1)==0) {
+            return refVcfRecs[marker].allele1(hapPair);
+        }
+        else {
+            return refVcfRecs[marker].allele2(hapPair);
+        }
+    }
+
+    @Override
+    public int nMarkers() {
+       return markers.nMarkers();
+    }
+
+    @Override
+    public Markers markers() {
+        return markers;
+    }
+
+    @Override
+    public Marker marker(int marker) {
+        return markers.marker(marker);
+    }
+
+    @Override
+    public int nHaps() {
+        return 2*samples.nSamples();
+    }
+
+    @Override
+    public int nHapPairs() {
+        return samples.nSamples();
+    }
+
+    @Override
+    public int nSamples() {
+        return samples.nSamples();
+    }
+
+    @Override
+    public Samples samples() {
+        return samples;
+    }
+
+    @Override
+    public Samples samples(int hapPair) {
+        if (hapPair < 0 || hapPair >= samples.nSamples()) {
+            throw new IndexOutOfBoundsException(String.valueOf(hapPair));
+        }
+        return samples;
+    }
+
+    @Override
+    public int sampleIndex(int hapPair) {
+        if (hapPair < 0 || hapPair >= samples.nSamples()) {
+            throw new IndexOutOfBoundsException(String.valueOf(hapPair));
+        }
+        return hapPair;
+    }
+
+    @Override
+    public int nAlleles(int marker) {
+        return refVcfRecs[marker].nAlleles();
+    }
+
+    @Override
+    public boolean storesNonMajorIndices(int marker) {
+        return refVcfRecs[marker].storesNonMajorIndices();
+    }
+
+    @Override
+    public int majorAllele(int marker) {
+        return refVcfRecs[marker].majorAllele();
+    }
+
+    @Override
+    public int alleleCount(int marker, int allele) {
+        return refVcfRecs[marker].alleleCount(allele);
+    }
+
+    @Override
+    public int hapIndex(int marker, int allele, int copy) {
+        return refVcfRecs[marker].hapIndex(allele, copy);
+    }
+}
diff --git a/ibd/HaploidIbd.java b/ibd/HaploidIbd.java
index 925593d..e4575e2 100644
--- a/ibd/HaploidIbd.java
+++ b/ibd/HaploidIbd.java
@@ -1,264 +1,264 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package ibd;
-
-import blbutil.IntPair;
-import blbutil.Utilities;
-import dag.Dag;
-import haplotype.HapPairs;
-import haplotype.SampleHapPairs;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import vcf.GL;
-
-/**
- * <p>Class {@code HaploidIbd} implements the Refined IBD algorithm.
- * The Refined IBD algorithm detects candidate haplotype IBD segments with the
- * Germline Algorithm and then evaluates candidate IBD segments using a
- * likelihood ratio test.
- * </p>
- * <p>Instances of class {@code HaploidIbd} are immutable.
- *</p>
- * Reference: Gusev A, Lowe JK, Stoffel M, Daly MJ, Altshuler D, Breslow JL,
- *      Friedman JM, Pe'er I.  Whole population, genomewide mapping
- *      of hidden relatedness.  Genome Research 2009;19(2):318-26.
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public final class HaploidIbd {
-
-    private final int ibdTrim;
-    private final float minIbdLod;
-    private final float minIbsLength;   // positions from Dag.posArray()
-    private final float minFreqLod;     // for shared haplotype
-
-    /**
-     * Constructs a new {@code HaploidIbd} instance from the specified data.
-     * @param ibdTrim the number of markers to trim from an IBS segment
-     * when computing the IBD versus non-IBD likelihood ratio
-     * @param minIbdLod the minimum IBD LOD score of reported IBD segments
-     *
-     * @throws IllegalArgumentException if {@code ibdTrim < 0 }
-     * @throws IllegalArgumentException if
-     * {@code ibdLod <= 0.0f || Float.isFinite(ibdLod) == false}
-     */
-    public HaploidIbd(int ibdTrim, float minIbdLod) {
-        if (ibdTrim < 0) {
-            throw new IllegalArgumentException("trim: " + ibdTrim);
-        }
-        if (minIbdLod <= 0.0 || Float.isFinite(minIbdLod) == false) {
-            throw new IllegalArgumentException("minIbdlod: " + minIbdLod);
-        }
-        this.ibdTrim = ibdTrim;
-        this.minIbdLod = minIbdLod;
-        this.minIbsLength = 0.8f*minIbdLod;
-        this.minFreqLod = minIbdLod;
-    }
-
-    /**
-     * Runs the Refined IBD algorithm, and returns a map whose keys are
-     * ordered pairs of haplotype indices and whose values are thread-safe
-     * lists of IBD segments for each haplotype pair. The minimum haplotype
-     * index is listed first in each ordered pair of haplotype indices.
-     *
-     * @param gl the HMM emission probabilities
-     * @param dag the HMM transition probabilities
-     * @param haps the sample haplotype pairs
-     * @param nThreads the number of threads of execution that may be used
-     * @return the detected IBD segments
-     *
-     * @throws IllegalArgumentException if {@code nThreads < 1}
-     * @throws IllegalArgumentException if
-     * {@code gl.samples().equals(haps.samples()) == false}
-     * @throws IllegalArgumentException if
-     * {@code gl.markers().equals(dag.markers()) == false
-                || gl.markers().equals(haps.markers()) == false}
-     * @throws NullPointerException if
-     * {@code gl == null || dag == null || haps == null}
-     */
-    @SuppressWarnings({"BroadCatchBlock", "TooBroadCatch"})
-    public Map<IntPair, List<IbdSegment>> run(GL gl, Dag dag,
-            SampleHapPairs haps, final int nThreads) {
-        checkParameters(gl, dag, haps);
-        double[] pos = dag.posArray();
-        IbsHapSegments ibsSegments = new IbsHapSegments(haps, pos, minIbsLength);
-        ConcurrentMap<IntPair, List<IbdSegment>> ibdMap
-                = new ConcurrentHashMap<>();
-
-        final BlockingQueue<Integer> qIn = new ArrayBlockingQueue<>(5*nThreads);
-        ExecutorService es = Executors.newFixedThreadPool(nThreads);
-        for (int j=0; j<nThreads; ++j) {
-            IbdBaum baum = new IbdBaum(dag, gl);
-            es.submit(new ProduceIbd(haps, baum, ibsSegments, qIn, ibdMap,
-                    ibdTrim, minIbdLod));
-        }
-        try {
-            for (int hap=0, n=haps.nHaps(); hap<n; ++hap) {
-                qIn.put(hap);
-            }
-            for (int j=0; j<nThreads; ++j) {
-               qIn.put(ProduceIbd.POISON);
-            }
-            es.shutdown();
-            es.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
-        }
-        catch (Throwable e) {
-            Utilities.exit("ERROR", e);
-        }
-        return ibdMap;
-    }
-
-    private void checkParameters(GL gl, Dag dag, SampleHapPairs haps) {
-        if (gl.samples().equals(haps.samples())==false) {
-            throw new IllegalArgumentException("inconstent samples");
-        }
-        if (gl.markers().equals(dag.markers())==false
-                || gl.markers().equals(haps.markers())==false) {
-            throw new IllegalArgumentException("inconsistent markers");
-        }
-    }
-
-    private static double freqLod(int hap, int start, int end, int ibdTrim,
-            Dag dag, HapPairs haps) {
-        int trimmedStart = start + ibdTrim;
-        int trimmedEnd = end - ibdTrim;
-        if (trimmedStart >= trimmedEnd) {
-            return 0.0f;
-        }
-        else {
-            return IbdBaum.freqLod(hap, trimmedStart, trimmedEnd, haps, dag);
-        }
-    }
-
-    private static double ibdLod(IbdBaum ibdBaum, int hap1, int hap2, int start,
-            int end, int ibdTrim) {
-        int trimmedStart = start + ibdTrim;
-        int trimmedEnd = end - ibdTrim;
-        if (trimmedStart >= trimmedEnd) {
-            return 0.0f;
-        }
-        else {
-            int sample1 = hap1/2;
-            int sample2 = hap2/2;
-            return ibdBaum.ibdLod(sample1, sample2, trimmedStart, trimmedEnd);
-        }
-    }
-
-    private class ProduceIbd implements Runnable {
-
-        public static final int POISON = -37;
-
-        private final SampleHapPairs haps;
-        private final IbdBaum baum;
-        private final IbsHapSegments ibsHapSegments;
-        private final BlockingQueue<Integer> qIn;
-        private final ConcurrentMap<IntPair, List<IbdSegment>> ibdMap;
-        private final int ibdTrim;
-        private final float minIbdLod;
-
-        public ProduceIbd(SampleHapPairs haps, IbdBaum baum,
-                IbsHapSegments ibsHapSegments, BlockingQueue<Integer> qIn,
-                ConcurrentMap<IntPair, List<IbdSegment>> ibdMap, int ibdTrim,
-                float minIbdLod) {
-            if (ibdTrim < 0) {
-                throw new IllegalArgumentException("trim < 0: " + ibdTrim);
-            }
-            if (minIbdLod <= 0.0 || Float.isNaN(minIbdLod)) {
-                throw new IllegalArgumentException("ibdlod: " + minIbdLod);
-            }
-            this.haps = haps;
-            this.baum = baum;
-            this.ibsHapSegments = ibsHapSegments;
-            this.qIn = qIn;
-            this.ibdMap = ibdMap;
-            this.ibdTrim = ibdTrim;
-            this.minIbdLod = minIbdLod;
-        }
-
-        /*
-         * Takes haplotype indices from a thread-safe work-queue and stores
-         * detected IBD segments that between the haplotype and
-         * haplotypes with larger index in {@code this.ibdMap}.  The method
-         * exits when {@code ProduceSingleSamples.POISON} is taken from the
-         * work queue.
-         *
-         * @throws IndexOutOfBounds exception if a negative integer
-         * other than {@code ProduceSingleSamples.POISON} is taken from the
-         * work queue
-         */
-        @Override
-        @SuppressWarnings({"BroadCatchBlock", "TooBroadCatch"})
-        public void run() {
-            try {
-                int hap = qIn.take();
-                while (hap!=POISON) {
-                    List<HapSegment> ibsSegs = ibsHapSegments.find(hap);
-                    for (int j=0, n=ibsSegs.size(); j<n; ++j) {
-                        HapSegment hs = ibsSegs.get(j);
-                        if (hap < hs.hap()) {
-                            int start = hs.start();
-                            int end = hs.end();
-                            double freqLod = HaploidIbd.freqLod(hap, start,
-                                    (end+1), ibdTrim, baum.dag(), haps);
-                            if (freqLod >= minFreqLod) {
-                                float ibdLod;
-                                if ( (hap/2) == (hs.hap()/2) ) {
-                                    int sample = hap/2;
-                                    ibdLod = (float) baum.hbdLod(sample, start, (end+1));
-                                }
-                                else {
-                                    ibdLod = (float) HaploidIbd.ibdLod(baum, hap,
-                                            hs.hap(), start, (end+1), ibdTrim);
-                                }
-                                if (ibdLod >= minIbdLod) {
-                                    IntPair hapPair = new IntPair(hap, hs.hap());
-                                    List<IbdSegment> list = ibdMap.get(hapPair);
-                                    if (list==null) {
-                                        list = Collections.synchronizedList(
-                                                new ArrayList<IbdSegment>(2));
-                                        ibdMap.putIfAbsent(hapPair, list);
-                                        list = ibdMap.get(hapPair);
-                                    }
-                                    IbdSegment segment = new IbdSegment(hapPair,
-                                            baum.gl().marker(start),
-                                            baum.gl().marker(end),
-                                            ibdLod, start, end );
-                                    list.add(segment);
-                                }
-                            }
-                        }
-                    }
-                    hap = qIn.take();
-                }
-            }
-            catch (Throwable e) {
-                Utilities.exit("ProduceSingleSamples: ERROR", e);
-            }
-        }
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package ibd;
+
+import blbutil.IntPair;
+import blbutil.Utilities;
+import dag.Dag;
+import haplotype.HapPairs;
+import haplotype.SampleHapPairs;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import vcf.GL;
+
+/**
+ * <p>Class {@code HaploidIbd} implements the Refined IBD algorithm.
+ * The Refined IBD algorithm detects candidate haplotype IBD segments with the
+ * Germline Algorithm and then evaluates candidate IBD segments using a
+ * likelihood ratio test.
+ * </p>
+ * <p>Instances of class {@code HaploidIbd} are immutable.
+ *</p>
+ * Reference: Gusev A, Lowe JK, Stoffel M, Daly MJ, Altshuler D, Breslow JL,
+ *      Friedman JM, Pe'er I.  Whole population, genomewide mapping
+ *      of hidden relatedness.  Genome Research 2009;19(2):318-26.
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public final class HaploidIbd {
+
+    private final int ibdTrim;
+    private final float minIbdLod;
+    private final float minIbsLength;   // positions from Dag.posArray()
+    private final float minFreqLod;     // for shared haplotype
+
+    /**
+     * Constructs a new {@code HaploidIbd} instance from the specified data.
+     * @param ibdTrim the number of markers to trim from an IBS segment
+     * when computing the IBD versus non-IBD likelihood ratio
+     * @param minIbdLod the minimum IBD LOD score of reported IBD segments
+     *
+     * @throws IllegalArgumentException if {@code ibdTrim < 0 }
+     * @throws IllegalArgumentException if
+     * {@code ibdLod <= 0.0f || Float.isFinite(ibdLod) == false}
+     */
+    public HaploidIbd(int ibdTrim, float minIbdLod) {
+        if (ibdTrim < 0) {
+            throw new IllegalArgumentException("trim: " + ibdTrim);
+        }
+        if (minIbdLod <= 0.0 || Float.isFinite(minIbdLod) == false) {
+            throw new IllegalArgumentException("minIbdlod: " + minIbdLod);
+        }
+        this.ibdTrim = ibdTrim;
+        this.minIbdLod = minIbdLod;
+        this.minIbsLength = 0.8f*minIbdLod;
+        this.minFreqLod = minIbdLod;
+    }
+
+    /**
+     * Runs the Refined IBD algorithm, and returns a map whose keys are
+     * ordered pairs of haplotype indices and whose values are thread-safe
+     * lists of IBD segments for each haplotype pair. The minimum haplotype
+     * index is listed first in each ordered pair of haplotype indices.
+     *
+     * @param gl the HMM emission probabilities
+     * @param dag the HMM transition probabilities
+     * @param haps the sample haplotype pairs
+     * @param nThreads the number of threads of execution that may be used
+     * @return the detected IBD segments
+     *
+     * @throws IllegalArgumentException if {@code nThreads < 1}
+     * @throws IllegalArgumentException if
+     * {@code gl.samples().equals(haps.samples()) == false}
+     * @throws IllegalArgumentException if
+     * {@code gl.markers().equals(dag.markers()) == false
+                || gl.markers().equals(haps.markers()) == false}
+     * @throws NullPointerException if
+     * {@code gl == null || dag == null || haps == null}
+     */
+    @SuppressWarnings({"BroadCatchBlock", "TooBroadCatch"})
+    public Map<IntPair, List<IbdSegment>> run(GL gl, Dag dag,
+            SampleHapPairs haps, final int nThreads) {
+        checkParameters(gl, dag, haps);
+        double[] pos = dag.posArray();
+        IbsHapSegments ibsSegments = new IbsHapSegments(haps, pos, minIbsLength);
+        ConcurrentMap<IntPair, List<IbdSegment>> ibdMap
+                = new ConcurrentHashMap<>();
+
+        final BlockingQueue<Integer> qIn = new ArrayBlockingQueue<>(5*nThreads);
+        ExecutorService es = Executors.newFixedThreadPool(nThreads);
+        for (int j=0; j<nThreads; ++j) {
+            IbdBaum baum = new IbdBaum(dag, gl);
+            es.submit(new ProduceIbd(haps, baum, ibsSegments, qIn, ibdMap,
+                    ibdTrim, minIbdLod));
+        }
+        try {
+            for (int hap=0, n=haps.nHaps(); hap<n; ++hap) {
+                qIn.put(hap);
+            }
+            for (int j=0; j<nThreads; ++j) {
+               qIn.put(ProduceIbd.POISON);
+            }
+            es.shutdown();
+            es.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
+        }
+        catch (Throwable e) {
+            Utilities.exit("ERROR", e);
+        }
+        return ibdMap;
+    }
+
+    private void checkParameters(GL gl, Dag dag, SampleHapPairs haps) {
+        if (gl.samples().equals(haps.samples())==false) {
+            throw new IllegalArgumentException("inconstent samples");
+        }
+        if (gl.markers().equals(dag.markers())==false
+                || gl.markers().equals(haps.markers())==false) {
+            throw new IllegalArgumentException("inconsistent markers");
+        }
+    }
+
+    private static double freqLod(int hap, int start, int end, int ibdTrim,
+            Dag dag, HapPairs haps) {
+        int trimmedStart = start + ibdTrim;
+        int trimmedEnd = end - ibdTrim;
+        if (trimmedStart >= trimmedEnd) {
+            return 0.0f;
+        }
+        else {
+            return IbdBaum.freqLod(hap, trimmedStart, trimmedEnd, haps, dag);
+        }
+    }
+
+    private static double ibdLod(IbdBaum ibdBaum, int hap1, int hap2, int start,
+            int end, int ibdTrim) {
+        int trimmedStart = start + ibdTrim;
+        int trimmedEnd = end - ibdTrim;
+        if (trimmedStart >= trimmedEnd) {
+            return 0.0f;
+        }
+        else {
+            int sample1 = hap1/2;
+            int sample2 = hap2/2;
+            return ibdBaum.ibdLod(sample1, sample2, trimmedStart, trimmedEnd);
+        }
+    }
+
+    private class ProduceIbd implements Runnable {
+
+        public static final int POISON = -37;
+
+        private final SampleHapPairs haps;
+        private final IbdBaum baum;
+        private final IbsHapSegments ibsHapSegments;
+        private final BlockingQueue<Integer> qIn;
+        private final ConcurrentMap<IntPair, List<IbdSegment>> ibdMap;
+        private final int ibdTrim;
+        private final float minIbdLod;
+
+        public ProduceIbd(SampleHapPairs haps, IbdBaum baum,
+                IbsHapSegments ibsHapSegments, BlockingQueue<Integer> qIn,
+                ConcurrentMap<IntPair, List<IbdSegment>> ibdMap, int ibdTrim,
+                float minIbdLod) {
+            if (ibdTrim < 0) {
+                throw new IllegalArgumentException("trim < 0: " + ibdTrim);
+            }
+            if (minIbdLod <= 0.0 || Float.isNaN(minIbdLod)) {
+                throw new IllegalArgumentException("ibdlod: " + minIbdLod);
+            }
+            this.haps = haps;
+            this.baum = baum;
+            this.ibsHapSegments = ibsHapSegments;
+            this.qIn = qIn;
+            this.ibdMap = ibdMap;
+            this.ibdTrim = ibdTrim;
+            this.minIbdLod = minIbdLod;
+        }
+
+        /*
+         * Takes haplotype indices from a thread-safe work-queue and stores
+         * detected IBD segments that between the haplotype and
+         * haplotypes with larger index in {@code this.ibdMap}.  The method
+         * exits when {@code ProduceSingleSamples.POISON} is taken from the
+         * work queue.
+         *
+         * @throws IndexOutOfBounds exception if a negative integer
+         * other than {@code ProduceSingleSamples.POISON} is taken from the
+         * work queue
+         */
+        @Override
+        @SuppressWarnings({"BroadCatchBlock", "TooBroadCatch"})
+        public void run() {
+            try {
+                int hap = qIn.take();
+                while (hap!=POISON) {
+                    List<HapSegment> ibsSegs = ibsHapSegments.find(hap);
+                    for (int j=0, n=ibsSegs.size(); j<n; ++j) {
+                        HapSegment hs = ibsSegs.get(j);
+                        if (hap < hs.hap()) {
+                            int start = hs.start();
+                            int end = hs.end();
+                            double freqLod = HaploidIbd.freqLod(hap, start,
+                                    (end+1), ibdTrim, baum.dag(), haps);
+                            if (freqLod >= minFreqLod) {
+                                float ibdLod;
+                                if ( (hap/2) == (hs.hap()/2) ) {
+                                    int sample = hap/2;
+                                    ibdLod = (float) baum.hbdLod(sample, start, (end+1));
+                                }
+                                else {
+                                    ibdLod = (float) HaploidIbd.ibdLod(baum, hap,
+                                            hs.hap(), start, (end+1), ibdTrim);
+                                }
+                                if (ibdLod >= minIbdLod) {
+                                    IntPair hapPair = new IntPair(hap, hs.hap());
+                                    List<IbdSegment> list = ibdMap.get(hapPair);
+                                    if (list==null) {
+                                        list = Collections.synchronizedList(
+                                                new ArrayList<IbdSegment>(2));
+                                        ibdMap.putIfAbsent(hapPair, list);
+                                        list = ibdMap.get(hapPair);
+                                    }
+                                    IbdSegment segment = new IbdSegment(hapPair,
+                                            baum.gl().marker(start),
+                                            baum.gl().marker(end),
+                                            ibdLod, start, end );
+                                    list.add(segment);
+                                }
+                            }
+                        }
+                    }
+                    hap = qIn.take();
+                }
+            }
+            catch (Throwable e) {
+                Utilities.exit("ProduceSingleSamples: ERROR", e);
+            }
+        }
+    }
+}
diff --git a/main/ConstrainedAlleleProbs.java b/main/ConstrainedAlleleProbs.java
index cfa050e..420acd1 100644
--- a/main/ConstrainedAlleleProbs.java
+++ b/main/ConstrainedAlleleProbs.java
@@ -81,25 +81,38 @@ public class ConstrainedAlleleProbs implements AlleleProbs {
         this.indexMap = indexMap.clone();
     }
 
+    /**
+     * Returns {@code true} if the specified marker is not present in the
+     * input data and returns {@code false} otherwise.
+     * @param marker a marker index
+     * @return {@code true} if the specified marker is not present in the
+     * input target data
+     * @throws IndexOutOfBoundsException if
+     * {@code marker < 0 || marker >= this.nMarkers()}
+     */
+    public boolean isImputed(int marker) {
+        return indexMap[marker] == -1;
+    }
+
     @Override
     public float alProb1(int marker, int sample, int allele) {
-        int glMarker = indexMap[marker];
-        if (glMarker == -1) {
+        int targetMarker = indexMap[marker];
+        if (targetMarker == -1) {
             return alProbs.alProb1(marker, sample, allele);
         }
         else {
-            return shp.allele1(glMarker, sample) == allele ? 1f : 0f;
+            return shp.allele1(targetMarker, sample) == allele ? 1f : 0f;
         }
     }
 
     @Override
     public float alProb2(int marker, int sample, int allele) {
-        int glMarker = indexMap[marker];
-        if (glMarker == -1) {
+        int targetMarker = indexMap[marker];
+        if (targetMarker == -1) {
             return alProbs.alProb2(marker, sample, allele);
         }
         else {
-            return shp.allele2(glMarker, sample) == allele ? 1f : 0f;
+            return shp.allele2(targetMarker, sample) == allele ? 1f : 0f;
         }
     }
 
@@ -110,23 +123,23 @@ public class ConstrainedAlleleProbs implements AlleleProbs {
 
     @Override
     public int allele1(int marker, int sample) {
-        int glMarker = indexMap[marker];
-        if (glMarker == -1) {
+        int targetMarker = indexMap[marker];
+        if (targetMarker == -1) {
             return alProbs.allele1(marker, sample);
         }
         else {
-            return shp.allele1(glMarker, sample);
+            return shp.allele1(targetMarker, sample);
         }
     }
 
     @Override
     public int allele2(int marker, int sample) {
-        int glMarker = indexMap[marker];
-        if (glMarker == -1) {
+        int targetMarker = indexMap[marker];
+        if (targetMarker == -1) {
             return alProbs.allele2(marker, sample);
         }
         else {
-            return shp.allele2(glMarker, sample);
+            return shp.allele2(targetMarker, sample);
         }
     }
 
diff --git a/main/Main.java b/main/Main.java
index 9ba5d4c..c072110 100644
--- a/main/Main.java
+++ b/main/Main.java
@@ -65,8 +65,8 @@ public class Main {
     /**
      * The program name and version.
      */
-    public static final String program = "beagle.22Feb16.8ef.jar (version 4.1)";
-    public static final String command = "java -jar beagle.22Feb16.8ef.jar";
+    public static final String program = "beagle.03May16.862.jar (version 4.1)";
+    public static final String command = "java -jar beagle.03May16.862.jar";
 
     /**
      * The copyright string.
@@ -78,7 +78,7 @@ public class Main {
      */
     public static final String shortHelp = Main.program
             + Const.nl + Main.copyright
-            + Const.nl + "Enter \"java -jar beagle.22Feb16.8ef.jar\" for a "
+            + Const.nl + "Enter \"java -jar beagle.03May16.862.jar\" for a "
             + "summary of command line " + "arguments.";
 
     private final Par par;
@@ -194,15 +194,7 @@ public class Main {
     private void printOutput(CurrentData cd, SampleHapPairs targetHapPairs,
             AlleleProbs alProbs, Map<IntPair, List<IbdSegment>> ibd) {
         assert par.gt()!=null;
-        boolean markersAreImputed = false;
-        boolean printGprobs = false;
-        if (cd.nTargetMarkers() < cd.nMarkers()){
-            alProbs = new ConstrainedAlleleProbs(targetHapPairs, alProbs,
-                    cd.targetMarkerIndices());
-            markersAreImputed = true;
-            printGprobs = par.gprobs();
-        }
-        windowOut.print(cd, alProbs, markersAreImputed, printGprobs);
+        windowOut.print(cd, targetHapPairs, alProbs, par.gprobs());
         if (par.ibd()) {
             windowOut.printIbd(cd, ibd);
         }
diff --git a/main/WindowWriter.java b/main/WindowWriter.java
index eb3a21a..ee0d794 100644
--- a/main/WindowWriter.java
+++ b/main/WindowWriter.java
@@ -22,6 +22,7 @@ import beagleutil.Samples;
 import blbutil.Const;
 import blbutil.FileUtil;
 import blbutil.IntPair;
+import haplotype.SampleHapPairs;
 import ibd.IbdSegment;
 import java.io.Closeable;
 import java.io.File;
@@ -140,11 +141,10 @@ public class WindowWriter implements Closeable {
      * {@code cd.nextSplice()} (exclusive).
      *
      * @param cd the input data for the current marker window
+     * @param targetHapPairs the target haplotype pairs
      * @param alProbs the estimated haplotype allele probabilities
-     * @param imputed {@code true} if there are imputed markers,
-     * and {@code false} otherwise
-     * @param gprobs {@code true} if the GP field should be printed, and
-     * {@code false} otherwise
+     * @param gprobs {@code true} if the GP field should be printed when
+     * imputed markers are present, and {@code false} otherwise
      *
      * @throws IllegalStateException if {@code this.isClosed() == true}
      * @throws IllegalArgumentException if
@@ -153,10 +153,11 @@ public class WindowWriter implements Closeable {
      * {@code this.samples().equals(alProbs.samples()) == false}
      * @throws IllegalArgumentException if
      * {@code cd.markers().equals(alProbs.markers()) == false}
-     * @throws NullPointerException if {@code cd == null || alProbs == null}
+     * @throws NullPointerException if
+     * {@code cd == null || targetHapPairs == null || alProbs == null}
      */
-    public void print(CurrentData cd, AlleleProbs alProbs, boolean imputed,
-            boolean gprobs) {
+    public void print(CurrentData cd, SampleHapPairs targetHapPairs,
+            AlleleProbs alProbs, boolean gprobs) {
         if (isClosed) {
             throw new IllegalStateException("isClosed()==true");
         }
@@ -169,7 +170,14 @@ public class WindowWriter implements Closeable {
         }
         int start = cd.prevSplice();
         int end = cd.nextSplice();
-        VcfWriter.appendRecords(alProbs, start, end, imputed, gprobs, vcfOut);
+        if (cd.nTargetMarkers() < cd.nMarkers()){
+            ConstrainedAlleleProbs constAlProbs = new ConstrainedAlleleProbs(
+                    targetHapPairs, alProbs, cd.targetMarkerIndices());
+            VcfWriter.appendRecords(constAlProbs, start, end, gprobs, vcfOut);
+        }
+        else {
+            VcfWriter.appendRecords(alProbs, start, end, vcfOut);
+        }
         vcfOut.flush();
     }
 
diff --git a/sample/DuoBaumLevel.java b/sample/DuoBaumLevel.java
index 163a909..8e794bb 100644
--- a/sample/DuoBaumLevel.java
+++ b/sample/DuoBaumLevel.java
@@ -1,641 +1,641 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package sample;
-
-import dag.Dag;
-import java.util.Arrays;
-import vcf.BasicGL;
-import vcf.GL;
-
-/**
- * <p>Class {@code DuoBaumLevel} computes forward and backward Baum
- * values at a level of a hidden Markov model (HMM) whose states are
- * ordered edge trios of a leveled directed acyclic graph (DAG).
- * </p>
- * <p>Instances of class {@code SingleBaumLevel} are not thread-safe.
- * </p>
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class DuoBaumLevel {
-
-    private static final int INITIAL_CAPACITY = 400;
-    private static final float MIN_VALUE = 100*Float.MIN_VALUE;
-    private final Dag dag;
-    private final GL gl;
-
-    private int marker = -1;
-    private int sampleA = -1;
-    private int sampleB = -1;
-    private int size = 0;
-
-    private int capacity = INITIAL_CAPACITY;
-    private int[] edgesAB1 = new int[INITIAL_CAPACITY];
-    private int[] edgesA2 = new int[INITIAL_CAPACITY];
-    private int[] edgesB2 = new int[INITIAL_CAPACITY];
-    private float[] fwdValues = new float[INITIAL_CAPACITY];
-    private float[] bwdValues = new float[INITIAL_CAPACITY];
-    private float fwdValueSum = 0f;
-    private float bwdValueSum = 0f;
-
-    private int nGenotypes = 0;
-    private float[] gtProbsA = new float[3];
-    private float[] gtProbsB = new float[3];
-
-    /**
-     * Constructs a new {@code DuoBaumLevel} instance from the specified data.
-     * @param dag the directed acyclic graph that the determines transition
-     * probabilities
-     * @param gl the emission probabilities
-     * @throws IllegalArgumentException if
-     * {@code dag.markers().equals(gl.markers()) == false}
-     * @throws NullPointerException if {@code dag == null || gl == null}
-     */
-    public DuoBaumLevel(Dag dag, GL gl) {
-        if (dag.markers().equals(gl.markers())==false) {
-            throw new IllegalArgumentException("marker inconsistency");
-        }
-        this.dag = dag;
-        this.gl = gl;
-    }
-
-    /**
-     * Sets the Baum forward algorithm values for this level of the HMM
-     * and records the child node trio values in the specified
-     * {@code nodes} parameter. When the method call returns, the {@code nodes}
-     * parameter will be reset to the child node trio values for this level of
-     * the HMM.
-     *
-     * @param nodes child node trio values at the previous level of the HMM
-     * @param marker the level of the HMM at which the Baum forward algorithm
-     * probabilities will be computed
-     * @param sampleA the parent sample index
-     * @param sampleB the offspring sample index
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code marker < 0 || marker >= this.dag().nMarkers()}
-     * @throws IndexOutOfBoundsException if
-     * {@code sampleA < 0 || sampleA >= this.gl().nSamples()}
-     * @throws IndexOutOfBoundsException if
-     * {@code sampleB < 0 || sampleB >= this.gl().nSamples()}
-     * @throws IndexOutOfBoundsException if any node in any node trio with
-     * non-zero value is not a valid parent node at the specified level of the
-     * HMM
-     * @throws NullPointerException if {@code nodes == null}
-     */
-    public void setForwardValues(DuoNodes nodes, int marker, int sampleA,
-            int sampleB) {
-        this.marker = marker;
-        this.sampleA = sampleA;
-        this.sampleB = sampleB;
-        this.nGenotypes = gl.marker(marker).nGenotypes();
-        this.size = 0;
-        this.fwdValueSum = 0f;
-        this.bwdValueSum = 0f;
-        initializeGtProbs(); // initialized here due to gtProbs() contract
-        setStates(nodes);
-        setChildNodes(nodes);
-    }
-
-    private void initializeGtProbs() {
-        if (gtProbsA.length < nGenotypes) {
-            int newLength = Math.max(nGenotypes, (3*gtProbsA.length/2 + 1));
-            gtProbsA = new float[newLength];
-            gtProbsB = new float[newLength];
-        }
-        else {
-            for (int j=0; j<nGenotypes; ++j) {
-                gtProbsA[j] = 0f;
-                gtProbsB[j] = 0f;
-            }
-        }
-    }
-
-    private void setStates(DuoNodes nodes) {
-        float valueSum = 0f;
-        for (int j=0, n=nodes.size(); j<n; ++j) {
-            int nodeAB1 = nodes.enumNodeAB1(j);
-            int nodeA2 = nodes.enumNodeA2(j);
-            int nodeB2 = nodes.enumNodeB2(j);
-            float nodeValue = nodes.enumValue(j);
-            for (int ab1=0, nAB1=dag.nOutEdges(marker, nodeAB1); ab1<nAB1; ++ab1) {
-                int edgeAB1 = dag.outEdge(marker, nodeAB1, ab1);
-                int symbolAB1 = dag.symbol(marker, edgeAB1);
-                for (int a2=0, nA2=dag.nOutEdges(marker, nodeA2); a2<nA2; ++a2) {
-                    int edgeA2 = dag.outEdge(marker, nodeA2, a2);
-                    int symbolA2 = dag.symbol(marker, edgeA2);
-                    float epA = gl.gl(marker, sampleA, symbolAB1, symbolA2);
-                    if (epA > 0.0) {
-                        for (int b2=0, nB2=dag.nOutEdges(marker, nodeB2); b2<nB2; ++b2) {
-                            int edgeB2 = dag.outEdge(marker, nodeB2, b2);
-                            int symbolB2 = dag.symbol(marker, edgeB2);
-                            float epB = gl.gl(marker, sampleB, symbolAB1, symbolB2);
-                            if (epB > 0.0) {
-                                if (size == capacity) {
-                                    ensureCapacity(size+1);
-                                }
-                                float tpAB1 = dag.condEdgeProb(marker, edgeAB1);
-                                float tpA2 = dag.condEdgeProb(marker, edgeA2);
-                                float tpB2 = dag.condEdgeProb(marker, edgeB2);
-                                float fwdValue = (epA * epB) * nodeValue
-                                        * (tpAB1 * tpA2 * tpB2);
-                                if (fwdValue<MIN_VALUE && nodeValue > 0.0) {
-                                    fwdValue = MIN_VALUE;
-                                }
-                                edgesAB1[size] = edgeAB1;
-                                edgesA2[size] = edgeA2;
-                                edgesB2[size] = edgeB2;
-                                fwdValues[size++] = fwdValue;
-                                valueSum += fwdValue;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        assert valueSum>0.0 ^ size==0;
-        for (int k=0; k<size; ++k) {
-            this.fwdValues[k] /= valueSum;
-        }
-        fwdValueSum = valueSum;
-    }
-
-    /**
-     * Stores the Baum forward algorithm child node trio values for this
-     * level of the HMM in the specified {@code DuoNodes} object.
-     *
-     * @param nodes the node trio values that will be set
-     *
-     * @throws NullPointerException if {@code nodes == null}
-     */
-    public void setChildNodes(DuoNodes nodes) {
-        nodes.clear();
-        for (int k=0; k<size; ++k) {
-            int nodeAB1 = dag.childNode(marker, edgesAB1[k]);
-            int nodeA2 = dag.childNode(marker, edgesA2[k]);
-            int nodeB2 = dag.childNode(marker, edgesB2[k]);
-            nodes.sumUpdate(nodeAB1, nodeA2, nodeB2, fwdValues[k]);
-        }
-    }
-
-    /**
-     * Sets the Baum backward algorithm values for this level of the HMM
-     * and stores the parent node trio values in the specified
-     * {@code nodes} parameter. When the method call returns, the
-     * ${@code nodes} parameter will be reset to the parent node trio values
-     * for this level of the HMM.
-     *
-     * @param nodes parent node trio values at the next level of HMM
-     *
-     * @throws IndexOutOfBoundsException if any node in any node trio with
-     * non-zero value is not a valid child node at this level of the HMM
-     * @throws NullPointerException if {@code nodes == null}
-     */
-    public void setBackwardValues(DuoNodes nodes) {
-        for (int j=0; j<size; ++j) {
-            int nodeAB1 = dag.childNode(marker, edgesAB1[j]);
-            int nodeA2 = dag.childNode(marker, edgesA2[j]);
-            int nodeB2 = dag.childNode(marker, edgesB2[j]);
-            float backwardValue = nodes.value(nodeAB1, nodeA2, nodeB2);
-            bwdValues[j] = backwardValue;
-            bwdValueSum += backwardValue;
-        }
-        nodes.clear();
-        float gtProbsSum = 0f;
-        for (int j=0; j<size; ++j) {
-            bwdValues[j] /= bwdValueSum;
-            int symbolAB1 = symbolAB1(j);
-            int symbolA2 = symbolA2(j);
-            int symbolB2 = symbolB2(j);
-            float tpAB1 = dag.condEdgeProb(marker, edgesAB1[j]);
-            float tpA2 = dag.condEdgeProb(marker, edgesA2[j]);
-            float tpB2 = dag.condEdgeProb(marker, edgesB2[j]);
-
-            float stateProb = fwdValues[j] * bwdValues[j];
-            int gtIndexA = BasicGL.genotype(symbolAB1, symbolA2);
-            int gtIndexB = BasicGL.genotype(symbolAB1, symbolB2);
-            // gtProbs[AB] assumed to be initialized in setForwardValues() method
-            gtProbsA[gtIndexA] += stateProb;
-            gtProbsB[gtIndexB] += stateProb;
-            gtProbsSum += stateProb;
-
-            float epA = gl.gl(marker, sampleA, symbolAB1, symbolA2);
-            float epB = gl.gl(marker, sampleB, symbolAB1, symbolB2);
-            float bwdValue = bwdValues[j] * (tpAB1 * tpA2 * tpB2) * (epA*epB);
-            if (bwdValue < MIN_VALUE && bwdValues[j] > 0f) {
-                bwdValue = MIN_VALUE;
-            }
-            int pnAB1 = dag.parentNode(marker, edgesAB1[j]);
-            int pnA2 = dag.parentNode(marker, edgesA2[j]);
-            int pnB2 = dag.parentNode(marker, edgesB2[j]);
-            nodes.sumUpdate(pnAB1, pnA2, pnB2, bwdValue);
-        }
-        for (int j=0; j<nGenotypes; ++j) {
-            gtProbsA[j] /= gtProbsSum;
-            gtProbsB[j] /= gtProbsSum;
-        }
-    }
-
-    /**
-     * Returns the directed acyclic graph that determines the transition
-     * probabilities.
-     * @return the directed acyclic graph that determines the transition
-     * probabilities
-     */
-    public Dag dag() {
-        return dag;
-    }
-
-    /**
-     * Returns the emission probabilities.
-     * @return the emission probabilities
-     */
-    public GL gl() {
-        return gl;
-    }
-
-    /**
-     * Return the level of the HMM.
-     * @return the level of the HMM
-     */
-    public int marker() {
-        return marker;
-    }
-
-    /**
-     * Return the number of possible genotypes at this level of the HMM.
-     * @return the number of possible genotypes at this level of the HMM
-     */
-    public int nGenotypes() {
-        return nGenotypes;
-    }
-
-    /**
-     * Returns the specified posterior genotype probability for the parent.
-     * Returns 0 if the Baum backward probabilities have not been set.
-     * @param gt a genotype index
-     * @return the specified posterior genotype probability for the parent
-     * @throws IndexOutOfBoundsException if
-     * {@code gt < 0 || gt >= this.nGenotypes()}
-     */
-    public float gtProbsA(int gt) {
-        checkGT(gt);
-        return gtProbsA[gt];
-    }
-
-    /**
-     * Returns the specified posterior genotype probability for the offspring.
-     * Returns 0 if the Baum backward probabilities have not been set.
-     * @param gt a genotype index
-     * @return the specified posterior genotype probability for the offspring
-     * @throws IndexOutOfBoundsException if
-     * {@code gt < 0 || gt >= this.nGenotypes()}
-     */
-    public float gtProbsB(int gt) {
-        checkGT(gt);
-        return gtProbsB[gt];
-    }
-
-    private void checkGT(int gt) {
-        if (gt >= nGenotypes) {
-            throw new IllegalArgumentException(String.valueOf(gt));
-        }
-    }
-
-    /**
-     * Return the number of states with nonzero forward probability at
-     * this level of the HMM.
-     *
-     * @return the number of states with nonzero forward probability at
-     * this level of the HMM
-     */
-    public int size() {
-        return size;
-    }
-
-    private void checkIndex(int state) {
-        if (state >= size) {
-            throw new IndexOutOfBoundsException(String.valueOf(size));
-        }
-    }
-
-    /**
-     * Returns the first edge of the specified HMM state with nonzero forward
-     * probability.
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the first edge of the specified HMM state with nonzero forward
-     * probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int edgeAB1(int state) {
-        checkIndex(state);
-        return edgesAB1[state];
-    }
-
-    /**
-     * Returns the second edge of the specified HMM state with nonzero forward
-     * probability.
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the second edge of the specified HMM state with nonzero forward
-     * probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int edgeA2(int state) {
-        checkIndex(state);
-        return edgesA2[state];
-    }
-
-    /**
-     * Returns the third edge of the specified HMM state with nonzero forward
-     * probability.
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the third edge of the specified HMM state with nonzero forward
-     * probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int edgeB2(int state) {
-        checkIndex(state);
-        return edgesB2[state];
-    }
-
-    /**
-     * Returns the parent node of the first edge of the specified HMM state
-     * with nonzero forward probability.
-     *
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the parent node of the first edge of the specified HMM state
-     * with nonzero forward probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int parentNodeAB1(int state) {
-        checkIndex(state);
-        return dag.parentNode(marker, edgesAB1[state]);
-    }
-
-    /**
-     * Returns the parent node of the second edge of the specified HMM state
-     * with nonzero forward probability.
-     *
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the parent node of the second edge of the specified HMM state
-     * with nonzero forward probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int parentNodeA2(int state) {
-        checkIndex(state);
-        return dag.parentNode(marker, edgesA2[state]);
-    }
-
-    /**
-     * Returns the parent node of the third edge of the specified HMM state
-     * with nonzero forward probability.
-     *
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the parent node of the third edge of the specified HMM state
-     * with nonzero forward probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int parentNodeB2(int state) {
-        checkIndex(state);
-        return dag.parentNode(marker, edgesB2[state]);
-    }
-
-    /**
-     * Returns the child node of the first edge of the specified HMM state
-     * with nonzero forward probability.
-     *
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the child node of the first edge of the specified HMM state
-     * with nonzero forward probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int childNodeAB1(int state) {
-        checkIndex(state);
-        return dag.childNode(marker, edgesAB1[state]);
-    }
-
-    /**
-     * Returns the child node of the second edge of the specified HMM state
-     * with nonzero forward probability.
-     *
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the child node of the second edge of the specified HMM state
-     * with nonzero forward probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int childNodeA2(int state) {
-        checkIndex(state);
-        return dag.childNode(marker, edgesA2[state]);
-    }
-
-    /**
-     * Returns the child node of the third edge of the specified HMM state
-     * with nonzero forward probability.
-     *
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the child node of the third edge of the specified HMM state
-     * with nonzero forward probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int childNodeB2(int state) {
-        checkIndex(state);
-        return dag.childNode(marker, edgesB2[state]);
-    }
-
-    /**
-     * Returns the symbol for the first edge of the specified HMM state
-     * with nonzero forward probability.
-     *
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the symbol for the first edge of the specified HMM state
-     * with nonzero forward probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int symbolAB1(int state) {
-        return dag.symbol(marker, edgeAB1(state));
-    }
-
-    /**
-     * Returns the symbol for the second edge of the specified HMM state
-     * with nonzero forward probability.
-     *
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the symbol for the second edge of the specified HMM state
-     * with nonzero forward probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int symbolA2(int state) {
-        return dag.symbol(marker, edgeA2(state));
-    }
-
-    /**
-     * Returns the symbol for the third edge of the specified HMM state
-     * with nonzero forward probability.
-     *
-     * @param state an index of a HMM state with nonzero forward probability
-     * @return the symbol for the third edge of the specified HMM state
-     * with nonzero forward probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public int symbolB2(int state) {
-        return dag.symbol(marker, edgeB2(state));
-    }
-
-    /**
-     * Returns the normalized forward value for the specified HMM state
-     * with nonzero forward probability.
-     * The normalized forward value is obtained by dividing the
-     * forward value by the sum of the forward values at this level
-     * of the HMM.
-     *
-     * @param state an index of a HMM state with nonzero forward probability
-     *
-     * @return the normalized forward value for the specified HMM state
-     * with nonzero forward probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public float forwardValue(int state) {
-        checkIndex(state);
-        return fwdValues[state];
-    }
-
-    /**
-     * Returns the normalized backward value for the specified HMM state
-     * with nonzero forward probability.
-     * The normalized backward value is obtained by dividing the
-     * backward value by the sum of the backward values at this level
-     * of the HMM.
-     *
-     * @param state an index of a state with nonzero forward probability
-     *
-     * @return the normalized backward value for the specified HMM state
-     * with nonzero forward probability
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code state < 0 || state >= this.size()}
-     */
-    public float backwardValue(int state) {
-        checkIndex(state);
-        return bwdValues[state];
-    }
-
-    /**
-     * Returns the sum of the forward values at this level of the HMM
-     * when the forward values are computed using forward values
-     * from the previous level that are normalized to sum to 1.
-     * @return the sum of the forward values at this level of the HMM
-     */
-    public float forwardValuesSum() {
-        return fwdValueSum;
-    }
-
-    /**
-     * Returns the sum of the backward values at this level of the HMM
-     * when the backward values are computed using backward
-     * values from the next level that are normalized to sum to 1.
-     * @return the sum of the backward values at this level of the HMM
-     */
-    public float backwardValuesSum() {
-        return bwdValueSum;
-    }
-
-    /**
-     * Returns a string description of {@code this}.  The exact details
-     * of the description are unspecified and subject to change.
-     *
-     * @return a string description of {@code this}.
-     */
-    @Override
-    public String toString() {
-        String space = " ";
-        String sep = " | ";
-        StringBuilder sb = new StringBuilder(100);
-        sb.append("level=");
-        sb.append(marker);
-        sb.append(" size=");
-        sb.append(size);
-        sb.append(" forwardValuesSum=");
-        sb.append(fwdValueSum);
-        sb.append(" backwardSum=");
-        sb.append(bwdValueSum);
-        for (int j=0; j<size; ++j) {
-            sb.append(sep);
-            sb.append("j=");
-            sb.append(j);
-            sb.append(": ");
-            sb.append( (int) edgeAB1(j));
-            sb.append(space);
-            sb.append( (int) edgeA2(j));
-            sb.append(space);
-            sb.append( (int) edgeB2(j));
-            sb.append(space);
-            sb.append(forwardValue(j));
-            sb.append(space);
-            sb.append(backwardValue(j));
-        }
-        sb.append(sep);
-        return sb.toString();
-    }
-
-    /*
-     * Increases the state capacity of array fields as necessary
-     * to be greater than or equal to the specified minimum capacity.
-     *
-     * @param minCapacity the desired minimum state capacity
-     */
-    private void ensureCapacity(int minCapacity) {
-        if (minCapacity > capacity) {
-            capacity = (capacity * 3)/2 + 1;
-            if (capacity < minCapacity) {
-                capacity = minCapacity;
-            }
-            edgesAB1 = Arrays.copyOf(edgesAB1, capacity);
-            edgesA2 = Arrays.copyOf(edgesA2, capacity);
-            edgesB2 = Arrays.copyOf(edgesB2, capacity);
-            fwdValues = Arrays.copyOf(fwdValues, capacity);
-            bwdValues = Arrays.copyOf(bwdValues, capacity);
-        }
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package sample;
+
+import dag.Dag;
+import java.util.Arrays;
+import vcf.BasicGL;
+import vcf.GL;
+
+/**
+ * <p>Class {@code DuoBaumLevel} computes forward and backward Baum
+ * values at a level of a hidden Markov model (HMM) whose states are
+ * ordered edge trios of a leveled directed acyclic graph (DAG).
+ * </p>
+ * <p>Instances of class {@code SingleBaumLevel} are not thread-safe.
+ * </p>
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class DuoBaumLevel {
+
+    private static final int INITIAL_CAPACITY = 400;
+    private static final float MIN_VALUE = 100*Float.MIN_VALUE;
+    private final Dag dag;
+    private final GL gl;
+
+    private int marker = -1;
+    private int sampleA = -1;
+    private int sampleB = -1;
+    private int size = 0;
+
+    private int capacity = INITIAL_CAPACITY;
+    private int[] edgesAB1 = new int[INITIAL_CAPACITY];
+    private int[] edgesA2 = new int[INITIAL_CAPACITY];
+    private int[] edgesB2 = new int[INITIAL_CAPACITY];
+    private float[] fwdValues = new float[INITIAL_CAPACITY];
+    private float[] bwdValues = new float[INITIAL_CAPACITY];
+    private float fwdValueSum = 0f;
+    private float bwdValueSum = 0f;
+
+    private int nGenotypes = 0;
+    private float[] gtProbsA = new float[3];
+    private float[] gtProbsB = new float[3];
+
+    /**
+     * Constructs a new {@code DuoBaumLevel} instance from the specified data.
+     * @param dag the directed acyclic graph that the determines transition
+     * probabilities
+     * @param gl the emission probabilities
+     * @throws IllegalArgumentException if
+     * {@code dag.markers().equals(gl.markers()) == false}
+     * @throws NullPointerException if {@code dag == null || gl == null}
+     */
+    public DuoBaumLevel(Dag dag, GL gl) {
+        if (dag.markers().equals(gl.markers())==false) {
+            throw new IllegalArgumentException("marker inconsistency");
+        }
+        this.dag = dag;
+        this.gl = gl;
+    }
+
+    /**
+     * Sets the Baum forward algorithm values for this level of the HMM
+     * and records the child node trio values in the specified
+     * {@code nodes} parameter. When the method call returns, the {@code nodes}
+     * parameter will be reset to the child node trio values for this level of
+     * the HMM.
+     *
+     * @param nodes child node trio values at the previous level of the HMM
+     * @param marker the level of the HMM at which the Baum forward algorithm
+     * probabilities will be computed
+     * @param sampleA the parent sample index
+     * @param sampleB the offspring sample index
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code marker < 0 || marker >= this.dag().nMarkers()}
+     * @throws IndexOutOfBoundsException if
+     * {@code sampleA < 0 || sampleA >= this.gl().nSamples()}
+     * @throws IndexOutOfBoundsException if
+     * {@code sampleB < 0 || sampleB >= this.gl().nSamples()}
+     * @throws IndexOutOfBoundsException if any node in any node trio with
+     * non-zero value is not a valid parent node at the specified level of the
+     * HMM
+     * @throws NullPointerException if {@code nodes == null}
+     */
+    public void setForwardValues(DuoNodes nodes, int marker, int sampleA,
+            int sampleB) {
+        this.marker = marker;
+        this.sampleA = sampleA;
+        this.sampleB = sampleB;
+        this.nGenotypes = gl.marker(marker).nGenotypes();
+        this.size = 0;
+        this.fwdValueSum = 0f;
+        this.bwdValueSum = 0f;
+        initializeGtProbs(); // initialized here due to gtProbs() contract
+        setStates(nodes);
+        setChildNodes(nodes);
+    }
+
+    private void initializeGtProbs() {
+        if (gtProbsA.length < nGenotypes) {
+            int newLength = Math.max(nGenotypes, (3*gtProbsA.length/2 + 1));
+            gtProbsA = new float[newLength];
+            gtProbsB = new float[newLength];
+        }
+        else {
+            for (int j=0; j<nGenotypes; ++j) {
+                gtProbsA[j] = 0f;
+                gtProbsB[j] = 0f;
+            }
+        }
+    }
+
+    private void setStates(DuoNodes nodes) {
+        float valueSum = 0f;
+        for (int j=0, n=nodes.size(); j<n; ++j) {
+            int nodeAB1 = nodes.enumNodeAB1(j);
+            int nodeA2 = nodes.enumNodeA2(j);
+            int nodeB2 = nodes.enumNodeB2(j);
+            float nodeValue = nodes.enumValue(j);
+            for (int ab1=0, nAB1=dag.nOutEdges(marker, nodeAB1); ab1<nAB1; ++ab1) {
+                int edgeAB1 = dag.outEdge(marker, nodeAB1, ab1);
+                int symbolAB1 = dag.symbol(marker, edgeAB1);
+                for (int a2=0, nA2=dag.nOutEdges(marker, nodeA2); a2<nA2; ++a2) {
+                    int edgeA2 = dag.outEdge(marker, nodeA2, a2);
+                    int symbolA2 = dag.symbol(marker, edgeA2);
+                    float epA = gl.gl(marker, sampleA, symbolAB1, symbolA2);
+                    if (epA > 0.0) {
+                        for (int b2=0, nB2=dag.nOutEdges(marker, nodeB2); b2<nB2; ++b2) {
+                            int edgeB2 = dag.outEdge(marker, nodeB2, b2);
+                            int symbolB2 = dag.symbol(marker, edgeB2);
+                            float epB = gl.gl(marker, sampleB, symbolAB1, symbolB2);
+                            if (epB > 0.0) {
+                                if (size == capacity) {
+                                    ensureCapacity(size+1);
+                                }
+                                float tpAB1 = dag.condEdgeProb(marker, edgeAB1);
+                                float tpA2 = dag.condEdgeProb(marker, edgeA2);
+                                float tpB2 = dag.condEdgeProb(marker, edgeB2);
+                                float fwdValue = (epA * epB) * nodeValue
+                                        * (tpAB1 * tpA2 * tpB2);
+                                if (fwdValue<MIN_VALUE && nodeValue > 0.0) {
+                                    fwdValue = MIN_VALUE;
+                                }
+                                edgesAB1[size] = edgeAB1;
+                                edgesA2[size] = edgeA2;
+                                edgesB2[size] = edgeB2;
+                                fwdValues[size++] = fwdValue;
+                                valueSum += fwdValue;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        assert valueSum>0.0 ^ size==0;
+        for (int k=0; k<size; ++k) {
+            this.fwdValues[k] /= valueSum;
+        }
+        fwdValueSum = valueSum;
+    }
+
+    /**
+     * Stores the Baum forward algorithm child node trio values for this
+     * level of the HMM in the specified {@code DuoNodes} object.
+     *
+     * @param nodes the node trio values that will be set
+     *
+     * @throws NullPointerException if {@code nodes == null}
+     */
+    public void setChildNodes(DuoNodes nodes) {
+        nodes.clear();
+        for (int k=0; k<size; ++k) {
+            int nodeAB1 = dag.childNode(marker, edgesAB1[k]);
+            int nodeA2 = dag.childNode(marker, edgesA2[k]);
+            int nodeB2 = dag.childNode(marker, edgesB2[k]);
+            nodes.sumUpdate(nodeAB1, nodeA2, nodeB2, fwdValues[k]);
+        }
+    }
+
+    /**
+     * Sets the Baum backward algorithm values for this level of the HMM
+     * and stores the parent node trio values in the specified
+     * {@code nodes} parameter. When the method call returns, the
+     * ${@code nodes} parameter will be reset to the parent node trio values
+     * for this level of the HMM.
+     *
+     * @param nodes parent node trio values at the next level of HMM
+     *
+     * @throws IndexOutOfBoundsException if any node in any node trio with
+     * non-zero value is not a valid child node at this level of the HMM
+     * @throws NullPointerException if {@code nodes == null}
+     */
+    public void setBackwardValues(DuoNodes nodes) {
+        for (int j=0; j<size; ++j) {
+            int nodeAB1 = dag.childNode(marker, edgesAB1[j]);
+            int nodeA2 = dag.childNode(marker, edgesA2[j]);
+            int nodeB2 = dag.childNode(marker, edgesB2[j]);
+            float backwardValue = nodes.value(nodeAB1, nodeA2, nodeB2);
+            bwdValues[j] = backwardValue;
+            bwdValueSum += backwardValue;
+        }
+        nodes.clear();
+        float gtProbsSum = 0f;
+        for (int j=0; j<size; ++j) {
+            bwdValues[j] /= bwdValueSum;
+            int symbolAB1 = symbolAB1(j);
+            int symbolA2 = symbolA2(j);
+            int symbolB2 = symbolB2(j);
+            float tpAB1 = dag.condEdgeProb(marker, edgesAB1[j]);
+            float tpA2 = dag.condEdgeProb(marker, edgesA2[j]);
+            float tpB2 = dag.condEdgeProb(marker, edgesB2[j]);
+
+            float stateProb = fwdValues[j] * bwdValues[j];
+            int gtIndexA = BasicGL.genotype(symbolAB1, symbolA2);
+            int gtIndexB = BasicGL.genotype(symbolAB1, symbolB2);
+            // gtProbs[AB] assumed to be initialized in setForwardValues() method
+            gtProbsA[gtIndexA] += stateProb;
+            gtProbsB[gtIndexB] += stateProb;
+            gtProbsSum += stateProb;
+
+            float epA = gl.gl(marker, sampleA, symbolAB1, symbolA2);
+            float epB = gl.gl(marker, sampleB, symbolAB1, symbolB2);
+            float bwdValue = bwdValues[j] * (tpAB1 * tpA2 * tpB2) * (epA*epB);
+            if (bwdValue < MIN_VALUE && bwdValues[j] > 0f) {
+                bwdValue = MIN_VALUE;
+            }
+            int pnAB1 = dag.parentNode(marker, edgesAB1[j]);
+            int pnA2 = dag.parentNode(marker, edgesA2[j]);
+            int pnB2 = dag.parentNode(marker, edgesB2[j]);
+            nodes.sumUpdate(pnAB1, pnA2, pnB2, bwdValue);
+        }
+        for (int j=0; j<nGenotypes; ++j) {
+            gtProbsA[j] /= gtProbsSum;
+            gtProbsB[j] /= gtProbsSum;
+        }
+    }
+
+    /**
+     * Returns the directed acyclic graph that determines the transition
+     * probabilities.
+     * @return the directed acyclic graph that determines the transition
+     * probabilities
+     */
+    public Dag dag() {
+        return dag;
+    }
+
+    /**
+     * Returns the emission probabilities.
+     * @return the emission probabilities
+     */
+    public GL gl() {
+        return gl;
+    }
+
+    /**
+     * Return the level of the HMM.
+     * @return the level of the HMM
+     */
+    public int marker() {
+        return marker;
+    }
+
+    /**
+     * Return the number of possible genotypes at this level of the HMM.
+     * @return the number of possible genotypes at this level of the HMM
+     */
+    public int nGenotypes() {
+        return nGenotypes;
+    }
+
+    /**
+     * Returns the specified posterior genotype probability for the parent.
+     * Returns 0 if the Baum backward probabilities have not been set.
+     * @param gt a genotype index
+     * @return the specified posterior genotype probability for the parent
+     * @throws IndexOutOfBoundsException if
+     * {@code gt < 0 || gt >= this.nGenotypes()}
+     */
+    public float gtProbsA(int gt) {
+        checkGT(gt);
+        return gtProbsA[gt];
+    }
+
+    /**
+     * Returns the specified posterior genotype probability for the offspring.
+     * Returns 0 if the Baum backward probabilities have not been set.
+     * @param gt a genotype index
+     * @return the specified posterior genotype probability for the offspring
+     * @throws IndexOutOfBoundsException if
+     * {@code gt < 0 || gt >= this.nGenotypes()}
+     */
+    public float gtProbsB(int gt) {
+        checkGT(gt);
+        return gtProbsB[gt];
+    }
+
+    private void checkGT(int gt) {
+        if (gt >= nGenotypes) {
+            throw new IllegalArgumentException(String.valueOf(gt));
+        }
+    }
+
+    /**
+     * Return the number of states with nonzero forward probability at
+     * this level of the HMM.
+     *
+     * @return the number of states with nonzero forward probability at
+     * this level of the HMM
+     */
+    public int size() {
+        return size;
+    }
+
+    private void checkIndex(int state) {
+        if (state >= size) {
+            throw new IndexOutOfBoundsException(String.valueOf(size));
+        }
+    }
+
+    /**
+     * Returns the first edge of the specified HMM state with nonzero forward
+     * probability.
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the first edge of the specified HMM state with nonzero forward
+     * probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int edgeAB1(int state) {
+        checkIndex(state);
+        return edgesAB1[state];
+    }
+
+    /**
+     * Returns the second edge of the specified HMM state with nonzero forward
+     * probability.
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the second edge of the specified HMM state with nonzero forward
+     * probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int edgeA2(int state) {
+        checkIndex(state);
+        return edgesA2[state];
+    }
+
+    /**
+     * Returns the third edge of the specified HMM state with nonzero forward
+     * probability.
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the third edge of the specified HMM state with nonzero forward
+     * probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int edgeB2(int state) {
+        checkIndex(state);
+        return edgesB2[state];
+    }
+
+    /**
+     * Returns the parent node of the first edge of the specified HMM state
+     * with nonzero forward probability.
+     *
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the parent node of the first edge of the specified HMM state
+     * with nonzero forward probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int parentNodeAB1(int state) {
+        checkIndex(state);
+        return dag.parentNode(marker, edgesAB1[state]);
+    }
+
+    /**
+     * Returns the parent node of the second edge of the specified HMM state
+     * with nonzero forward probability.
+     *
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the parent node of the second edge of the specified HMM state
+     * with nonzero forward probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int parentNodeA2(int state) {
+        checkIndex(state);
+        return dag.parentNode(marker, edgesA2[state]);
+    }
+
+    /**
+     * Returns the parent node of the third edge of the specified HMM state
+     * with nonzero forward probability.
+     *
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the parent node of the third edge of the specified HMM state
+     * with nonzero forward probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int parentNodeB2(int state) {
+        checkIndex(state);
+        return dag.parentNode(marker, edgesB2[state]);
+    }
+
+    /**
+     * Returns the child node of the first edge of the specified HMM state
+     * with nonzero forward probability.
+     *
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the child node of the first edge of the specified HMM state
+     * with nonzero forward probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int childNodeAB1(int state) {
+        checkIndex(state);
+        return dag.childNode(marker, edgesAB1[state]);
+    }
+
+    /**
+     * Returns the child node of the second edge of the specified HMM state
+     * with nonzero forward probability.
+     *
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the child node of the second edge of the specified HMM state
+     * with nonzero forward probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int childNodeA2(int state) {
+        checkIndex(state);
+        return dag.childNode(marker, edgesA2[state]);
+    }
+
+    /**
+     * Returns the child node of the third edge of the specified HMM state
+     * with nonzero forward probability.
+     *
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the child node of the third edge of the specified HMM state
+     * with nonzero forward probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int childNodeB2(int state) {
+        checkIndex(state);
+        return dag.childNode(marker, edgesB2[state]);
+    }
+
+    /**
+     * Returns the symbol for the first edge of the specified HMM state
+     * with nonzero forward probability.
+     *
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the symbol for the first edge of the specified HMM state
+     * with nonzero forward probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int symbolAB1(int state) {
+        return dag.symbol(marker, edgeAB1(state));
+    }
+
+    /**
+     * Returns the symbol for the second edge of the specified HMM state
+     * with nonzero forward probability.
+     *
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the symbol for the second edge of the specified HMM state
+     * with nonzero forward probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int symbolA2(int state) {
+        return dag.symbol(marker, edgeA2(state));
+    }
+
+    /**
+     * Returns the symbol for the third edge of the specified HMM state
+     * with nonzero forward probability.
+     *
+     * @param state an index of a HMM state with nonzero forward probability
+     * @return the symbol for the third edge of the specified HMM state
+     * with nonzero forward probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public int symbolB2(int state) {
+        return dag.symbol(marker, edgeB2(state));
+    }
+
+    /**
+     * Returns the normalized forward value for the specified HMM state
+     * with nonzero forward probability.
+     * The normalized forward value is obtained by dividing the
+     * forward value by the sum of the forward values at this level
+     * of the HMM.
+     *
+     * @param state an index of a HMM state with nonzero forward probability
+     *
+     * @return the normalized forward value for the specified HMM state
+     * with nonzero forward probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public float forwardValue(int state) {
+        checkIndex(state);
+        return fwdValues[state];
+    }
+
+    /**
+     * Returns the normalized backward value for the specified HMM state
+     * with nonzero forward probability.
+     * The normalized backward value is obtained by dividing the
+     * backward value by the sum of the backward values at this level
+     * of the HMM.
+     *
+     * @param state an index of a state with nonzero forward probability
+     *
+     * @return the normalized backward value for the specified HMM state
+     * with nonzero forward probability
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code state < 0 || state >= this.size()}
+     */
+    public float backwardValue(int state) {
+        checkIndex(state);
+        return bwdValues[state];
+    }
+
+    /**
+     * Returns the sum of the forward values at this level of the HMM
+     * when the forward values are computed using forward values
+     * from the previous level that are normalized to sum to 1.
+     * @return the sum of the forward values at this level of the HMM
+     */
+    public float forwardValuesSum() {
+        return fwdValueSum;
+    }
+
+    /**
+     * Returns the sum of the backward values at this level of the HMM
+     * when the backward values are computed using backward
+     * values from the next level that are normalized to sum to 1.
+     * @return the sum of the backward values at this level of the HMM
+     */
+    public float backwardValuesSum() {
+        return bwdValueSum;
+    }
+
+    /**
+     * Returns a string description of {@code this}.  The exact details
+     * of the description are unspecified and subject to change.
+     *
+     * @return a string description of {@code this}.
+     */
+    @Override
+    public String toString() {
+        String space = " ";
+        String sep = " | ";
+        StringBuilder sb = new StringBuilder(100);
+        sb.append("level=");
+        sb.append(marker);
+        sb.append(" size=");
+        sb.append(size);
+        sb.append(" forwardValuesSum=");
+        sb.append(fwdValueSum);
+        sb.append(" backwardSum=");
+        sb.append(bwdValueSum);
+        for (int j=0; j<size; ++j) {
+            sb.append(sep);
+            sb.append("j=");
+            sb.append(j);
+            sb.append(": ");
+            sb.append( (int) edgeAB1(j));
+            sb.append(space);
+            sb.append( (int) edgeA2(j));
+            sb.append(space);
+            sb.append( (int) edgeB2(j));
+            sb.append(space);
+            sb.append(forwardValue(j));
+            sb.append(space);
+            sb.append(backwardValue(j));
+        }
+        sb.append(sep);
+        return sb.toString();
+    }
+
+    /*
+     * Increases the state capacity of array fields as necessary
+     * to be greater than or equal to the specified minimum capacity.
+     *
+     * @param minCapacity the desired minimum state capacity
+     */
+    private void ensureCapacity(int minCapacity) {
+        if (minCapacity > capacity) {
+            capacity = (capacity * 3)/2 + 1;
+            if (capacity < minCapacity) {
+                capacity = minCapacity;
+            }
+            edgesAB1 = Arrays.copyOf(edgesAB1, capacity);
+            edgesA2 = Arrays.copyOf(edgesA2, capacity);
+            edgesB2 = Arrays.copyOf(edgesB2, capacity);
+            fwdValues = Arrays.copyOf(fwdValues, capacity);
+            bwdValues = Arrays.copyOf(bwdValues, capacity);
+        }
+    }
+}
diff --git a/sample/DuoNodes.java b/sample/DuoNodes.java
index 907e004..fd5afe5 100644
--- a/sample/DuoNodes.java
+++ b/sample/DuoNodes.java
@@ -1,338 +1,338 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package sample;
-
-/**
- * <p>Class {@code DuoNodes} stores ordered node trios and associated values.
- * </p>
- * <p>Instances of class {@code DuoNodes} are not thread safe.
- * </p>
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class DuoNodes {
-
-    private static final float loadFactor = 0.75f;
-
-    private int size;
-    private int capacity; // required to be a power of 2
-    private int rehashThreshold;
-
-    private int[] index;
-    private int[] nodeAB1;
-    private int[] nodeA2;
-    private int[] nodeB2;
-    private float[] value;
-
-    /**
-     * Creates a new instance of {@code DuoNodes} that has an
-     * initial value of 0 for each ordered node trio.
-     */
-    public DuoNodes() {
-        this.size = 0;
-        this.capacity = (1<<10);
-        this.rehashThreshold = (int) (loadFactor * capacity);
-        this.index = new int[capacity];
-        this.nodeAB1 = new int[capacity];
-        this.nodeA2 = new int[capacity];
-        this.nodeB2 = new int[capacity];
-        this.value = new float[capacity];
-    }
-
-    private static long hash1(int nodeAB1, int nodeA2, int nodeB2) {
-        long hash = 5;
-        hash = 71 * hash + nodeAB1;
-        hash = 71 * hash + nodeA2;
-        hash = 71 * hash + nodeB2;
-        return hash;
-    }
-
-    private static long hash2(int nodeAB1, int nodeA2, int nodeB2) {
-        long hash = 7;
-        hash = 97 * hash + nodeAB1;
-        hash = 97 * hash + nodeA2;
-        hash = 97 * hash + nodeB2;
-        return hash;
-    }
-
-    /*
-     * Return the storage index for specified node trio.  If the key is not
-     * currently stored in the hash table, the index at which the value
-     * should be stored is returned.
-     */
-    private int index(int ab1, int a2, int b2) {
-        long h1 = hash1(ab1, a2, b2);
-        long h2 = hash2(ab1, a2, b2);
-        if ((h2 & 1)==0) {
-            // h2 must be relatively prime to maxSize, which is a power of 2
-            ++h2;
-        }
-        long l = h1;
-        for (int k=0; k<capacity; ++k) {
-            int i = (int) (l % capacity);
-            if (value[i]==0.0 ||
-                    (nodeAB1[i]==ab1 && nodeA2[i]==a2 && nodeB2[i]==b2)) {
-                return i;
-            }
-            l += h2;
-        }
-        assert false;
-        return -1;
-    }
-
-    /*
-     * Increases the capacity of the internal hash table.
-     */
-    private void rehash() {
-        assert this.size>=this.rehashThreshold;
-        int newMaxSize = 2*capacity;
-        if (newMaxSize<0) {
-            throw new IllegalStateException("hash table overflow");
-        }
-        int[] oldIndex = index;
-        int[] oldNodeAB1 = nodeAB1;
-        int[] oldNodeA2 = nodeA2;
-        int[] oldNodeB2 = nodeB2;
-        float[] oldValue = value;
-
-        capacity = newMaxSize;
-        index = new int[newMaxSize];
-        nodeAB1 = new int[newMaxSize];
-        nodeA2 = new int[newMaxSize];
-        nodeB2 = new int[newMaxSize];
-        value = new float[newMaxSize];
-
-        for (int j=0; j<size; ++j) {
-            int oldInd = oldIndex[j];
-            int newIndex = index(oldNodeAB1[oldInd], oldNodeA2[oldInd],
-                    oldNodeB2[oldInd]);
-            index[j] = newIndex;
-            nodeAB1[newIndex] = oldNodeAB1[oldInd];
-            nodeA2[newIndex] = oldNodeA2[oldInd];
-            nodeB2[newIndex] = oldNodeB2[oldInd];
-            value[newIndex] = oldValue[oldInd];
-        }
-        rehashThreshold = (int) (loadFactor * capacity);
-    }
-
-    /**
-     * Adds the specified value to the stored value of the specified
-     * node trio.
-     *
-     * @param nodeAB1 the first node
-     * @param nodeA2 the second node
-     * @param nodeB2 the third node
-     * @param value the value
-     *
-     * @throws IllegalArgumentException if
-     * {@code (nodeAB1 < 0 || nodeA2 < 0 || nodeB2 < 0)}
-     * @throws IllegalArgumentException if
-     * {@code value <= 0 || (Double.isFinite(value) == false)}
-     */
-    public void sumUpdate(int nodeAB1, int nodeA2, int nodeB2, float value) {
-        if (nodeAB1 < 0) {
-            throw new IllegalArgumentException(String.valueOf(nodeAB1));
-        }
-        if (nodeA2 < 0) {
-            throw new IllegalArgumentException(String.valueOf(nodeA2));
-        }
-        if (nodeB2 < 0) {
-            throw new IllegalArgumentException(String.valueOf(nodeB2));
-        }
-        if (value <= 0 || (Double.isFinite(value)==false) ) {
-            throw new IllegalArgumentException(String.valueOf(value));
-        }
-        if (value>0.0) {
-            int i = index(nodeAB1, nodeA2, nodeB2);
-            boolean addNode = (this.value[i]==0f);
-            this.value[i] += value;
-            if (addNode) {
-                this.index[size++] = i;
-                this.nodeAB1[i] = nodeAB1;
-                this.nodeA2[i] = nodeA2;
-                this.nodeB2[i] = nodeB2;
-                if (this.size>=this.rehashThreshold) {
-                    rehash();
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns the number of node trios with non-zero value.
-     * @return the number of node trios with non-zero value
-     */
-    public int size() {
-        return size;
-    }
-
-    private void checkSize(int index) {
-        if (index>=size()) {
-            throw new IndexOutOfBoundsException(String.valueOf(index));
-        }
-    }
-
-    /**
-     * Returns the first node of the specified node trio in a list of
-     * node trios with non-zero value. Repeated invocations of this
-     * method with the same parameter will return the same value if
-     * node values are not modified between invocations. If
-     * {@code (index >= 0 && index < this.size())}, then the following
-     * expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNodeAB1(index), this.enumNodeA2(index),
-     * this.enumNodeB2(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of node trios with non-zero value.
-     * @return the first node of the specified node trio in a list of
-     * node trios with non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public int enumNodeAB1(int index) {
-        checkSize(index);
-        return nodeAB1[this.index[index]];
-    }
-
-    /**
-     * Returns the second node of the specified node trio in a list of
-     * node trios with non-zero value. Repeated invocations of this
-     * method with the same parameter will return the same value if
-     * node values are not modified between invocations. If
-     * {@code (index >= 0 && index < this.size())}, then the following
-     * expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNodeAB1(index), this.enumNodeA2(index),
-     * this.enumNodeB2(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of node trios with non-zero value
-     * @return the second node of the specified node trio in a list of
-     * node trios with non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public int enumNodeA2(int index) {
-        checkSize(index);
-        return nodeA2[this.index[index]];
-    }
-
-    /**
-     * Returns the third node of the specified node trio in a list of
-     * node trios with non-zero value. Repeated invocations of this
-     * method with the same parameter will return the same value if
-     * node values are not modified between invocations. If
-     * {@code (index >= 0 && index < this.size())}, then the following
-     * expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNodeAB1(index), this.enumNodeA2(index),
-     * this.enumNodeB2(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of node trios with non-zero value
-     * @return the third node of the specified node trio in a list of
-     * node trios with non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public int enumNodeB2(int index) {
-        checkSize(index);
-        return nodeB2[this.index[index]];
-    }
-
-    /**
-     * Returns the value of the specified ordered node trio in a list of
-     * node trios with non-zero value. Repeated invocations of this
-     * method with the same parameter will return the same value if
-     * node values are not modified between invocations. If
-     * {@code (index >= 0 && index < this.size())}, then the following
-     * expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNodeAB1(index), this.enumNodeA2(index),
-     * this.enumNodeB2(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of node trios with non-zero value
-     * @return the value of the specified ordered node trio in a list of
-     * node trios with non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public float enumValue(int index) {
-        checkSize(index);
-        return value[this.index[index]];
-    }
-
-    /**
-     * Returns the specified node trio value.
-     *
-     * @param nodeAB1 the first node
-     * @param nodeA2 the second node
-     * @param nodeB2 the third node
-     * @return the specified node trio value
-     * @throws IllegalArgumentException if
-     * {@code (nodeAB1 < 0 || nodeA2 < 0 || nodeB2 < 0)}
-     */
-    public float value(int nodeAB1, int nodeA2, int nodeB2) {
-        if (nodeAB1 < 0) {
-            throw new IllegalArgumentException(String.valueOf(nodeAB1));
-        }
-        if (nodeA2 < 0) {
-            throw new IllegalArgumentException(String.valueOf(nodeA2));
-        }
-        if (nodeB2 < 0) {
-            throw new IllegalArgumentException(String.valueOf(nodeB2));
-        }
-        return value[index(nodeAB1, nodeA2, nodeB2)];
-    }
-
-    /**
-     * Sets the value of each node trio to 0.
-     */
-    public void clear() {
-        for (int j=0; j<this.size; ++j) {
-            value[index[j]] = 0f;
-        }
-        size = 0;
-    }
-
-    /**
-     * Returns a string representation of {@code this}.  The exact
-     * details of the representation are unspecified and subject to change.
-     *
-     * @return a string representation of {@code this}.
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder(80);
-        sb.append("size=");
-        sb.append(size);
-        for (int j=0; j<size; ++j) {
-            sb.append(" (");
-            sb.append(j);
-            sb.append(": nodeAB1=");
-            sb.append(enumNodeAB1(j));
-            sb.append(" nodeA2=");
-            sb.append((int) enumNodeA2(j));
-            sb.append(" nodeB2=");
-            sb.append(enumNodeB2(j));
-            sb.append(" value=");
-            sb.append(enumValue(j));
-            sb.append(") ");
-        }
-        return sb.toString();
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package sample;
+
+/**
+ * <p>Class {@code DuoNodes} stores ordered node trios and associated values.
+ * </p>
+ * <p>Instances of class {@code DuoNodes} are not thread safe.
+ * </p>
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class DuoNodes {
+
+    private static final float loadFactor = 0.75f;
+
+    private int size;
+    private int capacity; // required to be a power of 2
+    private int rehashThreshold;
+
+    private int[] index;
+    private int[] nodeAB1;
+    private int[] nodeA2;
+    private int[] nodeB2;
+    private float[] value;
+
+    /**
+     * Creates a new instance of {@code DuoNodes} that has an
+     * initial value of 0 for each ordered node trio.
+     */
+    public DuoNodes() {
+        this.size = 0;
+        this.capacity = (1<<10);
+        this.rehashThreshold = (int) (loadFactor * capacity);
+        this.index = new int[capacity];
+        this.nodeAB1 = new int[capacity];
+        this.nodeA2 = new int[capacity];
+        this.nodeB2 = new int[capacity];
+        this.value = new float[capacity];
+    }
+
+    private static long hash1(int nodeAB1, int nodeA2, int nodeB2) {
+        long hash = 5;
+        hash = 71 * hash + nodeAB1;
+        hash = 71 * hash + nodeA2;
+        hash = 71 * hash + nodeB2;
+        return hash;
+    }
+
+    private static long hash2(int nodeAB1, int nodeA2, int nodeB2) {
+        long hash = 7;
+        hash = 97 * hash + nodeAB1;
+        hash = 97 * hash + nodeA2;
+        hash = 97 * hash + nodeB2;
+        return hash;
+    }
+
+    /*
+     * Return the storage index for specified node trio.  If the key is not
+     * currently stored in the hash table, the index at which the value
+     * should be stored is returned.
+     */
+    private int index(int ab1, int a2, int b2) {
+        long h1 = hash1(ab1, a2, b2);
+        long h2 = hash2(ab1, a2, b2);
+        if ((h2 & 1)==0) {
+            // h2 must be relatively prime to maxSize, which is a power of 2
+            ++h2;
+        }
+        long l = h1;
+        for (int k=0; k<capacity; ++k) {
+            int i = (int) (l % capacity);
+            if (value[i]==0.0 ||
+                    (nodeAB1[i]==ab1 && nodeA2[i]==a2 && nodeB2[i]==b2)) {
+                return i;
+            }
+            l += h2;
+        }
+        assert false;
+        return -1;
+    }
+
+    /*
+     * Increases the capacity of the internal hash table.
+     */
+    private void rehash() {
+        assert this.size>=this.rehashThreshold;
+        int newMaxSize = 2*capacity;
+        if (newMaxSize<0) {
+            throw new IllegalStateException("hash table overflow");
+        }
+        int[] oldIndex = index;
+        int[] oldNodeAB1 = nodeAB1;
+        int[] oldNodeA2 = nodeA2;
+        int[] oldNodeB2 = nodeB2;
+        float[] oldValue = value;
+
+        capacity = newMaxSize;
+        index = new int[newMaxSize];
+        nodeAB1 = new int[newMaxSize];
+        nodeA2 = new int[newMaxSize];
+        nodeB2 = new int[newMaxSize];
+        value = new float[newMaxSize];
+
+        for (int j=0; j<size; ++j) {
+            int oldInd = oldIndex[j];
+            int newIndex = index(oldNodeAB1[oldInd], oldNodeA2[oldInd],
+                    oldNodeB2[oldInd]);
+            index[j] = newIndex;
+            nodeAB1[newIndex] = oldNodeAB1[oldInd];
+            nodeA2[newIndex] = oldNodeA2[oldInd];
+            nodeB2[newIndex] = oldNodeB2[oldInd];
+            value[newIndex] = oldValue[oldInd];
+        }
+        rehashThreshold = (int) (loadFactor * capacity);
+    }
+
+    /**
+     * Adds the specified value to the stored value of the specified
+     * node trio.
+     *
+     * @param nodeAB1 the first node
+     * @param nodeA2 the second node
+     * @param nodeB2 the third node
+     * @param value the value
+     *
+     * @throws IllegalArgumentException if
+     * {@code (nodeAB1 < 0 || nodeA2 < 0 || nodeB2 < 0)}
+     * @throws IllegalArgumentException if
+     * {@code value <= 0 || (Double.isFinite(value) == false)}
+     */
+    public void sumUpdate(int nodeAB1, int nodeA2, int nodeB2, float value) {
+        if (nodeAB1 < 0) {
+            throw new IllegalArgumentException(String.valueOf(nodeAB1));
+        }
+        if (nodeA2 < 0) {
+            throw new IllegalArgumentException(String.valueOf(nodeA2));
+        }
+        if (nodeB2 < 0) {
+            throw new IllegalArgumentException(String.valueOf(nodeB2));
+        }
+        if (value <= 0 || (Double.isFinite(value)==false) ) {
+            throw new IllegalArgumentException(String.valueOf(value));
+        }
+        if (value>0.0) {
+            int i = index(nodeAB1, nodeA2, nodeB2);
+            boolean addNode = (this.value[i]==0f);
+            this.value[i] += value;
+            if (addNode) {
+                this.index[size++] = i;
+                this.nodeAB1[i] = nodeAB1;
+                this.nodeA2[i] = nodeA2;
+                this.nodeB2[i] = nodeB2;
+                if (this.size>=this.rehashThreshold) {
+                    rehash();
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the number of node trios with non-zero value.
+     * @return the number of node trios with non-zero value
+     */
+    public int size() {
+        return size;
+    }
+
+    private void checkSize(int index) {
+        if (index>=size()) {
+            throw new IndexOutOfBoundsException(String.valueOf(index));
+        }
+    }
+
+    /**
+     * Returns the first node of the specified node trio in a list of
+     * node trios with non-zero value. Repeated invocations of this
+     * method with the same parameter will return the same value if
+     * node values are not modified between invocations. If
+     * {@code (index >= 0 && index < this.size())}, then the following
+     * expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNodeAB1(index), this.enumNodeA2(index),
+     * this.enumNodeB2(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of node trios with non-zero value.
+     * @return the first node of the specified node trio in a list of
+     * node trios with non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public int enumNodeAB1(int index) {
+        checkSize(index);
+        return nodeAB1[this.index[index]];
+    }
+
+    /**
+     * Returns the second node of the specified node trio in a list of
+     * node trios with non-zero value. Repeated invocations of this
+     * method with the same parameter will return the same value if
+     * node values are not modified between invocations. If
+     * {@code (index >= 0 && index < this.size())}, then the following
+     * expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNodeAB1(index), this.enumNodeA2(index),
+     * this.enumNodeB2(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of node trios with non-zero value
+     * @return the second node of the specified node trio in a list of
+     * node trios with non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public int enumNodeA2(int index) {
+        checkSize(index);
+        return nodeA2[this.index[index]];
+    }
+
+    /**
+     * Returns the third node of the specified node trio in a list of
+     * node trios with non-zero value. Repeated invocations of this
+     * method with the same parameter will return the same value if
+     * node values are not modified between invocations. If
+     * {@code (index >= 0 && index < this.size())}, then the following
+     * expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNodeAB1(index), this.enumNodeA2(index),
+     * this.enumNodeB2(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of node trios with non-zero value
+     * @return the third node of the specified node trio in a list of
+     * node trios with non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public int enumNodeB2(int index) {
+        checkSize(index);
+        return nodeB2[this.index[index]];
+    }
+
+    /**
+     * Returns the value of the specified ordered node trio in a list of
+     * node trios with non-zero value. Repeated invocations of this
+     * method with the same parameter will return the same value if
+     * node values are not modified between invocations. If
+     * {@code (index >= 0 && index < this.size())}, then the following
+     * expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNodeAB1(index), this.enumNodeA2(index),
+     * this.enumNodeB2(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of node trios with non-zero value
+     * @return the value of the specified ordered node trio in a list of
+     * node trios with non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public float enumValue(int index) {
+        checkSize(index);
+        return value[this.index[index]];
+    }
+
+    /**
+     * Returns the specified node trio value.
+     *
+     * @param nodeAB1 the first node
+     * @param nodeA2 the second node
+     * @param nodeB2 the third node
+     * @return the specified node trio value
+     * @throws IllegalArgumentException if
+     * {@code (nodeAB1 < 0 || nodeA2 < 0 || nodeB2 < 0)}
+     */
+    public float value(int nodeAB1, int nodeA2, int nodeB2) {
+        if (nodeAB1 < 0) {
+            throw new IllegalArgumentException(String.valueOf(nodeAB1));
+        }
+        if (nodeA2 < 0) {
+            throw new IllegalArgumentException(String.valueOf(nodeA2));
+        }
+        if (nodeB2 < 0) {
+            throw new IllegalArgumentException(String.valueOf(nodeB2));
+        }
+        return value[index(nodeAB1, nodeA2, nodeB2)];
+    }
+
+    /**
+     * Sets the value of each node trio to 0.
+     */
+    public void clear() {
+        for (int j=0; j<this.size; ++j) {
+            value[index[j]] = 0f;
+        }
+        size = 0;
+    }
+
+    /**
+     * Returns a string representation of {@code this}.  The exact
+     * details of the representation are unspecified and subject to change.
+     *
+     * @return a string representation of {@code this}.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(80);
+        sb.append("size=");
+        sb.append(size);
+        for (int j=0; j<size; ++j) {
+            sb.append(" (");
+            sb.append(j);
+            sb.append(": nodeAB1=");
+            sb.append(enumNodeAB1(j));
+            sb.append(" nodeA2=");
+            sb.append((int) enumNodeA2(j));
+            sb.append(" nodeB2=");
+            sb.append(enumNodeB2(j));
+            sb.append(" value=");
+            sb.append(enumValue(j));
+            sb.append(") ");
+        }
+        return sb.toString();
+    }
+}
diff --git a/sample/HapNodes.java b/sample/HapNodes.java
index 805bb83..6d99d82 100644
--- a/sample/HapNodes.java
+++ b/sample/HapNodes.java
@@ -1,233 +1,233 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package sample;
-
-/**
- * <p>Class {@code HapNodes} stores nodes and associated values.
- * </p>
- * <p>Instances of class {@code HapNodes} are not thread safe.</p>
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class HapNodes {
-
-    private static final float loadFactor = 0.75f;
-
-    private int size;
-    private int capacity; // required to be a power of 2
-    private int rehashThreshold;
-
-    private int[] index;
-    private int[] node;
-    private float[] value;
-
-    /**
-     * Creates a new instance of {@code HapNodes} that has an
-     * initial value of 0 for each node.
-     */
-    public HapNodes() {
-        this.size = 0;
-        this.capacity = (1<<10);
-        this.rehashThreshold = (int) (loadFactor * capacity);
-        this.index = new int[capacity];
-        this.node = new int[capacity];
-        this.value = new float[capacity];
-    }
-
-    /*
-     * Return the storage index for specified node.  If the key is not
-     * currently stored in the hash table, the index at which the value
-     * should be stored is returned.
-     */
-    private int index(int node) {
-        long l = (71 * 5) + node;
-        long h2 = (97 * 7) + node;
-        if ((h2 & 1)==0) {
-            // h2 must be relatively prime to maxSize, which is a power of 2
-            ++h2;
-        }
-        for (int k=0; k<capacity; ++k) {
-            int i = (int) (l % capacity);
-            if (value[i]==0.0 || (this.node[i]==node)) {
-                return i;
-            }
-            l += h2;
-        }
-        assert false;
-        return -1;
-    }
-
-    /*
-     * Increases the capacity of the internal hash table.
-     */
-    private void rehash() {
-        assert this.size>=this.rehashThreshold;
-        int newMaxSize = 2*capacity;
-        if (newMaxSize<0) {
-            throw new IllegalStateException("hash table overflow");
-        }
-        int[] oldIndex = index;
-        int[] oldNode = node;
-        float[] oldValue = value;
-
-        capacity = newMaxSize;
-        index = new int[newMaxSize];
-        node = new int[newMaxSize];
-        value = new float[newMaxSize];
-
-        for (int j=0; j<size; ++j) {
-            int oldInd = oldIndex[j];
-            int newIndex = index(oldNode[oldInd]);
-            index[j] = newIndex;
-            node[newIndex] = oldNode[oldInd];
-            value[newIndex] = oldValue[oldInd];
-        }
-        rehashThreshold = (int) (loadFactor * capacity);
-    }
-
-    /**
-     * Adds the specified value to the stored value of the specified
-     * node.
-     *
-     * @param node the node
-     * @param value the value
-     *
-     * @throws IllegalArgumentException if {@code node < 0}
-     * @throws IllegalArgumentException if
-     * {@code value <= 0 || (Double.isFinite(value) == false)}
-     */
-    public void sumUpdate(int node, float value) {
-        if (node < 0) {
-            throw new IllegalArgumentException(String.valueOf(node));
-        }
-        if (value <= 0 || (Double.isFinite(value)==false) ) {
-            throw new IllegalArgumentException(String.valueOf(value));
-        }
-        int i = index(node);
-        boolean addNode = (this.value[i]==0f);
-        this.value[i] += value;
-        if (addNode) {
-            this.index[size++] = i;
-            this.node[i] = node;
-            if (this.size>=this.rehashThreshold) {
-                rehash();
-            }
-        }
-    }
-
-    /**
-     * Returns the number of nodes with non-zero value.
-     * @return the number of nodes with non-zero value
-     */
-    public int size() {
-        return size;
-    }
-
-    private void checkSize(int index) {
-        if (index>=size()) {
-            throw new IndexOutOfBoundsException(String.valueOf(index));
-        }
-    }
-
-    /**
-     * Returns the specified node in a list of nodes with non-zero value.
-     * Repeated invocations of this method with the same parameter will
-     * return the same value if node values are not modified between
-     * invocations. If {@code (index >= 0 && index < this.size())}, then the
-     * following expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNode(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of nodes with non-zero value
-     * @return the specified node in the list of nodes with non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public int enumNode(int index) {
-        checkSize(index);
-        return node[this.index[index]];
-    }
-
-    /**
-     * Returns the value of the specified node in a list of nodes with
-     * non-zero value. Repeated invocations of this method with the same
-     * parameter will return the same value if node values are not modified
-     * between invocations. If {@code (index >= 0 && index < this.size())}, then
-     * the following expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNode(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of nodes with non-zero value
-     * @return the value of the specified node in a list of nodes with
-     * non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public float enumValue(int index) {
-        checkSize(index);
-        return value[this.index[index]];
-    }
-
-    /**
-     * Returns the specified node value.
-     *
-     * @param node the first node
-     * @return the specified node value
-     * @throws IllegalArgumentException if {@code node < 0}
-     */
-    public float value(int node) {
-        if (node < 0) {
-            throw new IllegalArgumentException(String.valueOf(node));
-        }
-        return value[index(node)];
-    }
-
-    /**
-     * Sets the value of each node to 0.
-     */
-    public void clear() {
-        for (int j=0; j<this.size; ++j) {
-            value[index[j]] = 0f;
-        }
-        size = 0;
-    }
-
-    /**
-     * Returns a string representation of {@code this}. The exact
-     * details of the representation are unspecified and subject to change.
-     *
-     * @return a string representation of {@code this}
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder(80);
-        sb.append("size=");
-        sb.append(size);
-        for (int j=0; j<size; ++j) {
-            sb.append(" (");
-            sb.append(j);
-            sb.append(": node=");
-            sb.append(enumNode(j));
-            sb.append(" value=");
-            sb.append(enumValue(j));
-            sb.append(") ");
-        }
-        return sb.toString();
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package sample;
+
+/**
+ * <p>Class {@code HapNodes} stores nodes and associated values.
+ * </p>
+ * <p>Instances of class {@code HapNodes} are not thread safe.</p>
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class HapNodes {
+
+    private static final float loadFactor = 0.75f;
+
+    private int size;
+    private int capacity; // required to be a power of 2
+    private int rehashThreshold;
+
+    private int[] index;
+    private int[] node;
+    private float[] value;
+
+    /**
+     * Creates a new instance of {@code HapNodes} that has an
+     * initial value of 0 for each node.
+     */
+    public HapNodes() {
+        this.size = 0;
+        this.capacity = (1<<10);
+        this.rehashThreshold = (int) (loadFactor * capacity);
+        this.index = new int[capacity];
+        this.node = new int[capacity];
+        this.value = new float[capacity];
+    }
+
+    /*
+     * Return the storage index for specified node.  If the key is not
+     * currently stored in the hash table, the index at which the value
+     * should be stored is returned.
+     */
+    private int index(int node) {
+        long l = (71 * 5) + node;
+        long h2 = (97 * 7) + node;
+        if ((h2 & 1)==0) {
+            // h2 must be relatively prime to maxSize, which is a power of 2
+            ++h2;
+        }
+        for (int k=0; k<capacity; ++k) {
+            int i = (int) (l % capacity);
+            if (value[i]==0.0 || (this.node[i]==node)) {
+                return i;
+            }
+            l += h2;
+        }
+        assert false;
+        return -1;
+    }
+
+    /*
+     * Increases the capacity of the internal hash table.
+     */
+    private void rehash() {
+        assert this.size>=this.rehashThreshold;
+        int newMaxSize = 2*capacity;
+        if (newMaxSize<0) {
+            throw new IllegalStateException("hash table overflow");
+        }
+        int[] oldIndex = index;
+        int[] oldNode = node;
+        float[] oldValue = value;
+
+        capacity = newMaxSize;
+        index = new int[newMaxSize];
+        node = new int[newMaxSize];
+        value = new float[newMaxSize];
+
+        for (int j=0; j<size; ++j) {
+            int oldInd = oldIndex[j];
+            int newIndex = index(oldNode[oldInd]);
+            index[j] = newIndex;
+            node[newIndex] = oldNode[oldInd];
+            value[newIndex] = oldValue[oldInd];
+        }
+        rehashThreshold = (int) (loadFactor * capacity);
+    }
+
+    /**
+     * Adds the specified value to the stored value of the specified
+     * node.
+     *
+     * @param node the node
+     * @param value the value
+     *
+     * @throws IllegalArgumentException if {@code node < 0}
+     * @throws IllegalArgumentException if
+     * {@code value <= 0 || (Double.isFinite(value) == false)}
+     */
+    public void sumUpdate(int node, float value) {
+        if (node < 0) {
+            throw new IllegalArgumentException(String.valueOf(node));
+        }
+        if (value <= 0 || (Double.isFinite(value)==false) ) {
+            throw new IllegalArgumentException(String.valueOf(value));
+        }
+        int i = index(node);
+        boolean addNode = (this.value[i]==0f);
+        this.value[i] += value;
+        if (addNode) {
+            this.index[size++] = i;
+            this.node[i] = node;
+            if (this.size>=this.rehashThreshold) {
+                rehash();
+            }
+        }
+    }
+
+    /**
+     * Returns the number of nodes with non-zero value.
+     * @return the number of nodes with non-zero value
+     */
+    public int size() {
+        return size;
+    }
+
+    private void checkSize(int index) {
+        if (index>=size()) {
+            throw new IndexOutOfBoundsException(String.valueOf(index));
+        }
+    }
+
+    /**
+     * Returns the specified node in a list of nodes with non-zero value.
+     * Repeated invocations of this method with the same parameter will
+     * return the same value if node values are not modified between
+     * invocations. If {@code (index >= 0 && index < this.size())}, then the
+     * following expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNode(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of nodes with non-zero value
+     * @return the specified node in the list of nodes with non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public int enumNode(int index) {
+        checkSize(index);
+        return node[this.index[index]];
+    }
+
+    /**
+     * Returns the value of the specified node in a list of nodes with
+     * non-zero value. Repeated invocations of this method with the same
+     * parameter will return the same value if node values are not modified
+     * between invocations. If {@code (index >= 0 && index < this.size())}, then
+     * the following expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNode(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of nodes with non-zero value
+     * @return the value of the specified node in a list of nodes with
+     * non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public float enumValue(int index) {
+        checkSize(index);
+        return value[this.index[index]];
+    }
+
+    /**
+     * Returns the specified node value.
+     *
+     * @param node the first node
+     * @return the specified node value
+     * @throws IllegalArgumentException if {@code node < 0}
+     */
+    public float value(int node) {
+        if (node < 0) {
+            throw new IllegalArgumentException(String.valueOf(node));
+        }
+        return value[index(node)];
+    }
+
+    /**
+     * Sets the value of each node to 0.
+     */
+    public void clear() {
+        for (int j=0; j<this.size; ++j) {
+            value[index[j]] = 0f;
+        }
+        size = 0;
+    }
+
+    /**
+     * Returns a string representation of {@code this}. The exact
+     * details of the representation are unspecified and subject to change.
+     *
+     * @return a string representation of {@code this}
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(80);
+        sb.append("size=");
+        sb.append(size);
+        for (int j=0; j<size; ++j) {
+            sb.append(" (");
+            sb.append(j);
+            sb.append(": node=");
+            sb.append(enumNode(j));
+            sb.append(" value=");
+            sb.append(enumValue(j));
+            sb.append(") ");
+        }
+        return sb.toString();
+    }
+}
diff --git a/sample/RecombSingleBaumLevel.java b/sample/RecombSingleBaumLevel.java
index d8ba8ad..2392bfa 100644
--- a/sample/RecombSingleBaumLevel.java
+++ b/sample/RecombSingleBaumLevel.java
@@ -255,9 +255,11 @@ public class RecombSingleBaumLevel {
 
             float nextBaseBwdValue =  nextBaseBwdValue(edges1[j], edges2[j],
                     bwdValues[j]);
-            int pn1 = dag.parentNode(marker, edges1[j]);
-            int pn2 = dag.parentNode(marker, edges2[j]);
-            nodes.sumUpdate(pn1, pn2, nextBaseBwdValue);
+            if (nextBaseBwdValue>0f) {
+                int pn1 = dag.parentNode(marker, edges1[j]);
+                int pn2 = dag.parentNode(marker, edges2[j]);
+                nodes.sumUpdate(pn1, pn2, nextBaseBwdValue);
+            }
         }
         for (int j=0; j<nGenotypes; ++j) {
             gtProbs[j] /= gtProbsSum;
diff --git a/sample/RecombSingleNodes.java b/sample/RecombSingleNodes.java
index 45dfa58..741ef55 100644
--- a/sample/RecombSingleNodes.java
+++ b/sample/RecombSingleNodes.java
@@ -1,361 +1,361 @@
-/*
- * Copyright (C) 2015 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package sample;
-
-/**
- * <p>Class {@code RecombSingleNodes} stores ordered node pairs and
- * associated values.
- * </p>
- * <p>Instances of class {@code RecombSingleNodes} are not thread safe.
- * </p>
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class RecombSingleNodes {
-
-    private static final double loadFactor = 0.75;
-
-    private final int nNodes;
-
-    private int size = 0;
-    private int capacity; // required to be a power of 2
-    private int rehashThreshold;
-
-    private int[] index;
-    private int[] node1;
-    private int[] node2;
-    private float[] value;
-    private float[] sumNode1Value;
-    private float[] sumNode2Value;
-    private float sumValue;
-
-    /**
-     * Creates a new instance of {@code RecombSingleNodes} that has an
-     * initial value of 0 for each ordered node pair. The first node
-     * has index 0.
-     * @param nNodes the maximum number of distinct nodes
-     * which will be paired to form ordered node pairs
-     * @throws IllegalArgumentException if {@code nNodes < 1}
-     */
-    public RecombSingleNodes(int nNodes) {
-        if (nNodes < 1) {
-            throw new IllegalArgumentException("nNodes < 1: " + nNodes);
-        }
-        this.nNodes = nNodes;
-        this.size = 0;
-        this.capacity = (1<<10);
-        this.rehashThreshold = (int) (loadFactor * capacity);
-        this.index = new int[capacity];
-        this.node1 = new int[capacity];
-        this.node2 = new int[capacity];
-        this.value = new float[capacity];
-        this.sumNode1Value = new float[nNodes];
-        this.sumNode2Value = new float[nNodes];
-        this.sumValue = 0.0f;
-    }
-
-    private static long hash1(int node1, int node2) {
-        long hash = 5;
-        hash = 71 * hash + node1;
-        hash = 71 * hash + node2;
-        return hash;
-    }
-
-    private static long hash2(int node1, int node2) {
-        long hash = 7;
-        hash = 97 * hash + node1;
-        hash = 97 * hash + node2;
-        return hash;
-    }
-    /*
-     * Return the storage index for specified node pair.  If the key is not
-     * currently stored in the hash table, the index at which the value
-     * should be stored is returned.
-     */
-    private int index(int node1, int node2) {
-        long h1 = hash1(node1, node2);
-        long h2 = hash2(node1, node2);
-        if ((h2 & 1)==0) {
-            // h2 must be relatively prime to maxSize, which is a power of 2
-            ++h2;
-        }
-        long l = h1;
-        for (int k=0; k<capacity; ++k) {
-            int i = (int) (l % capacity);
-            if (value[i]==0.0
-                    || (this.node1[i]==node1 && this.node2[i]==node2)) {
-                return i;
-            }
-            l += h2;
-        }
-        assert false;
-        return -1;
-    }
-
-    /*
-     * Increases the capacity of the internal hash table.
-     */
-    private void rehash() {
-        assert this.size>=this.rehashThreshold;
-        int newMaxSize = 2*capacity;
-        if (newMaxSize<0) {
-            throw new IllegalStateException("hash table overflow");
-        }
-        int[] oldIndex = index;
-        int[] oldNode1 = node1;
-        int[] oldNode2 = node2;
-        float[] oldValue = value;
-
-        capacity = newMaxSize;
-        index = new int[newMaxSize];
-        node1 = new int[newMaxSize];
-        node2 = new int[newMaxSize];
-        value = new float[newMaxSize];
-
-        for (int j=0; j<size; ++j) {
-            int oldInd = oldIndex[j];
-            int newIndex = index(oldNode1[oldInd], oldNode2[oldInd]);
-            index[j] = newIndex;
-            node1[newIndex] = oldNode1[oldInd];
-            node2[newIndex] = oldNode2[oldInd];
-            value[newIndex] = oldValue[oldInd];
-        }
-        rehashThreshold = (int) (loadFactor * capacity);
-    }
-
-    /**
-     * Adds the specified positive value to the stored value of the specified
-     * node pair.
-     *
-     * @param node1 the first node
-     * @param node2 the second node
-     * @param value the value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code node1 < 0 || node1 >= this.nNodes()}
-     * @throws IndexOutOfBoundsException if
-     * {@code node2 < 0 || node2 >= this.nNodes()}
-     * @throws IllegalArgumentException if
-     * {@code value <= 0 || (Double.isFinite(value) == false)}
-     */
-    public void sumUpdate(int node1, int node2, float value) {
-        if (value <= 0 || (Double.isFinite(value)==false) ) {
-            throw new IllegalArgumentException(String.valueOf(value));
-        }
-        int i = index(node1, node2);
-        boolean addNode = (this.value[i]==0f);
-        this.value[i] += value;
-        this.sumNode1Value[node1] += value;
-        this.sumNode2Value[node2] += value;
-        this.sumValue += value;
-        if (addNode) {
-            this.index[size++] = i;
-            this.node1[i] = node1;
-            this.node2[i] = node2;
-            if (this.size>=this.rehashThreshold) {
-                rehash();
-            }
-        }
-    }
-
-    /**
-     * Returns the number of node pairs with non-zero value.
-     * @return the number of node pairs with non-zero value
-     */
-    public int size() {
-        return size;
-    }
-
-    private void checkSize(int index) {
-        if (index>=size()) {
-            throw new IndexOutOfBoundsException(String.valueOf(index));
-        }
-    }
-
-    /**
-     * Returns the number of nodes.
-     *
-     * @return the number of nodes
-     */
-    public int nNodes() {
-        return nNodes;
-    }
-
-    /**
-     * Returns the first node of the specified node pair in the list of
-     * node pairs with non-zero value.  Repeated invocations of this
-     * method with the same parameter will return the same value if
-     * node values are not modified between invocations. If
-     * {@code (index >= 0 && index < this.size())}, then the following
-     * expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNode1(index),
-     * this.enumNode2(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of node pairs with non-zero
-     * value
-     * @return the first node of the specified node pair in a list of
-     * node pairs with non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public int enumNode1(int index) {
-        checkSize(index);
-        return node1[this.index[index]];
-    }
-
-    /**
-     * Returns the second node of the specified node pair in a list of
-     * node pairs with non-zero value.  Repeated invocations of this
-     * method with the same parameter will return the same value if
-     * node values are not modified between invocations. If
-     * {@code (index >= 0 && index < this.size())}, then the following
-     * expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNode1(index),
-     * this.enumNode2(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of node pairs with non-zero value
-     * @return the second node of the specified node pair in a list of
-     * node pairs with non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public int enumNode2(int index) {
-        checkSize(index);
-        return node2[this.index[index]];
-    }
-
-    /**
-     * Returns the value of the specified node pair in a list of
-     * node pairs with non-zero value.  Repeated invocations of this
-     * method with the same parameter will return the same value if
-     * node values are not modified between invocations. If
-     * {@code (index >= 0 && index < this.size())}, then the following
-     * expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNode1(index),
-     * this.enumNode2(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of node pairs with non-zero value
-     * @return the value of the specified ordered node pair in a list of
-     * node pairs with non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public float enumValue(int index) {
-        checkSize(index);
-        return value[this.index[index]];
-    }
-
-    /**
-     * Returns the value of the specified node pair.
-     *
-     * @param node1 the first node
-     * @param node2 the second node
-     * @return the value of the specified node pair
-     * @throws IllegalArgumentException if {@code node1 < 0 || node2 < 0}
-     */
-    public float value(int node1, int node2) {
-        if (node1 < 0 || node1 >= nNodes) {
-            throw new IndexOutOfBoundsException(String.valueOf(node1));
-        }
-        if (node2 < 0 || node2 >= nNodes) {
-            throw new IndexOutOfBoundsException(String.valueOf(node2));
-        }
-        return value[index(node1, node2)];
-    }
-
-    /**
-     * Returns the sum of the values of the node pairs that have the specified
-     * first node
-     *
-     * @param node1 a node
-     * @return the sum of the values of the node pairs that have the specified
-     * first node
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code node1 < 0 || node1 >= this.nNodes()}
-     */
-    public float sumNode1Value(int node1) {
-        return sumNode1Value[node1];
-    }
-
-    /**
-     * Returns the sum of the values of the node pairs that have the specified
-     * second node.
-     *
-     * @param node2 a node
-     * @return the sum of the values of the node pairs that have the specified
-     * second node
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code node2 < 0 || node2 >= this.nNodes()}
-     */
-    public float sumNode2Value(int node2) {
-        return sumNode2Value[node2];
-    }
-
-    /**
-     * Returns the sum of the values of all node pairs.
-     *
-     * @return the sum of the values of all node pairs
-     */
-    public float sumValue() {
-        return sumValue;
-    }
-
-    /**
-     * Sets the value of each ordered node pair to 0.
-     */
-    public void clear() {
-        for (int j=0; j<this.size; ++j) {
-            value[index[j]] = 0f;
-            sumNode1Value[node1[index[j]]]=0f;
-            sumNode2Value[node2[index[j]]]=0f;
-        }
-        sumValue = 0.0f;
-        size = 0;
-    }
-
-    /**
-     * Returns a string representation of {@code this}.  The exact
-     * details of the representation are unspecified and subject to change.
-     *
-     * @return a string representation of {@code this}
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder(80);
-        sb.append("size=");
-        sb.append(size);
-        for (int j=0; j<size; ++j) {
-            sb.append(" (");
-            sb.append(j);
-            sb.append(": node1=");
-            sb.append(enumNode1(j));
-            sb.append(" node2=");
-            sb.append(enumNode2(j));
-            sb.append(" value=");
-            sb.append(enumValue(j));
-            sb.append(")");
-        }
-        return sb.toString();
-    }
-}
+/*
+ * Copyright (C) 2015 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package sample;
+
+/**
+ * <p>Class {@code RecombSingleNodes} stores ordered node pairs and
+ * associated values.
+ * </p>
+ * <p>Instances of class {@code RecombSingleNodes} are not thread safe.
+ * </p>
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class RecombSingleNodes {
+
+    private static final double loadFactor = 0.75;
+
+    private final int nNodes;
+
+    private int size = 0;
+    private int capacity; // required to be a power of 2
+    private int rehashThreshold;
+
+    private int[] index;
+    private int[] node1;
+    private int[] node2;
+    private float[] value;
+    private float[] sumNode1Value;
+    private float[] sumNode2Value;
+    private float sumValue;
+
+    /**
+     * Creates a new instance of {@code RecombSingleNodes} that has an
+     * initial value of 0 for each ordered node pair. The first node
+     * has index 0.
+     * @param nNodes the maximum number of distinct nodes
+     * which will be paired to form ordered node pairs
+     * @throws IllegalArgumentException if {@code nNodes < 1}
+     */
+    public RecombSingleNodes(int nNodes) {
+        if (nNodes < 1) {
+            throw new IllegalArgumentException("nNodes < 1: " + nNodes);
+        }
+        this.nNodes = nNodes;
+        this.size = 0;
+        this.capacity = (1<<10);
+        this.rehashThreshold = (int) (loadFactor * capacity);
+        this.index = new int[capacity];
+        this.node1 = new int[capacity];
+        this.node2 = new int[capacity];
+        this.value = new float[capacity];
+        this.sumNode1Value = new float[nNodes];
+        this.sumNode2Value = new float[nNodes];
+        this.sumValue = 0.0f;
+    }
+
+    private static long hash1(int node1, int node2) {
+        long hash = 5;
+        hash = 71 * hash + node1;
+        hash = 71 * hash + node2;
+        return hash;
+    }
+
+    private static long hash2(int node1, int node2) {
+        long hash = 7;
+        hash = 97 * hash + node1;
+        hash = 97 * hash + node2;
+        return hash;
+    }
+    /*
+     * Return the storage index for specified node pair.  If the key is not
+     * currently stored in the hash table, the index at which the value
+     * should be stored is returned.
+     */
+    private int index(int node1, int node2) {
+        long h1 = hash1(node1, node2);
+        long h2 = hash2(node1, node2);
+        if ((h2 & 1)==0) {
+            // h2 must be relatively prime to maxSize, which is a power of 2
+            ++h2;
+        }
+        long l = h1;
+        for (int k=0; k<capacity; ++k) {
+            int i = (int) (l % capacity);
+            if (value[i]==0.0
+                    || (this.node1[i]==node1 && this.node2[i]==node2)) {
+                return i;
+            }
+            l += h2;
+        }
+        assert false;
+        return -1;
+    }
+
+    /*
+     * Increases the capacity of the internal hash table.
+     */
+    private void rehash() {
+        assert this.size>=this.rehashThreshold;
+        int newMaxSize = 2*capacity;
+        if (newMaxSize<0) {
+            throw new IllegalStateException("hash table overflow");
+        }
+        int[] oldIndex = index;
+        int[] oldNode1 = node1;
+        int[] oldNode2 = node2;
+        float[] oldValue = value;
+
+        capacity = newMaxSize;
+        index = new int[newMaxSize];
+        node1 = new int[newMaxSize];
+        node2 = new int[newMaxSize];
+        value = new float[newMaxSize];
+
+        for (int j=0; j<size; ++j) {
+            int oldInd = oldIndex[j];
+            int newIndex = index(oldNode1[oldInd], oldNode2[oldInd]);
+            index[j] = newIndex;
+            node1[newIndex] = oldNode1[oldInd];
+            node2[newIndex] = oldNode2[oldInd];
+            value[newIndex] = oldValue[oldInd];
+        }
+        rehashThreshold = (int) (loadFactor * capacity);
+    }
+
+    /**
+     * Adds the specified positive value to the stored value of the specified
+     * node pair.
+     *
+     * @param node1 the first node
+     * @param node2 the second node
+     * @param value the value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code node1 < 0 || node1 >= this.nNodes()}
+     * @throws IndexOutOfBoundsException if
+     * {@code node2 < 0 || node2 >= this.nNodes()}
+     * @throws IllegalArgumentException if
+     * {@code value <= 0 || (Double.isFinite(value) == false)}
+     */
+    public void sumUpdate(int node1, int node2, float value) {
+        if (value <= 0 || (Double.isFinite(value)==false) ) {
+            throw new IllegalArgumentException(String.valueOf(value));
+        }
+        int i = index(node1, node2);
+        boolean addNode = (this.value[i]==0f);
+        this.value[i] += value;
+        this.sumNode1Value[node1] += value;
+        this.sumNode2Value[node2] += value;
+        this.sumValue += value;
+        if (addNode) {
+            this.index[size++] = i;
+            this.node1[i] = node1;
+            this.node2[i] = node2;
+            if (this.size>=this.rehashThreshold) {
+                rehash();
+            }
+        }
+    }
+
+    /**
+     * Returns the number of node pairs with non-zero value.
+     * @return the number of node pairs with non-zero value
+     */
+    public int size() {
+        return size;
+    }
+
+    private void checkSize(int index) {
+        if (index>=size()) {
+            throw new IndexOutOfBoundsException(String.valueOf(index));
+        }
+    }
+
+    /**
+     * Returns the number of nodes.
+     *
+     * @return the number of nodes
+     */
+    public int nNodes() {
+        return nNodes;
+    }
+
+    /**
+     * Returns the first node of the specified node pair in the list of
+     * node pairs with non-zero value.  Repeated invocations of this
+     * method with the same parameter will return the same value if
+     * node values are not modified between invocations. If
+     * {@code (index >= 0 && index < this.size())}, then the following
+     * expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNode1(index),
+     * this.enumNode2(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of node pairs with non-zero
+     * value
+     * @return the first node of the specified node pair in a list of
+     * node pairs with non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public int enumNode1(int index) {
+        checkSize(index);
+        return node1[this.index[index]];
+    }
+
+    /**
+     * Returns the second node of the specified node pair in a list of
+     * node pairs with non-zero value.  Repeated invocations of this
+     * method with the same parameter will return the same value if
+     * node values are not modified between invocations. If
+     * {@code (index >= 0 && index < this.size())}, then the following
+     * expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNode1(index),
+     * this.enumNode2(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of node pairs with non-zero value
+     * @return the second node of the specified node pair in a list of
+     * node pairs with non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public int enumNode2(int index) {
+        checkSize(index);
+        return node2[this.index[index]];
+    }
+
+    /**
+     * Returns the value of the specified node pair in a list of
+     * node pairs with non-zero value.  Repeated invocations of this
+     * method with the same parameter will return the same value if
+     * node values are not modified between invocations. If
+     * {@code (index >= 0 && index < this.size())}, then the following
+     * expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNode1(index),
+     * this.enumNode2(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of node pairs with non-zero value
+     * @return the value of the specified ordered node pair in a list of
+     * node pairs with non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public float enumValue(int index) {
+        checkSize(index);
+        return value[this.index[index]];
+    }
+
+    /**
+     * Returns the value of the specified node pair.
+     *
+     * @param node1 the first node
+     * @param node2 the second node
+     * @return the value of the specified node pair
+     * @throws IllegalArgumentException if {@code node1 < 0 || node2 < 0}
+     */
+    public float value(int node1, int node2) {
+        if (node1 < 0 || node1 >= nNodes) {
+            throw new IndexOutOfBoundsException(String.valueOf(node1));
+        }
+        if (node2 < 0 || node2 >= nNodes) {
+            throw new IndexOutOfBoundsException(String.valueOf(node2));
+        }
+        return value[index(node1, node2)];
+    }
+
+    /**
+     * Returns the sum of the values of the node pairs that have the specified
+     * first node
+     *
+     * @param node1 a node
+     * @return the sum of the values of the node pairs that have the specified
+     * first node
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code node1 < 0 || node1 >= this.nNodes()}
+     */
+    public float sumNode1Value(int node1) {
+        return sumNode1Value[node1];
+    }
+
+    /**
+     * Returns the sum of the values of the node pairs that have the specified
+     * second node.
+     *
+     * @param node2 a node
+     * @return the sum of the values of the node pairs that have the specified
+     * second node
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code node2 < 0 || node2 >= this.nNodes()}
+     */
+    public float sumNode2Value(int node2) {
+        return sumNode2Value[node2];
+    }
+
+    /**
+     * Returns the sum of the values of all node pairs.
+     *
+     * @return the sum of the values of all node pairs
+     */
+    public float sumValue() {
+        return sumValue;
+    }
+
+    /**
+     * Sets the value of each ordered node pair to 0.
+     */
+    public void clear() {
+        for (int j=0; j<this.size; ++j) {
+            value[index[j]] = 0f;
+            sumNode1Value[node1[index[j]]]=0f;
+            sumNode2Value[node2[index[j]]]=0f;
+        }
+        sumValue = 0.0f;
+        size = 0;
+    }
+
+    /**
+     * Returns a string representation of {@code this}.  The exact
+     * details of the representation are unspecified and subject to change.
+     *
+     * @return a string representation of {@code this}
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(80);
+        sb.append("size=");
+        sb.append(size);
+        for (int j=0; j<size; ++j) {
+            sb.append(" (");
+            sb.append(j);
+            sb.append(": node1=");
+            sb.append(enumNode1(j));
+            sb.append(" node2=");
+            sb.append(enumNode2(j));
+            sb.append(" value=");
+            sb.append(enumValue(j));
+            sb.append(")");
+        }
+        return sb.toString();
+    }
+}
diff --git a/sample/SingleBaumLevel.java b/sample/SingleBaumLevel.java
index ee75939..af90296 100644
--- a/sample/SingleBaumLevel.java
+++ b/sample/SingleBaumLevel.java
@@ -214,9 +214,11 @@ public class SingleBaumLevel {
             if (bwdValue < MIN_VALUE && bwdValues[j]>0.0) {
                 bwdValue = MIN_VALUE;
             }
-            int pn1 = dag.parentNode(marker, edge1);
-            int pn2 = dag.parentNode(marker, edge2);
-            nodes.sumUpdate(pn1, pn2, bwdValue);
+            if (bwdValue>0f) {
+                int pn1 = dag.parentNode(marker, edge1);
+                int pn2 = dag.parentNode(marker, edge2);
+                nodes.sumUpdate(pn1, pn2, bwdValue);
+            }
         }
         for (int j=0; j<nGenotypes; ++j) {
             gtProbs[j] /= gtProbsSum;
diff --git a/sample/SingleNodes.java b/sample/SingleNodes.java
index 0801493..18651c7 100644
--- a/sample/SingleNodes.java
+++ b/sample/SingleNodes.java
@@ -1,294 +1,294 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package sample;
-
-/**
- * <p>Class {@code SingleNodes} stores ordered node pairs and associated values.
- * </p>
- * <p>Instances of class {@code SingleNodes} are not thread safe.</p>
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class SingleNodes {
-
-    private static final float loadFactor = 0.75f;
-
-    private int size;
-    private int capacity; // required to be a power of 2
-    private int rehashThreshold;
-
-    private int[] index;
-    private int[] node1;
-    private int[] node2;
-    private float[] value;
-
-    /**
-     * Creates a new instance of {@code SingleNodes} that has an
-     * initial value of 0 for each ordered node pair. The first node
-     * has index 0.
-     */
-    public SingleNodes() {
-        this.size = 0;
-        this.capacity = (1<<10);
-        this.rehashThreshold = (int) (loadFactor * capacity);
-        this.index = new int[capacity];
-        this.node1 = new int[capacity];
-        this.node2 = new int[capacity];
-        this.value = new float[capacity];
-    }
-
-    private static long hash1(int node1, int node2) {
-        long hash = 5;
-        hash = 71 * hash + node1;
-        hash = 71 * hash + node2;
-        return hash;
-    }
-
-    private static long hash2(int node1, int node2) {
-        long hash = 7;
-        hash = 97 * hash + node1;
-        hash = 97 * hash + node2;
-        return hash;
-    }
-
-    /*
-     * Return the storage index for specified node pair.  If the key is not
-     * currently stored in the hash table, the index at which the value
-     * should be stored is returned.
-     */
-    private int index(int node1, int node2) {
-        long h1 = hash1(node1, node2);
-        long h2 = hash2(node1, node2);
-        if ((h2 & 1)==0) {
-            // h2 must be relatively prime to maxSize, which is a power of 2
-            ++h2;
-        }
-        long l = h1;
-        for (int k=0; k<capacity; ++k) {
-            int i = (int) (l % capacity);
-            if (value[i]==0.0
-                    || (this.node1[i]==node1 && this.node2[i]==node2)) {
-                return i;
-            }
-            l += h2;
-        }
-        assert false;
-        return -1;
-    }
-
-    /*
-     * Increases the capacity of the internal hash table.
-     */
-    private void rehash() {
-        assert this.size>=this.rehashThreshold;
-        int newMaxSize = 2*capacity;
-        if (newMaxSize<0) {
-            throw new IllegalStateException("hash table overflow");
-        }
-        int[] oldIndex = index;
-        int[] oldNode1 = node1;
-        int[] oldNode2 = node2;
-        float[] oldValue = value;
-
-        capacity = newMaxSize;
-        index = new int[newMaxSize];
-        node1 = new int[newMaxSize];
-        node2 = new int[newMaxSize];
-        value = new float[newMaxSize];
-
-        for (int j=0; j<size; ++j) {
-            int oldInd = oldIndex[j];
-            int newIndex = index(oldNode1[oldInd], oldNode2[oldInd]);
-            index[j] = newIndex;
-            node1[newIndex] = oldNode1[oldInd];
-            node2[newIndex] = oldNode2[oldInd];
-            value[newIndex] = oldValue[oldInd];
-        }
-        rehashThreshold = (int) (loadFactor * capacity);
-    }
-
-    /**
-     * Adds the specified positive value to the stored value of the specified
-     * node pair.
-     *
-     * @param node1 the first node
-     * @param node2 the second node
-     * @param value the value
-     *
-     * @throws IllegalArgumentException if {@code node1 < 0 || node2 < 0}
-     * @throws IllegalArgumentException if
-     * {@code value <= 0 || (Double.isFinite(value) == false)}
-     */
-    public void sumUpdate(int node1, int node2, float value) {
-        if (node1 < 0) {
-            throw new IllegalArgumentException(String.valueOf(node1));
-        }
-        if (node2 < 0) {
-            throw new IllegalArgumentException(String.valueOf(node2));
-        }
-        if (value <= 0 || (Double.isFinite(value)==false) ) {
-            throw new IllegalArgumentException(String.valueOf(value));
-        }
-        int i = index(node1, node2);
-        boolean addNode = (this.value[i]==0f);
-        this.value[i] += value;
-        if (addNode) {
-            this.index[size++] = i;
-            this.node1[i] = node1;
-            this.node2[i] = node2;
-            if (this.size>=this.rehashThreshold) {
-                rehash();
-            }
-        }
-    }
-
-    /**
-     * Returns the number of node pairs with non-zero value.
-     * @return the number of node pairs with non-zero value
-     */
-    public int size() {
-        return size;
-    }
-
-    private void checkSize(int index) {
-        if (index>=size()) {
-            throw new IndexOutOfBoundsException(String.valueOf(index));
-        }
-    }
-
-    /**
-     * Returns the first node of the specified node pair in the list of
-     * node pairs with non-zero value.  Repeated invocations of this
-     * method with the same parameter will return the same value if
-     * node values are not modified between invocations. If
-     * {@code (index >= 0 && index < this.size())}, then the following
-     * expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNode1(index),
-     * this.enumNode2(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of node pairs with non-zero
-     * value
-     * @return the first node of the specified node pair in a list of
-     * node pairs with non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public int enumNode1(int index) {
-        checkSize(index);
-        return node1[this.index[index]];
-    }
-
-    /**
-     * Returns the second node of the specified node pair in a list of
-     * node pairs with non-zero value.  Repeated invocations of this
-     * method with the same parameter will return the same value if
-     * node values are not modified between invocations. If
-     * {@code (index >= 0 && index < this.size())}, then the following
-     * expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNode1(index),
-     * this.enumNode2(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of node pairs with non-zero value
-     * @return the second node of the specified node pair in a list of
-     * node pairs with non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public int enumNode2(int index) {
-        checkSize(index);
-        return node2[this.index[index]];
-    }
-
-    /**
-     * Returns the value of the specified node pair in a list of
-     * node pairs with non-zero value.  Repeated invocations of this
-     * method with the same parameter will return the same value if
-     * node values are not modified between invocations. If
-     * {@code (index >= 0 && index < this.size())}, then the following
-     * expression will always evaluate to {@code true}:<br>
-     * {@code (this.value(this.enumNode1(index),
-     * this.enumNode2(index)) == this.enumValue(index))}.
-     *
-     * @param index an index in a list of node pairs with non-zero value
-     * @return the value of the specified ordered node pair in a list of
-     * node pairs with non-zero value
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code index < 0 || index >= this.size()}
-     */
-    public float enumValue(int index) {
-        checkSize(index);
-        return value[this.index[index]];
-    }
-
-    /**
-     * Returns the value of the specified node pair.
-     *
-     * @param node1 the first node
-     * @param node2 the second node
-     * @return the value of the specified node pair
-     * @throws IllegalArgumentException if {@code node1 < 0 || node2 < 0}
-     */
-    public float value(int node1, int node2) {
-        if (node1 < 0) {
-            throw new IllegalArgumentException(String.valueOf(node1));
-        }
-        if (node2 < 0) {
-            throw new IllegalArgumentException(String.valueOf(node2));
-        }
-        return value[index(node1, node2)];
-    }
-
-    /**
-     * Sets the value of each ordered node pair to 0.
-     */
-    public void clear() {
-        for (int j=0; j<this.size; ++j) {
-            value[index[j]] = 0f;
-        }
-        size = 0;
-    }
-
-    /**
-     * Returns a string representation of {@code this}.  The exact
-     * details of the representation are unspecified and subject to change.
-     *
-     * @return a string representation of {@code this}
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder(80);
-        sb.append("size=");
-        sb.append(size);
-        for (int j=0; j<size; ++j) {
-            sb.append(" (");
-            sb.append(j);
-            sb.append(": node1=");
-            sb.append(enumNode1(j));
-            sb.append(" node2=");
-            sb.append(enumNode2(j));
-            sb.append(" value=");
-            sb.append(enumValue(j));
-            sb.append(") ");
-        }
-        return sb.toString();
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package sample;
+
+/**
+ * <p>Class {@code SingleNodes} stores ordered node pairs and associated values.
+ * </p>
+ * <p>Instances of class {@code SingleNodes} are not thread safe.</p>
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class SingleNodes {
+
+    private static final float loadFactor = 0.75f;
+
+    private int size;
+    private int capacity; // required to be a power of 2
+    private int rehashThreshold;
+
+    private int[] index;
+    private int[] node1;
+    private int[] node2;
+    private float[] value;
+
+    /**
+     * Creates a new instance of {@code SingleNodes} that has an
+     * initial value of 0 for each ordered node pair. The first node
+     * has index 0.
+     */
+    public SingleNodes() {
+        this.size = 0;
+        this.capacity = (1<<10);
+        this.rehashThreshold = (int) (loadFactor * capacity);
+        this.index = new int[capacity];
+        this.node1 = new int[capacity];
+        this.node2 = new int[capacity];
+        this.value = new float[capacity];
+    }
+
+    private static long hash1(int node1, int node2) {
+        long hash = 5;
+        hash = 71 * hash + node1;
+        hash = 71 * hash + node2;
+        return hash;
+    }
+
+    private static long hash2(int node1, int node2) {
+        long hash = 7;
+        hash = 97 * hash + node1;
+        hash = 97 * hash + node2;
+        return hash;
+    }
+
+    /*
+     * Return the storage index for specified node pair.  If the key is not
+     * currently stored in the hash table, the index at which the value
+     * should be stored is returned.
+     */
+    private int index(int node1, int node2) {
+        long h1 = hash1(node1, node2);
+        long h2 = hash2(node1, node2);
+        if ((h2 & 1)==0) {
+            // h2 must be relatively prime to maxSize, which is a power of 2
+            ++h2;
+        }
+        long l = h1;
+        for (int k=0; k<capacity; ++k) {
+            int i = (int) (l % capacity);
+            if (value[i]==0.0
+                    || (this.node1[i]==node1 && this.node2[i]==node2)) {
+                return i;
+            }
+            l += h2;
+        }
+        assert false;
+        return -1;
+    }
+
+    /*
+     * Increases the capacity of the internal hash table.
+     */
+    private void rehash() {
+        assert this.size>=this.rehashThreshold;
+        int newMaxSize = 2*capacity;
+        if (newMaxSize<0) {
+            throw new IllegalStateException("hash table overflow");
+        }
+        int[] oldIndex = index;
+        int[] oldNode1 = node1;
+        int[] oldNode2 = node2;
+        float[] oldValue = value;
+
+        capacity = newMaxSize;
+        index = new int[newMaxSize];
+        node1 = new int[newMaxSize];
+        node2 = new int[newMaxSize];
+        value = new float[newMaxSize];
+
+        for (int j=0; j<size; ++j) {
+            int oldInd = oldIndex[j];
+            int newIndex = index(oldNode1[oldInd], oldNode2[oldInd]);
+            index[j] = newIndex;
+            node1[newIndex] = oldNode1[oldInd];
+            node2[newIndex] = oldNode2[oldInd];
+            value[newIndex] = oldValue[oldInd];
+        }
+        rehashThreshold = (int) (loadFactor * capacity);
+    }
+
+    /**
+     * Adds the specified positive value to the stored value of the specified
+     * node pair.
+     *
+     * @param node1 the first node
+     * @param node2 the second node
+     * @param value the value
+     *
+     * @throws IllegalArgumentException if {@code node1 < 0 || node2 < 0}
+     * @throws IllegalArgumentException if
+     * {@code value <= 0 || (Double.isFinite(value) == false)}
+     */
+    public void sumUpdate(int node1, int node2, float value) {
+        if (node1 < 0) {
+            throw new IllegalArgumentException(String.valueOf(node1));
+        }
+        if (node2 < 0) {
+            throw new IllegalArgumentException(String.valueOf(node2));
+        }
+        if (value <= 0 || (Double.isFinite(value)==false) ) {
+            throw new IllegalArgumentException(String.valueOf(value));
+        }
+        int i = index(node1, node2);
+        boolean addNode = (this.value[i]==0f);
+        this.value[i] += value;
+        if (addNode) {
+            this.index[size++] = i;
+            this.node1[i] = node1;
+            this.node2[i] = node2;
+            if (this.size>=this.rehashThreshold) {
+                rehash();
+            }
+        }
+    }
+
+    /**
+     * Returns the number of node pairs with non-zero value.
+     * @return the number of node pairs with non-zero value
+     */
+    public int size() {
+        return size;
+    }
+
+    private void checkSize(int index) {
+        if (index>=size()) {
+            throw new IndexOutOfBoundsException(String.valueOf(index));
+        }
+    }
+
+    /**
+     * Returns the first node of the specified node pair in the list of
+     * node pairs with non-zero value.  Repeated invocations of this
+     * method with the same parameter will return the same value if
+     * node values are not modified between invocations. If
+     * {@code (index >= 0 && index < this.size())}, then the following
+     * expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNode1(index),
+     * this.enumNode2(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of node pairs with non-zero
+     * value
+     * @return the first node of the specified node pair in a list of
+     * node pairs with non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public int enumNode1(int index) {
+        checkSize(index);
+        return node1[this.index[index]];
+    }
+
+    /**
+     * Returns the second node of the specified node pair in a list of
+     * node pairs with non-zero value.  Repeated invocations of this
+     * method with the same parameter will return the same value if
+     * node values are not modified between invocations. If
+     * {@code (index >= 0 && index < this.size())}, then the following
+     * expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNode1(index),
+     * this.enumNode2(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of node pairs with non-zero value
+     * @return the second node of the specified node pair in a list of
+     * node pairs with non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public int enumNode2(int index) {
+        checkSize(index);
+        return node2[this.index[index]];
+    }
+
+    /**
+     * Returns the value of the specified node pair in a list of
+     * node pairs with non-zero value.  Repeated invocations of this
+     * method with the same parameter will return the same value if
+     * node values are not modified between invocations. If
+     * {@code (index >= 0 && index < this.size())}, then the following
+     * expression will always evaluate to {@code true}:<br>
+     * {@code (this.value(this.enumNode1(index),
+     * this.enumNode2(index)) == this.enumValue(index))}.
+     *
+     * @param index an index in a list of node pairs with non-zero value
+     * @return the value of the specified ordered node pair in a list of
+     * node pairs with non-zero value
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code index < 0 || index >= this.size()}
+     */
+    public float enumValue(int index) {
+        checkSize(index);
+        return value[this.index[index]];
+    }
+
+    /**
+     * Returns the value of the specified node pair.
+     *
+     * @param node1 the first node
+     * @param node2 the second node
+     * @return the value of the specified node pair
+     * @throws IllegalArgumentException if {@code node1 < 0 || node2 < 0}
+     */
+    public float value(int node1, int node2) {
+        if (node1 < 0) {
+            throw new IllegalArgumentException(String.valueOf(node1));
+        }
+        if (node2 < 0) {
+            throw new IllegalArgumentException(String.valueOf(node2));
+        }
+        return value[index(node1, node2)];
+    }
+
+    /**
+     * Sets the value of each ordered node pair to 0.
+     */
+    public void clear() {
+        for (int j=0; j<this.size; ++j) {
+            value[index[j]] = 0f;
+        }
+        size = 0;
+    }
+
+    /**
+     * Returns a string representation of {@code this}.  The exact
+     * details of the representation are unspecified and subject to change.
+     *
+     * @return a string representation of {@code this}
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(80);
+        sb.append("size=");
+        sb.append(size);
+        for (int j=0; j<size; ++j) {
+            sb.append(" (");
+            sb.append(j);
+            sb.append(": node1=");
+            sb.append(enumNode1(j));
+            sb.append(" node2=");
+            sb.append(enumNode2(j));
+            sb.append(" value=");
+            sb.append(enumValue(j));
+            sb.append(") ");
+        }
+        return sb.toString();
+    }
+}
diff --git a/vcf/AllData.java b/vcf/AllData.java
index be5f132..bf190b2 100644
--- a/vcf/AllData.java
+++ b/vcf/AllData.java
@@ -1,355 +1,355 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package vcf;
-
-import beagleutil.SampleIds;
-import beagleutil.Samples;
-import blbutil.SampleFileIt;
-import haplotype.HapPair;
-import haplotype.RefHapPairs;
-import haplotype.SampleHapPairs;
-import haplotype.WrappedHapPair;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * <p>Class {@code AllData} represents a sliding window of
- * reference and target VCF records.
- * </p>
- * <p>Instances of class {@code AllData} are not thread-safe.
- * </p>
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class AllData implements Data {
-
-    private int window = 0;
-    private VcfEmission[] refData;
-    private SampleHapPairs refSampleHapPairs;
-    private GL refEmissions;
-    private VcfEmission[] targetData;  // missing markers as null entries
-    private int[] refIndices;
-    private int[] targetIndices;
-    private GL targetEmissions;
-    private final Samples allSamples;
-
-    private final List<HapPair> refHapPairs;
-    private final List<HapPair> targetRefHapPairs; // at target markers
-    private final VcfWindow refWindow;
-    private final RestrictedVcfWindow targetWindow;
-
-    /**
-     * Constructs and returns a new {@code AllData} instance from VCF records
-     * returned by the specified {@code SampleFileIt} objects.
-     *
-     * @param refIt an iterator that returns reference VCF records
-     * @param targetIt an iterator that returns target VCF records
-     * @return a new {@code AllData} instance.
-     *
-     * @throws IllegalArgumentException if either the reference data or
-     * target data contain no samples
-     * @throws IllegalArgumentException if a format error is detected
-     * in a string VCF record
-     * @throws NullPointerException if {@code refIt == null || targetIt == null}
-     */
-    public static AllData allData(SampleFileIt<VcfEmission> refIt,
-            SampleFileIt<? extends VcfEmission> targetIt) {
-        if (refIt.samples().nSamples()==0 && targetIt.samples().nSamples()==0) {
-            throw new IllegalArgumentException("nSamples==0");
-        }
-        VcfWindow refWindow = new VcfWindow(refIt);
-        RestrictedVcfWindow targetWindow = new RestrictedVcfWindow(targetIt);
-        return new AllData(refWindow, targetWindow);
-    }
-
-    private AllData(VcfWindow refWind, RestrictedVcfWindow targetWind) {
-        checkSampleOverlap(refWind.samples(), targetWind.samples());
-        this.refWindow = refWind;
-        this.targetWindow = targetWind;
-
-        this.refData = new VcfEmission[0];
-        this.refSampleHapPairs = null;
-        this.refEmissions = new RefGL(refWind.samples(), refData);
-        this.targetData = new VcfEmission[0];
-        this.refIndices = new int[0];
-        this.targetIndices = new int[0];
-        this.targetEmissions = new BasicGL(targetWind.samples(), targetData);
-        this.allSamples = allSamples(refWind.samples(), targetWind.samples());
-
-        this.refHapPairs = new ArrayList<>(0);
-        this.targetRefHapPairs = new ArrayList<>(0);
-    }
-
-    private static Samples allSamples(Samples ref, Samples target) {
-        /*
-           Target samples are listed first so that sample indices agree
-           with sample indices in target data genotype likelihoods.
-        */
-        int nRef = ref.nSamples();
-        int nTarget = target.nSamples();
-        int[] idIndices = new int[nRef + nTarget];
-        for (int j=0; j<nTarget; ++j) {
-            idIndices[j] = target.idIndex(j);
-        }
-        for (int j=0; j<nRef; ++j) {
-            idIndices[nTarget + j] = ref.idIndex(j);
-        }
-        return new Samples(idIndices);
-    }
-
-    private static void checkSampleOverlap(Samples ref, Samples nonRef) {
-        int nRef = ref.nSamples();
-        int nNonRef = nonRef.nSamples();
-        int n = nRef + nNonRef;
-        int[] idIndices = new int[n];
-        for (int j=0; j<nRef; ++j) {
-            idIndices[j] = ref.idIndex(j);
-        }
-        for (int j=0; j<nNonRef; ++j) {
-            idIndices[nRef + j] = nonRef.idIndex(j);
-        }
-        Arrays.sort(idIndices);
-        for (int j=1; j<idIndices.length; ++j) {
-            if (idIndices[j-1]==idIndices[j]) {
-                String s = "Overlap between reference and non-reference samples: "
-                        + SampleIds.instance().id(idIndices[j-1]);
-                throw new IllegalArgumentException(s);
-            }
-        }
-    }
-
-    @Override
-    public boolean lastWindowOnChrom() {
-        return refWindow.lastWindowOnChrom();
-    }
-
-    @Override
-    public boolean canAdvanceWindow() {
-        return refWindow.canAdvanceWindow();
-    }
-
-    @Override
-    public void advanceWindow(int requestedOverlap, int windowSize) {
-        Samples refSamples = refWindow.samples();
-        refData = refWindow.advanceWindow(requestedOverlap, windowSize);
-        refEmissions = new RefGL(refSamples, refData);
-        refSampleHapPairs = new RefHapPairs(refEmissions.markers(), refSamples, refData);
-        targetData = targetWindow.advanceWindow(refEmissions.markers());
-        refIndices = refIndices(targetData);
-        targetIndices = targetIndices(targetData);
-        targetEmissions = targetEmissions(targetWindow.samples(),
-                targetData, refIndices);
-        ++window;
-        setRefHaplotypes(refEmissions.markers(), refData);
-        setTargetRefHaplotypes(targetEmissions.markers(), refData, refIndices);
-    }
-
-    @Override
-    public int window() {
-        return window;
-    }
-
-    private static int[] refIndices(VcfEmission[] vma) {
-        int nonNullCnt = 0;
-        for (VcfEmission vm : vma) {
-            if (vm!=null) {
-                ++nonNullCnt;
-            }
-        }
-        int[] inclusionMap = new int[nonNullCnt];
-        int index = 0;
-        for (int j=0; j<vma.length; ++j) {
-            if (vma[j]!=null) {
-                inclusionMap[index++] = j;
-            }
-        }
-        if (index != inclusionMap.length) {
-            throw new IllegalStateException("vma modification detected");
-        }
-        return inclusionMap;
-    }
-
-    private static int[] targetIndices(VcfEmission[] vma) {
-        int[] inclusionMap = new int[vma.length];
-        int index = 0;
-        for (int j=0; j<inclusionMap.length; ++j) {
-            if (vma[j]!=null) {
-                inclusionMap[j] = index++;
-            }
-            else {
-                inclusionMap[j] = -1;
-            }
-        }
-        return inclusionMap;
-    }
-
-    private static GL targetEmissions(Samples samples,
-            VcfEmission[] vma, int[] refMarkerIndex) {
-        VcfEmission[] restricted = new VcfEmission[refMarkerIndex.length];
-        for (int j=0; j<refMarkerIndex.length; ++j) {
-            restricted[j] = vma[refMarkerIndex[j]];
-        }
-        return new BasicGL(samples, restricted);
-    }
-
-    private void setRefHaplotypes(Markers refMarkers, VcfEmission[] refData) {
-        refHapPairs.clear();
-        SampleHapPairs refHaplotypes =
-                new RefHapPairs(refMarkers, refWindow.samples(), refData);
-        for (int j=0, n=refHaplotypes.nSamples(); j<n; ++j) {
-            refHapPairs.add(new WrappedHapPair(refHaplotypes, j));
-        }
-    }
-
-    private void setTargetRefHaplotypes(Markers targetMarkers, VcfEmission[] refData,
-            int[] refMarkerIndices) {
-        assert targetMarkers.nMarkers()==refMarkerIndices.length;
-        targetRefHapPairs.clear();
-        VcfEmission[] vma = new VcfEmission[refMarkerIndices.length];
-        for (int j=0; j<refMarkerIndices.length; ++j) {
-                vma[j] = refData[refMarkerIndices[j]];
-        }
-        SampleHapPairs refHaplotypes
-                = new RefHapPairs(targetMarkers, refWindow.samples(), vma);
-        for (int j=0, n=refHaplotypes.nSamples(); j<n; ++j) {
-            targetRefHapPairs.add(new WrappedHapPair(refHaplotypes, j));
-        }
-    }
-
-    @Override
-    public int targetOverlap() {
-        return targetWindow.overlap();
-    }
-
-    @Override
-    public int overlap() {
-        return refWindow.overlap();
-    }
-
-    @Override
-    public int nTargetMarkers() {
-        return targetEmissions.markers().nMarkers();
-    }
-
-    @Override
-    public int nTargetMarkersSoFar() {
-        return targetWindow.cumMarkerCnt();
-    }
-
-    @Override
-    public Markers targetMarkers() {
-        return targetEmissions.markers();
-    }
-
-
-    @Override
-    public int nMarkers() {
-        return refEmissions.nMarkers();
-    }
-
-    @Override
-    public int nMarkersSoFar() {
-        return refWindow.cumMarkerCnt();
-    }
-
-    @Override
-    public Markers markers() {
-        return refEmissions.markers();
-    }
-
-    @Override
-    public int targetMarkerIndex(int refIndex) {
-        return targetIndices[refIndex];
-    }
-
-    @Override
-    public int markerIndex(int nonRefIndex) {
-        return refIndices[nonRefIndex];
-    }
-
-    @Override
-    public int nTargetSamples() {
-        return targetEmissions.nSamples();
-    }
-
-    @Override
-    public Samples targetSamples() {
-        return targetEmissions.samples();
-    }
-
-    @Override
-    public int nRefSamples() {
-        return refWindow.nSamples();
-    }
-
-    @Override
-    public Samples refSamples() {
-        return refWindow.samples();
-    }
-
-    @Override
-    public int nAllSamples() {
-        return allSamples.nSamples();
-    }
-
-    @Override
-    public Samples allSamples() {
-        return allSamples;
-    }
-
-
-    @Override
-    public GL targetGL() {
-       return targetEmissions;
-    }
-
-    @Override
-    public List<HapPair> restrictedRefHapPairs() {
-        return new ArrayList<>(targetRefHapPairs);
-    }
-
-    @Override
-    public List<HapPair> refHapPairs() {
-        return new ArrayList<>(refHapPairs);
-    }
-
-    @Override
-    public SampleHapPairs refSampleHapPairs() {
-        return refSampleHapPairs;
-    }
-
-    @Override
-    public void close() {
-        refWindow.close();
-        targetWindow.close();
-    }
-
-    /**
-     * Returns a string representation of {@code this}.  The exact
-     * details of the representation are unspecified and subject to change.
-     * @return a string representation of {@code this}
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder(20);
-        sb.append(this.getClass().toString());
-        return sb.toString();
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package vcf;
+
+import beagleutil.SampleIds;
+import beagleutil.Samples;
+import blbutil.SampleFileIt;
+import haplotype.HapPair;
+import haplotype.RefHapPairs;
+import haplotype.SampleHapPairs;
+import haplotype.WrappedHapPair;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * <p>Class {@code AllData} represents a sliding window of
+ * reference and target VCF records.
+ * </p>
+ * <p>Instances of class {@code AllData} are not thread-safe.
+ * </p>
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class AllData implements Data {
+
+    private int window = 0;
+    private VcfEmission[] refData;
+    private SampleHapPairs refSampleHapPairs;
+    private GL refEmissions;
+    private VcfEmission[] targetData;  // missing markers as null entries
+    private int[] refIndices;
+    private int[] targetIndices;
+    private GL targetEmissions;
+    private final Samples allSamples;
+
+    private final List<HapPair> refHapPairs;
+    private final List<HapPair> targetRefHapPairs; // at target markers
+    private final VcfWindow refWindow;
+    private final RestrictedVcfWindow targetWindow;
+
+    /**
+     * Constructs and returns a new {@code AllData} instance from VCF records
+     * returned by the specified {@code SampleFileIt} objects.
+     *
+     * @param refIt an iterator that returns reference VCF records
+     * @param targetIt an iterator that returns target VCF records
+     * @return a new {@code AllData} instance.
+     *
+     * @throws IllegalArgumentException if either the reference data or
+     * target data contain no samples
+     * @throws IllegalArgumentException if a format error is detected
+     * in a string VCF record
+     * @throws NullPointerException if {@code refIt == null || targetIt == null}
+     */
+    public static AllData allData(SampleFileIt<VcfEmission> refIt,
+            SampleFileIt<? extends VcfEmission> targetIt) {
+        if (refIt.samples().nSamples()==0 && targetIt.samples().nSamples()==0) {
+            throw new IllegalArgumentException("nSamples==0");
+        }
+        VcfWindow refWindow = new VcfWindow(refIt);
+        RestrictedVcfWindow targetWindow = new RestrictedVcfWindow(targetIt);
+        return new AllData(refWindow, targetWindow);
+    }
+
+    private AllData(VcfWindow refWind, RestrictedVcfWindow targetWind) {
+        checkSampleOverlap(refWind.samples(), targetWind.samples());
+        this.refWindow = refWind;
+        this.targetWindow = targetWind;
+
+        this.refData = new VcfEmission[0];
+        this.refSampleHapPairs = null;
+        this.refEmissions = new RefGL(refWind.samples(), refData);
+        this.targetData = new VcfEmission[0];
+        this.refIndices = new int[0];
+        this.targetIndices = new int[0];
+        this.targetEmissions = new BasicGL(targetWind.samples(), targetData);
+        this.allSamples = allSamples(refWind.samples(), targetWind.samples());
+
+        this.refHapPairs = new ArrayList<>(0);
+        this.targetRefHapPairs = new ArrayList<>(0);
+    }
+
+    private static Samples allSamples(Samples ref, Samples target) {
+        /*
+           Target samples are listed first so that sample indices agree
+           with sample indices in target data genotype likelihoods.
+        */
+        int nRef = ref.nSamples();
+        int nTarget = target.nSamples();
+        int[] idIndices = new int[nRef + nTarget];
+        for (int j=0; j<nTarget; ++j) {
+            idIndices[j] = target.idIndex(j);
+        }
+        for (int j=0; j<nRef; ++j) {
+            idIndices[nTarget + j] = ref.idIndex(j);
+        }
+        return new Samples(idIndices);
+    }
+
+    private static void checkSampleOverlap(Samples ref, Samples nonRef) {
+        int nRef = ref.nSamples();
+        int nNonRef = nonRef.nSamples();
+        int n = nRef + nNonRef;
+        int[] idIndices = new int[n];
+        for (int j=0; j<nRef; ++j) {
+            idIndices[j] = ref.idIndex(j);
+        }
+        for (int j=0; j<nNonRef; ++j) {
+            idIndices[nRef + j] = nonRef.idIndex(j);
+        }
+        Arrays.sort(idIndices);
+        for (int j=1; j<idIndices.length; ++j) {
+            if (idIndices[j-1]==idIndices[j]) {
+                String s = "Overlap between reference and non-reference samples: "
+                        + SampleIds.instance().id(idIndices[j-1]);
+                throw new IllegalArgumentException(s);
+            }
+        }
+    }
+
+    @Override
+    public boolean lastWindowOnChrom() {
+        return refWindow.lastWindowOnChrom();
+    }
+
+    @Override
+    public boolean canAdvanceWindow() {
+        return refWindow.canAdvanceWindow();
+    }
+
+    @Override
+    public void advanceWindow(int requestedOverlap, int windowSize) {
+        Samples refSamples = refWindow.samples();
+        refData = refWindow.advanceWindow(requestedOverlap, windowSize);
+        refEmissions = new RefGL(refSamples, refData);
+        refSampleHapPairs = new RefHapPairs(refEmissions.markers(), refSamples, refData);
+        targetData = targetWindow.advanceWindow(refEmissions.markers());
+        refIndices = refIndices(targetData);
+        targetIndices = targetIndices(targetData);
+        targetEmissions = targetEmissions(targetWindow.samples(),
+                targetData, refIndices);
+        ++window;
+        setRefHaplotypes(refEmissions.markers(), refData);
+        setTargetRefHaplotypes(targetEmissions.markers(), refData, refIndices);
+    }
+
+    @Override
+    public int window() {
+        return window;
+    }
+
+    private static int[] refIndices(VcfEmission[] vma) {
+        int nonNullCnt = 0;
+        for (VcfEmission vm : vma) {
+            if (vm!=null) {
+                ++nonNullCnt;
+            }
+        }
+        int[] inclusionMap = new int[nonNullCnt];
+        int index = 0;
+        for (int j=0; j<vma.length; ++j) {
+            if (vma[j]!=null) {
+                inclusionMap[index++] = j;
+            }
+        }
+        if (index != inclusionMap.length) {
+            throw new IllegalStateException("vma modification detected");
+        }
+        return inclusionMap;
+    }
+
+    private static int[] targetIndices(VcfEmission[] vma) {
+        int[] inclusionMap = new int[vma.length];
+        int index = 0;
+        for (int j=0; j<inclusionMap.length; ++j) {
+            if (vma[j]!=null) {
+                inclusionMap[j] = index++;
+            }
+            else {
+                inclusionMap[j] = -1;
+            }
+        }
+        return inclusionMap;
+    }
+
+    private static GL targetEmissions(Samples samples,
+            VcfEmission[] vma, int[] refMarkerIndex) {
+        VcfEmission[] restricted = new VcfEmission[refMarkerIndex.length];
+        for (int j=0; j<refMarkerIndex.length; ++j) {
+            restricted[j] = vma[refMarkerIndex[j]];
+        }
+        return new BasicGL(samples, restricted);
+    }
+
+    private void setRefHaplotypes(Markers refMarkers, VcfEmission[] refData) {
+        refHapPairs.clear();
+        SampleHapPairs refHaplotypes =
+                new RefHapPairs(refMarkers, refWindow.samples(), refData);
+        for (int j=0, n=refHaplotypes.nSamples(); j<n; ++j) {
+            refHapPairs.add(new WrappedHapPair(refHaplotypes, j));
+        }
+    }
+
+    private void setTargetRefHaplotypes(Markers targetMarkers, VcfEmission[] refData,
+            int[] refMarkerIndices) {
+        assert targetMarkers.nMarkers()==refMarkerIndices.length;
+        targetRefHapPairs.clear();
+        VcfEmission[] vma = new VcfEmission[refMarkerIndices.length];
+        for (int j=0; j<refMarkerIndices.length; ++j) {
+                vma[j] = refData[refMarkerIndices[j]];
+        }
+        SampleHapPairs refHaplotypes
+                = new RefHapPairs(targetMarkers, refWindow.samples(), vma);
+        for (int j=0, n=refHaplotypes.nSamples(); j<n; ++j) {
+            targetRefHapPairs.add(new WrappedHapPair(refHaplotypes, j));
+        }
+    }
+
+    @Override
+    public int targetOverlap() {
+        return targetWindow.overlap();
+    }
+
+    @Override
+    public int overlap() {
+        return refWindow.overlap();
+    }
+
+    @Override
+    public int nTargetMarkers() {
+        return targetEmissions.markers().nMarkers();
+    }
+
+    @Override
+    public int nTargetMarkersSoFar() {
+        return targetWindow.cumMarkerCnt();
+    }
+
+    @Override
+    public Markers targetMarkers() {
+        return targetEmissions.markers();
+    }
+
+
+    @Override
+    public int nMarkers() {
+        return refEmissions.nMarkers();
+    }
+
+    @Override
+    public int nMarkersSoFar() {
+        return refWindow.cumMarkerCnt();
+    }
+
+    @Override
+    public Markers markers() {
+        return refEmissions.markers();
+    }
+
+    @Override
+    public int targetMarkerIndex(int refIndex) {
+        return targetIndices[refIndex];
+    }
+
+    @Override
+    public int markerIndex(int nonRefIndex) {
+        return refIndices[nonRefIndex];
+    }
+
+    @Override
+    public int nTargetSamples() {
+        return targetEmissions.nSamples();
+    }
+
+    @Override
+    public Samples targetSamples() {
+        return targetEmissions.samples();
+    }
+
+    @Override
+    public int nRefSamples() {
+        return refWindow.nSamples();
+    }
+
+    @Override
+    public Samples refSamples() {
+        return refWindow.samples();
+    }
+
+    @Override
+    public int nAllSamples() {
+        return allSamples.nSamples();
+    }
+
+    @Override
+    public Samples allSamples() {
+        return allSamples;
+    }
+
+
+    @Override
+    public GL targetGL() {
+       return targetEmissions;
+    }
+
+    @Override
+    public List<HapPair> restrictedRefHapPairs() {
+        return new ArrayList<>(targetRefHapPairs);
+    }
+
+    @Override
+    public List<HapPair> refHapPairs() {
+        return new ArrayList<>(refHapPairs);
+    }
+
+    @Override
+    public SampleHapPairs refSampleHapPairs() {
+        return refSampleHapPairs;
+    }
+
+    @Override
+    public void close() {
+        refWindow.close();
+        targetWindow.close();
+    }
+
+    /**
+     * Returns a string representation of {@code this}.  The exact
+     * details of the representation are unspecified and subject to change.
+     * @return a string representation of {@code this}
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(20);
+        sb.append(this.getClass().toString());
+        return sb.toString();
+    }
+}
diff --git a/vcf/GprobsStatistics.java b/vcf/GprobsStatistics.java
index d03a057..f45c903 100644
--- a/vcf/GprobsStatistics.java
+++ b/vcf/GprobsStatistics.java
@@ -40,16 +40,18 @@ import main.GenotypeValues;
  */
 public class GprobsStatistics {
 
+    private static final double MIN_R2_DEN = 1e-8;
+
     private final Marker marker;
     private final int nSamples;
-    private final float[] alleleFreq;
+    private final double[] alleleFreq;
 
-    private float sumCall = 0;
-    private float sumSquareCall = 0;
-    private float sumExpected = 0;
-    private float sumExpectedSquare = 0;
-    private float sumSquareExpected= 0;
-    private float sumCallExpected = 0;
+    private double sumCall = 0;
+    private double sumSquareCall = 0;
+    private double sumExpected = 0;
+    private double sumExpectedSquare = 0;
+    private double sumSquareExpected= 0;
+    private double sumCallExpected = 0;
 
     /**
      * Constructs a new {@code GprobsStatistics} instance from the
@@ -64,17 +66,17 @@ public class GprobsStatistics {
         int nAlleles = gv.marker(marker).nAlleles();
         this.marker = gv.marker(marker);
         this.nSamples = gv.nSamples();
-        this.alleleFreq = new float[nAlleles];
-        float[] alProbs = new float[nAlleles];
-        float[] gtProbs = new float[3];
+        this.alleleFreq = new double[nAlleles];
+        double[] alProbs = new double[nAlleles];
+        double[] gtProbs = new double[3];
         for (int j=0; j<this.nSamples; ++j) {
             setProbs(gv, marker, j, gtProbs, alProbs);
             for (int a=0; a<nAlleles; ++a) {
                 alleleFreq[a] += alProbs[a];
             }
             int call = maxIndex(gtProbs);
-            float exp = (gtProbs[1] + 2*gtProbs[2]);
-            float expSquare = (gtProbs[1] + 4*gtProbs[2]);
+            double exp = (gtProbs[1] + 2*gtProbs[2]);
+            double expSquare = (gtProbs[1] + 4*gtProbs[2]);
             sumCall += call;
             sumSquareCall += call*call;
             sumExpected += exp;
@@ -82,12 +84,12 @@ public class GprobsStatistics {
             sumSquareExpected += (exp*exp);
             sumCallExpected += (call*exp);
         }
-        float sum = sum(alleleFreq);
+        double sum = sum(alleleFreq);
         divideBy(alleleFreq, sum);
     }
 
     private static void setProbs(GenotypeValues gv, int marker, int sample,
-            float[] gtProbs, float[] alProbs) {
+            double[] gtProbs, double[] alProbs) {
         Arrays.fill(gtProbs, 0.0f);
         Arrays.fill(alProbs, 0.0f);
         int gt = 0;
@@ -107,7 +109,7 @@ public class GprobsStatistics {
                 }
             }
         }
-        float sum = sum(gtProbs);
+        double sum = sum(gtProbs);
         divideBy(gtProbs, sum);
         divideBy(alProbs, 2*sum);
     }
@@ -125,17 +127,17 @@ public class GprobsStatistics {
         int nAlleles = alleleProbs.marker(marker).nAlleles();
         this.marker = alleleProbs.marker(marker);
         this.nSamples = alleleProbs.nSamples();
-        this.alleleFreq = new float[nAlleles];
-        float[] alProbs = new float[nAlleles];
-        float[] gtProbs = new float[3];
+        this.alleleFreq = new double[nAlleles];
+        double[] alProbs = new double[nAlleles];
+        double[] gtProbs = new double[3];
         for (int j=0; j<this.nSamples; ++j) {
             setProbs(alleleProbs, marker, j, gtProbs, alProbs);
             for (int a=0; a<nAlleles; ++a) {
                 alleleFreq[a] += alProbs[a];
             }
             int call = maxIndex(gtProbs);
-            float exp = (gtProbs[1] + 2*gtProbs[2]);
-            float expSquare = (gtProbs[1] + 4*gtProbs[2]);
+            double exp = (gtProbs[1] + 2*gtProbs[2]);
+            double expSquare = (gtProbs[1] + 4*gtProbs[2]);
             sumCall += call;
             sumSquareCall += call*call;
             sumExpected += exp;
@@ -143,17 +145,17 @@ public class GprobsStatistics {
             sumSquareExpected += (exp*exp);
             sumCallExpected += (call*exp);
         }
-        float sum = sum(alleleFreq);
+        double sum = sum(alleleFreq);
         divideBy(alleleFreq, sum);
     }
 
     private static void setProbs(AlleleProbs ap, int marker, int sample,
-            float[] gtProbs, float[] alProbs) {
+            double[] gtProbs, double[] alProbs) {
         Arrays.fill(gtProbs, 0.0f);
         Arrays.fill(alProbs, 0.0f);
         for (int a2=0; a2<alProbs.length; ++a2) {
             for (int a1=0; a1<=a2; ++a1) {
-                float gprob = ap.gtProb(marker, sample, a1, a2);
+                double gprob = ap.gtProb(marker, sample, a1, a2);
                 if (a1 != a2) {
                     gprob += ap.gtProb(marker, sample, a2, a1);
                 }
@@ -170,12 +172,12 @@ public class GprobsStatistics {
                 }
             }
         }
-        float sum = sum(gtProbs);
+        double sum = sum(gtProbs);
         divideBy(gtProbs, sum);
         divideBy(alProbs, 2*sum);
     }
 
-    private static int maxIndex(float[] fa) {
+    private static int maxIndex(double[] fa) {
         int maxIndex = 0;
         for (int j=1; j<fa.length; ++j) {
             if (fa[j]>fa[maxIndex]) {
@@ -185,15 +187,15 @@ public class GprobsStatistics {
         return maxIndex;
     }
 
-    private static float sum(float[] fa) {
-        float sum = 0.0f;
-        for (float f : fa) {
+    private static double sum(double[] fa) {
+        double sum = 0.0f;
+        for (double f : fa) {
             sum += f;
         }
         return sum;
     }
 
-    private static void divideBy(float[] fa, float divisor) {
+    private static void divideBy(double[] fa, double divisor) {
         for (int j=0; j<fa.length; ++j) {
             fa[j] /= divisor;
         }
@@ -215,7 +217,7 @@ public class GprobsStatistics {
      * {@code j}-th element is the estimated sample frequency of allele
      * {@code j}
      */
-    public float[] alleleFreq() {
+    public double[] alleleFreq() {
         return alleleFreq.clone();
     }
 
@@ -228,13 +230,13 @@ public class GprobsStatistics {
      * @return the estimated squared correlation between the most likely
      * allele dose and the true allele dose
      */
-    public float allelicR2() {
-        float f = 1.0f /  nSamples;
-        float cov = sumCallExpected - (sumCall * sumExpected * f);
-        float varBest = sumSquareCall - (sumCall * sumCall * f);
-        float varExp = sumExpectedSquare - (sumExpected * sumExpected * f);
-        float den = varBest * varExp;
-        return (den==0.0f) ? 0.0f : Math.abs( (cov*cov) / den );
+    public double allelicR2() {
+        double f = 1.0f /  nSamples;
+        double cov = sumCallExpected - (sumCall * sumExpected * f);
+        double varBest = sumSquareCall - (sumCall * sumCall * f);
+        double varExp = sumExpectedSquare - (sumExpected * sumExpected * f);
+        double den = varBest*varExp;
+        return (den < MIN_R2_DEN) ? 0.0f : (cov*cov/den);
     }
 
     /**
@@ -245,11 +247,14 @@ public class GprobsStatistics {
      * @return the estimated squared correlation between the estimated
      * ALT allele dose and the true ALT allele dose
      */
-    public float doseR2() {
-        float f = 1.0f / (float) nSamples;
-        float num = sumSquareExpected - (sumExpected * sumExpected * f);
-        float den = sumExpectedSquare - (sumExpected * sumExpected * f);
-        return (den==0.0f) ? 0.0f : Math.abs(num / den);
+    public double doseR2() {
+        double f = 1.0f / (double) nSamples;
+        double num = sumSquareExpected - (sumExpected * sumExpected * f);
+        double den = sumExpectedSquare - (sumExpected * sumExpected * f);
+        if (num < 0.0) {
+            num = 0.0;
+        }
+        return (den < MIN_R2_DEN) ? 0.0f : (num / den);
     }
 
     /**
@@ -261,12 +266,15 @@ public class GprobsStatistics {
      * @return the estimated squared correlation between the estimated
      * ALT allele dose and the true ALT allele dose
      */
-    public float hweDoseR2() {
-        float f = 1.0f / nSamples;
-        float num = (sumSquareExpected - (sumExpected*sumExpected*f))/nSamples;
-        float altFreq = sumExpected / (2.0f * nSamples);
-        float den = 2.0f * altFreq * (1.0f - altFreq);
-        return (den==0.0f) ? 0.0f : Math.abs(num / den);
+    public double hweDoseR2() {
+        double f = 1.0f / nSamples;
+        double altFreq = sumExpected / (2.0f * nSamples);
+        double num = (sumSquareExpected - (sumExpected*sumExpected*f))/nSamples;
+        double den = 2.0f * altFreq * (1.0f - altFreq);
+        if (num < 0.0) {
+            num = 0.0;
+        }
+        return (den < MIN_R2_DEN) ? 0.0f : (num/den);
     }
 
     /**
@@ -296,7 +304,7 @@ public class GprobsStatistics {
         return sb.toString();
     }
 
-    private static String format(DecimalFormat df, float d) {
+    private static String format(DecimalFormat df, double d) {
         if (Double.isNaN(d)) {
             return "NaN";
         }
diff --git a/vcf/Markers.java b/vcf/Markers.java
index 13b6645..0117396 100644
--- a/vcf/Markers.java
+++ b/vcf/Markers.java
@@ -1,389 +1,389 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package vcf;
-
-import beagleutil.ChromIds;
-import blbutil.Const;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * <p>Class {@code Markers} represent a list of markers in chromosome order.
- * </p>
- * <p>Instances of class {@code Markers} are immutable.
- * </p>
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public final class Markers {
-
-    private final Set<Marker> markerSet;
-
-    private final Marker[] fwdMarkerArray;
-    private final int[] fwdSumAlleles;
-    private final int[] fwdSumGenotypes;
-    private final int[] fwdSumHaplotypeBits;
-    private final int fwdHashCode;
-
-    private final Marker[] bwdMarkerArray;
-    private final int[] bwdSumAlleles;
-    private final int[] bwdSumGenotypes;
-    private final int[] bwdSumHaplotypeBits;
-    private final int bwdHashCode;
-    private final Markers bwdMarkers;
-
-    /**
-     * Construct and return a new {@code Markers} instance that represents the
-     * specified list of markers.
-     * @param markers a list of markers in chromosome order
-     * @return a new {@code Markers} instance that represents the
-     * specified list of markers
-     *
-     * @throws IllegalArgumentException if markers on a chromosome are not
-     * in chromosome order
-     * @throws IllegalArgumentException if there are duplicate markers
-     * @throws IllegalArgumentException if the markers on a chromosome
-     * do not form a contiguous set of entries within the array
-     *
-     * @throws NullPointerException if
-     * {@code markers == null} or if {@code markers[j] == null}
-     * for any {@code j} satisfying {@code (0 <= j && j < markers.length)}
-     */
-    public static Markers create(Marker[] markers) {
-        Markers fwd = new Markers(markers);
-        return new Markers(fwd.reverse());
-    }
-
-    /**
-     * Construct a new {@code Markers} instance that represents the
-     * specified list of markers.
-     * @param markers a list of markers in chromosome order
-     *
-     * @throws IllegalArgumentException if markers on a chromosome are not
-     * in chromosome order
-     * @throws IllegalArgumentException if there are duplicate markers
-     * @throws IllegalArgumentException if the markers on a chromosome
-     * do not form a contiguous set of entries within the array
-     *
-     * @throws NullPointerException if
-     * {@code markers == null} or if {@code markers[j] == null}
-     * for any {@code j} satisfying {@code (0 <= j && j < markers.length)}
-     */
-    private Markers(Marker[] markers) {
-        checkMarkerPosOrder(markers);
-        this.fwdMarkerArray = markers.clone();
-        this.bwdMarkerArray = reverse(this.fwdMarkerArray);
-        this.markerSet = markerSet(fwdMarkerArray);
-
-        this.fwdSumAlleles = cumSumAlleles(fwdMarkerArray);
-        this.fwdSumGenotypes = cumSumGenotypes(fwdMarkerArray);
-        this.fwdSumHaplotypeBits = cumSumHaplotypeBits(fwdMarkerArray);
-        this.fwdHashCode = Arrays.deepHashCode(fwdMarkerArray);
-
-        this.bwdSumAlleles = cumSumAlleles(bwdMarkerArray);
-        this.bwdSumGenotypes = cumSumGenotypes(bwdMarkerArray);
-        this.bwdSumHaplotypeBits = cumSumHaplotypeBits(bwdMarkerArray);
-        this.bwdHashCode = Arrays.deepHashCode(bwdMarkerArray);
-        this.bwdMarkers = null;
-    }
-
-    /**
-     * Constructs a new {@code Markers} instance whose {@code reverse()}
-     * method returns the specified {@code Markers}
-     * @param bwdMarkers a list of markers
-     */
-    private Markers(Markers bwdMarkers) {
-        this.markerSet = bwdMarkers.markerSet;
-        this.fwdMarkerArray = bwdMarkers.bwdMarkerArray;
-        this.bwdMarkerArray = bwdMarkers.fwdMarkerArray;
-
-        this.fwdSumAlleles = bwdMarkers.bwdSumAlleles;
-        this.fwdSumGenotypes = bwdMarkers.bwdSumGenotypes;
-        this.fwdSumHaplotypeBits = bwdMarkers.bwdSumHaplotypeBits;
-        this.fwdHashCode = bwdMarkers.bwdHashCode;
-
-        this.bwdSumAlleles = bwdMarkers.fwdSumAlleles;
-        this.bwdSumGenotypes = bwdMarkers.fwdSumGenotypes;
-        this.bwdSumHaplotypeBits = bwdMarkers.fwdSumHaplotypeBits;
-        this.bwdHashCode = bwdMarkers.fwdHashCode;
-        this.bwdMarkers = bwdMarkers;
-    }
-
-    private static void checkMarkerPosOrder(Marker[] markers) {
-        if (markers.length < 2) {
-            return;
-        }
-        Set<Integer> chromIndices = new HashSet<>();
-        chromIndices.add(markers[0].chromIndex());
-        chromIndices.add(markers[1].chromIndex());
-        for (int j=2; j<markers.length; ++j) {
-            int chr0 = markers[j-2].chromIndex();
-            int chr1 = markers[j-1].chromIndex();
-            int chr2 = markers[j].chromIndex();
-            if (chr0 == chr1 && chr1==chr2) {
-                int pos0 = markers[j-2].pos();
-                int pos1 = markers[j-1].pos();
-                int pos2 = markers[j].pos();
-                if ( (pos1<pos0 && pos1<pos2) || (pos1>pos0 && pos1>pos2) ) {
-                    String s = "markers not in chromosomal order: "
-                            + Const.nl + markers[j-2]
-                            + Const.nl + markers[j-1]
-                            + Const.nl + markers[j];
-                    throw new IllegalArgumentException(s);
-                }
-            }
-            else if (chr1!=chr2) {
-                if (chromIndices.contains(chr2)) {
-                    String s = "markers on chromosome are not contiguous: "
-                            + ChromIds.instance().id(chr2);
-                    throw new IllegalArgumentException(s);
-                }
-                chromIndices.add(chr2);
-            }
-        }
-    }
-
-    private static Marker[] reverse(Marker[] markers) {
-        int lastIndex = markers.length - 1;
-        Marker[] rev = new Marker[markers.length];
-        for (int j=0; j<markers.length; ++j) {
-            rev[j] = markers[lastIndex - j];
-        }
-        return rev;
-    }
-
-    private static Set<Marker> markerSet(Marker[] markers) {
-        Set<Marker> markerSet = new HashSet<>(markers.length);
-        for (Marker m : markers) {
-            if (markerSet.add(m)==false) {
-                throw new IllegalArgumentException("Duplicate marker: " + m);
-            }
-        }
-        return markerSet;
-    }
-
-    private static int[] cumSumAlleles(Marker[] markers) {
-        int[] ia = new int[markers.length + 1];
-        for (int j=1; j<ia.length; ++j) {
-            ia[j] = ia[j-1] + markers[j-1].nAlleles();
-        }
-        return ia;
-    }
-
-    private static int[] cumSumGenotypes(Marker[] markers) {
-        int[] ia = new int[markers.length + 1];
-        for (int j=1; j<ia.length; ++j) {
-            ia[j] = ia[j-1] + markers[j-1].nGenotypes();
-        }
-        return ia;
-    }
-
-    private static int[] cumSumHaplotypeBits(Marker[] markers) {
-        int[] ia = new int[markers.length + 1];
-        for (int j=1; j<ia.length; ++j) {
-            int nAllelesM1 = markers[j-1].nAlleles() - 1;
-            int nStorageBits = Integer.SIZE
-                    - Integer.numberOfLeadingZeros(nAllelesM1);
-            ia[j] = ia[j-1] + nStorageBits;
-        }
-        return ia;
-    }
-
-    /**
-     * Returns a hash code value for the object.
-     * The returned hash code equals
-     * {@code Arrays.deepHashCode(this.markers())}.
-     * @return a hash code value for the object
-     */
-    @Override
-    public int hashCode() {
-        return fwdHashCode;
-    }
-
-    /**
-     * Returns {@code true} if the specified object is a {@code Markers}
-     * instance which represents the same list of markers as {@code this},
-     * and returns {@code false} otherwise. Two lists of markers are
-     * the same if the lists have the same size and if markers with the
-     * same index in the two lists are equal.
-     *
-     * @param obj the object to be tested for equality with {@code this}
-     *
-     * @return {@code true} if the specified object is a {@code Markers}
-     * instance which represents the same list of markers as {@code this}
-     */
-    @Override
-    public boolean equals(Object obj) {
-        if (this==obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final Markers other = (Markers) obj;
-        return Arrays.deepEquals(this.fwdMarkerArray, other.fwdMarkerArray);
-    }
-
-    /**
-     * Constructs and returns a new {@code Markers} instance that is
-     * obtained by reversing the order of markers in {@code this}.
-     * @return a new {@code Markers} instance that is obtained by
-     * reversing the order of markers in {@code this}
-     */
-    public Markers reverse() {
-        return bwdMarkers==null ? new Markers(this) : bwdMarkers;
-    }
-
-    /**
-     * Returns the number of markers.
-     * @return the number of markers
-     */
-    public int nMarkers() {
-        return fwdMarkerArray.length;
-    }
-
-    /**
-     * Returns the specified marker.
-     * @param marker a marker index
-     * @return the specified marker
-     * @throws IndexOutOfBoundsException if
-     * {@code marker < 0 || marker >= this.nMarkers()}
-     */
-    public Marker marker(int marker) {
-        return fwdMarkerArray[marker];
-    }
-
-    /**
-     * Returns the list of markers.
-     * @return the list of markers
-     */
-    public Marker[] markers() {
-        return fwdMarkerArray.clone();
-    }
-
-    /**
-     * Returns {@code true} if the specified marker is not {@code null}
-     * and is an element in the list of markers represented by {@code this},
-     * and returns {@code false} otherwise.
-     *
-     * @param marker a marker
-     *
-     * @return {@code true} if the specified marker is not {@code null} and
-     * is an element in the list of markers represented by {@code this}
-     */
-    public boolean contains(Marker marker) {
-        return markerSet.contains(marker);
-    }
-
-    /**
-     * Returns a {@code Markers} instance that represents
-     * the specified range of marker indices.
-     * @param start the starting marker index (inclusive)
-     * @param end the ending marker index (exclusive)
-     * @return a {@code Markers} instance that represents
-     * the specified range of marker indices
-     *
-     * @throws IndexOutOfBoundsException if
-     * {@code start < 0 || end > this.nMarkers()}
-     * @throws IllegalArgumentException if {@code start >= end}.
-     */
-    public Markers restrict(int start, int end) {
-        if (end > fwdMarkerArray.length) {
-            throw new IndexOutOfBoundsException("end > this.nMarkers(): " + end);
-        }
-        return new Markers(Arrays.copyOfRange(fwdMarkerArray, start, end));
-    }
-
-    /**
-     * Returns the sum of the number of alleles for
-     * the markers with index less than the specified index.
-     * @param marker a marker index
-     * @return the sum of the number of alleles for
-     * the markers with index less than the specified index
-     * @throws IndexOutOfBoundsException if
-     * {@code marker < 0 || marker > this.nMarkers()}
-     */
-    public int sumAlleles(int marker) {
-        return fwdSumAlleles[marker];
-    }
-
-    /**
-     * Returns {@code this.sumAlleles(this.nMarkers())}.
-     * @return {@code this.sumAlleles(this.nMarkers())}
-     */
-    public int sumAlleles() {
-        return fwdSumAlleles[fwdMarkerArray.length];
-    }
-
-    /**
-     * Returns the sum of the number of possible genotypes for the markers
-     * with index less than the specified index.
-     * @param marker a marker index
-     * @return the sum of the number of possible genotypes for the markers
-     * with index less than the specified index
-     * @throws IndexOutOfBoundsException if
-     * {@code marker < 0 || marker > this.nMarkers()}
-     */
-    public int sumGenotypes(int marker) {
-        return fwdSumGenotypes[marker];
-    }
-
-    /**
-     * Returns {@code this.sumGenotypes(this.nMarkers())}.
-     * @return {@code this.sumGenotypes(this.nMarkers())}
-     */
-    public int sumGenotypes() {
-        return fwdSumGenotypes[fwdMarkerArray.length];
-    }
-
-    /**
-     * Returns the number of bits requires to store a haplotype for the
-     * markers with index less than the specified index.
-     * @param marker a marker index
-     * @return the number of bits requires to store a haplotype for the
-     * markers with index less than the specified index
-     * @throws IndexOutOfBoundsException if
-     * {@code marker < 0 || marker > this.nMarkers()}
-     */
-    public int sumHaplotypeBits(int marker) {
-        return fwdSumHaplotypeBits[marker];
-    }
-
-    /**
-     * Returns {@code this.sumHaplotypeBits(this.nMarkers())}.
-     * @return {@code this.sumHaplotypeBits(this.nMarkers())}
-     */
-    public int sumHaplotypeBits() {
-        return fwdSumHaplotypeBits[fwdMarkerArray.length];
-    }
-
-    /**
-     * Returns a string representation of {@code this}.
-     * The exact details of the representation are unspecified and
-     * subject to change.
-     * @return a string representation of {@code this}
-     */
-    @Override
-    public String toString() {
-        return Arrays.toString(fwdMarkerArray);
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package vcf;
+
+import beagleutil.ChromIds;
+import blbutil.Const;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <p>Class {@code Markers} represent a list of markers in chromosome order.
+ * </p>
+ * <p>Instances of class {@code Markers} are immutable.
+ * </p>
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public final class Markers {
+
+    private final Set<Marker> markerSet;
+
+    private final Marker[] fwdMarkerArray;
+    private final int[] fwdSumAlleles;
+    private final int[] fwdSumGenotypes;
+    private final int[] fwdSumHaplotypeBits;
+    private final int fwdHashCode;
+
+    private final Marker[] bwdMarkerArray;
+    private final int[] bwdSumAlleles;
+    private final int[] bwdSumGenotypes;
+    private final int[] bwdSumHaplotypeBits;
+    private final int bwdHashCode;
+    private final Markers bwdMarkers;
+
+    /**
+     * Construct and return a new {@code Markers} instance that represents the
+     * specified list of markers.
+     * @param markers a list of markers in chromosome order
+     * @return a new {@code Markers} instance that represents the
+     * specified list of markers
+     *
+     * @throws IllegalArgumentException if markers on a chromosome are not
+     * in chromosome order
+     * @throws IllegalArgumentException if there are duplicate markers
+     * @throws IllegalArgumentException if the markers on a chromosome
+     * do not form a contiguous set of entries within the array
+     *
+     * @throws NullPointerException if
+     * {@code markers == null} or if {@code markers[j] == null}
+     * for any {@code j} satisfying {@code (0 <= j && j < markers.length)}
+     */
+    public static Markers create(Marker[] markers) {
+        Markers fwd = new Markers(markers);
+        return new Markers(fwd.reverse());
+    }
+
+    /**
+     * Construct a new {@code Markers} instance that represents the
+     * specified list of markers.
+     * @param markers a list of markers in chromosome order
+     *
+     * @throws IllegalArgumentException if markers on a chromosome are not
+     * in chromosome order
+     * @throws IllegalArgumentException if there are duplicate markers
+     * @throws IllegalArgumentException if the markers on a chromosome
+     * do not form a contiguous set of entries within the array
+     *
+     * @throws NullPointerException if
+     * {@code markers == null} or if {@code markers[j] == null}
+     * for any {@code j} satisfying {@code (0 <= j && j < markers.length)}
+     */
+    private Markers(Marker[] markers) {
+        checkMarkerPosOrder(markers);
+        this.fwdMarkerArray = markers.clone();
+        this.bwdMarkerArray = reverse(this.fwdMarkerArray);
+        this.markerSet = markerSet(fwdMarkerArray);
+
+        this.fwdSumAlleles = cumSumAlleles(fwdMarkerArray);
+        this.fwdSumGenotypes = cumSumGenotypes(fwdMarkerArray);
+        this.fwdSumHaplotypeBits = cumSumHaplotypeBits(fwdMarkerArray);
+        this.fwdHashCode = Arrays.deepHashCode(fwdMarkerArray);
+
+        this.bwdSumAlleles = cumSumAlleles(bwdMarkerArray);
+        this.bwdSumGenotypes = cumSumGenotypes(bwdMarkerArray);
+        this.bwdSumHaplotypeBits = cumSumHaplotypeBits(bwdMarkerArray);
+        this.bwdHashCode = Arrays.deepHashCode(bwdMarkerArray);
+        this.bwdMarkers = null;
+    }
+
+    /**
+     * Constructs a new {@code Markers} instance whose {@code reverse()}
+     * method returns the specified {@code Markers}
+     * @param bwdMarkers a list of markers
+     */
+    private Markers(Markers bwdMarkers) {
+        this.markerSet = bwdMarkers.markerSet;
+        this.fwdMarkerArray = bwdMarkers.bwdMarkerArray;
+        this.bwdMarkerArray = bwdMarkers.fwdMarkerArray;
+
+        this.fwdSumAlleles = bwdMarkers.bwdSumAlleles;
+        this.fwdSumGenotypes = bwdMarkers.bwdSumGenotypes;
+        this.fwdSumHaplotypeBits = bwdMarkers.bwdSumHaplotypeBits;
+        this.fwdHashCode = bwdMarkers.bwdHashCode;
+
+        this.bwdSumAlleles = bwdMarkers.fwdSumAlleles;
+        this.bwdSumGenotypes = bwdMarkers.fwdSumGenotypes;
+        this.bwdSumHaplotypeBits = bwdMarkers.fwdSumHaplotypeBits;
+        this.bwdHashCode = bwdMarkers.fwdHashCode;
+        this.bwdMarkers = bwdMarkers;
+    }
+
+    private static void checkMarkerPosOrder(Marker[] markers) {
+        if (markers.length < 2) {
+            return;
+        }
+        Set<Integer> chromIndices = new HashSet<>();
+        chromIndices.add(markers[0].chromIndex());
+        chromIndices.add(markers[1].chromIndex());
+        for (int j=2; j<markers.length; ++j) {
+            int chr0 = markers[j-2].chromIndex();
+            int chr1 = markers[j-1].chromIndex();
+            int chr2 = markers[j].chromIndex();
+            if (chr0 == chr1 && chr1==chr2) {
+                int pos0 = markers[j-2].pos();
+                int pos1 = markers[j-1].pos();
+                int pos2 = markers[j].pos();
+                if ( (pos1<pos0 && pos1<pos2) || (pos1>pos0 && pos1>pos2) ) {
+                    String s = "markers not in chromosomal order: "
+                            + Const.nl + markers[j-2]
+                            + Const.nl + markers[j-1]
+                            + Const.nl + markers[j];
+                    throw new IllegalArgumentException(s);
+                }
+            }
+            else if (chr1!=chr2) {
+                if (chromIndices.contains(chr2)) {
+                    String s = "markers on chromosome are not contiguous: "
+                            + ChromIds.instance().id(chr2);
+                    throw new IllegalArgumentException(s);
+                }
+                chromIndices.add(chr2);
+            }
+        }
+    }
+
+    private static Marker[] reverse(Marker[] markers) {
+        int lastIndex = markers.length - 1;
+        Marker[] rev = new Marker[markers.length];
+        for (int j=0; j<markers.length; ++j) {
+            rev[j] = markers[lastIndex - j];
+        }
+        return rev;
+    }
+
+    private static Set<Marker> markerSet(Marker[] markers) {
+        Set<Marker> markerSet = new HashSet<>(markers.length);
+        for (Marker m : markers) {
+            if (markerSet.add(m)==false) {
+                throw new IllegalArgumentException("Duplicate marker: " + m);
+            }
+        }
+        return markerSet;
+    }
+
+    private static int[] cumSumAlleles(Marker[] markers) {
+        int[] ia = new int[markers.length + 1];
+        for (int j=1; j<ia.length; ++j) {
+            ia[j] = ia[j-1] + markers[j-1].nAlleles();
+        }
+        return ia;
+    }
+
+    private static int[] cumSumGenotypes(Marker[] markers) {
+        int[] ia = new int[markers.length + 1];
+        for (int j=1; j<ia.length; ++j) {
+            ia[j] = ia[j-1] + markers[j-1].nGenotypes();
+        }
+        return ia;
+    }
+
+    private static int[] cumSumHaplotypeBits(Marker[] markers) {
+        int[] ia = new int[markers.length + 1];
+        for (int j=1; j<ia.length; ++j) {
+            int nAllelesM1 = markers[j-1].nAlleles() - 1;
+            int nStorageBits = Integer.SIZE
+                    - Integer.numberOfLeadingZeros(nAllelesM1);
+            ia[j] = ia[j-1] + nStorageBits;
+        }
+        return ia;
+    }
+
+    /**
+     * Returns a hash code value for the object.
+     * The returned hash code equals
+     * {@code Arrays.deepHashCode(this.markers())}.
+     * @return a hash code value for the object
+     */
+    @Override
+    public int hashCode() {
+        return fwdHashCode;
+    }
+
+    /**
+     * Returns {@code true} if the specified object is a {@code Markers}
+     * instance which represents the same list of markers as {@code this},
+     * and returns {@code false} otherwise. Two lists of markers are
+     * the same if the lists have the same size and if markers with the
+     * same index in the two lists are equal.
+     *
+     * @param obj the object to be tested for equality with {@code this}
+     *
+     * @return {@code true} if the specified object is a {@code Markers}
+     * instance which represents the same list of markers as {@code this}
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this==obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final Markers other = (Markers) obj;
+        return Arrays.deepEquals(this.fwdMarkerArray, other.fwdMarkerArray);
+    }
+
+    /**
+     * Constructs and returns a new {@code Markers} instance that is
+     * obtained by reversing the order of markers in {@code this}.
+     * @return a new {@code Markers} instance that is obtained by
+     * reversing the order of markers in {@code this}
+     */
+    public Markers reverse() {
+        return bwdMarkers==null ? new Markers(this) : bwdMarkers;
+    }
+
+    /**
+     * Returns the number of markers.
+     * @return the number of markers
+     */
+    public int nMarkers() {
+        return fwdMarkerArray.length;
+    }
+
+    /**
+     * Returns the specified marker.
+     * @param marker a marker index
+     * @return the specified marker
+     * @throws IndexOutOfBoundsException if
+     * {@code marker < 0 || marker >= this.nMarkers()}
+     */
+    public Marker marker(int marker) {
+        return fwdMarkerArray[marker];
+    }
+
+    /**
+     * Returns the list of markers.
+     * @return the list of markers
+     */
+    public Marker[] markers() {
+        return fwdMarkerArray.clone();
+    }
+
+    /**
+     * Returns {@code true} if the specified marker is not {@code null}
+     * and is an element in the list of markers represented by {@code this},
+     * and returns {@code false} otherwise.
+     *
+     * @param marker a marker
+     *
+     * @return {@code true} if the specified marker is not {@code null} and
+     * is an element in the list of markers represented by {@code this}
+     */
+    public boolean contains(Marker marker) {
+        return markerSet.contains(marker);
+    }
+
+    /**
+     * Returns a {@code Markers} instance that represents
+     * the specified range of marker indices.
+     * @param start the starting marker index (inclusive)
+     * @param end the ending marker index (exclusive)
+     * @return a {@code Markers} instance that represents
+     * the specified range of marker indices
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code start < 0 || end > this.nMarkers()}
+     * @throws IllegalArgumentException if {@code start >= end}.
+     */
+    public Markers restrict(int start, int end) {
+        if (end > fwdMarkerArray.length) {
+            throw new IndexOutOfBoundsException("end > this.nMarkers(): " + end);
+        }
+        return new Markers(Arrays.copyOfRange(fwdMarkerArray, start, end));
+    }
+
+    /**
+     * Returns the sum of the number of alleles for
+     * the markers with index less than the specified index.
+     * @param marker a marker index
+     * @return the sum of the number of alleles for
+     * the markers with index less than the specified index
+     * @throws IndexOutOfBoundsException if
+     * {@code marker < 0 || marker > this.nMarkers()}
+     */
+    public int sumAlleles(int marker) {
+        return fwdSumAlleles[marker];
+    }
+
+    /**
+     * Returns {@code this.sumAlleles(this.nMarkers())}.
+     * @return {@code this.sumAlleles(this.nMarkers())}
+     */
+    public int sumAlleles() {
+        return fwdSumAlleles[fwdMarkerArray.length];
+    }
+
+    /**
+     * Returns the sum of the number of possible genotypes for the markers
+     * with index less than the specified index.
+     * @param marker a marker index
+     * @return the sum of the number of possible genotypes for the markers
+     * with index less than the specified index
+     * @throws IndexOutOfBoundsException if
+     * {@code marker < 0 || marker > this.nMarkers()}
+     */
+    public int sumGenotypes(int marker) {
+        return fwdSumGenotypes[marker];
+    }
+
+    /**
+     * Returns {@code this.sumGenotypes(this.nMarkers())}.
+     * @return {@code this.sumGenotypes(this.nMarkers())}
+     */
+    public int sumGenotypes() {
+        return fwdSumGenotypes[fwdMarkerArray.length];
+    }
+
+    /**
+     * Returns the number of bits requires to store a haplotype for the
+     * markers with index less than the specified index.
+     * @param marker a marker index
+     * @return the number of bits requires to store a haplotype for the
+     * markers with index less than the specified index
+     * @throws IndexOutOfBoundsException if
+     * {@code marker < 0 || marker > this.nMarkers()}
+     */
+    public int sumHaplotypeBits(int marker) {
+        return fwdSumHaplotypeBits[marker];
+    }
+
+    /**
+     * Returns {@code this.sumHaplotypeBits(this.nMarkers())}.
+     * @return {@code this.sumHaplotypeBits(this.nMarkers())}
+     */
+    public int sumHaplotypeBits() {
+        return fwdSumHaplotypeBits[fwdMarkerArray.length];
+    }
+
+    /**
+     * Returns a string representation of {@code this}.
+     * The exact details of the representation are unspecified and
+     * subject to change.
+     * @return a string representation of {@code this}
+     */
+    @Override
+    public String toString() {
+        return Arrays.toString(fwdMarkerArray);
+    }
+}
diff --git a/vcf/VcfWindow.java b/vcf/VcfWindow.java
index 4750286..8fc44f5 100644
--- a/vcf/VcfWindow.java
+++ b/vcf/VcfWindow.java
@@ -1,321 +1,321 @@
-/*
- * Copyright (C) 2014 Brian L. Browning
- *
- * This file is part of Beagle
- *
- * Beagle is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Beagle is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package vcf;
-
-import beagleutil.Samples;
-import blbutil.SampleFileIt;
-import java.io.Closeable;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import main.GeneticMap;
-
-/**
- * <p>Class {@code VcfWindow} represents a sliding window of VCF records.
- * </p>
- * Instances of class {@code VcfWindow} are not thread-safe.
- *
- * @author Brian L. Browning {@code <browning at uw.edu>}
- */
-public class VcfWindow implements Closeable {
-
-    private final SampleFileIt<? extends VcfEmission> it;
-    private final List<VcfEmission> window;
-    private int overlap;
-    private int cumMarkerCnt;
-    private VcfEmission next;
-
-    /**
-     * Constructs a new {@code VcfWindow} instance.
-     * @param it an iterator that returns VCF records
-     * @throws IllegalArgumentException if {@code it.hasNext() == false}
-     * @throws IllegalArgumentException if a format error is detected in
-     * a VCF record
-     * @throws NullPointerException if {@code it == null}
-     */
-    public VcfWindow(SampleFileIt<? extends VcfEmission> it) {
-        if (it.hasNext()==false) {
-            throw new IllegalArgumentException("it.hasNext()==false");
-        }
-        this.it = it;
-        this.overlap = 0;
-        this.cumMarkerCnt = 0;
-        this.window = new ArrayList<>(20000);
-        this.next = it.next();
-    }
-
-    /**
-     * Returns {@code true} if the sliding window of VCF Records is the last
-     * window for the chromosome and returns {@code false} otherwise.
-     * @return {@code true} if the sliding window of VCF Records is the last
-     * window for the chromosome
-     */
-    public boolean lastWindowOnChrom() {
-        return next==null || (sameChrom(next, window.get(0))==false);
-    }
-
-    private boolean sameChrom(VcfEmission a, VcfEmission b) {
-        return a.marker().chromIndex()==b.marker().chromIndex();
-    }
-
-    /**
-     * Returns {@code true} if the sliding window of VCF records can advance
-     * and returns {@code false} otherwise.
-     * @return {@code true} if the sliding window of VCF records can advance
-     */
-    public boolean canAdvanceWindow() {
-        return next!=null;
-    }
-
-    /**
-     * Advances the sliding window of VCF records, and returns the advanced
-     * window as a {@code VcfEmission[]} object.  The size of the advanced
-     * window and the number of markers of overlap between the marker window
-     * immediately before method invocation and the marker window immediately
-     * after method invocation may differ from the requested values.  If the
-     * advanced window size or overlap is less than the requested value, the
-     * actual value will be as large as possible. If
-     * {@code this.lastWindowOnChrom() == true} before method invocation, then
-     * there will be no overlap between the advanced window and the previous
-     * window.
-     *
-     * @param overlap the requested number of markers of overlap
-     * @param windowSize the requested number of the markers in the window
-     * immediately after the method returns
-     * @return the advanced window of VCF records
-     *
-     * @throws IllegalArgumentException if a format error is detected in
-     * a VCF record
-     * @throws IllegalArgumentException if
-     * {@code overlap < 0 || overlap >= windowSize}
-     * @throws IllegalStateException if
-     * {@code this.canAdvanceWindow() == false}
-     */
-    public VcfEmission[] advanceWindow(int overlap, int windowSize) {
-        if (canAdvanceWindow()==false) {
-            throw new IllegalStateException("canAdvanceWindow()==false");
-        }
-        checkParameters(overlap, windowSize);
-        overlap = getActualOverlap(overlap);
-        List<VcfEmission> newWindow = new ArrayList<>(windowSize);
-
-        newWindow.addAll(window.subList(window.size() - overlap, window.size()));
-        int currentChromIndex = currentChromIndex(newWindow);
-        while (newWindow.size() < windowSize
-                && next != null
-                && next.marker().chromIndex()==currentChromIndex) {
-            newWindow.add(next);
-            next = it.hasNext() ? it.next() : null;
-        }
-        // add all markers at the same marker position
-        VcfEmission last = newWindow.get(newWindow.size()-1);
-        while (next!=null && samePosition(last, next)) {
-            newWindow.add(next);
-            next = it.hasNext() ? it.next() : null;
-        }
-        this.overlap = overlap;
-        this.window.clear();
-        this.window.addAll(newWindow);
-        this.cumMarkerCnt += (window.size() - overlap);
-        return window.toArray(new VcfEmission[0]);
-    }
-
-    /**
-     * Advances the sliding window of VCF records, and returns the advanced
-     * window as a {@code VcfEmission[]} object.  The size of the advanced
-     * window and the number of markers of overlap between the marker window
-     * immediately before method invocation and the marker window immediately
-     * after method invocation may differ from the requested values.  If the
-     * distance the window is advanced or the overlap is less than the requested
-     * value, the actual distance or overlap will be as large as possible. If
-     * {@code this.lastWindowOnChrom() == true}
-     * before method invocation, then there will be no overlap between the
-     * advanced window and the previous window
-     *
-     * @param overlap the requested number of markers of overlap
-     * @param cM the requested distance in cM to advance the window
-     * @param map the genetic map
-     * @return the advanced window of VCF records
-     *
-     * @throws IllegalArgumentException if a format error is detected in
-     * a VCF record
-     * @throws IllegalArgumentException if {@code overlap < 0 || cM <= 0}
-     * @throws IllegalStateException if
-     * {@code this.canAdvanceWindow() == false}
-     */
-    public VcfEmission[] advanceWindow(int overlap, double cM, GeneticMap map) {
-        if (canAdvanceWindow()==false) {
-            throw new IllegalStateException("canAdvanceWindow()==false");
-        }
-        if (overlap < 0) {
-            throw new IllegalArgumentException(String.valueOf(overlap));
-        }
-        if (cM < 0) {
-            throw new IllegalArgumentException(String.valueOf(cM));
-        }
-
-        overlap = getActualOverlap(overlap);
-        List<VcfEmission> newWindow = new ArrayList<>(overlap + 1000);
-
-        newWindow.addAll(window.subList(window.size() - overlap, window.size()));
-        int currentChromIndex = currentChromIndex(newWindow);
-        double endMapPos = startMapPos(newWindow, map) + cM;
-        while (next != null
-                && next.marker().chromIndex()==currentChromIndex
-                && map.genPos(next.marker()) < endMapPos) {
-            newWindow.add(next);
-            next = it.hasNext() ? it.next() : null;
-        }
-        // add all markers at the same marker position
-        VcfEmission last = newWindow.get(newWindow.size()-1);
-        while (next!=null && samePosition(last, next)) {
-            newWindow.add(next);
-            next = it.hasNext() ? it.next() : null;
-        }
-        this.overlap = overlap;
-        this.window.clear();
-        this.window.addAll(newWindow);
-        this.cumMarkerCnt += (window.size() - overlap);
-        return window.toArray(new VcfEmission[0]);
-    }
-
-    private void checkParameters(int overlap, int windowSize) {
-        if (overlap < 0 || overlap >= windowSize) {
-            String s = "overlap=" + overlap + "windowSize=" + windowSize;
-            throw new IllegalArgumentException(s);
-        }
-    }
-
-    private int getActualOverlap(int overlap) {
-        if (window.isEmpty() || lastWindowOnChrom()) {
-            return 0;
-        }
-        int n = window.size();
-        if (overlap > n) {
-            overlap = n;
-        }
-        while (overlap > 0 && overlap < n
-                && window.get(n - overlap).marker().pos()
-                    == window.get(n - overlap - 1).marker().pos()) {
-            ++overlap;
-        }
-        return overlap;
-    }
-
-    private int currentChromIndex(List<VcfEmission> currentWindow) {
-        if (currentWindow.isEmpty()==false) {
-            return currentWindow.get(0).marker().chromIndex();
-        }
-        else if (next!=null) {
-            return next.marker().chromIndex();
-        }
-        else {
-            return -1;
-        }
-    }
-
-    private double startMapPos(List<VcfEmission> currentWindow, GeneticMap map) {
-        if (currentWindow.isEmpty()==false) {
-            Marker m = currentWindow.get(currentWindow.size() - 1).marker();
-            return map.genPos(m);
-        }
-        else if (next!=null) {
-            return map.genPos(next.marker());
-        }
-        else {
-            return 0;
-        }
-    }
-
-    private boolean samePosition(VcfEmission a, VcfEmission b) {
-        return a.marker().chromIndex()==b.marker().chromIndex()
-                && a.marker().pos()==b.marker().pos();
-    }
-
-    /**
-     * Returns the file from which VCF records are read, or returns
-     * {@code null} if the source is standard input.
-     * @return the file from which VCF records are read, or
-     * {@code null} if the source is standard input
-     */
-    public File file() {
-        return it.file();
-    }
-
-    /**
-     * Returns the list of samples.
-     * @return the list of samples
-     */
-    public Samples samples() {
-        return it.samples();
-    }
-
-    /**
-     * Returns the number of samples.
-     * @return the number of samples
-     */
-    public int nSamples() {
-        return  it.samples().nSamples();
-    }
-
-    /**
-     * Returns the number of VCF records in the overlap between the current
-     * window and the previous window.  Returns 0 if the current window
-     * is the first window.
-     *
-     * @return the number of VCF records in the overlap between the current
-     * window and the previous window
-     */
-    public int overlap() {
-        return overlap;
-    }
-
-    /**
-     * Returns the number of distinct VCF records in the union of the current
-     * window and all previous windows.
-     *
-     * @return the number of distinct VCF records in the union of the current
-     * window and all previous windows
-     */
-    public int cumMarkerCnt() {
-        return cumMarkerCnt;
-    }
-
-    /**
-     * Releases any I/O resources controlled by this object.
-     */
-    @Override
-    public void close() {
-        it.close();
-    }
-
-    /**
-     * Returns a string representation of {@code this}.  The exact
-     * details of the representation are unspecified and subject to change.
-     * @return a string representation of {@code this}
-     */
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder(1100);
-        sb.append(this.getClass().toString());
-        sb.append("; next: ");
-        sb.append(next);
-        return sb.toString();
-    }
-}
+/*
+ * Copyright (C) 2014 Brian L. Browning
+ *
+ * This file is part of Beagle
+ *
+ * Beagle is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Beagle is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package vcf;
+
+import beagleutil.Samples;
+import blbutil.SampleFileIt;
+import java.io.Closeable;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import main.GeneticMap;
+
+/**
+ * <p>Class {@code VcfWindow} represents a sliding window of VCF records.
+ * </p>
+ * Instances of class {@code VcfWindow} are not thread-safe.
+ *
+ * @author Brian L. Browning {@code <browning at uw.edu>}
+ */
+public class VcfWindow implements Closeable {
+
+    private final SampleFileIt<? extends VcfEmission> it;
+    private final List<VcfEmission> window;
+    private int overlap;
+    private int cumMarkerCnt;
+    private VcfEmission next;
+
+    /**
+     * Constructs a new {@code VcfWindow} instance.
+     * @param it an iterator that returns VCF records
+     * @throws IllegalArgumentException if {@code it.hasNext() == false}
+     * @throws IllegalArgumentException if a format error is detected in
+     * a VCF record
+     * @throws NullPointerException if {@code it == null}
+     */
+    public VcfWindow(SampleFileIt<? extends VcfEmission> it) {
+        if (it.hasNext()==false) {
+            throw new IllegalArgumentException("it.hasNext()==false");
+        }
+        this.it = it;
+        this.overlap = 0;
+        this.cumMarkerCnt = 0;
+        this.window = new ArrayList<>(20000);
+        this.next = it.next();
+    }
+
+    /**
+     * Returns {@code true} if the sliding window of VCF Records is the last
+     * window for the chromosome and returns {@code false} otherwise.
+     * @return {@code true} if the sliding window of VCF Records is the last
+     * window for the chromosome
+     */
+    public boolean lastWindowOnChrom() {
+        return next==null || (sameChrom(next, window.get(0))==false);
+    }
+
+    private boolean sameChrom(VcfEmission a, VcfEmission b) {
+        return a.marker().chromIndex()==b.marker().chromIndex();
+    }
+
+    /**
+     * Returns {@code true} if the sliding window of VCF records can advance
+     * and returns {@code false} otherwise.
+     * @return {@code true} if the sliding window of VCF records can advance
+     */
+    public boolean canAdvanceWindow() {
+        return next!=null;
+    }
+
+    /**
+     * Advances the sliding window of VCF records, and returns the advanced
+     * window as a {@code VcfEmission[]} object.  The size of the advanced
+     * window and the number of markers of overlap between the marker window
+     * immediately before method invocation and the marker window immediately
+     * after method invocation may differ from the requested values.  If the
+     * advanced window size or overlap is less than the requested value, the
+     * actual value will be as large as possible. If
+     * {@code this.lastWindowOnChrom() == true} before method invocation, then
+     * there will be no overlap between the advanced window and the previous
+     * window.
+     *
+     * @param overlap the requested number of markers of overlap
+     * @param windowSize the requested number of the markers in the window
+     * immediately after the method returns
+     * @return the advanced window of VCF records
+     *
+     * @throws IllegalArgumentException if a format error is detected in
+     * a VCF record
+     * @throws IllegalArgumentException if
+     * {@code overlap < 0 || overlap >= windowSize}
+     * @throws IllegalStateException if
+     * {@code this.canAdvanceWindow() == false}
+     */
+    public VcfEmission[] advanceWindow(int overlap, int windowSize) {
+        if (canAdvanceWindow()==false) {
+            throw new IllegalStateException("canAdvanceWindow()==false");
+        }
+        checkParameters(overlap, windowSize);
+        overlap = getActualOverlap(overlap);
+        List<VcfEmission> newWindow = new ArrayList<>(windowSize);
+
+        newWindow.addAll(window.subList(window.size() - overlap, window.size()));
+        int currentChromIndex = currentChromIndex(newWindow);
+        while (newWindow.size() < windowSize
+                && next != null
+                && next.marker().chromIndex()==currentChromIndex) {
+            newWindow.add(next);
+            next = it.hasNext() ? it.next() : null;
+        }
+        // add all markers at the same marker position
+        VcfEmission last = newWindow.get(newWindow.size()-1);
+        while (next!=null && samePosition(last, next)) {
+            newWindow.add(next);
+            next = it.hasNext() ? it.next() : null;
+        }
+        this.overlap = overlap;
+        this.window.clear();
+        this.window.addAll(newWindow);
+        this.cumMarkerCnt += (window.size() - overlap);
+        return window.toArray(new VcfEmission[0]);
+    }
+
+    /**
+     * Advances the sliding window of VCF records, and returns the advanced
+     * window as a {@code VcfEmission[]} object.  The size of the advanced
+     * window and the number of markers of overlap between the marker window
+     * immediately before method invocation and the marker window immediately
+     * after method invocation may differ from the requested values.  If the
+     * distance the window is advanced or the overlap is less than the requested
+     * value, the actual distance or overlap will be as large as possible. If
+     * {@code this.lastWindowOnChrom() == true}
+     * before method invocation, then there will be no overlap between the
+     * advanced window and the previous window
+     *
+     * @param overlap the requested number of markers of overlap
+     * @param cM the requested distance in cM to advance the window
+     * @param map the genetic map
+     * @return the advanced window of VCF records
+     *
+     * @throws IllegalArgumentException if a format error is detected in
+     * a VCF record
+     * @throws IllegalArgumentException if {@code overlap < 0 || cM <= 0}
+     * @throws IllegalStateException if
+     * {@code this.canAdvanceWindow() == false}
+     */
+    public VcfEmission[] advanceWindow(int overlap, double cM, GeneticMap map) {
+        if (canAdvanceWindow()==false) {
+            throw new IllegalStateException("canAdvanceWindow()==false");
+        }
+        if (overlap < 0) {
+            throw new IllegalArgumentException(String.valueOf(overlap));
+        }
+        if (cM < 0) {
+            throw new IllegalArgumentException(String.valueOf(cM));
+        }
+
+        overlap = getActualOverlap(overlap);
+        List<VcfEmission> newWindow = new ArrayList<>(overlap + 1000);
+
+        newWindow.addAll(window.subList(window.size() - overlap, window.size()));
+        int currentChromIndex = currentChromIndex(newWindow);
+        double endMapPos = startMapPos(newWindow, map) + cM;
+        while (next != null
+                && next.marker().chromIndex()==currentChromIndex
+                && map.genPos(next.marker()) < endMapPos) {
+            newWindow.add(next);
+            next = it.hasNext() ? it.next() : null;
+        }
+        // add all markers at the same marker position
+        VcfEmission last = newWindow.get(newWindow.size()-1);
+        while (next!=null && samePosition(last, next)) {
+            newWindow.add(next);
+            next = it.hasNext() ? it.next() : null;
+        }
+        this.overlap = overlap;
+        this.window.clear();
+        this.window.addAll(newWindow);
+        this.cumMarkerCnt += (window.size() - overlap);
+        return window.toArray(new VcfEmission[0]);
+    }
+
+    private void checkParameters(int overlap, int windowSize) {
+        if (overlap < 0 || overlap >= windowSize) {
+            String s = "overlap=" + overlap + "windowSize=" + windowSize;
+            throw new IllegalArgumentException(s);
+        }
+    }
+
+    private int getActualOverlap(int overlap) {
+        if (window.isEmpty() || lastWindowOnChrom()) {
+            return 0;
+        }
+        int n = window.size();
+        if (overlap > n) {
+            overlap = n;
+        }
+        while (overlap > 0 && overlap < n
+                && window.get(n - overlap).marker().pos()
+                    == window.get(n - overlap - 1).marker().pos()) {
+            ++overlap;
+        }
+        return overlap;
+    }
+
+    private int currentChromIndex(List<VcfEmission> currentWindow) {
+        if (currentWindow.isEmpty()==false) {
+            return currentWindow.get(0).marker().chromIndex();
+        }
+        else if (next!=null) {
+            return next.marker().chromIndex();
+        }
+        else {
+            return -1;
+        }
+    }
+
+    private double startMapPos(List<VcfEmission> currentWindow, GeneticMap map) {
+        if (currentWindow.isEmpty()==false) {
+            Marker m = currentWindow.get(currentWindow.size() - 1).marker();
+            return map.genPos(m);
+        }
+        else if (next!=null) {
+            return map.genPos(next.marker());
+        }
+        else {
+            return 0;
+        }
+    }
+
+    private boolean samePosition(VcfEmission a, VcfEmission b) {
+        return a.marker().chromIndex()==b.marker().chromIndex()
+                && a.marker().pos()==b.marker().pos();
+    }
+
+    /**
+     * Returns the file from which VCF records are read, or returns
+     * {@code null} if the source is standard input.
+     * @return the file from which VCF records are read, or
+     * {@code null} if the source is standard input
+     */
+    public File file() {
+        return it.file();
+    }
+
+    /**
+     * Returns the list of samples.
+     * @return the list of samples
+     */
+    public Samples samples() {
+        return it.samples();
+    }
+
+    /**
+     * Returns the number of samples.
+     * @return the number of samples
+     */
+    public int nSamples() {
+        return  it.samples().nSamples();
+    }
+
+    /**
+     * Returns the number of VCF records in the overlap between the current
+     * window and the previous window.  Returns 0 if the current window
+     * is the first window.
+     *
+     * @return the number of VCF records in the overlap between the current
+     * window and the previous window
+     */
+    public int overlap() {
+        return overlap;
+    }
+
+    /**
+     * Returns the number of distinct VCF records in the union of the current
+     * window and all previous windows.
+     *
+     * @return the number of distinct VCF records in the union of the current
+     * window and all previous windows
+     */
+    public int cumMarkerCnt() {
+        return cumMarkerCnt;
+    }
+
+    /**
+     * Releases any I/O resources controlled by this object.
+     */
+    @Override
+    public void close() {
+        it.close();
+    }
+
+    /**
+     * Returns a string representation of {@code this}.  The exact
+     * details of the representation are unspecified and subject to change.
+     * @return a string representation of {@code this}
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(1100);
+        sb.append(this.getClass().toString());
+        sb.append("; next: ");
+        sb.append(next);
+        return sb.toString();
+    }
+}
diff --git a/vcf/VcfWriter.java b/vcf/VcfWriter.java
index 7df50e2..5c6fe53 100644
--- a/vcf/VcfWriter.java
+++ b/vcf/VcfWriter.java
@@ -26,6 +26,7 @@ import java.text.DecimalFormat;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import main.AlleleProbs;
+import main.ConstrainedAlleleProbs;
 import main.GenotypeValues;
 
 /**
@@ -43,18 +44,21 @@ public final class VcfWriter {
     private static final DecimalFormat df2 = new DecimalFormat("#.##");
     private static final DecimalFormat df3 = new DecimalFormat("#.###");
     private static final DecimalFormat df2_fixed = new DecimalFormat("0.00");
-    private static final MathContext mathContext2 = new MathContext(2);
+    private static final MathContext mc2 = new MathContext(2);
+    private static final BigDecimal ONE = new BigDecimal(1.0);
 
     private static final String fileformat = "##fileformat=VCFv4.2";
 
     private static final String afInfo = "##INFO=<ID=AF,Number=A,Type=Float,"
-            + "Description=\"Estimated Allele Frequencies\">";
+            + "Description=\"Estimated ALT Allele Frequencies\">";
     private static final String ar2Info = "##INFO=<ID=AR2,Number=1,Type=Float,"
             + "Description=\"Allelic R-Squared: estimated squared correlation between "
             + "most probable REF dose and true REF dose\">";
     private static final String dr2Info = "##INFO=<ID=DR2,Number=1,Type=Float,"
             + "Description=\"Dosage R-Squared: estimated squared correlation between "
             + "estimated REF dose [P(RA) + 2*P(RR)] and true REF dose\">";
+    private static final String impInfo = "##INFO=<ID=IMP,Number=1,Type=Flag,"
+            + "Description=\"Imputed marker\">";
 
     private static final String gtFormat = "##FORMAT=<ID=GT,Number=1,Type=String,"
             + "Description=\"Genotype\">";
@@ -134,6 +138,7 @@ public final class VcfWriter {
             out.println(afInfo);
             out.println(ar2Info);
             out.println(dr2Info);
+            out.println(impInfo);
         }
         if (printGT) {
             out.println(gtFormat);
@@ -238,8 +243,6 @@ public final class VcfWriter {
      * @param alProbs the sample haplotype pairs
      * @param start the starting marker index (inclusive)
      * @param end the ending marker index (exclusive)
-     * @param imputed {@code true} if there are imputed markers,
-     * and {@code false} otherwise
      * @param gprobs {@code true} if the GP field should be printed, and
      * {@code false} otherwise.
      * @param out the {@code PrintWriter} to which VCF records will
@@ -249,30 +252,68 @@ public final class VcfWriter {
      * {@code (start < 0 || start > end || end > alProbs.nMarkers())}
      * @throws NullPointerException if {@code haps == null || out == null}
      */
-    public static void appendRecords(AlleleProbs alProbs, int start, int end,
-            boolean imputed, boolean gprobs, PrintWriter out) {
+    public static void appendRecords(ConstrainedAlleleProbs alProbs,
+            int start, int end, boolean gprobs, PrintWriter out) {
         if (start > end) {
             throw new IllegalArgumentException("start=" + start + " end=" + end);
         }
-        for (int marker=start; marker<end; ++marker) {
-            printFixedFields(alProbs, marker, imputed, gprobs, out);
+        boolean ds = true;
+        String format = format(ds, gprobs);
+        for (int m=start; m<end; ++m) {
+            Marker marker = alProbs.marker(m);
+            boolean isImputed = alProbs.isImputed(m);
+            GprobsStatistics gps = new GprobsStatistics(alProbs, m);
+            String info = info(gps, isImputed);
+            printFixedFields(marker, info, format, out);
             for (int sample=0, n=alProbs.nSamples(); sample<n; ++sample) {
-                printGTandDose(alProbs, marker, sample, imputed, out);
+                printGTandDose(alProbs, m, sample, ds, out);
                 if (gprobs) {
-                     printGP(alProbs, marker, sample, out);
+                     printGP(alProbs, m, sample, out);
                 }
             }
             out.println();
         }
     }
 
+    /**
+     * Writes the specified genotype data as VCF records to the specified
+     * {@code PrintWriter}.
+     * @param alProbs the sample haplotype pairs
+     * @param start the starting marker index (inclusive)
+     * @param end the ending marker index (exclusive)
+     * @param out the {@code PrintWriter} to which VCF records will
+     * be written
+     *
+     * @throws IndexOutOfBoundsException if
+     * {@code (start < 0 || start > end || end > alProbs.nMarkers())}
+     * @throws NullPointerException if {@code haps == null || out == null}
+     */
+    public static void appendRecords(AlleleProbs alProbs, int start, int end,
+            PrintWriter out) {
+        boolean ds = false;
+        boolean gp = false;
+        String info = Const.MISSING_DATA_STRING;
+        String format = format(ds, gp);
+        if (start > end) {
+            throw new IllegalArgumentException("start=" + start + " end=" + end);
+        }
+        for (int m=start; m<end; ++m) {
+            Marker marker = alProbs.marker(m);
+            printFixedFields(marker, info, format, out);
+            for (int sample=0, n=alProbs.nSamples(); sample<n; ++sample) {
+                printGTandDose(alProbs, m, sample, ds, out);
+            }
+            out.println();
+        }
+    }
+
     private static void printGTandDose(AlleleProbs alProbs, int marker, int
-            sample, boolean imputed, PrintWriter out) {
+            sample, boolean ds, PrintWriter out) {
         out.print(Const.tab);
         out.print(alProbs.allele1(marker, sample));
         out.append(Const.phasedSep);
         out.print(alProbs.allele2(marker, sample));
-        if (imputed) {
+        if (ds) {
             int nAlleles = alProbs.marker(marker).nAlleles();
             for (int j = 1; j < nAlleles; ++j) {
                 float p1 = alProbs.alProb1(marker, sample, j);
@@ -324,7 +365,7 @@ public final class VcfWriter {
     private static void printFixedFields(GenotypeValues gv, int marker,
             PrintWriter out) {
         GprobsStatistics gpm = new GprobsStatistics(gv, marker);
-        float[] alleleFreq = gpm.alleleFreq();
+        double[] alleleFreq = gpm.alleleFreq();
         out.print(gv.marker(marker));
         out.print(Const.tab);
         out.print(Const.MISSING_DATA_CHAR); // QUAL
@@ -337,44 +378,65 @@ public final class VcfWriter {
         out.print(df2_fixed.format(gpm.doseR2()));
         for (int j=1; j<alleleFreq.length; ++j) {
             out.print( (j==1) ? ";AF=" : Const.comma);
-            BigDecimal bd = new BigDecimal(alleleFreq[j]).round(mathContext2);
-            out.print(bd.doubleValue());
+            out.print(formatProb(alleleFreq[j]));
         }
         out.print(Const.tab);
         out.print("GT:DS:GP");
     }
 
-    private static void printFixedFields(AlleleProbs alProbs,
-            int marker, boolean printR2, boolean gprobs, PrintWriter out) {
-        GprobsStatistics gpm = new GprobsStatistics(alProbs, marker);
-        float[] alleleFreq = gpm.alleleFreq();
-        out.print(alProbs.marker(marker));
-        out.print(Const.tab);
-        out.print(Const.MISSING_DATA_CHAR); // QUAL
-        out.print(Const.tab);
-        out.print(PASS);                    // FILTER
-        if (printR2) {
-            out.print(Const.tab);
-            out.print("AR2=");                  // INFO
-            out.print(df2_fixed.format(gpm.allelicR2()));
-            out.print(";DR2=");
-            out.print(df2_fixed.format(gpm.doseR2()));
-            for (int j=1; j<alleleFreq.length; ++j) {
-                out.print( (j==1) ? ";AF=" : Const.comma);
-                BigDecimal bd = new BigDecimal(alleleFreq[j]).round(mathContext2);
-                out.print(bd.doubleValue());
+    private static String info(GprobsStatistics gps, boolean isImputed) {
+        double[] alleleFreq = gps.alleleFreq();
+        StringBuilder sb = new StringBuilder(20);
+        sb.append("AR2=");                  // INFO
+        sb.append(df2_fixed.format(gps.allelicR2()));
+        sb.append(";DR2=");
+        sb.append(df2_fixed.format(gps.doseR2()));
+        for (int j=1; j<alleleFreq.length; ++j) {
+            sb.append( (j==1) ? ";AF=" : Const.comma);
+            sb.append(formatProb(alleleFreq[j]));
+        }
+        if (isImputed) {
+            sb.append(";IMP");
+        }
+        return sb.toString();
+    }
+
+    private static String format(boolean ds, boolean gp) {
+        if (ds) {
+            if (gp) {
+                return "GT:DS:GP";
+            }
+            else {
+                return "GT:DS";
             }
         }
         else {
-            out.print(Const.tab);
-            out.print(Const.MISSING_DATA_CHAR);
+            return "GT";
         }
+    }
+
+    private static void printFixedFields(Marker marker, String info,
+            String format, PrintWriter out) {
+        out.print(marker);
+        out.print(Const.tab);
+        out.print(Const.MISSING_DATA_CHAR); // QUAL
         out.print(Const.tab);
-        if (printR2) {
-            out.print(gprobs ? "GT:DS:GP" : "GT:DS");
+        out.print(PASS);                    // FILTER
+        out.print(Const.tab);
+        out.print(info);
+        out.print(Const.tab);
+        out.print(format);
+    }
+
+    private static String formatProb(double d) {
+        if (d>=0 && d <= 0.5) {
+            return new BigDecimal(d).round(mc2).toPlainString();
+        }
+        else if (d <= 1.0) {
+            return new BigDecimal(d-1.0).round(mc2).add(ONE).toString();
         }
         else {
-            out.print("GT");
+            throw new IllegalArgumentException(String.valueOf(d));
         }
     }
 }

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/beagle.git



More information about the debian-med-commit mailing list