Repository: mitre/menelaus (language: python)

Issue:
Make streaming and batch abstract classes
kdq-tree has a lot of weirdness due to handling both streaming and batch, e.g. in the validation: right now, the dimensions of new numpy arrays aren't checked. We could probably add checks for the length of `self.input_cols` vs the shape of a numpy array. This still doesn't seem ideal, but it's definitely break-able as it is.

Rather than continuing to fool with the complications introduced by the detector taking both streaming and batch input, we should probably have parallel classes that have dependency on the same partitioner/similar. This seems like a better structure going forward, as we add more detectors that can do both. The further we get into implementing validation and similar, the more troublesome these problems are.


Discussion / hints:

Fix patch:
diff --git a/docs/source/examples/data_drift/data_drift_examples.ipynb b/docs/source/examples/data_drift/data_drift_examples.ipynb
index 0fe62b2d..9ebd7b9e 100644
--- a/docs/source/examples/data_drift/data_drift_examples.ipynb
+++ b/docs/source/examples/data_drift/data_drift_examples.ipynb
@@ -24,7 +24,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -39,13 +39,13 @@
     "from menelaus.data_drift.cdbd import CDBD\n",
     "from menelaus.data_drift.hdddm import HDDDM\n",
     "from menelaus.data_drift import PCACD\n",
-    "from menelaus.data_drift import KdqTree\n",
+    "from menelaus.data_drift import KdqTreeStreaming, KdqTreeBatch\n",
     "from menelaus.datasets import make_example_batch_data"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -517,7 +517,7 @@
     "# Note that the default input_type for KDQTree is \"stream\".\n",
     "# The window size, corresponding to the portion of the stream which KDQTree\n",
     "# monitors, must be specified.\n",
-    "det = KdqTree(window_size=500, alpha=0.05, bootstrap_samples=500, count_ubound=50)\n",
+    "det = KdqTreeStreaming(window_size=500, alpha=0.05, bootstrap_samples=500, count_ubound=50)\n",
     "\n",
     "# setup DF to record results\n",
     "status = pd.DataFrame(columns=[\"index\", \"var1\", \"var2\", \"drift_detected\"])\n",
@@ -536,7 +536,7 @@
     "\n",
     "plot_data = {}\n",
     "for i in range(len(circle_data)):\n",
-    "    det.update(data.iloc[[i]].values)\n",
+    "    det.update(data.iloc[[i]])\n",
     "    status.loc[i] = [i, data.iloc[i, 0], data.iloc[i, 1], det.drift_state]\n",
     "    if det.drift_state is not None:\n",
     "        # capture the visualization data\n",
@@ -567,8 +567,8 @@
     ")\n",
     "\n",
     "plt.legend()\n",
-    "\n",
-    "\n"
+    "plt.show()\n",
+    "# plt.savefig(\"example_streaming_kdqtree_feature_stream.png\")\n"
    ]
   },
   {
@@ -579,16 +579,6 @@
     "a change after about half of the data within its window is in the new regime.\n"
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "plt.show()\n",
-    "# plt.savefig(\"example_streaming_kdqtree_feature_stream.png\")"
-   ]
-  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -677,7 +667,7 @@
     "\n",
     "plot_data = {}\n",
     "status = pd.DataFrame(columns=[\"year\", \"drift\"])\n",
-    "det = KdqTree(input_type=\"batch\")\n",
+    "det = KdqTreeBatch()\n",
     "\n",
     "# Set up reference batch, using 2007 as reference year\n",
     "det.set_reference(df[df.year == 2007].drop(columns=['year']))"
diff --git a/examples/batch/kdqtree_batch_example.py b/examples/batch/kdqtree_batch_example.py
index be8f1c72..f5e3f6f5 100644
--- a/examples/batch/kdqtree_batch_example.py
+++ b/examples/batch/kdqtree_batch_example.py
@@ -20,7 +20,7 @@
 import numpy as np
 import plotly.express as px
 
-from menelaus.data_drift.kdq_tree import KdqTree
+from menelaus.data_drift.kdq_tree import KdqTreeBatch
 from menelaus.datasets import make_example_batch_data
 
 # kdq-Tree does use bootstrapping to define its critical thresholds, so setting
@@ -43,7 +43,7 @@
 
 plot_data = {}
 status = pd.DataFrame(columns=["year", "drift"])
-det = KdqTree(input_type="batch")
+det = KdqTreeBatch()
 
 # Set up reference batch, using 2007 as reference year
 det.set_reference(df[df.year == 2007].drop(columns=['year']))
diff --git a/examples/streaming/change_detection_example.py b/examples/streaming/change_detection_example.py
index 757e3c59..7d48520c 100644
--- a/examples/streaming/change_detection_example.py
+++ b/examples/streaming/change_detection_example.py
@@ -27,7 +27,7 @@
 
 
 # read in Circle dataset
-data_path = os.path.join("..", "..", "src", "menelaus", "tools", "artifacts", "dataCircleGSev3Sp3Train.csv")
+data_path = os.path.join("..", "..", "src", "menelaus", "datasets", "dataCircleGSev3Sp3Train.csv")
 df = pd.read_csv(data_path, usecols=[0, 1, 2], names=["var1", "var2", "y"])
 
 drift_start, drift_end = 1000, 1250
diff --git a/examples/streaming/concept_drift_example.py b/examples/streaming/concept_drift_example.py
index 18b251d2..604aad99 100644
--- a/examples/streaming/concept_drift_example.py
+++ b/examples/streaming/concept_drift_example.py
@@ -40,7 +40,7 @@
 
 
 # read in Circle dataset
-data_path = os.path.join("..", "..", "src", "menelaus", "tools", "artifacts", "dataCircleGSev3Sp3Train.csv")
+data_path = os.path.join("..", "..", "src", "menelaus", "datasets", "dataCircleGSev3Sp3Train.csv")
 df = pd.read_csv(data_path, usecols=[0, 1, 2], names=["var1", "var2", "y"])
 
 drift_start, drift_end = 1000, 1250
diff --git a/examples/streaming/data_drift_example.py b/examples/streaming/data_drift_example.py
index 1eb54f58..b658f5a2 100644
--- a/examples/streaming/data_drift_example.py
+++ b/examples/streaming/data_drift_example.py
@@ -24,11 +24,11 @@
 import numpy as np
 import matplotlib.pyplot as plt
 import plotly.express as px
-from menelaus.data_drift import PCACD, KdqTree
+from menelaus.data_drift import PCACD, KdqTreeStreaming
 
 
 # read in Circle dataset
-data_path = os.path.join('..', '..', 'src', 'menelaus', 'tools', 'artifacts', 'dataCircleGSev3Sp3Train.csv')
+data_path = os.path.join('..', '..', 'src', 'menelaus', 'datasets', 'dataCircleGSev3Sp3Train.csv')
 df = pd.read_csv(data_path, usecols=[0, 1, 2], names=["var1", "var2", "y"])
 
 ################################################################################
@@ -121,7 +121,7 @@
 # Note that the default input_type for KDQTree is "stream".
 # The window size, corresponding to the portion of the stream which KDQTree
 # monitors, must be specified.
-det = KdqTree(window_size=500, alpha=0.05, bootstrap_samples=500, count_ubound=50)
+det = KdqTreeStreaming(window_size=500, alpha=0.05, bootstrap_samples=500, count_ubound=50)
 
 # setup DF to record results
 status = pd.DataFrame(columns=["index", "var1", "var2", "drift_detected"])
diff --git a/src/menelaus/data_drift/__init__.py b/src/menelaus/data_drift/__init__.py
index 63246176..099411b7 100644
--- a/src/menelaus/data_drift/__init__.py
+++ b/src/menelaus/data_drift/__init__.py
@@ -14,5 +14,5 @@
 applicable in unsupervised learning settings.
 """
 from menelaus.data_drift.hdddm import HDDDM
-from menelaus.data_drift.kdq_tree import KdqTree
+from menelaus.data_drift.kdq_tree import KdqTreeStreaming, KdqTreeBatch
 from menelaus.data_drift.pca_cd import PCACD
diff --git a/src/menelaus/data_drift/kdq_tree.py b/src/menelaus/data_drift/kdq_tree.py
index ca88c9bf..1e7b5f83 100644
--- a/src/menelaus/data_drift/kdq_tree.py
+++ b/src/menelaus/data_drift/kdq_tree.py
@@ -1,40 +1,45 @@
 import numpy as np
+from numpy import unique
 import pandas as pd
+from pandas import DataFrame
 import scipy.stats
-from pandas import DataFrame  # more efficient for later for loop
-from numpy import unique
-from menelaus.drift_detector import DriftDetector
+
+from menelaus.drift_detector import StreamingDetector, BatchDetector
 from menelaus.partitioners.KDQTreePartitioner import KDQTreePartitioner
 
 
-class KdqTree(DriftDetector):
-    """kdqTree is a drift detection algorithm which detects drift via the
+class KdqTreeDetector:
+    """
+    Parent class for kdqTree-based drift detector classes. Whether
+    reliant on streaming or batch data, kdqTree detectors have some
+    common attributes, logic, and functions.
+
+    kdqTree is a drift detection algorithm which detects drift via the
     Kullback-Leibler divergence, calculated after partitioning the data space
     via constructing a k-d-quad-tree (kdq-tree). A reference window of initial
     data is compared to a test window of later data. The Kullback-Leibler
     divergence between the empirical distributions of the reference and test
     windows is calculated, and drift is alarmed when a threshold is reached.
-
     A kdqtree is a combination of k-d trees and quad-trees; it is a binary tree
     (k-d) whose nodes contain square cells (quad) which are created via
     sequential splits along each dimension. This structure allows the
     calculation of the K-L divergence for continuous distributions, as the K-L
-    divergence is defined on prwobability mass functions. The number of samples
+    divergence is defined on probability mass functions. The number of samples
     in each leaf of the tree is an empirical distribution for either dataset;
     this allows us to calculate the K-L divergence.
 
     If used in a streaming data setting, the reference window is used to
     construct a kdq-tree, and the data in both the reference and test window are
-    filed into it. If used in a batch data setting, the reference window, the
-    first batch passed in, is used to construct a kdq-tree, and data in test
+    filed into it. If used in a batch data setting, the reference window - the
+    first batch passed in - is used to construct a kdq-tree, and data in test
     batches are compared to it. When drift is detected on a test batch, that
     test batch is set to be the new reference window - unless the user specifies
     a reference window using the set_reference method.
 
     The threshold for drift is determined using the desired alpha level by a
-    bootstrap estimate for the critical value of the K-L divergence, drawing
-    num_bootstrap_samples samples, ``2 * window_size times``, from the reference
-    window.
+    bootstrap estimate for the critical value of the K-L divergence, drawing a
+    sample of ``num_boostrap_samples`` repeatedly, ``2 * window_size`` times,
+    from the reference window.
 
     Additionally, the Kulldorff spatial scan statistic, which is a special case
     of the KL-divergence, can be calculated at each node of the kdq-tree, which
@@ -43,7 +48,7 @@ class KdqTree(DriftDetector):
     visualize which regions of data space have the greatest drift. Note that
     these statistics are specific to the partitions of the data space by the
     kdq-tree, rather than (necessarily) the maximally different region in
-    general. KSS is made available via ``to_plotly_dataframe``, which produces
+    general. KSSs are made available via ``to_plotly_dataframe``, which produces
     output structured for use with ``plotly.express.treemap``.
 
     Note that this algorithm could be used with other types of trees; the
@@ -54,27 +59,17 @@ class KdqTree(DriftDetector):
 
     Ref. :cite:t:`dasu2006information`
 
-
     Attributes:
-        total_updates (int): number of samples/batches the drift detector has
-            ever been updated with.
-        updates_since_reset (int): number of samples/batches since the last time
-            the drift detector was reset
-        drift_state (str): detector's current drift state. Can take values
-            ``"drift"`` or ``None``.
+        drift_state (str): detector's current drift state, can
+            take ``str`` values ``"drift"`` or ``None``.
     """
 
-    input_type = None
-
     def __init__(
         self,
         alpha=0.01,
         bootstrap_samples=500,
         count_ubound=100,
         cutpoint_proportion_lbound=2e-10,
-        window_size=None,
-        persistence=0.05,
-        input_type="stream",
     ):
         """
         Args:
@@ -92,117 +87,33 @@ def __init__(
                 size of the leaf nodes. No node shall have a size length smaller
                 than this proportion, relative to the original feature length.
                 Defaults to 2e-10.
-            window_size (int, optional): The minimum number of samples required
-                to test whether drift has occurred. Only meaningful if
-                input_type is "stream". If input_type is "batch", kdqTree uses
-                all samples within each batch.
-            persistence (float, optional): Persistence factor. If stream is
-                ``True``, how many samples in a row, as a proportion of the
-                window size, must be in the "drift region" of K-L divergence, in
-                order for ``kdqTree`` to alarm and reset. Defaults to 0.05.
-            input_type (str, optional): Whether data will be received one sample
-                at a time, vs. received in batches of arbitrary size. For
-                ``input_type == "batch"``, the reference window is defined with
-                the first call to ``update()`` after a reset/initialization. For
-                ``input_type == "stream"``, new samples are passed to update one
-                at a time, and the reference data is defined by ``window_size``.
-                Defaults to "stream".
         """
-        if input_type == "stream" and window_size is None:
-            raise ValueError(
-                "Streaming kdqTree's window_size must be a positive integer."
-            )
-        if window_size is not None and window_size < 1 and input_type == "stream":
-            raise ValueError(
-                "Streaming kdqTree's window_size must be a positive integer."
-            )
-        super().__init__()
-        self.input_type = input_type
-        self.window_size = window_size
-        self.persistence = persistence
         self.alpha = alpha
         self.bootstrap_samples = bootstrap_samples
         self.count_ubound = count_ubound
         self.cutpoint_proportion_lbound = cutpoint_proportion_lbound
-        self.reset()
 
     def reset(self):
-        """Initialize the detector's drift state and other relevant attributes.
-        Intended for use after ``drift_state == 'drift'`` or initialization.
         """
-        super().reset()
+        Initialize the detector's drift state and other relevant attributes.
+        Intended for use after ``drift_state == "drift"`` or initialization.
+        """
         self._ref_data = np.array([])
         self._test_data_size = 0
         self._kdqtree = None
         self._critical_dist = None
-        self._drift_counter = 0  # samples consecutively in the drift region
         self.input_cols = None
 
-    def _inner_set_reference(self, ary):
+    def _prepare_data(self, data):
         """
-        Initialize detector with a reference batch. This does not perform
-        validation, and is therefore not intended to be called directly by the
-        user - instead, use ``set_reference``.
+        Validates and processes new data to be used in update sequence,
+        adjusting saved feature names where needed.
 
         Args:
-            ary (numpy array): initial baseline dataset
-        """
-        self.reset()
+            data (pandas.DataFrame or numpy.ndarray): new data for update
 
-        self._kdqtree = KDQTreePartitioner(
-            count_ubound=self.count_ubound,
-            cutpoint_proportion_lbound=self.cutpoint_proportion_lbound,
-        )
-        self._kdqtree.build(ary)
-
-        ref_counts = self._kdqtree.leaf_counts("build")
-        self._critical_dist = self._get_critical_kld(ref_counts)
-
-    def set_reference(self, data):
-        """
-        Initialize detector with a reference batch. The user may specify an
-        alternate reference batch than the one maintained by kdq-Tree. This will
-        reset the detector.
-        If ``input_type`` is ``"stream"``, this method should not be used.
-
-        Args:
-            data (pandas.DataFrame or numpy array): initial baseline dataset
-        """
-        if self.input_type == "stream":
-            raise ValueError("This method is only available for batch data.")
-
-        if isinstance(data, pd.DataFrame):
-            self._inner_set_reference(data.values)
-            self.input_cols = data.columns
-        elif isinstance(data, np.ndarray):
-            self._inner_set_reference(data)
-        else:
-            raise ValueError(
-                "This method is only available for data inputs in the form of a Pandas DataFrame or a Numpy Array."
-            )
-
-    def update(self, data):
-        """
-        Update the detector with a new sample/batch. Constructs the reference
-        data's kdqtree; then, when sufficient samples have been received, puts
-        the test data into the same tree; then, checks divergence between the
-        reference and test data.
-
-        If ``input_type`` is ``"stream"``, the reference window is maintained as
-        the initial window until drift. Upon drift, the user may continue
-        passing streaming data to update and new reference windows will be
-        constructed once sufficient samples have been received.
-
-        If ``input_type`` is ``"batch"``, the initial batch will be used as the
-        reference at each update step, regardless of drift state.  If the user
-        wishes to change reference batch, use the ``set_reference`` method and
-        then continue passing new batches to ``update``.
-
-        Args:
-            data (pandas.DataFrame or numpy array): If just reset/initialized,
-            the reference data. Otherwise, a new sample to put into the test
-            window (if streaming) or a new batch of data to be compared to the
-            reference window (if not streaming).
+        Returns
+            pandas.DataFrame or numpy.ndarray: validated data for update
         """
         if isinstance(data, pd.DataFrame):
             if self.input_cols is None:
@@ -225,73 +136,142 @@ def update(self, data):
                 """This method is only available for data inputs in the form of 
                 a Pandas DataFrame or a Numpy Array."""
             )
+        return ary
 
-        if self.drift_state == "drift":
-            if self.input_type == "batch":
-                # this will both reset the detector and initialize the reference
-                # kdq-tree.
-                self.set_reference(self.ref_data)
-            else:
-                self.reset()
-
-        super().update()
-
-        if self._kdqtree is None:  # ary is part of the new reference tree
+    def _evaluate_kdqtree(self, ary, input_type):
+        """
+        Deploy internal KDQTreePartitioner and evaluate results on
+        new data, setting drift state accordingly.
 
+        Args:
+            ary (numpy.ndarray): new data to be evaluated
+            input_type (str): context for incoming data, either
+                ``'stream'`` or ``'batch'``
+        """
+        # XXX - This is one final spot where we are still technically
+        #       'branching' on input_type. However, in practice, this
+        #       decomposes the update function so much that the two
+        #       classes always call this with their respective input
+        #       type - so it may not be much of an issue? @Anmol-Srivastava
+        if self._kdqtree is None:  # ary part of new ref tree
             self._ref_data = (
                 np.vstack([self._ref_data, ary]) if self._ref_data.size else ary
             )
 
             if (
-                self.input_type == "stream" and len(self._ref_data) == self.window_size
-            ) or self.input_type == "batch":
-
-                self._inner_set_reference(self._ref_data)
+                input_type == "stream" and len(self._ref_data) == self.window_size
+            ) or input_type == "batch":
+                # XXX - _inner_set_reference calls KLD itself, so needs to know sample_size
+                #       and hence input type; we may streamline this later - Anmol-Srivastava
+                self._inner_set_reference(self._ref_data, input_type)
 
         else:  # new test sample(s)
-
-            self._kdqtree.fill(ary, tree_id="test", reset=(self.input_type == "batch"))
-            if self.input_type == "stream":
+            self._kdqtree.fill(ary, tree_id="test", reset=(input_type == "batch"))
+            if input_type == "stream":
                 self._test_data_size += 1
             # check for drift if either: we're streaming and the reference
             # window is full, or we're doing batch detection
-            if self.input_type == "batch" or (self._test_data_size >= self.window_size):
+            if input_type == "batch" or (self._test_data_size >= self.window_size):
                 test_dist = self._kdqtree.kl_distance(tree_id1="build", tree_id2="test")
                 if test_dist > self._critical_dist:
-                    if self.input_type == "stream":
+                    if input_type == "stream":
                         self._drift_counter += 1
                         if self._drift_counter > self.persistence * self.window_size:
                             self.drift_state = "drift"
-
                     else:
                         self.drift_state = "drift"
-                        if isinstance(data, pd.DataFrame):
-                            self.input_cols = data.columns
                         self.ref_data = ary
 
-    def _get_critical_kld(self, ref_counts):
-        """Find the critical value of the Kullback-Leibler divergence, using the
+    def _inner_set_reference(self, ary, input_type):
+        """
+        Initialize detector with a reference batch. This does not perform
+        validation, and is therefore not intended to be called directly by the
+        user.
+
+        Args:
+            ary (numpy.ndarray): baseline dataset
+            input_type (str): context for incoming data, either
+                ``"stream"`` or ``"batch"``
+        """
+        # TODO ensure self.reset (or e.g. any function in that place uses right local version)
+        self.reset()
+        self._kdqtree = KDQTreePartitioner(
+            count_ubound=self.count_ubound,
+            cutpoint_proportion_lbound=self.cutpoint_proportion_lbound,
+        )
+        self._kdqtree.build(ary)
+        ref_counts = self._kdqtree.leaf_counts("build")
+        sample_size = self.window_size if input_type == "stream" else sum(ref_counts)
+        self._critical_dist = self._get_critical_kld(ref_counts, sample_size)
+        if input_type == "stream":
+            self._ref_data = np.array([])
+
+    def to_plotly_dataframe(
+        self, tree_id1="build", tree_id2="test", max_depth=None, input_cols=None
+    ):
+        """
+        Generates a dataframe containing information about the kdqTree's structure
+        and some node characteristics, intended for use with ``plotly``.
+
+        Args:
+            tree_id1 (str, optional): Reference tree. If ``tree_id2`` is not
+                specified, the only tree described. Defaults to ``"build"``.
+            tree_id2 (str, optional): Test tree. If this is specified, the
+                dataframe will also contain information about the difference
+                between counts in each node for the reference vs. the test tree.
+                Defaults to ``"test"``.
+            max_depth (int, optional): Depth in the tree to which to recurse.
+                Defaults to ``None``.
+            input_cols (list, optional): List of column names for the input
+                data. Defaults to ``None``.
+        Returns:
+            pd.DataFrame: A dataframe where each row corresponds to a node, and
+            each column contains some information:
+                * ``name``: a label corresponding to which feature this split is on
+                * ``idx``: a unique ID for the node, to pass
+                    ``plotly.express.treemap``'s id argument
+                * ``parent_idx``: the ID of the node's parent
+                * ``cell_count``: how many samples are in this node in the
+                    reference tree.
+                * ``depth``: how deep the node is in the tree
+                * ``count_diff``: if ``tree_id2`` is specified, the change in
+                    counts from the reference tree.
+                * ``kss``: the Kulldorff Spatial Scan Statistic for this node,
+                    defined as the Kullback-Leibler divergence for this node
+                    between the reference and test trees, using the individual
+                    node and all other nodes combined as the bins for the
+                    distributions.
+        """
+        if input_cols is not None:
+            return self._kdqtree.to_plotly_dataframe(
+                tree_id1, tree_id2, max_depth, input_cols
+            )
+        else:
+            return self._kdqtree.to_plotly_dataframe(
+                tree_id1, tree_id2, max_depth, self.input_cols
+            )
+
+    def _get_critical_kld(self, ref_counts, sample_size):
+        """
+        Find the critical value of the Kullback-Leibler divergence, using the
         empirical distribution defined by the counts of the reference tree
         across the leaves.
 
         Args:
             ref_counts (numpy array): The counts from the reference tree across
                 its leaves.
+            sample_size (int): Size of reference data. Has slightly different
+                contexts in streaming and batch cases.
 
         Returns:
             float: the critical distance corresponding to ``self.alpha``, the
                 achievable significance level.
         """
         ref_dist = KDQTreePartitioner._distn_from_counts(ref_counts)
-
-        if self.input_type == "stream":
-            sample_size = self.window_size
-        else:
-            sample_size = sum(ref_counts)
-
         b_dist_pairs = []
         bin_indices = list(range(len(ref_counts)))
         bin_indices_df = DataFrame({"leaf": bin_indices})
+
         for _ in range(self.bootstrap_samples):
             # note the maintenance of the leaf order!
             b_sample = np.random.choice(bin_indices, size=2 * sample_size, p=ref_dist)
@@ -316,49 +296,268 @@ def _get_critical_kld(self, ref_counts):
         critical_distances = [scipy.stats.entropy(a, b) for a, b in b_dist_pairs]
         return np.quantile(critical_distances, 1 - self.alpha, method="nearest")
 
-    def to_plotly_dataframe(
-        self, tree_id1="build", tree_id2="test", max_depth=None, input_cols=None
+
+class KdqTreeStreaming(KdqTreeDetector, StreamingDetector):
+    """
+    Implements the kdqTree drift detection algorithm in a
+    streaming data context. Inherits from ``KdqTreeDetector``
+    and ``StreamingDetector`` (see docs).
+
+    kdqTree is a drift detection algorithm which detects drift via the
+    Kullback-Leibler divergence, calculated after partitioning the data space
+    via constructing a k-d-quad-tree (kdq-tree).
+
+    If used in a streaming data setting, the reference window is used to
+    construct a kdq-tree, and the data in both the reference and test window are
+    filed into it. If used in a batch data setting, the reference window - the
+    first batch passed in - is used to construct a kdq-tree, and data in test
+    batches are compared to it. When drift is detected on a test batch, that
+    test batch is set to be the new reference window - unless the user specifies
+    a reference window using the set_reference method.
+
+    The threshold for drift is determined using the desired alpha level by a
+    bootstrap estimate for the critical value of the K-L divergence, drawing a
+    sample of ``num_boostrap_samples`` repeatedly, ``2 * window_size`` times,
+    from the reference window.
+
+    Additionally, the Kulldorff spatial scan statistic, which is a special case
+    of the KL-divergence, can be calculated at each node of the kdq-tree, which
+    gives a measure of the regions of the data space which have the greatest
+    divergence between the reference and test windows. This can be used to
+    visualize which regions of data space have the greatest drift. Note that
+    these statistics are specific to the partitions of the data space by the
+    kdq-tree, rather than (necessarily) the maximally different region in
+    general. KSSs are made available via ``to_plotly_dataframe``, which produces
+    output structured for use with ``plotly.express.treemap``.
+
+    Ref. :cite:t:`dasu2006information`
+
+    Attributes:
+        total_samples (int): total number of samples the drift
+            detector has been udpated with
+        samples_since_reset (int): number of samples since last
+            drift detection
+        drift_state (str): detector's current drift state, can
+            take ``str`` values e.g. ``"drift"`` or ``None``
+    """
+
+    def __init__(
+        self,
+        window_size,
+        persistence=0.05,
+        alpha=0.01,
+        bootstrap_samples=500,
+        count_ubound=100,
+        cutpoint_proportion_lbound=2e-10,
     ):
-        """Generates a dataframe containing information about the kdqTree's structure
-        and some node characteristics, intended for use with plotly.
+        """
+        Args:
+            window_size (int): The minimum number of samples required
+                to test whether drift has occurred.
+            persistence (float, optional): Persistence factor: how many samples
+                in a row, as a proportion of the window size, must be in the
+                "drift region" of K-L divergence, in order for ``kdqTree`` to
+                alarm and reset. Defaults to 0.05.
+            alpha (float, optional): Achievable significance level. Defaults to
+                0.01.
+            bootstrap_samples (int, optional): The number of bootstrap samples
+                to use to approximate the empirical distributions. Equivalent to
+                kappa in Dasu (2006), which recommends 500-1000 samples.
+                Defaults to 500.
+            count_ubound (int, optional): An upper bound for the number of
+                samples stored in a leaf node of the kdqTree. No leaf shall
+                contain more samples than this value, unless further divisions
+                violate the cutpoint_proportion_lbound restriction. Default 100.
+            cutpoint_proportion_lbound (float, optional): A lower bound for the
+                size of the leaf nodes. No node shall have a size length smaller
+                than this proportion, relative to the original feature length.
+                Defaults to 2e-10.
+        """
+        if not isinstance(window_size, int) or window_size < 1:
+            raise ValueError(f"window_size must be positive integer, was {window_size}")
+
+        StreamingDetector.__init__(self)
+        KdqTreeDetector.__init__(
+            self, alpha, bootstrap_samples, count_ubound, cutpoint_proportion_lbound
+        )
+        self.window_size = (
+            window_size  # XXX - could be generalized into StreamingDetector.init
+        )
+        self.persistence = persistence
+        self.reset()
+
+    def reset(self):
+        """
+        Initialize the detector's drift state and other relevant attributes.
+        Intended for use after ``drift_state == "drift"`` or initialization.
+        """
+        StreamingDetector.reset(self)
+        KdqTreeDetector.reset(self)
+        self._drift_counter = 0  # samples consecutively in the drift region
+
+    def update(self, data):
+        """
+        Update the detector with a new sample point. Constructs the reference
+        data's kdqtree; then, when sufficient samples have been received, puts
+        the test data into the same tree; then, checks divergence between the
+        reference and test data.
+
+        The reference window is maintained as the initial window until drift.
+        Upon drift, the user may continue passing data to update and new
+        reference windows will be constructed once sufficient samples are received.
 
         Args:
-            tree_id1 (str, optional): Reference tree. If ``tree_id2`` is not
-                specified, the only tree described. Defaults to ``"build"``.
-            tree_id2 (str, optional): Test tree. If this is specified, the
-                dataframe will also contain information about the difference
-                between counts in each node for the reference vs. the test tree.
-                Defaults to ``"test"``.
-            max_depth (int, optional): Depth in the tree to which to recurse.
-                Defaults to ``None``.
-            input_cols (list, optional): List of column names for the input
-                data. Defaults to ``None``.
+            data (pandas.DataFrame or numpy array): If just reset/initialized,
+            the reference data. Otherwise, a new sample to put into the test
+            window.
+        """
+        ary = self._prepare_data(data)
 
-        Returns:
-            pd.DataFrame: A dataframe where each row corresponds to a node, and
-            each column contains some information:
+        if self.drift_state == "drift":
+            self.reset()
 
-                * ``name``: a label corresponding to which feature this split is on
-                * ``idx``: a unique ID for the node, to pass
-                    ``plotly.express.treemap``'s id argument
-                * ``parent_idx``: the ID of the node's parent
-                * ``cell_count``: how many samples are in this node in the
-                    reference tree.
-                * ``depth``: how deep the node is in the tree
-                * ``count_diff``: if ``tree_id2`` is specified, the change in
-                    counts from the reference tree.
-                * ``kss``: the Kulldorff Spatial Scan Statistic for this node,
-                    defined as the Kullback-Leibler divergence for this node
-                    between the reference and test trees, using the individual
-                    node and all other nodes combined as the bins for the
-                    distributions.
+        StreamingDetector.update(self)
+        KdqTreeDetector._evaluate_kdqtree(self, ary, "stream")
+
+
+class KdqTreeBatch(KdqTreeDetector, BatchDetector):
+    """
+    Implements the kdqTree drift detection algorithm in a
+    batch data context. Inherits from ``KdqTreeDetector``
+    and ``BatchDetector`` (see docs).
+
+    kdqTree is a drift detection algorithm which detects drift via the
+    Kullback-Leibler divergence, calculated after partitioning the data space
+    via constructing a k-d-quad-tree (kdq-tree). A reference window of initial
+    data is compared to a test window of later data. The Kullback-Leibler
+    divergence between the empirical distributions of the reference and test
+    windows is calculated, and drift is alarmed when a threshold is reached.
+    A kdqtree is a combination of k-d trees and quad-trees; it is a binary tree
+    (k-d) whose nodes contain square cells (quad) which are created via
+    sequential splits along each dimension. This structure allows the
+    calculation of the K-L divergence for continuous distributions, as the K-L
+    divergence is defined on probability mass functions. The number of samples
+    in each leaf of the tree is an empirical distribution for either dataset;
+    this allows us to calculate the K-L divergence.
+
+    If used in a streaming data setting, the reference window is used to
+    construct a kdq-tree, and the data in both the reference and test window are
+    filed into it. If used in a batch data setting, the reference window - the
+    first batch passed in - is used to construct a kdq-tree, and data in test
+    batches are compared to it. When drift is detected on a test batch, that
+    test batch is set to be the new reference window - unless the user specifies
+    a reference window using the set_reference method.
+
+    The threshold for drift is determined using the desired alpha level by a
+    bootstrap estimate for the critical value of the K-L divergence, drawing a
+    sample of ``num_boostrap_samples`` repeatedly, ``2 * window_size`` times,
+    from the reference window.
+
+    Additionally, the Kulldorff spatial scan statistic, which is a special case
+    of the KL-divergence, can be calculated at each node of the kdq-tree, which
+    gives a measure of the regions of the data space which have the greatest
+    divergence between the reference and test windows. This can be used to
+    visualize which regions of data space have the greatest drift. Note that
+    these statistics are specific to the partitions of the data space by the
+    kdq-tree, rather than (necessarily) the maximally different region in
+    general. KSSs are made available via ``to_plotly_dataframe``, which produces
+    output structured for use with ``plotly.express.treemap``.
+
+    Ref. :cite:t:`dasu2006information`
+
+    Attributes:
+        total_samples (int): total number of samples the drift
+            detector has been udpated with
+        samples_since_reset (int): number of samples since last
+            drift detection
+        drift_state (str): detector's current drift state, can
+            take ``str`` values e.g. ``"drift"`` or ``None``
+    """
+
+    def __init__(
+        self,
+        alpha=0.01,
+        bootstrap_samples=500,
+        count_ubound=100,
+        cutpoint_proportion_lbound=2e-10,
+    ):
+        """
+        Args:
+            alpha (float, optional): Achievable significance level. Defaults to
+                0.01.
+            bootstrap_samples (int, optional): The number of bootstrap samples
+                to use to approximate the empirical distributions. Equivalent to
+                kappa in Dasu (2006), which recommends 500-1000 samples.
+                Defaults to 500.
+            count_ubound (int, optional): An upper bound for the number of
+                samples stored in a leaf node of the kdqTree. No leaf shall
+                contain more samples than this value, unless further divisions
+                violate the cutpoint_proportion_lbound restriction. Default 100.
+            cutpoint_proportion_lbound (float, optional): A lower bound for the
+                size of the leaf nodes. No node shall have a size length smaller
+                than this proportion, relative to the original feature length.
+                Defaults to 2e-10.
         """
+        BatchDetector.__init__(self)
+        KdqTreeDetector.__init__(
+            self, alpha, bootstrap_samples, count_ubound, cutpoint_proportion_lbound
+        )
+        self.reset()
 
-        if input_cols is not None:
-            return self._kdqtree.to_plotly_dataframe(
-                tree_id1, tree_id2, max_depth, input_cols
-            )
+    def reset(self):
+        """
+        Initialize the detector's drift state and other relevant attributes.
+        Intended for use after ``drift_state == "drift"`` or initialization.
+        """
+        BatchDetector.reset(self)
+        KdqTreeDetector.reset(self)
+
+    def set_reference(self, data):
+        """
+        Initialize detector with a reference batch. The user may specify an
+        alternate reference batch than the one maintained by kdq-Tree. This will
+        reset the detector.
+
+        Args:
+            data (pandas.DataFrame or numpy array): baseline dataset
+        """
+        if isinstance(data, pd.DataFrame):
+            # XXX - notice how inner_set calling KLD requires us to continue
+            #       branching on input_type, which is not ideal - Anmol Srivastava
+            self._inner_set_reference(data.values, input_type="batch")
+            self.input_cols = data.columns
+        elif isinstance(data, np.ndarray):
+            self._inner_set_reference(data, input_type="batch")
         else:
-            return self._kdqtree.to_plotly_dataframe(
-                tree_id1, tree_id2, max_depth, self.input_cols
+            raise ValueError(
+                "This method is only available for data inputs in the form of a Pandas DataFrame or a Numpy Array."
             )
+
+    def update(self, data):
+        """
+        Update the detector with a new batch. Constructs the reference
+        data's kdqtree; then, when sufficient samples have been received, puts
+        the test data into the same tree; then, checks divergence between the
+        reference and test data.
+
+        The initial batch will be used as the reference at each update step,
+        regardless of drift state.  If the user wishes to change reference
+        batch, use the ``set_reference`` method and then continue passing new
+        batches to ``update``.
+
+        Args:
+            data (pandas.DataFrame or numpy array): If just reset/initialized,
+            the reference data. Otherwise, a new batch of data to be compared
+            to the reference window.
+        """
+        ary = self._prepare_data(data)
+
+        if self.drift_state == "drift":
+            self.set_reference(self.ref_data)
+
+        BatchDetector.update(self)
+        KdqTreeDetector._evaluate_kdqtree(self, ary, "batch")
+
+        # if _evaluate_kdqtree resulted in drift for batch data, do a redundant check
+        if self.drift_state == "drift" and isinstance(data, pd.DataFrame):
+            self.input_cols = data.columns
diff --git a/src/menelaus/drift_detector.py b/src/menelaus/drift_detector.py
index 8a8bb110..8296f983 100644
--- a/src/menelaus/drift_detector.py
+++ b/src/menelaus/drift_detector.py
@@ -1,6 +1,121 @@
 from abc import ABC, abstractmethod
 
 
+class StreamingDetector(ABC):
+    """
+    Abstract base class for all streaming data-based detectors.
+    Minimally implements abstract methods common to all stream
+    based detection algorithms.
+
+    Attributes:
+        total_samples (int): total number of samples the drift
+            detector has been updated with
+        samples_since_reset (int): number of samples since last
+            drift detection
+        drift_state (str): detector's current drift state, can
+            take ``str`` values ``"drift"``, ``"warning"``, or ``None``.
+    """
+
+    def __init__(self, *args, **kwargs):
+        self.total_samples = 0
+        self.samples_since_reset = 0
+        self._drift_state = None
+
+    @abstractmethod
+    def update(self, *args, **kwargs):
+        """Update detector with new sample (data point)."""
+        self.total_samples += 1
+        self.samples_since_reset += 1
+
+    @abstractmethod
+    def reset(self, *args, **kwargs):
+        """
+        Initialize the detector's drift state and other relevant attributes.
+        Intended for use after ``drift_state == "drift"``.
+        """
+        self.samples_since_reset = 0
+        self.drift_state = None
+
+    @property
+    def drift_state(self):
+        """Set detector's drift state to ``"drift"``, ``"warning"``, or ``None``."""
+        return self._drift_state
+
+    @drift_state.setter
+    def drift_state(self, value):
+        """Set detector's drift state to ``"drift"``, ``"warning"``, or ``None``.
+
+        Args:
+            value (str): ``"drift"``, ``"warning"``, or ``None``
+
+        Raises:
+            ValueError: raised if disallowed value is given
+        """
+        if value not in ("drift", "warning", None):
+            raise ValueError("tbd")
+        else:
+            self._drift_state = value
+
+
+class BatchDetector(ABC):
+    """
+    Abstract base class for all batch data-based detectors.
+    Minimally implements abstract methods common to all batch
+    based detection algorithms.
+
+    Attributes:
+        total_batches (int): total number of batches the drift
+            detector has been updated with
+        batches_since_reset (int): number of batches since last
+            drift detection
+        drift_state (str): detector's current drift state, can
+            take ``str`` values ``"drift"``, ``"warning"``, or ``None``.
+    """
+
+    def __init__(self, *args, **kwargs):
+        self.total_batches = 0
+        self.batches_since_reset = 0
+        self._drift_state = None
+
+    @abstractmethod
+    def update(self, *args, **kwargs):
+        """Update detector with new batch of data"""
+        self.total_batches += 1
+        self.batches_since_reset += 1
+
+    @abstractmethod
+    def reset(self, *args, **kwargs):
+        """
+        Initialize the detector's drift state and other relevant attributes.
+        Intended for use after ``drift_state == 'drift'``.
+        """
+        self.batches_since_reset = 0
+        self.drift_state = None
+
+    @property
+    def drift_state(self):
+        """Set detector's drift state to ``"drift"``, ``"warning"``, or ``None``."""
+        return self._drift_state
+
+    @drift_state.setter
+    def drift_state(self, value):
+        """Set detector's drift state to ``"drift"``, ``"warning"``, or ``None``.
+
+        Args:
+            value (str): ``"drift"``, ``"warning"``, or ``None``
+
+        Raises:
+            ValueError: raised if disallowed value is given
+        """
+        if value not in ("drift", "warning", None):
+            raise ValueError("tbd")
+        else:
+            self._drift_state = value
+
+
+#######################
+# Deprecated in 0.2.0
+#######################
 class DriftDetector(ABC):
     """Base class for Menelaus drift detectors.
     A DriftDetector object implements the ``update`` and ``reset`` methods and
@@ -21,7 +136,6 @@ class DriftDetector(ABC):
             has ever been updated with
         updates_since_reset (int): number of samples/batches since the last
             time the drift detector was reset
-
     """
 
     def __init__(self, *args, **kwargs):


Test patch:
diff --git a/tests/menelaus/artifacts/dataCircleGSev3Sp3Train.csv b/tests/menelaus/artifacts/dataCircleGSev3Sp3Train.csv
deleted file mode 100644
index 6973affe..00000000
--- a/tests/menelaus/artifacts/dataCircleGSev3Sp3Train.csv
+++ /dev/null
@@ -1,2000 +0,0 @@
-0.350395,0.095046,0.0,
-0.473468,0.583694,1.0,
-0.115968,0.078085,0.0,
-0.447701,0.313451,1.0,
-0.192150,0.471360,0.0,
-0.357969,0.587134,1.0,
-0.360822,0.336477,0.0,
-0.457335,0.621747,1.0,
-0.011081,0.233113,0.0,
-0.614379,0.464292,1.0,
-0.119393,0.634370,0.0,
-0.644955,0.363297,1.0,
-0.601185,0.117607,0.0,
-0.550439,0.634050,1.0,
-0.940407,0.415557,0.0,
-0.408810,0.671201,1.0,
-0.921315,0.542025,0.0,
-0.625154,0.366551,1.0,
-0.320382,0.657881,0.0,
-0.614114,0.417874,1.0,
-0.789684,0.215886,0.0,
-0.461285,0.620958,1.0,
-0.862149,0.143770,0.0,
-0.438024,0.464972,1.0,
-0.710129,0.142832,0.0,
-0.530981,0.402411,1.0,
-0.046425,0.270999,0.0,
-0.571154,0.387757,1.0,
-0.340164,0.139813,0.0,
-0.510077,0.668634,1.0,
-0.062254,0.303957,0.0,
-0.539475,0.350070,1.0,
-0.834589,0.945255,0.0,
-0.662268,0.542648,1.0,
-0.444027,0.757435,0.0,
-0.383908,0.540292,1.0,
-0.659553,0.107523,0.0,
-0.387898,0.399009,1.0,
-0.125379,0.241267,0.0,
-0.601337,0.473065,1.0,
-0.749978,0.884405,0.0,
-0.484690,0.384339,1.0,
-0.704789,0.393082,0.0,
-0.509106,0.342868,1.0,
-0.207079,0.371579,0.0,
-0.509743,0.653311,1.0,
-0.232444,0.681034,0.0,
-0.357081,0.463541,1.0,
-0.581848,0.123304,0.0,
-0.445838,0.594735,1.0,
-0.037413,0.794502,0.0,
-0.377712,0.408427,1.0,
-0.820034,0.318044,0.0,
-0.443386,0.582186,1.0,
-0.752423,0.967694,0.0,
-0.645014,0.558567,1.0,
-0.357867,0.672850,0.0,
-0.537336,0.404311,1.0,
-0.882843,0.934033,0.0,
-0.420217,0.379975,1.0,
-0.356178,0.289505,0.0,
-0.581890,0.432458,1.0,
-0.554905,0.287817,0.0,
-0.438651,0.613374,1.0,
-0.208042,0.565205,0.0,
-0.463008,0.376743,1.0,
-0.093426,0.213934,0.0,
-0.538355,0.329772,1.0,
-0.934461,0.490361,0.0,
-0.497448,0.410039,1.0,
-0.571598,0.851651,0.0,
-0.576405,0.637722,1.0,
-0.221804,0.858593,0.0,
-0.446951,0.511025,1.0,
-0.726068,0.018172,0.0,
-0.465739,0.677557,1.0,
-0.992456,0.213499,0.0,
-0.410347,0.507241,1.0,
-0.765402,0.104673,0.0,
-0.392374,0.424083,1.0,
-0.647964,0.324234,0.0,
-0.461616,0.583738,1.0,
-0.961724,0.686965,0.0,
-0.626891,0.365376,1.0,
-0.951887,0.367945,0.0,
-0.526990,0.514025,1.0,
-0.808986,0.626687,0.0,
-0.558363,0.608269,1.0,
-0.701708,0.610958,0.0,
-0.449432,0.597592,1.0,
-0.068325,0.336051,0.0,
-0.302939,0.503536,1.0,
-0.093071,0.239777,0.0,
-0.674120,0.529840,1.0,
-0.845206,0.378891,0.0,
-0.307961,0.507592,1.0,
-0.486599,0.264187,0.0,
-0.378498,0.413381,1.0,
-0.969588,0.860778,0.0,
-0.341592,0.536517,1.0,
-0.839396,0.722214,0.0,
-0.401573,0.641790,1.0,
-0.162826,0.608972,0.0,
-0.343616,0.549269,1.0,
-0.679249,0.137562,0.0,
-0.459091,0.550363,1.0,
-0.622416,0.941450,0.0,
-0.676803,0.424068,1.0,
-0.042578,0.613246,0.0,
-0.628463,0.381736,1.0,
-0.350375,0.755984,0.0,
-0.628830,0.547945,1.0,
-0.016303,0.003058,0.0,
-0.460169,0.667888,1.0,
-0.736021,0.313170,0.0,
-0.477837,0.402933,1.0,
-0.986227,0.520181,0.0,
-0.571396,0.455967,1.0,
-0.326612,0.368112,0.0,
-0.640920,0.548096,1.0,
-0.367010,0.341247,0.0,
-0.435648,0.527708,1.0,
-0.710820,0.744916,0.0,
-0.475708,0.622012,1.0,
-0.156715,0.907199,0.0,
-0.418423,0.636849,1.0,
-0.534720,0.034448,0.0,
-0.581917,0.484944,1.0,
-0.895265,0.725482,0.0,
-0.371219,0.470866,1.0,
-0.349875,0.346988,0.0,
-0.353583,0.476570,1.0,
-0.016429,0.114096,0.0,
-0.545668,0.446279,1.0,
-0.270706,0.751186,0.0,
-0.372703,0.615937,1.0,
-0.881767,0.856913,0.0,
-0.532926,0.689912,1.0,
-0.125962,0.040907,0.0,
-0.507584,0.359685,1.0,
-0.801678,0.806410,0.0,
-0.433966,0.662700,1.0,
-0.749215,0.057620,0.0,
-0.464885,0.320255,1.0,
-0.046163,0.862875,0.0,
-0.433637,0.331088,1.0,
-0.239860,0.321013,0.0,
-0.404302,0.512096,1.0,
-0.762064,0.013961,0.0,
-0.554218,0.537532,1.0,
-0.521283,0.203632,0.0,
-0.475622,0.385573,1.0,
-0.517625,0.726304,0.0,
-0.543619,0.405709,1.0,
-0.608263,0.068113,0.0,
-0.608243,0.540112,1.0,
-0.903419,0.769771,0.0,
-0.513986,0.354681,1.0,
-0.552881,0.277375,0.0,
-0.636257,0.568795,1.0,
-0.104453,0.537428,0.0,
-0.520124,0.329062,1.0,
-0.108825,0.028921,0.0,
-0.452907,0.614050,1.0,
-0.680059,0.746365,0.0,
-0.365696,0.445834,1.0,
-0.613970,0.001908,0.0,
-0.475102,0.446267,1.0,
-0.785565,0.986775,0.0,
-0.672166,0.489287,1.0,
-0.356710,0.231546,0.0,
-0.539673,0.483534,1.0,
-0.639085,0.093224,0.0,
-0.628738,0.600330,1.0,
-0.126894,0.711583,0.0,
-0.531942,0.556400,1.0,
-0.291628,0.383743,0.0,
-0.524810,0.686176,1.0,
-0.140883,0.816655,0.0,
-0.508085,0.376951,1.0,
-0.292624,0.127673,0.0,
-0.617770,0.652751,1.0,
-0.699373,0.363556,0.0,
-0.410969,0.548169,1.0,
-0.439510,0.840730,0.0,
-0.357329,0.625604,1.0,
-0.071185,0.399162,0.0,
-0.585995,0.611591,1.0,
-0.275636,0.611759,0.0,
-0.634143,0.637051,1.0,
-0.025188,0.330066,0.0,
-0.470051,0.353249,1.0,
-0.373369,0.211485,0.0,
-0.474138,0.631614,1.0,
-0.601104,0.754097,0.0,
-0.345059,0.410357,1.0,
-0.914423,0.707455,0.0,
-0.381033,0.625986,1.0,
-0.110342,0.522200,0.0,
-0.546622,0.473775,1.0,
-0.600040,0.875281,0.0,
-0.639467,0.517158,1.0,
-0.148593,0.395926,0.0,
-0.428081,0.553221,1.0,
-0.698948,0.216766,0.0,
-0.626337,0.639231,1.0,
-0.621061,0.175911,0.0,
-0.514165,0.563535,1.0,
-0.072586,0.953880,0.0,
-0.646395,0.559432,1.0,
-0.690331,0.388276,0.0,
-0.603616,0.375723,1.0,
-0.687822,0.230153,0.0,
-0.372407,0.451665,1.0,
-0.571143,0.206031,0.0,
-0.607235,0.390810,1.0,
-0.614677,0.880529,0.0,
-0.317383,0.462703,1.0,
-0.389835,0.962828,0.0,
-0.400336,0.638753,1.0,
-0.541501,0.012055,0.0,
-0.543293,0.531808,1.0,
-0.998032,0.915621,0.0,
-0.633733,0.557562,1.0,
-0.131921,0.188356,0.0,
-0.582989,0.492822,1.0,
-0.897841,0.020793,0.0,
-0.483904,0.380734,1.0,
-0.248023,0.516241,0.0,
-0.482182,0.637609,1.0,
-0.491515,0.893288,0.0,
-0.495289,0.521291,1.0,
-0.076707,0.210118,0.0,
-0.484052,0.457423,1.0,
-0.527583,0.088421,0.0,
-0.694540,0.533113,1.0,
-0.804867,0.394554,0.0,
-0.410252,0.503878,1.0,
-0.451062,0.993620,0.0,
-0.607527,0.505009,1.0,
-0.978380,0.624281,0.0,
-0.419438,0.502823,1.0,
-0.105288,0.578443,0.0,
-0.403102,0.333084,1.0,
-0.094202,0.248338,0.0,
-0.624215,0.576585,1.0,
-0.420723,0.087607,0.0,
-0.465445,0.536015,1.0,
-0.777712,0.012862,0.0,
-0.368191,0.391501,1.0,
-0.385140,0.047261,0.0,
-0.429583,0.598574,1.0,
-0.196405,0.977202,0.0,
-0.473695,0.385588,1.0,
-0.182779,0.961624,0.0,
-0.370395,0.622209,1.0,
-0.400324,0.237904,0.0,
-0.479676,0.511951,1.0,
-0.659093,0.372918,0.0,
-0.551087,0.322223,1.0,
-0.760618,0.705970,0.0,
-0.394204,0.389271,1.0,
-0.948691,0.646048,0.0,
-0.513949,0.542790,1.0,
-0.439886,0.172309,0.0,
-0.611925,0.416333,1.0,
-0.010481,0.155763,0.0,
-0.661908,0.491944,1.0,
-0.996330,0.324328,0.0,
-0.487991,0.666657,1.0,
-0.007978,0.090100,0.0,
-0.425671,0.456396,1.0,
-0.357584,0.913823,0.0,
-0.548511,0.619551,1.0,
-0.745223,0.963377,0.0,
-0.489742,0.499155,1.0,
-0.014604,0.448229,0.0,
-0.454934,0.668749,1.0,
-0.925043,0.204015,0.0,
-0.393312,0.592933,1.0,
-0.325242,0.336610,0.0,
-0.458820,0.393297,1.0,
-0.089585,0.652853,0.0,
-0.497568,0.418527,1.0,
-0.191102,0.847448,0.0,
-0.459876,0.540932,1.0,
-0.190167,0.130542,0.0,
-0.565365,0.687570,1.0,
-0.736808,0.526492,0.0,
-0.597847,0.607055,1.0,
-0.673155,0.709242,0.0,
-0.389777,0.376731,1.0,
-0.059019,0.924457,0.0,
-0.438424,0.398273,1.0,
-0.183304,0.782811,0.0,
-0.583960,0.410163,1.0,
-0.784829,0.619248,0.0,
-0.583114,0.596932,1.0,
-0.339084,0.981157,0.0,
-0.421492,0.613192,1.0,
-0.534256,0.237780,0.0,
-0.446675,0.661163,1.0,
-0.150121,0.076614,0.0,
-0.563620,0.557434,1.0,
-0.752779,0.951188,0.0,
-0.545390,0.563953,1.0,
-0.647896,0.182437,0.0,
-0.389836,0.577314,1.0,
-0.035327,0.741931,0.0,
-0.557030,0.610935,1.0,
-0.699479,0.136001,0.0,
-0.608736,0.425338,1.0,
-0.393537,0.169754,0.0,
-0.396991,0.434195,1.0,
-0.549823,0.877969,0.0,
-0.466320,0.441031,1.0,
-0.757673,0.202228,0.0,
-0.636423,0.563585,1.0,
-0.201742,0.677607,0.0,
-0.515314,0.683328,1.0,
-0.484501,0.015958,0.0,
-0.379935,0.562667,1.0,
-0.537698,0.083766,0.0,
-0.645035,0.505651,1.0,
-0.952439,0.634922,0.0,
-0.353386,0.365756,1.0,
-0.887831,0.770633,0.0,
-0.603021,0.365934,1.0,
-0.207063,0.115649,0.0,
-0.582379,0.643362,1.0,
-0.206394,0.861846,0.0,
-0.535039,0.594834,1.0,
-0.189458,0.221868,0.0,
-0.671925,0.435622,1.0,
-0.468258,0.015058,0.0,
-0.332659,0.397139,1.0,
-0.597956,0.842496,0.0,
-0.629796,0.387767,1.0,
-0.764580,0.302526,0.0,
-0.521552,0.331153,1.0,
-0.959711,0.859299,0.0,
-0.397899,0.483475,1.0,
-0.163479,0.586230,0.0,
-0.607500,0.450278,1.0,
-0.615034,0.869219,0.0,
-0.587553,0.402517,1.0,
-0.506462,0.104941,0.0,
-0.599864,0.518409,1.0,
-0.004050,0.068203,0.0,
-0.415776,0.545069,1.0,
-0.205876,0.166272,0.0,
-0.516664,0.568062,1.0,
-0.313372,0.837519,0.0,
-0.370775,0.613413,1.0,
-0.818623,0.595090,0.0,
-0.568468,0.437543,1.0,
-0.194492,0.826641,0.0,
-0.443181,0.342062,1.0,
-0.021231,0.835151,0.0,
-0.455205,0.429895,1.0,
-0.851612,0.046346,0.0,
-0.455430,0.618906,1.0,
-0.252579,0.090103,0.0,
-0.442144,0.517133,1.0,
-0.367515,0.829344,0.0,
-0.381164,0.426958,1.0,
-0.702458,0.204291,0.0,
-0.505241,0.584748,1.0,
-0.409102,0.770098,0.0,
-0.317791,0.512138,1.0,
-0.520336,0.293445,0.0,
-0.303536,0.529773,1.0,
-0.482535,0.961224,0.0,
-0.419725,0.513413,1.0,
-0.094354,0.808453,0.0,
-0.565027,0.608331,1.0,
-0.299977,0.709706,0.0,
-0.311556,0.517051,1.0,
-0.930706,0.377361,0.0,
-0.420976,0.350932,1.0,
-0.027548,0.999608,0.0,
-0.462601,0.338619,1.0,
-0.673000,0.106058,0.0,
-0.507985,0.306550,1.0,
-0.228089,0.484251,0.0,
-0.619353,0.462628,1.0,
-0.237773,0.250191,0.0,
-0.327760,0.467488,1.0,
-0.049877,0.286473,0.0,
-0.602451,0.387789,1.0,
-0.669739,0.305414,0.0,
-0.334968,0.403556,1.0,
-0.154554,0.583994,0.0,
-0.375934,0.523007,1.0,
-0.189296,0.493110,0.0,
-0.580609,0.490483,1.0,
-0.622530,0.855044,0.0,
-0.588174,0.438032,1.0,
-0.751734,0.399396,0.0,
-0.562388,0.658109,1.0,
-0.846838,0.802989,0.0,
-0.637316,0.371528,1.0,
-0.435772,0.015975,0.0,
-0.495552,0.544103,1.0,
-0.697310,0.684436,0.0,
-0.425742,0.438341,1.0,
-0.727299,0.712955,0.0,
-0.552877,0.399937,1.0,
-0.099993,0.587819,0.0,
-0.490722,0.571997,1.0,
-0.704711,0.076585,0.0,
-0.362723,0.487643,1.0,
-0.292437,0.996272,0.0,
-0.436619,0.456807,1.0,
-0.736339,0.646073,0.0,
-0.522603,0.394035,1.0,
-0.103420,0.181288,0.0,
-0.662389,0.574198,1.0,
-0.102416,0.297642,0.0,
-0.484392,0.581138,1.0,
-0.721770,0.790019,0.0,
-0.637751,0.473725,1.0,
-0.481959,0.277525,0.0,
-0.443160,0.389236,1.0,
-0.489073,0.848559,0.0,
-0.595607,0.566971,1.0,
-0.445095,0.718725,0.0,
-0.547585,0.661045,1.0,
-0.223468,0.828205,0.0,
-0.556944,0.354976,1.0,
-0.973431,0.457149,0.0,
-0.423934,0.457521,1.0,
-0.967823,0.203566,0.0,
-0.434297,0.622315,1.0,
-0.864085,0.683833,0.0,
-0.375639,0.369588,1.0,
-0.920184,0.525881,0.0,
-0.491013,0.647184,1.0,
-0.786390,0.864477,0.0,
-0.404762,0.632628,1.0,
-0.202881,0.814715,0.0,
-0.665973,0.403913,1.0,
-0.172879,0.575636,0.0,
-0.584481,0.373208,1.0,
-0.019088,0.815163,0.0,
-0.480126,0.482852,1.0,
-0.988249,0.506088,0.0,
-0.625404,0.571472,1.0,
-0.562038,0.170600,0.0,
-0.408984,0.401828,1.0,
-0.574770,0.152739,0.0,
-0.335494,0.442097,1.0,
-0.554112,0.957859,0.0,
-0.595579,0.494806,1.0,
-0.258199,0.546005,0.0,
-0.585126,0.413612,1.0,
-0.704190,0.323965,0.0,
-0.648575,0.402812,1.0,
-0.917401,0.756312,0.0,
-0.434207,0.318190,1.0,
-0.297630,0.261494,0.0,
-0.402722,0.356105,1.0,
-0.694756,0.771811,0.0,
-0.634347,0.461848,1.0,
-0.444652,0.263310,0.0,
-0.483561,0.604592,1.0,
-0.190498,0.692312,0.0,
-0.573418,0.436778,1.0,
-0.086413,0.342357,0.0,
-0.695110,0.519113,1.0,
-0.872170,0.560216,0.0,
-0.522602,0.367237,1.0,
-0.139954,0.199730,0.0,
-0.644337,0.379177,1.0,
-0.811781,0.595727,0.0,
-0.451602,0.670245,1.0,
-0.750581,0.012235,0.0,
-0.556093,0.448380,1.0,
-0.124245,0.184363,0.0,
-0.537040,0.624236,1.0,
-0.569464,0.987486,0.0,
-0.568271,0.331465,1.0,
-0.082568,0.714031,0.0,
-0.585253,0.543496,1.0,
-0.096042,0.182947,0.0,
-0.612777,0.344336,1.0,
-0.894615,0.796177,0.0,
-0.435985,0.600667,1.0,
-0.282847,0.817425,0.0,
-0.484793,0.510307,1.0,
-0.565064,0.030973,0.0,
-0.523459,0.380376,1.0,
-0.194236,0.526881,0.0,
-0.414549,0.329909,1.0,
-0.818972,0.465563,0.0,
-0.587656,0.533751,1.0,
-0.613296,0.670984,0.0,
-0.394026,0.601714,1.0,
-0.280559,0.352550,0.0,
-0.421244,0.442382,1.0,
-0.528339,0.800651,0.0,
-0.519130,0.423642,1.0,
-0.139931,0.816700,0.0,
-0.407982,0.347147,1.0,
-0.015674,0.434361,0.0,
-0.418986,0.506020,1.0,
-0.835211,0.395775,0.0,
-0.615211,0.448370,1.0,
-0.130584,0.718483,0.0,
-0.514175,0.335433,1.0,
-0.812533,0.242122,0.0,
-0.437892,0.659035,1.0,
-0.739252,0.608153,0.0,
-0.388257,0.441981,1.0,
-0.674738,0.329228,0.0,
-0.433604,0.574527,1.0,
-0.925732,0.784412,0.0,
-0.542281,0.315882,1.0,
-0.322082,0.233912,0.0,
-0.440288,0.524646,1.0,
-0.079471,0.670287,0.0,
-0.507506,0.648336,1.0,
-0.200679,0.804206,0.0,
-0.419381,0.341682,1.0,
-0.128693,0.937788,0.0,
-0.463347,0.477856,1.0,
-0.048829,0.665818,0.0,
-0.459050,0.653453,1.0,
-0.206170,0.107044,0.0,
-0.389742,0.591585,1.0,
-0.673348,0.955659,0.0,
-0.470796,0.475349,1.0,
-0.711346,0.587711,0.0,
-0.564874,0.431374,1.0,
-0.996600,0.854661,0.0,
-0.417218,0.379857,1.0,
-0.392092,0.895855,0.0,
-0.551112,0.340568,1.0,
-0.028590,0.507415,0.0,
-0.348655,0.443699,1.0,
-0.860316,0.333632,0.0,
-0.440254,0.356039,1.0,
-0.978003,0.291974,0.0,
-0.596880,0.355768,1.0,
-0.233497,0.383084,0.0,
-0.497680,0.316017,1.0,
-0.006789,0.100943,0.0,
-0.517325,0.484356,1.0,
-0.160667,0.332154,0.0,
-0.506754,0.417812,1.0,
-0.182947,0.793252,0.0,
-0.373178,0.597722,1.0,
-0.545617,0.188621,0.0,
-0.359077,0.404927,1.0,
-0.776941,0.045288,0.0,
-0.362801,0.590226,1.0,
-0.587630,0.303573,0.0,
-0.363056,0.478520,1.0,
-0.943660,0.097233,0.0,
-0.379460,0.591594,1.0,
-0.054195,0.847505,0.0,
-0.678519,0.472119,1.0,
-0.996460,0.502563,0.0,
-0.533554,0.441655,1.0,
-0.974695,0.694159,0.0,
-0.591487,0.519074,1.0,
-0.942177,0.166663,0.0,
-0.339643,0.584223,1.0,
-0.322258,0.188953,0.0,
-0.592432,0.396458,1.0,
-0.903032,0.259953,0.0,
-0.518460,0.357856,1.0,
-0.968638,0.901113,0.0,
-0.577330,0.585856,1.0,
-0.958867,0.673332,0.0,
-0.576595,0.632061,1.0,
-0.355456,0.150438,0.0,
-0.465216,0.685188,1.0,
-0.643735,0.262531,0.0,
-0.443021,0.449036,1.0,
-0.125538,0.912822,0.0,
-0.618140,0.478029,1.0,
-0.351825,0.127454,0.0,
-0.350044,0.582113,1.0,
-0.700115,0.835705,0.0,
-0.574333,0.618202,1.0,
-0.041644,0.906873,0.0,
-0.626564,0.458671,1.0,
-0.957174,0.227282,0.0,
-0.363250,0.550001,1.0,
-0.431488,0.015428,0.0,
-0.420345,0.545863,1.0,
-0.781721,0.387813,0.0,
-0.338502,0.611412,1.0,
-0.741034,0.552358,0.0,
-0.491829,0.375049,1.0,
-0.350386,0.942205,0.0,
-0.552883,0.497159,1.0,
-0.137542,0.666705,0.0,
-0.426121,0.419727,1.0,
-0.979384,0.509626,0.0,
-0.414915,0.482424,1.0,
-0.013012,0.696556,0.0,
-0.309177,0.544893,1.0,
-0.811210,0.002523,0.0,
-0.463615,0.571804,1.0,
-0.541753,0.244446,0.0,
-0.461013,0.443716,1.0,
-0.568274,0.978872,0.0,
-0.658748,0.585492,1.0,
-0.666697,0.174483,0.0,
-0.515959,0.314613,1.0,
-0.000027,0.446455,0.0,
-0.529250,0.502242,1.0,
-0.696776,0.707058,0.0,
-0.510714,0.564053,1.0,
-0.837774,0.463471,0.0,
-0.522500,0.650404,1.0,
-0.106945,0.418169,0.0,
-0.366418,0.587588,1.0,
-0.747529,0.716615,0.0,
-0.306176,0.494965,1.0,
-0.949377,0.172592,0.0,
-0.599937,0.536108,1.0,
-0.652117,0.137193,0.0,
-0.622882,0.573325,1.0,
-0.417460,0.251775,0.0,
-0.534870,0.552409,1.0,
-0.613184,0.791106,0.0,
-0.474468,0.591063,1.0,
-0.744472,0.333229,0.0,
-0.532431,0.365025,1.0,
-0.183607,0.877097,0.0,
-0.446477,0.540998,1.0,
-0.118150,0.740054,0.0,
-0.335454,0.577842,1.0,
-0.208535,0.842569,0.0,
-0.324282,0.408298,1.0,
-0.802866,0.776158,0.0,
-0.503412,0.650026,1.0,
-0.234129,0.999280,0.0,
-0.659998,0.385366,1.0,
-0.857988,0.211639,0.0,
-0.455534,0.352519,1.0,
-0.715978,0.440073,0.0,
-0.424834,0.380586,1.0,
-0.040851,0.581086,0.0,
-0.427348,0.635157,1.0,
-0.467851,0.173019,0.0,
-0.421709,0.670968,1.0,
-0.127451,0.074707,0.0,
-0.540771,0.540736,1.0,
-0.128855,0.664824,0.0,
-0.579338,0.329726,1.0,
-0.018096,0.147556,0.0,
-0.374338,0.502115,1.0,
-0.346286,0.029732,0.0,
-0.581548,0.682437,1.0,
-0.048684,0.234697,0.0,
-0.520567,0.576475,1.0,
-0.798391,0.554552,0.0,
-0.441708,0.394249,1.0,
-0.119624,0.527825,0.0,
-0.464066,0.552043,1.0,
-0.223154,0.543754,0.0,
-0.443682,0.363204,1.0,
-0.658675,0.351444,0.0,
-0.584743,0.382847,1.0,
-0.042862,0.382322,0.0,
-0.575096,0.645389,1.0,
-0.394764,0.799773,0.0,
-0.616742,0.590879,1.0,
-0.020174,0.060919,0.0,
-0.645398,0.598264,1.0,
-0.296505,0.356477,0.0,
-0.422345,0.555705,1.0,
-0.096038,0.108862,0.0,
-0.558096,0.520344,1.0,
-0.312466,0.621507,0.0,
-0.568922,0.470932,1.0,
-0.149438,0.597647,0.0,
-0.563611,0.418041,1.0,
-0.769686,0.113006,0.0,
-0.419620,0.353241,1.0,
-0.052206,0.426811,0.0,
-0.466137,0.564889,1.0,
-0.964750,0.544927,0.0,
-0.535911,0.453348,1.0,
-0.307113,0.649778,0.0,
-0.546560,0.628429,1.0,
-0.764899,0.657304,0.0,
-0.485262,0.397587,1.0,
-0.366166,0.149214,0.0,
-0.637629,0.438643,1.0,
-0.749340,0.159532,0.0,
-0.401159,0.484323,1.0,
-0.794793,0.094292,0.0,
-0.605938,0.602483,1.0,
-0.862433,0.916188,0.0,
-0.451311,0.586014,1.0,
-0.598124,0.678232,0.0,
-0.317123,0.480566,1.0,
-0.925530,0.388425,0.0,
-0.401929,0.620288,1.0,
-0.691438,0.005323,0.0,
-0.482570,0.359971,1.0,
-0.816986,0.091052,0.0,
-0.426452,0.373353,1.0,
-0.126176,0.645083,0.0,
-0.662617,0.398149,1.0,
-0.984481,0.176281,0.0,
-0.605010,0.600910,1.0,
-0.380572,0.276856,0.0,
-0.346231,0.512468,1.0,
-0.378715,0.069506,0.0,
-0.462550,0.682192,1.0,
-0.748760,0.402460,0.0,
-0.357889,0.440188,1.0,
-0.098924,0.609530,0.0,
-0.447015,0.381340,1.0,
-0.706607,0.944198,0.0,
-0.629803,0.494206,1.0,
-0.049377,0.883481,0.0,
-0.563347,0.365582,1.0,
-0.721941,0.656865,0.0,
-0.610873,0.345277,1.0,
-0.421607,0.948278,0.0,
-0.583220,0.379118,1.0,
-0.354958,0.779424,0.0,
-0.611473,0.424319,1.0,
-0.590481,0.220300,0.0,
-0.532995,0.649796,1.0,
-0.686088,0.074509,0.0,
-0.410003,0.328776,1.0,
-0.106838,0.621595,0.0,
-0.356250,0.497936,1.0,
-0.775145,0.866198,0.0,
-0.374390,0.566387,1.0,
-0.904223,0.280268,0.0,
-0.482537,0.594678,1.0,
-0.643968,0.171208,0.0,
-0.494343,0.622009,1.0,
-0.009023,0.657599,0.0,
-0.407209,0.562193,1.0,
-0.684183,0.055643,0.0,
-0.375989,0.638733,1.0,
-0.702319,0.872721,0.0,
-0.631024,0.627650,1.0,
-0.031000,0.016867,0.0,
-0.490144,0.449856,1.0,
-0.574805,0.742589,0.0,
-0.578885,0.324624,1.0,
-0.638605,0.026241,0.0,
-0.402126,0.331290,1.0,
-0.712304,0.685105,0.0,
-0.523162,0.575682,1.0,
-0.831872,0.271942,0.0,
-0.509887,0.673130,1.0,
-0.996545,0.926931,0.0,
-0.674070,0.500142,1.0,
-0.464104,0.200299,0.0,
-0.469917,0.491863,1.0,
-0.024309,0.558084,0.0,
-0.387767,0.605303,1.0,
-0.072640,0.856582,0.0,
-0.526404,0.668819,1.0,
-0.855197,0.294207,0.0,
-0.592220,0.444954,1.0,
-0.623131,0.956899,0.0,
-0.537732,0.656913,1.0,
-0.764677,0.921637,0.0,
-0.566025,0.578976,1.0,
-0.858687,0.958816,0.0,
-0.626406,0.611639,1.0,
-0.311948,0.902705,0.0,
-0.604687,0.374340,1.0,
-0.775643,0.228345,0.0,
-0.615207,0.386923,1.0,
-0.306878,0.697218,0.0,
-0.535227,0.567093,1.0,
-0.587090,0.229376,0.0,
-0.451446,0.446482,1.0,
-0.825733,0.102019,0.0,
-0.550506,0.549999,1.0,
-0.347284,0.800366,0.0,
-0.508570,0.533365,1.0,
-0.231691,0.026946,0.0,
-0.650132,0.570738,1.0,
-0.740097,0.817192,0.0,
-0.518921,0.503191,1.0,
-0.181400,0.786571,0.0,
-0.660243,0.502114,1.0,
-0.337841,0.100458,0.0,
-0.462291,0.329040,1.0,
-0.192835,0.977735,0.0,
-0.616111,0.378463,1.0,
-0.837569,0.020917,0.0,
-0.522212,0.614143,1.0,
-0.508016,0.224517,0.0,
-0.485837,0.456726,1.0,
-0.239979,0.323765,0.0,
-0.507067,0.468392,1.0,
-0.413523,0.084480,0.0,
-0.641363,0.386813,1.0,
-0.644600,0.795293,0.0,
-0.492697,0.551423,1.0,
-0.179556,0.797671,0.0,
-0.480626,0.480715,1.0,
-0.207984,0.585563,0.0,
-0.523801,0.524987,1.0,
-0.403159,0.894005,0.0,
-0.513662,0.518093,1.0,
-0.741547,0.184718,0.0,
-0.522116,0.607327,1.0,
-0.107496,0.680794,0.0,
-0.323365,0.593440,1.0,
-0.624440,0.954756,0.0,
-0.536329,0.689267,1.0,
-0.012116,0.633832,0.0,
-0.626972,0.516749,1.0,
-0.263508,0.784193,0.0,
-0.434526,0.470271,1.0,
-0.669487,0.074525,0.0,
-0.519350,0.522137,1.0,
-0.645137,0.812053,0.0,
-0.366984,0.508104,1.0,
-0.996545,0.931893,0.0,
-0.432053,0.521652,1.0,
-0.248288,0.974727,0.0,
-0.397966,0.422378,1.0,
-0.005419,0.069261,0.0,
-0.325078,0.577987,1.0,
-0.635624,0.939552,0.0,
-0.677595,0.543262,1.0,
-0.269097,0.705398,0.0,
-0.550915,0.632878,1.0,
-0.695976,0.262639,0.0,
-0.371581,0.554349,1.0,
-0.129751,0.723424,0.0,
-0.533378,0.676268,1.0,
-0.857178,0.597552,0.0,
-0.541840,0.512124,1.0,
-0.926972,0.624499,0.0,
-0.599165,0.361540,1.0,
-0.763007,0.856935,0.0,
-0.500339,0.594715,1.0,
-0.193887,0.651696,0.0,
-0.655891,0.566512,1.0,
-0.185802,0.767369,0.0,
-0.467013,0.485738,1.0,
-0.262842,0.586013,0.0,
-0.345090,0.520107,1.0,
-0.344004,0.677674,0.0,
-0.564886,0.634800,1.0,
-0.453611,0.844103,0.0,
-0.531592,0.660824,1.0,
-0.935947,0.461167,0.0,
-0.631573,0.644195,1.0,
-0.223863,0.466571,0.0,
-0.560781,0.439901,1.0,
-0.308579,0.291759,0.0,
-0.534590,0.648999,1.0,
-0.083371,0.220513,0.0,
-0.362250,0.533281,1.0,
-0.899611,0.767186,0.0,
-0.516634,0.474917,1.0,
-0.834878,0.797831,0.0,
-0.359566,0.627574,1.0,
-0.826673,0.894880,0.0,
-0.402044,0.550831,1.0,
-0.283093,0.939808,0.0,
-0.444452,0.506851,1.0,
-0.930863,0.011638,0.0,
-0.542621,0.429091,1.0,
-0.075452,0.124048,0.0,
-0.599220,0.493319,1.0,
-0.801808,0.980638,0.0,
-0.533905,0.344039,1.0,
-0.425064,0.058311,0.0,
-0.310339,0.468551,1.0,
-0.095427,0.838787,0.0,
-0.499251,0.309145,1.0,
-0.263351,0.141886,0.0,
-0.567855,0.540060,1.0,
-0.713374,0.682052,0.0,
-0.401304,0.511574,1.0,
-0.829604,0.147224,0.0,
-0.457093,0.554853,1.0,
-0.270504,0.359050,0.0,
-0.518988,0.423470,1.0,
-0.906988,0.749805,0.0,
-0.686284,0.569419,1.0,
-0.321271,0.609377,0.0,
-0.467698,0.400281,1.0,
-0.170555,0.512607,0.0,
-0.452903,0.543857,1.0,
-0.270081,0.250421,0.0,
-0.564545,0.512080,1.0,
-0.088441,0.424997,0.0,
-0.584957,0.367002,1.0,
-0.261894,0.648887,0.0,
-0.639132,0.490114,1.0,
-0.110314,0.039744,0.0,
-0.345063,0.475572,1.0,
-0.109626,0.477527,0.0,
-0.620853,0.468822,1.0,
-0.726336,0.537164,0.0,
-0.432212,0.385194,1.0,
-0.326110,0.925181,0.0,
-0.506883,0.584305,1.0,
-0.777694,0.700872,0.0,
-0.519237,0.611276,1.0,
-0.031556,0.368227,0.0,
-0.618454,0.564272,1.0,
-0.060884,0.274513,0.0,
-0.595150,0.484951,1.0,
-0.182149,0.374644,0.0,
-0.558695,0.582560,1.0,
-0.668581,0.840631,0.0,
-0.491959,0.556392,1.0,
-0.960400,0.441473,0.0,
-0.446344,0.310388,1.0,
-0.497964,0.279830,0.0,
-0.387800,0.356871,1.0,
-0.583995,0.202694,0.0,
-0.569641,0.559536,1.0,
-0.055277,0.038093,0.0,
-0.390460,0.666734,1.0,
-0.229774,0.804429,0.0,
-0.578900,0.573722,1.0,
-0.100407,0.541728,0.0,
-0.630776,0.455074,1.0,
-0.837771,0.418336,0.0,
-0.471367,0.465068,1.0,
-0.734707,0.214242,0.0,
-0.607641,0.415727,1.0,
-0.572015,0.859342,0.0,
-0.431208,0.316679,1.0,
-0.799228,0.623476,0.0,
-0.603964,0.614626,1.0,
-0.814924,0.429581,0.0,
-0.568005,0.662971,1.0,
-0.148377,0.766815,0.0,
-0.641291,0.371334,1.0,
-0.256825,0.450476,0.0,
-0.358768,0.415957,1.0,
-0.210036,0.066801,0.0,
-0.588454,0.344829,1.0,
-0.881218,0.633325,0.0,
-0.418895,0.566294,1.0,
-0.018290,0.400774,0.0,
-0.625190,0.576197,1.0,
-0.120783,0.993930,0.0,
-0.346476,0.624117,1.0,
-0.583511,0.077255,0.0,
-0.472985,0.452187,1.0,
-0.525862,0.166237,0.0,
-0.680974,0.533880,1.0,
-0.055480,0.445037,0.0,
-0.493065,0.347920,1.0,
-0.469940,0.274669,0.0,
-0.443132,0.320935,1.0,
-0.619807,0.092807,0.0,
-0.622853,0.493446,1.0,
-0.121453,0.267541,0.0,
-0.526841,0.409587,1.0,
-0.627846,0.200351,0.0,
-0.418738,0.331081,1.0,
-0.928214,0.486289,0.0,
-0.427571,0.380322,1.0,
-0.938403,0.732515,0.0,
-0.454532,0.321599,1.0,
-0.528694,0.766090,0.0,
-0.570127,0.325842,1.0,
-0.819436,0.258702,0.0,
-0.426704,0.611483,1.0,
-0.720689,0.619060,0.0,
-0.515136,0.486339,1.0,
-0.493014,0.083623,0.0,
-0.483397,0.647162,1.0,
-0.865172,0.946048,0.0,
-0.390498,0.500197,1.0,
-0.769667,0.785770,0.0,
-0.476421,0.605348,1.0,
-0.972890,0.363080,0.0,
-0.417538,0.553092,1.0,
-0.273208,0.803606,0.0,
-0.384165,0.457803,1.0,
-0.282246,0.706836,0.0,
-0.616766,0.589210,1.0,
-0.888337,0.282115,0.0,
-0.502512,0.319579,1.0,
-0.663373,0.313444,0.0,
-0.320975,0.425577,1.0,
-0.420117,0.913202,0.0,
-0.373769,0.532600,1.0,
-0.267810,0.079950,0.0,
-0.589962,0.489984,1.0,
-0.665331,0.210491,0.0,
-0.591843,0.500417,1.0,
-0.039511,0.064105,0.0,
-0.482395,0.606959,1.0,
-0.902284,0.691837,0.0,
-0.414653,0.467203,1.0,
-0.885108,0.012333,0.0,
-0.305961,0.478200,1.0,
-0.765385,0.831329,0.0,
-0.551632,0.673049,1.0,
-0.733145,0.969883,0.0,
-0.328907,0.535036,1.0,
-0.258522,0.971675,0.0,
-0.480668,0.380559,1.0,
-0.422156,0.172445,0.0,
-0.454456,0.643637,1.0,
-0.294648,0.142514,0.0,
-0.557406,0.525735,1.0,
-0.189694,0.178700,0.0,
-0.304414,0.481030,1.0,
-0.196804,0.690990,0.0,
-0.652729,0.612282,1.0,
-0.569097,0.809287,0.0,
-0.384937,0.643051,1.0,
-0.732344,0.502853,0.0,
-0.583708,0.585051,1.0,
-0.452201,0.143736,0.0,
-0.405139,0.574132,1.0,
-0.181806,0.614178,0.0,
-0.401716,0.633272,1.0,
-0.115493,0.090117,0.0,
-0.562412,0.655726,1.0,
-0.746172,0.913846,0.0,
-0.879946,0.246769,1.0,
-0.405356,0.825592,0.0,
-0.623824,0.401991,1.0,
-0.615042,0.008255,0.0,
-0.457688,0.555390,1.0,
-0.316407,0.852548,0.0,
-0.398669,0.629339,1.0,
-0.721289,0.712047,0.0,
-0.664888,0.566238,1.0,
-0.071650,0.216554,0.0,
-0.468747,0.434343,1.0,
-0.875498,0.498666,0.0,
-0.582925,0.228318,1.0,
-0.974858,0.440346,0.0,
-0.357434,0.391327,1.0,
-0.469040,0.148895,0.0,
-0.630200,0.374657,1.0,
-0.394411,0.859285,0.0,
-0.841466,0.517289,1.0,
-0.904910,0.827269,0.0,
-0.524898,0.569063,1.0,
-0.212488,0.290651,0.0,
-0.514752,0.445046,1.0,
-0.041282,0.834401,0.0,
-0.564216,0.583361,1.0,
-0.867708,0.571124,0.0,
-0.598632,0.615127,1.0,
-0.834877,0.785489,0.0,
-0.391736,0.514166,1.0,
-0.956473,0.437717,0.0,
-0.575751,0.440173,1.0,
-0.229433,0.085584,0.0,
-0.581056,0.407739,1.0,
-0.935412,0.477567,0.0,
-0.487415,0.582993,1.0,
-0.618081,0.081466,0.0,
-0.680167,0.558636,1.0,
-0.810301,0.734193,0.0,
-0.340692,0.608583,1.0,
-0.787452,0.702432,0.0,
-0.519672,0.334945,1.0,
-0.523995,0.779523,0.0,
-0.345070,0.595767,1.0,
-0.188268,0.215296,0.0,
-0.374845,0.614656,1.0,
-0.296483,0.985421,0.0,
-0.554133,0.313853,1.0,
-0.947415,0.210335,0.0,
-0.699623,0.560696,1.0,
-0.153820,0.249558,0.0,
-0.576243,0.316039,1.0,
-0.369503,0.235935,0.0,
-0.581708,0.565623,1.0,
-0.045140,0.662190,0.0,
-0.411630,0.460408,1.0,
-0.826473,0.531350,0.0,
-0.653345,0.569818,1.0,
-0.338548,0.980895,0.0,
-0.582919,0.520698,1.0,
-0.054083,0.970617,0.0,
-0.644274,0.306261,1.0,
-0.080232,0.450881,0.0,
-0.382910,0.565265,1.0,
-0.372033,0.762242,0.0,
-0.384477,0.507017,1.0,
-0.174034,0.981251,0.0,
-0.469877,0.627298,1.0,
-0.738775,0.599351,0.0,
-0.649079,0.463296,1.0,
-0.209047,0.460098,0.0,
-0.511516,0.441752,1.0,
-0.867494,0.965641,0.0,
-0.485732,0.304177,1.0,
-0.841083,0.083513,0.0,
-0.397566,0.489337,1.0,
-0.144523,0.998209,0.0,
-0.551973,0.404518,1.0,
-0.069126,0.804411,0.0,
-0.585921,0.581702,1.0,
-0.215644,0.333978,0.0,
-0.379419,0.900808,1.0,
-0.966404,0.359757,0.0,
-0.454717,0.632327,1.0,
-0.350990,0.093256,0.0,
-0.628872,0.454666,1.0,
-0.904544,0.667705,0.0,
-0.772335,0.635280,1.0,
-0.114174,0.924485,0.0,
-0.568379,0.537478,1.0,
-0.152145,0.100182,0.0,
-0.471849,0.566096,1.0,
-0.552673,0.768234,0.0,
-0.656502,0.424081,1.0,
-0.944100,0.489904,0.0,
-0.615383,0.534344,1.0,
-0.885572,0.812429,0.0,
-0.602238,0.408687,1.0,
-0.585839,0.188644,0.0,
-0.635652,0.409614,1.0,
-0.087451,0.793415,0.0,
-0.497719,0.565521,1.0,
-0.803954,0.062134,0.0,
-0.411973,0.627926,1.0,
-0.957739,0.722147,0.0,
-0.055906,0.617560,1.0,
-0.034428,0.635904,0.0,
-0.677091,0.476239,1.0,
-0.853626,0.884726,0.0,
-0.453700,0.327606,1.0,
-0.860425,0.164345,0.0,
-0.314643,0.212194,1.0,
-0.035032,0.780420,0.0,
-0.417817,0.446258,1.0,
-0.033586,0.472985,0.0,
-0.528747,0.442885,1.0,
-0.272239,0.516251,0.0,
-0.441969,0.366419,1.0,
-0.254882,0.803692,0.0,
-0.534728,0.569149,1.0,
-0.037691,0.466437,0.0,
-0.442776,0.339594,1.0,
-0.606824,0.895025,0.0,
-0.539426,0.333260,1.0,
-0.427009,0.747850,0.0,
-0.488967,0.064351,1.0,
-0.299705,0.139920,0.0,
-0.546967,0.681311,1.0,
-0.909381,0.964100,0.0,
-0.518170,0.685334,1.0,
-0.531015,0.764325,0.0,
-0.981520,0.404001,1.0,
-0.123274,0.859042,0.0,
-0.524725,0.453387,1.0,
-0.283159,0.052225,0.0,
-0.696323,0.501141,1.0,
-0.132730,0.792253,0.0,
-0.490385,0.507841,1.0,
-0.163906,0.765319,0.0,
-0.443888,0.619275,1.0,
-0.256141,0.963213,0.0,
-0.498139,0.423989,1.0,
-0.413145,0.725132,0.0,
-0.090060,0.645305,1.0,
-0.319054,0.337475,0.0,
-0.421886,0.442878,1.0,
-0.852805,0.097066,0.0,
-0.610950,0.436863,1.0,
-0.814083,0.286228,0.0,
-0.476503,0.384783,1.0,
-0.025437,0.512024,0.0,
-0.616878,0.460864,1.0,
-0.123869,0.874306,0.0,
-0.494375,0.359480,1.0,
-0.957622,0.757752,0.0,
-0.454741,0.624159,1.0,
-0.070147,0.955977,0.0,
-0.549424,0.162459,1.0,
-0.950685,0.166302,0.0,
-0.824372,0.226379,1.0,
-0.117992,0.087236,0.0,
-0.470198,0.618468,1.0,
-0.873867,0.074677,0.0,
-0.753688,0.233551,1.0,
-0.038005,0.752899,0.0,
-0.529085,0.534518,1.0,
-0.530495,0.027850,0.0,
-0.583223,0.224186,1.0,
-0.565017,0.248258,0.0,
-0.345067,0.537817,1.0,
-0.178149,0.142704,0.0,
-0.537401,0.695041,1.0,
-0.162366,0.886256,0.0,
-0.355054,0.388385,1.0,
-0.164146,0.801902,0.0,
-0.559973,0.460312,1.0,
-0.704511,0.721021,0.0,
-0.931611,0.589725,1.0,
-0.371450,0.965772,0.0,
-0.453465,0.389005,1.0,
-0.267568,0.011976,0.0,
-0.451801,0.418380,1.0,
-0.784802,0.173811,0.0,
-0.465417,0.266825,1.0,
-0.241640,0.238648,0.0,
-0.333137,0.428206,1.0,
-0.987379,0.874628,0.0,
-0.493597,0.488482,1.0,
-0.321947,0.968201,0.0,
-0.335063,0.396892,1.0,
-0.770390,0.946485,0.0,
-0.354347,0.504130,1.0,
-0.924030,0.168983,0.0,
-0.389848,0.183133,1.0,
-0.381096,0.084065,0.0,
-0.444379,0.472059,1.0,
-0.843885,0.183378,0.0,
-0.877811,0.373620,1.0,
-0.968989,0.790167,0.0,
-0.420536,0.947543,1.0,
-0.645089,0.006383,0.0,
-0.241576,0.176014,1.0,
-0.014615,0.640202,0.0,
-0.365929,0.361652,1.0,
-0.725829,0.003636,0.0,
-0.193872,0.407286,1.0,
-0.161241,0.982216,0.0,
-0.013299,0.509961,1.0,
-0.019911,0.649946,0.0,
-0.525460,0.399021,1.0,
-0.032612,0.105386,0.0,
-0.245979,0.167503,1.0,
-0.001673,0.120184,0.0,
-0.580269,0.386901,1.0,
-0.789375,0.022668,0.0,
-0.647373,0.593004,1.0,
-0.931700,0.084029,0.0,
-0.606828,0.952760,1.0,
-0.795984,0.095119,0.0,
-0.417538,0.555252,1.0,
-0.937476,0.156507,0.0,
-0.364186,0.870741,1.0,
-0.978524,0.057594,0.0,
-0.407596,0.674042,1.0,
-0.930735,0.870735,0.0,
-0.486841,0.331864,1.0,
-0.593609,0.789154,0.0,
-0.951482,0.560123,1.0,
-0.236184,0.539669,0.0,
-0.628044,0.537961,1.0,
-0.142493,0.884043,0.0,
-0.620715,0.352779,1.0,
-0.074911,0.028430,0.0,
-0.489197,0.528933,1.0,
-0.218302,0.009618,0.0,
-0.426510,0.546437,1.0,
-0.225407,0.420298,0.0,
-0.365265,0.415825,1.0,
-0.850584,0.758343,0.0,
-0.168584,0.390215,1.0,
-0.788305,0.049505,0.0,
-0.313275,0.208350,1.0,
-0.957242,0.372561,0.0,
-0.400443,0.439347,1.0,
-0.769617,0.945962,0.0,
-0.513709,0.508194,1.0,
-0.878355,0.510186,0.0,
-0.581633,0.498384,1.0,
-0.521926,0.006088,0.0,
-0.716458,0.517473,1.0,
-0.011073,0.103235,0.0,
-0.956476,0.487008,1.0,
-0.911404,0.965206,0.0,
-0.219724,0.897317,1.0,
-0.004504,0.706559,0.0,
-0.736340,0.671454,1.0,
-0.945439,0.990614,0.0,
-0.660692,0.255886,1.0,
-0.400083,0.189065,0.0,
-0.419874,0.625991,1.0,
-0.755575,0.942924,0.0,
-0.640259,0.635181,1.0,
-0.144754,0.882663,0.0,
-0.541383,0.423158,1.0,
-0.592208,0.237253,0.0,
-0.099795,0.259476,1.0,
-0.137807,0.129051,0.0,
-0.409865,0.408605,1.0,
-0.911043,0.904544,0.0,
-0.533197,0.446246,1.0,
-0.563767,0.235027,0.0,
-0.981762,0.471788,1.0,
-0.945970,0.918740,0.0,
-0.322119,0.860131,1.0,
-0.098761,0.882134,0.0,
-0.815829,0.639433,1.0,
-0.617055,0.839638,0.0,
-0.584967,0.542557,1.0,
-0.484145,0.029928,0.0,
-0.661426,0.383093,1.0,
-0.132325,0.984776,0.0,
-0.143849,0.672404,1.0,
-0.766291,0.048115,0.0,
-0.554840,0.589840,1.0,
-0.038372,0.910499,0.0,
-0.582275,0.500996,1.0,
-0.900568,0.839410,0.0,
-0.477616,0.697542,1.0,
-0.926303,0.374743,0.0,
-0.280662,0.091178,1.0,
-0.017274,0.330422,0.0,
-0.791092,0.880150,1.0,
-0.879693,0.002741,0.0,
-0.442206,0.154711,1.0,
-0.389956,0.994444,0.0,
-0.410049,0.694647,1.0,
-0.347576,0.703265,0.0,
-0.465677,0.440112,1.0,
-0.652229,0.004808,0.0,
-0.514781,0.522836,1.0,
-0.036767,0.934835,0.0,
-0.440543,0.413782,1.0,
-0.721779,0.936661,0.0,
-0.211234,0.209480,1.0,
-0.996174,0.698065,0.0,
-0.345853,0.749659,1.0,
-0.751710,0.995673,0.0,
-0.382293,0.199352,1.0,
-0.929626,0.227697,0.0,
-0.486144,0.418016,1.0,
-0.239993,0.569067,0.0,
-0.358594,0.884608,1.0,
-0.301421,0.982541,0.0,
-0.734173,0.246906,1.0,
-0.045225,0.099816,0.0,
-0.420708,0.843753,1.0,
-0.439410,0.156902,0.0,
-0.840151,0.412602,1.0,
-0.304693,0.969265,0.0,
-0.763800,0.185977,1.0,
-0.337129,0.129134,0.0,
-0.593296,0.529943,1.0,
-0.753442,0.092572,0.0,
-0.591360,0.386479,1.0,
-0.953472,0.997589,0.0,
-0.726388,0.408451,1.0,
-0.264263,0.473766,0.0,
-0.693146,0.697763,1.0,
-0.956154,0.073234,0.0,
-0.637659,0.531983,1.0,
-0.888202,0.011427,0.0,
-0.118306,0.363218,1.0,
-0.930444,0.973251,0.0,
-0.611187,0.214761,1.0,
-0.138341,0.101067,0.0,
-0.625997,0.137609,1.0,
-0.887989,0.434361,0.0,
-0.330678,0.705902,1.0,
-0.097994,0.981358,0.0,
-0.404066,0.145524,1.0,
-0.082781,0.306381,0.0,
-0.541522,0.362222,1.0,
-0.872846,0.922234,0.0,
-0.502499,0.505650,1.0,
-0.268146,0.737022,0.0,
-0.625160,0.072308,1.0,
-0.182311,0.094073,0.0,
-0.887703,0.627069,1.0,
-0.050757,0.072919,0.0,
-0.666354,0.409134,1.0,
-0.086700,0.169794,0.0,
-0.755802,0.759758,1.0,
-0.024500,0.769024,0.0,
-0.600649,0.510537,1.0,
-0.895933,0.945365,0.0,
-0.217063,0.179000,1.0,
-0.942755,0.889979,0.0,
-0.658056,0.545803,1.0,
-0.117340,0.125701,0.0,
-0.211985,0.830197,1.0,
-0.963884,0.000637,0.0,
-0.120763,0.661451,1.0,
-0.628235,0.741286,0.0,
-0.603743,0.511148,1.0,
-0.510555,0.902257,0.0,
-0.106117,0.514867,1.0,
-0.287857,0.006321,0.0,
-0.936255,0.643475,1.0,
-0.480633,0.995217,0.0,
-0.666771,0.412826,1.0,
-0.950139,0.979435,0.0,
-0.226955,0.427143,1.0,
-0.269895,0.128991,0.0,
-0.423627,0.500545,1.0,
-0.920506,0.936305,0.0,
-0.585071,0.280128,1.0,
-0.114891,0.978174,0.0,
-0.450278,0.829103,1.0,
-0.075372,0.773256,0.0,
-0.521958,0.541043,1.0,
-0.928955,0.946933,0.0,
-0.517246,0.357111,1.0,
-0.122010,0.627887,0.0,
-0.507057,0.313461,1.0,
-0.976809,0.234234,0.0,
-0.696935,0.379450,1.0,
-0.143698,0.135893,0.0,
-0.477382,0.362440,1.0,
-0.941885,0.264061,0.0,
-0.669022,0.247968,1.0,
-0.774974,0.986923,0.0,
-0.410227,0.685447,1.0,
-0.027209,0.294850,0.0,
-0.489948,0.552992,1.0,
-0.126137,0.989757,0.0,
-0.357972,0.633194,1.0,
-0.112696,0.074596,0.0,
-0.497401,0.824496,1.0,
-0.847685,0.049361,0.0,
-0.569233,0.105513,1.0,
-0.914562,0.050099,0.0,
-0.889241,0.478381,1.0,
-0.012921,0.158276,0.0,
-0.630551,0.669130,1.0,
-0.762005,0.013148,0.0,
-0.320818,0.587345,1.0,
-0.125245,0.993917,0.0,
-0.477653,0.909311,1.0,
-0.880651,0.105519,0.0,
-0.257706,0.264987,1.0,
-0.346165,0.994367,0.0,
-0.457976,0.202125,1.0,
-0.930626,0.033072,0.0,
-0.334768,0.446288,1.0,
-0.929385,0.165538,0.0,
-0.552541,0.554389,1.0,
-0.813765,0.947385,0.0,
-0.357804,0.607932,1.0,
-0.786155,0.911761,0.0,
-0.543196,0.498041,1.0,
-0.011605,0.048258,0.0,
-0.928148,0.390362,1.0,
-0.831850,0.901363,0.0,
-0.398240,0.217703,1.0,
-0.219761,0.515483,0.0,
-0.656288,0.239971,1.0,
-0.130190,0.102782,0.0,
-0.109284,0.728167,1.0,
-0.217831,0.085059,0.0,
-0.169707,0.259614,1.0,
-0.880712,0.120998,0.0,
-0.803866,0.578254,1.0,
-0.058670,0.063469,0.0,
-0.798813,0.648459,1.0,
-0.261916,0.028724,0.0,
-0.268387,0.777166,1.0,
-0.200508,0.942878,0.0,
-0.317122,0.470676,1.0,
-0.789016,0.999369,0.0,
-0.922785,0.252581,1.0,
-0.163855,0.912879,0.0,
-0.583631,0.084469,1.0,
-0.914082,0.977035,0.0,
-0.131685,0.235649,1.0,
-0.014080,0.648713,0.0,
-0.122706,0.318416,1.0,
-0.754447,0.994063,0.0,
-0.491183,0.319144,1.0,
-0.994730,0.425747,0.0,
-0.503721,0.034518,1.0,
-0.918893,0.833189,0.0,
-0.349797,0.035132,1.0,
-0.997089,0.077616,0.0,
-0.310329,0.702151,1.0,
-0.843400,0.030109,0.0,
-0.676913,0.884336,1.0,
-0.257453,0.016223,0.0,
-0.831356,0.602485,1.0,
-0.049903,0.724476,0.0,
-0.623855,0.138282,1.0,
-0.577557,0.001957,0.0,
-0.533017,0.412328,1.0,
-0.010497,0.419981,0.0,
-0.546263,0.048819,1.0,
-0.942819,0.963303,0.0,
-0.120236,0.810899,1.0,
-0.036111,0.917029,0.0,
-0.911248,0.339162,1.0,
-0.039100,0.145399,0.0,
-0.727224,0.461033,1.0,
-0.088764,0.862579,0.0,
-0.359690,0.317265,1.0,
-0.068493,0.166207,0.0,
-0.439221,0.994243,1.0,
-0.019894,0.353342,0.0,
-0.620850,0.633148,1.0,
-0.917368,0.208521,0.0,
-0.611112,0.962159,1.0,
-0.998946,0.284246,0.0,
-0.325835,0.303890,1.0,
-0.184626,0.014341,0.0,
-0.030134,0.464398,1.0,
-0.041764,0.928448,0.0,
-0.421700,0.518216,1.0,
-0.014991,0.953789,0.0,
-0.332199,0.267473,1.0,
-0.994636,0.839146,0.0,
-0.528376,0.414779,1.0,
-0.186524,0.903133,0.0,
-0.957133,0.529052,1.0,
-0.851142,0.144030,0.0,
-0.714318,0.539568,1.0,
-0.199557,0.961182,0.0,
-0.578973,0.795513,1.0,
-0.313500,0.001165,0.0,
-0.587696,0.408485,1.0,
-0.096504,0.946517,0.0,
-0.110874,0.455930,1.0,
-0.817880,0.109276,0.0,
-0.597780,0.881882,1.0,
-0.961559,0.919498,0.0,
-0.596038,0.610658,1.0,
-0.923438,0.224070,0.0,
-0.671048,0.308828,1.0,
-0.118400,0.955595,0.0,
-0.682076,0.656114,1.0,
-0.188140,0.075935,0.0,
-0.238365,0.205474,1.0,
-0.944235,0.765077,0.0,
-0.644504,0.183045,1.0,
-0.242399,0.003331,0.0,
-0.347279,0.722110,1.0,
-0.024468,0.236346,0.0,
-0.263387,0.742184,1.0,
-0.881064,0.039573,0.0,
-0.111117,0.540144,1.0,
-0.207236,0.013479,0.0,
-0.545074,0.063027,1.0,
-0.971217,0.249862,0.0,
-0.431988,0.420792,1.0,
-0.910343,0.141753,0.0,
-0.450565,0.638775,1.0,
-0.891530,0.940947,0.0,
-0.496298,0.279163,1.0,
-0.858457,0.079746,0.0,
-0.295163,0.801601,1.0,
-0.951444,0.918963,0.0,
-0.544912,0.333839,1.0,
-0.330221,0.028977,0.0,
-0.988245,0.439008,1.0,
-0.025281,0.903245,0.0,
-0.835923,0.358936,1.0,
-0.731661,0.028488,0.0,
-0.542520,0.138225,1.0,
-0.028965,0.814396,0.0,
-0.558204,0.731007,1.0,
-0.031578,0.737875,0.0,
-0.457408,0.655653,1.0,
-0.126905,0.893022,0.0,
-0.177636,0.526608,1.0,
-0.261614,0.945231,0.0,
-0.505618,0.921040,1.0,
-0.062342,0.777818,0.0,
-0.794950,0.719452,1.0,
-0.962745,0.862574,0.0,
-0.275704,0.757360,1.0,
-0.942172,0.082874,0.0,
-0.855548,0.199977,1.0,
-0.977373,0.712632,0.0,
-0.674471,0.829645,1.0,
-0.039986,0.047675,0.0,
-0.276544,0.866683,1.0,
-0.102344,0.087708,0.0,
-0.113547,0.386850,1.0,
-0.033325,0.091072,0.0,
-0.640266,0.952417,1.0,
-0.994446,0.659034,0.0,
-0.384306,0.037395,1.0,
-0.005494,0.334135,0.0,
-0.805400,0.360326,1.0,
-0.992660,0.632722,0.0,
-0.162441,0.149291,1.0,
-0.128287,0.119189,0.0,
-0.215053,0.397362,1.0,
-0.709884,0.014469,0.0,
-0.853737,0.749867,1.0,
-0.938412,0.895014,0.0,
-0.505426,0.687302,1.0,
-0.924903,0.851972,0.0,
-0.087552,0.489976,1.0,
-0.026433,0.260008,0.0,
-0.541420,0.642988,1.0,
-0.811742,0.944236,0.0,
-0.337649,0.869091,1.0,
-0.809066,0.975218,0.0,
-0.483508,0.320927,1.0,
-0.262453,0.045015,0.0,
-0.564754,0.823768,1.0,
-0.075147,0.000265,0.0,
-0.456812,0.640374,1.0,
-0.074194,0.980533,0.0,
-0.810564,0.151215,1.0,
-0.259895,0.047420,0.0,
-0.228018,0.296724,1.0,
-0.210451,0.057877,0.0,
-0.744225,0.189440,1.0,
-0.991508,0.271740,0.0,
-0.914486,0.767146,1.0,
-0.917228,0.845051,0.0,
-0.777546,0.222120,1.0,
-0.162907,0.971755,0.0,
-0.279074,0.394329,1.0,
-0.683170,0.033180,0.0,
-0.659231,0.692583,1.0,
-0.340988,0.987070,0.0,
-0.693728,0.481280,1.0,
-0.999439,0.575173,0.0,
-0.165133,0.382019,1.0,
-0.953358,0.096235,0.0,
-0.426992,0.458204,1.0,
-0.028101,0.291598,0.0,
-0.881618,0.360614,1.0,
-0.839178,0.065719,0.0,
-0.536237,0.536735,1.0,
-0.155589,0.983774,0.0,
-0.288880,0.199954,1.0,
-0.659666,0.012821,0.0,
-0.483667,0.998617,1.0,
-0.863454,0.064906,0.0,
-0.876617,0.294342,1.0,
-0.000088,0.479419,0.0,
-0.588093,0.074055,1.0,
-0.634437,0.990888,0.0,
-0.854992,0.843803,1.0,
-0.998120,0.397708,0.0,
-0.276812,0.379376,1.0,
-0.188139,0.060462,0.0,
-0.188158,0.373752,1.0,
-0.058068,0.946587,0.0,
-0.295442,0.489392,1.0,
-0.968927,0.760545,0.0,
-0.480603,0.495559,1.0,
-0.812454,0.910763,0.0,
-0.188515,0.365017,1.0,
-0.796868,0.958435,0.0,
-0.412762,0.296200,1.0,
-0.911058,0.149830,0.0,
-0.194291,0.440672,1.0,
-0.756176,0.043972,0.0,
-0.044182,0.570502,1.0,
-0.151190,0.053472,0.0,
-0.696903,0.844455,1.0,
-0.759802,0.984834,0.0,
-0.106102,0.261543,1.0,
-0.720476,0.047371,0.0,
-0.166908,0.230555,1.0,
-0.089794,0.162502,0.0,
-0.168096,0.191396,1.0,
-0.903734,0.057500,0.0,
-0.410767,0.760537,1.0,
-0.168441,0.989147,0.0,
-0.596454,0.610492,1.0,
-0.122813,0.113825,0.0,
-0.049057,0.505411,1.0,
-0.204018,0.924369,0.0,
-0.400699,0.556140,1.0,
-0.030652,0.175891,0.0,
-0.077273,0.723587,1.0,
-0.033845,0.828133,0.0,
-0.428513,0.021114,1.0,
-0.133167,0.139484,0.0,
-0.314029,0.884728,1.0,
-0.143032,0.946275,0.0,
-0.039244,0.579338,1.0,
-0.823471,0.076938,0.0,
-0.696073,0.892183,1.0,
-0.255250,0.978455,0.0,
-0.890776,0.269625,1.0,
-0.082993,0.861697,0.0,
-0.544287,0.837394,1.0,
-0.088525,0.845530,0.0,
-0.463237,0.617699,1.0,
-0.867130,0.848723,0.0,
-0.479084,0.967820,1.0,
-0.158151,0.046461,0.0,
-0.069118,0.663160,1.0,
-0.871719,0.979415,0.0,
-0.029540,0.474093,1.0,
-0.342059,0.992807,0.0,
-0.298816,0.201628,1.0,
-0.766113,0.063709,0.0,
-0.758880,0.489155,1.0,
-0.022727,0.979280,0.0,
-0.759180,0.545592,1.0,
-0.568394,0.995931,0.0,
-0.604858,0.850998,1.0,
-0.015152,0.657291,0.0,
-0.658542,0.113485,1.0,
-0.001818,0.550514,0.0,
-0.491377,0.576980,1.0,
-0.861425,0.974854,0.0,
-0.368249,0.165556,1.0,
-0.960782,0.860958,0.0,
-0.125851,0.172823,1.0,
-0.143039,0.061745,0.0,
-0.747771,0.780261,1.0,
-0.851081,0.119214,0.0,
-0.630187,0.556258,1.0,
-0.032898,0.913446,0.0,
-0.282458,0.266590,1.0,
-0.571012,0.003378,0.0,
-0.772991,0.665147,1.0,
-0.121549,0.869231,0.0,
-0.170058,0.169971,1.0,
-0.282205,0.022193,0.0,
-0.633447,0.336953,1.0,
-0.052593,0.935705,0.0,
-0.395305,0.895689,1.0,
-0.089970,0.119492,0.0,
-0.825055,0.703334,1.0,
-0.197245,0.088506,0.0,
-0.512111,0.054782,1.0,
-0.720001,0.050316,0.0,
-0.667851,0.568712,1.0,
-0.012315,0.981350,0.0,
-0.554788,0.317098,1.0,
-0.027441,0.203174,0.0,
-0.748764,0.473312,1.0,
-0.956319,0.845452,0.0,
-0.504170,0.583348,1.0,
-0.129819,0.873712,0.0,
-0.481394,0.788506,1.0,
-0.998485,0.542034,0.0,
-0.211623,0.755620,1.0,
-0.993474,0.317520,0.0,
-0.561899,0.835415,1.0,
-0.917599,0.091824,0.0,
-0.293909,0.722136,1.0,
-0.938704,0.797640,0.0,
-0.214240,0.728639,1.0,
-0.238952,0.061706,0.0,
-0.840349,0.748161,1.0,
-0.236566,0.972608,0.0,
-0.629958,0.711860,1.0,
-0.956554,0.798339,0.0,
-0.689441,0.430245,1.0,
-0.134518,0.851342,0.0,
-0.502936,0.839158,1.0,
-0.991113,0.633954,0.0,
-0.864808,0.827251,1.0,
-0.112104,0.125919,0.0,
-0.324011,0.657565,1.0,
-0.991950,0.702150,0.0,
-0.298700,0.255092,1.0,
-0.106257,0.859592,0.0,
-0.163643,0.353149,1.0,
-0.007850,0.928747,0.0,
-0.443991,0.158397,1.0,
-0.931810,0.931475,0.0,
-0.304472,0.265553,1.0,
-0.151001,0.879151,0.0,
-0.890796,0.615010,1.0,
-0.970845,0.987655,0.0,
-0.523107,0.867080,1.0,
-0.020716,0.175712,0.0,
-0.191548,0.349626,1.0,
-0.165046,0.933115,0.0,
-0.595785,0.364806,1.0,
-0.293868,0.041359,0.0,
-0.124202,0.468689,1.0,
-0.977398,0.133495,0.0,
-0.655956,0.644766,1.0,
-0.990136,0.211899,0.0,
-0.387023,0.690900,1.0,
-0.961280,0.229977,0.0,
-0.218073,0.145638,1.0,
-0.906693,0.796597,0.0,
-0.409585,0.888000,1.0,
-0.015999,0.889403,0.0,
-0.188104,0.469462,1.0,
-0.139755,0.869633,0.0,
-0.926602,0.400164,1.0,
-0.014326,0.778336,0.0,
-0.499140,0.048989,1.0,
-0.050270,0.896233,0.0,
-0.638655,0.875770,1.0,
-0.068233,0.789513,0.0,
-0.351185,0.363543,1.0,
-0.920811,0.067366,0.0,
-0.363431,0.179125,1.0,
-0.988460,0.050368,0.0,
-0.542401,0.127310,1.0,
-0.005418,0.067648,0.0,
-0.852362,0.640062,1.0,
-0.159339,0.004219,0.0,
-0.914176,0.559182,1.0,
-0.927352,0.000149,0.0,
-0.503241,0.966924,1.0,
-0.095488,0.868745,0.0,
-0.917518,0.718688,1.0,
-0.988979,0.768357,0.0,
-0.773058,0.785836,1.0,
-0.694414,0.018334,0.0,
-0.146291,0.708061,1.0,
-0.055219,0.071841,0.0,
-0.439289,0.128151,1.0,
-0.174038,0.053841,0.0,
-0.906031,0.664057,1.0,
-0.064673,0.966562,0.0,
-0.015385,0.581119,1.0,
-0.873510,0.076282,0.0,
-0.178341,0.370636,1.0,
-0.980110,0.711202,0.0,
-0.171288,0.843944,1.0,
-0.162785,0.933768,0.0,
-0.839041,0.768429,1.0,
-0.986208,0.195300,0.0,
-0.413367,0.457387,1.0,
-0.013748,0.068144,0.0,
-0.217799,0.542433,1.0,
-0.670018,0.988510,0.0,
-0.468593,0.646913,1.0,
-0.874749,0.913058,0.0,
-0.772499,0.397488,1.0,
-0.050446,0.851877,0.0,
-0.503871,0.555470,1.0,
-0.969240,0.009800,0.0,
-0.716640,0.563629,1.0,
-0.918070,0.001059,0.0,
-0.804153,0.393482,1.0,
-0.258224,0.974562,0.0,
-0.459018,0.720263,1.0,
-0.049617,0.919617,0.0,
-0.045077,0.606319,1.0,
-0.994422,0.251836,0.0,
-0.604727,0.648087,1.0,
-0.988993,0.002686,0.0,
-0.145973,0.364788,1.0,
-0.974047,0.804619,0.0,
-0.234135,0.102831,1.0,
-0.994031,0.677380,0.0,
-0.732713,0.708835,1.0,
-0.977408,0.303210,0.0,
-0.413426,0.445380,1.0,
-0.202471,0.933451,0.0,
-0.505308,0.713507,1.0,
-0.278933,0.020231,0.0,
-0.702568,0.065049,1.0,
-0.001095,0.410415,0.0,
-0.836703,0.468903,1.0,
-0.946204,0.851428,0.0,
-0.882221,0.489963,1.0,
-0.805322,0.054418,0.0,
-0.597330,0.328661,1.0,
-0.848396,0.999084,0.0,
-0.603176,0.577848,1.0,
-0.894040,0.136817,0.0,
-0.481072,0.373778,1.0,
-0.979452,0.651814,0.0,
-0.871915,0.279952,1.0,
-0.006854,0.199287,0.0,
-0.424963,0.356200,1.0,
-0.049268,0.042359,0.0,
-0.372373,0.478700,1.0,
-0.160401,0.867743,0.0,
-0.153319,0.839381,1.0,
-0.856369,0.989789,0.0,
-0.387445,0.781246,1.0,
-0.056989,0.820975,0.0,
-0.118849,0.498164,1.0,
-0.865883,0.889807,0.0,
-0.066794,0.604647,1.0,
-0.986385,0.173657,0.0,
-0.644796,0.085169,1.0,
-0.697211,0.032571,0.0,
-0.422637,0.264128,1.0,
-0.967048,0.175056,0.0,
-0.169046,0.159561,1.0,
-0.899201,0.874863,0.0,
-0.821816,0.260574,1.0,
-0.043188,0.863019,0.0,
-0.759249,0.692132,1.0,
-0.995609,0.204616,0.0,
-0.987953,0.524650,1.0,
-0.263342,0.989937,0.0,
-0.863481,0.522819,1.0,
-0.021063,0.006138,0.0,
-0.160657,0.157712,1.0,
-0.100021,0.054952,0.0,
-0.572767,0.494859,1.0,
-0.721665,0.028593,0.0,
-0.555164,0.633683,1.0,
-0.038149,0.165033,0.0,
-0.716051,0.666645,1.0,
-0.773191,0.017376,0.0,
-0.044655,0.518860,1.0,
-0.012058,0.655223,0.0,
-0.332400,0.652386,1.0,
-0.092117,0.203093,0.0,
-0.378202,0.444315,1.0,
-0.836201,0.036358,0.0,
-0.063933,0.519082,1.0,
-0.214082,0.074013,0.0,
-0.936591,0.292470,1.0,
-0.005902,0.199849,0.0,
-0.856200,0.151642,1.0,
-0.984484,0.227043,0.0,
-0.916619,0.608395,1.0,
-0.075158,0.184381,0.0,
-0.883278,0.255578,1.0,
-0.043129,0.862668,0.0,
-0.862244,0.731060,1.0,
-0.918486,0.994640,0.0,
-0.919335,0.264553,1.0,
-0.354494,0.982333,0.0,
-0.073404,0.708114,1.0,
-0.330576,0.986527,0.0,
-0.555714,0.880609,1.0,
-0.408580,0.999032,0.0,
-0.724541,0.356524,1.0,
-0.888081,0.979827,0.0,
-0.465407,0.088650,1.0,
-0.944555,0.130875,0.0,
-0.609744,0.969968,1.0,
-0.841501,0.109205,0.0,
-0.415325,0.363623,1.0,
-0.085789,0.848949,0.0,
-0.278340,0.066310,1.0,
-0.140358,0.000242,0.0,
-0.460362,0.301961,1.0,
-0.052066,0.074666,0.0,
-0.916956,0.283084,1.0,
-0.273692,0.946850,0.0,
-0.700522,0.664987,1.0,
-0.033748,0.209123,0.0,
-0.736584,0.770304,1.0,
-0.171173,0.898285,0.0,
-0.474040,0.183854,1.0,
-0.040295,0.234907,0.0,
-0.075609,0.762576,1.0,
-0.842976,0.900349,0.0,
-0.169172,0.273236,1.0,
-0.054248,0.748305,0.0,
-0.759181,0.551595,1.0,
-0.257632,0.016274,0.0,
-0.514309,0.997092,1.0,
-0.138933,0.041832,0.0,
-0.068679,0.293593,1.0,
-0.994593,0.119637,0.0,
-0.733361,0.595088,1.0,
-0.029465,0.226084,0.0,
-0.794447,0.264038,1.0,
-0.216578,0.029339,0.0,
-0.125293,0.805606,1.0,
-0.095797,0.067051,0.0,
-0.927620,0.516293,1.0,
-0.331586,0.973254,0.0,
-0.484913,0.940956,1.0,
-0.977104,0.193217,0.0,
-0.400257,0.119210,1.0,
-0.068848,0.122606,0.0,
-0.640568,0.025595,1.0,
-0.177422,0.924818,0.0,
-0.424285,0.964239,1.0,
-0.967101,0.069368,0.0,
-0.860560,0.424123,1.0,
-0.233298,0.039769,0.0,
-0.392636,0.024876,1.0,
-0.780629,0.030907,0.0,
-0.461336,0.682278,1.0,
-0.917832,0.995977,0.0,
-0.393373,0.414692,1.0,
-0.847088,0.014273,0.0,
-0.248128,0.288193,1.0,
-0.287322,0.017376,0.0,
-0.034716,0.465870,1.0,
-0.018484,0.667618,0.0,
-0.658814,0.686057,1.0,
-0.010953,0.095356,0.0,
-0.650095,0.144251,1.0,
-0.989392,0.707427,0.0,
-0.727546,0.865838,1.0,
-0.992005,0.633457,0.0,
-0.508069,0.117819,1.0,
-0.032307,0.976242,0.0,
-0.700550,0.144156,1.0,
-0.346700,0.991569,0.0,
-0.301784,0.091977,1.0,
-0.852435,0.871996,0.0,
-0.630188,0.570917,1.0,
diff --git a/tests/menelaus/test_drift_detector.py b/tests/menelaus/test_drift_detector.py
index 82617484..7e0c57dd 100644
--- a/tests/menelaus/test_drift_detector.py
+++ b/tests/menelaus/test_drift_detector.py
@@ -1,12 +1,34 @@
 """Module for checking behavior of DriftDetector."""
-from menelaus.drift_detector import DriftDetector
+from menelaus.drift_detector import DriftDetector, StreamingDetector, BatchDetector
 import pytest
 
 # override this attribute so that it is instantiable
 # hacky, but proper child classes doesn't cover the input_type setter.
 DriftDetector.__abstractmethods__ = set()
+StreamingDetector.__abstractmethods__ = set()
+BatchDetector.__abstractmethods__ = set()
 
 
+def test_validation_streaming():
+    '''Ensure ValueError on invalid drift_state for StreamingDetector'''
+    det = StreamingDetector()
+    with pytest.raises(ValueError) as _:
+        det.drift_state = "balloon"
+
+    for state in ["drift", "warning", None]:
+        det.drift_state = state
+        assert det.drift_state == state
+
+def test_validation_batch():
+    '''Ensure ValueError on invalid drift_state for BatchDetector'''
+    det = BatchDetector()
+    with pytest.raises(ValueError) as _:
+        det.drift_state = "balloon"
+
+    for state in ["drift", "warning", None]:
+        det.drift_state = state
+        assert det.drift_state == state
+
 def test_validation():
     """Confirm that bad drift_state values raise ValueErrors."""
     det = DriftDetector()
@@ -17,7 +39,6 @@ def test_validation():
         det.drift_state = state
         assert det.drift_state == state
 
-
 def test_input_type():
     det = DriftDetector()
     det._input_type = "stream"
diff --git a/tests/menelaus/test_kdq_tree.py b/tests/menelaus/test_kdq_tree.py
index 097bd0a4..57ca135b 100644
--- a/tests/menelaus/test_kdq_tree.py
+++ b/tests/menelaus/test_kdq_tree.py
@@ -3,7 +3,7 @@
 import pytest
 import numpy as np
 import pandas as pd
-from menelaus.data_drift.kdq_tree import KdqTree
+from menelaus.data_drift.kdq_tree import KdqTreeStreaming, KdqTreeBatch
 
 
 NUM_FEATURES = 3
@@ -19,7 +19,7 @@ def fixture_kdq_det_stream():
     # instance of KdqTree detector which has been updated with a
     # stream of data which triggers drift detection twice
     np.random.seed(123)
-    det = KdqTree(window_size=50, count_ubound=3)
+    det = KdqTreeStreaming(window_size=50, count_ubound=3)
 
     # could be much faster if kdqTree accepted non-df args; numpy array, itertuples, ...
     input_df = pd.DataFrame(np.random.sample((60, NUM_FEATURES)))
@@ -46,7 +46,7 @@ def fixture_kdq_det_batch():
     """
     np.random.seed(123)
     in_df = np.random.sample((10, NUM_FEATURES))
-    det = KdqTree(input_type="batch", count_ubound=1, bootstrap_samples=10)
+    det = KdqTreeBatch(count_ubound=1, bootstrap_samples=10)
     det.set_reference(in_df)
     det.update(in_df)
     det.update(50 * in_df)
@@ -88,7 +88,7 @@ def test_set_reference(kdq_det_batch):
     assert det.drift_state is None
 
     # make sure that input_cols is set appropriately when initialized
-    det = det = KdqTree(input_type="batch", count_ubound=1, bootstrap_samples=10)
+    det = det = KdqTreeBatch(count_ubound=1, bootstrap_samples=10)
     new_sample = pd.DataFrame(np.random.sample((1, NUM_FEATURES)))
     det.set_reference(new_sample)
     assert det.input_cols.equals(new_sample.columns)
@@ -96,15 +96,9 @@ def test_set_reference(kdq_det_batch):
 
 def test_init_validation_stream(kdq_det_stream):
     with pytest.raises(ValueError) as _:
-        det = KdqTree(window_size=None, input_type="stream")
+        det = KdqTreeStreaming(window_size=None)
     with pytest.raises(ValueError) as _:
-        det = KdqTree(window_size=-5, input_type="stream")
-
-
-def test_set_reference_validation_stream(kdq_det_stream):
-    with pytest.raises(ValueError) as _:
-        det = copy.copy(kdq_det_stream)
-        det.set_reference("garbage input")
+        det = KdqTreeStreaming(window_size=-5)
 
 
 def test_set_reference_validation_batch(kdq_det_batch):