This is an automated email from the ASF dual-hosted git repository.

domino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/madlib.git

commit c0ae940bbebba4d934a8f48dbb4f84d285465841
Author: Orhan Kislal <okis...@apache.org>
AuthorDate: Thu Jul 2 14:33:38 2020 -0400

    DBSCAN: Add indexing optimizations to improve the runtime
    
    JIRA: MADLIB-1017
    
    The brute force DBSCAN runs on N^2 time. To improve this,
    we added two layers of indexing.
    1. The data is broken into chunks with kd-tree
    2. Each leaf of the kd-tree creates an rtree index for
    efficient range queries.
    
    In addition we added a separate process to reduce the number
    of edges to consider during wcc operation.
---
 src/ports/postgres/modules/dbscan/dbscan.py_in     |  592 ++++++++++-
 src/ports/postgres/modules/dbscan/dbscan.sql_in    |  139 ++-
 .../postgres/modules/dbscan/test/dbscan.sql_in     | 1087 ++++++++++++++++++++
 .../dbscan/test/unit_tests/test_dbscan.py_in       |    3 +-
 4 files changed, 1772 insertions(+), 49 deletions(-)

diff --git a/src/ports/postgres/modules/dbscan/dbscan.py_in 
b/src/ports/postgres/modules/dbscan/dbscan.py_in
index e8d67a6..40e1205 100644
--- a/src/ports/postgres/modules/dbscan/dbscan.py_in
+++ b/src/ports/postgres/modules/dbscan/dbscan.py_in
@@ -26,6 +26,8 @@ from utilities.utilities import add_postfix
 from utilities.utilities import NUMERIC, ONLY_ARRAY
 from utilities.utilities import is_valid_psql_type
 from utilities.utilities import is_platform_pg
+from utilities.utilities import num_features
+from utilities.utilities import get_seg_number
 from utilities.validate_args import input_tbl_valid, output_tbl_valid
 from utilities.validate_args import is_var_valid
 from utilities.validate_args import cols_in_tbl_valid
@@ -33,74 +35,284 @@ from utilities.validate_args import get_expr_type
 from utilities.validate_args import get_algorithm_name
 from graph.wcc import wcc
 
+from math import log
+from math import floor
+from math import sqrt
+
+from scipy.spatial import distance
+
+try:
+    from rtree import index
+except ImportError:
+    RTREE_ENABLED=0
+else:
+    RTREE_ENABLED=1
+
 BRUTE_FORCE = 'brute_force'
 KD_TREE = 'kd_tree'
+DEFAULT_MIN_SAMPLES = 5
+DEFAULT_KD_DEPTH = 3
+DEFAULT_METRIC = 'squared_dist_norm2'
 
-def dbscan(schema_madlib, source_table, output_table, id_column, expr_point, 
eps, min_samples, metric, algorithm, **kwargs):
+def dbscan(schema_madlib, source_table, output_table, id_column, expr_point,
+           eps, min_samples, metric, algorithm, depth, **kwargs):
 
     with MinWarning("warning"):
 
-        min_samples = 5 if not min_samples else min_samples
-        metric = 'squared_dist_norm2' if not metric else metric
-        algorithm = 'brute' if not algorithm else algorithm
+        min_samples = DEFAULT_MIN_SAMPLES if not min_samples else min_samples
+        metric = DEFAULT_METRIC if not metric else metric
+        algorithm = BRUTE_FORCE if not algorithm else algorithm
+        depth = DEFAULT_KD_DEPTH if not depth else depth
 
         algorithm = get_algorithm_name(algorithm, BRUTE_FORCE,
             [BRUTE_FORCE, KD_TREE], 'DBSCAN')
 
         _validate_dbscan(schema_madlib, source_table, output_table, id_column,
-                         expr_point, eps, min_samples, metric, algorithm)
+                         expr_point, eps, min_samples, metric, algorithm, 
depth)
 
         dist_src_sql = ''  if is_platform_pg() else 'DISTRIBUTED BY (__src__)'
         dist_id_sql = ''  if is_platform_pg() else 'DISTRIBUTED BY 
({0})'.format(id_column)
         dist_reach_sql = ''  if is_platform_pg() else 'DISTRIBUTED BY 
(__reachable_id__)'
+        dist_leaf_sql = ''  if is_platform_pg() else 'DISTRIBUTED BY 
(__leaf_id__)'
 
-        # Calculate pairwise distances
+        core_points_table = unique_string(desp='core_points_table')
+        core_edge_table = unique_string(desp='core_edge_table')
         distance_table = unique_string(desp='distance_table')
-        plpy.execute("DROP TABLE IF EXISTS {0}".format(distance_table))
+        plpy.execute("DROP TABLE IF EXISTS {0}, {1}, {2}".format(
+            core_points_table, core_edge_table, distance_table))
 
+        source_view = unique_string(desp='source_view')
+        plpy.execute("DROP VIEW IF EXISTS {0}".format(source_view))
         sql = """
-            CREATE TABLE {distance_table} AS
-            SELECT __src__, __dest__ FROM (
-                SELECT  __t1__.{id_column} AS __src__,
-                        __t2__.{id_column} AS __dest__,
-                        {schema_madlib}.{metric}(
-                            __t1__.{expr_point}, __t2__.{expr_point}) AS 
__dist__
-                FROM {source_table} AS __t1__, {source_table} AS __t2__
-                WHERE __t1__.{id_column} != __t2__.{id_column}) q1
-            WHERE __dist__ < {eps}
-            {dist_src_sql}
+            CREATE VIEW {source_view} AS
+            SELECT {id_column}, {expr_point} AS __expr_point__
+            FROM {source_table}
             """.format(**locals())
         plpy.execute(sql)
+        expr_point = '__expr_point__'
+
+        if algorithm == KD_TREE:
+            cur_source_table, border_table1, border_table2 = dbscan_kd(
+                schema_madlib, source_view, id_column, expr_point, eps,
+                min_samples, metric, depth)
+
+            sql = """
+                SELECT count(*), __leaf_id__ FROM {cur_source_table} GROUP BY 
__leaf_id__
+                """.format(**locals())
+            result = plpy.execute(sql)
+            rt_counts_dict = {}
+            for i in result:
+                rt_counts_dict[i['__leaf_id__']] = int(i['count'])
+            rt_counts_list = []
+            for i in sorted(rt_counts_dict):
+                rt_counts_list.append(rt_counts_dict[i])
+
+            find_core_points_table = 
unique_string(desp='find_core_points_table')
+            rt_edge_table = unique_string(desp='rt_edge_table')
+            rt_core_points_table = unique_string(desp='rt_core_points_table')
+            border_core_points_table = 
unique_string(desp='border_core_points_table')
+            border_edge_table = unique_string(desp='border_edge_table')
+            plpy.execute("DROP TABLE IF EXISTS {0}, {1}, {2}, {3}, {4}".format(
+                find_core_points_table, rt_edge_table, rt_core_points_table,
+                border_core_points_table, border_edge_table))
+
+            sql = """
+            CREATE TABLE {find_core_points_table} AS
+            SELECT __leaf_id__,
+                   {schema_madlib}.find_core_points( {id_column},
+                                               {expr_point}::DOUBLE 
PRECISION[],
+                                               {eps},
+                                               {min_samples},
+                                               '{metric}',
+                                               ARRAY{rt_counts_list},
+                                               __leaf_id__
+                                               )
+            FROM {cur_source_table} GROUP BY __leaf_id__
+            {dist_leaf_sql}
+            """.format(**locals())
+            plpy.execute(sql)
+
+            sql = """
+            CREATE TABLE {rt_edge_table} AS
+            SELECT (unpacked_2d).src AS __src__, (unpacked_2d).dest AS __dest__
+            FROM (
+                SELECT {schema_madlib}.unpack_2d(find_core_points) AS 
unpacked_2d
+                FROM {find_core_points_table}
+                ) q1
+            WHERE (unpacked_2d).src NOT IN (SELECT {id_column} FROM 
{border_table1})
+            {dist_src_sql}
+            """.format(**locals())
+            plpy.execute(sql)
+
+            sql = """
+                CREATE TABLE {rt_core_points_table} AS
+                SELECT DISTINCT(__src__) AS {id_column} FROM {rt_edge_table}
+                """.format(**locals())
+            plpy.execute(sql)
+
+            # # Start border
+            sql = """
+                CREATE TABLE {border_edge_table} AS
+                SELECT __src__, __dest__ FROM (
+                    SELECT  __t1__.{id_column} AS __src__,
+                            __t2__.{id_column} AS __dest__,
+                            {schema_madlib}.{metric}(
+                                __t1__.{expr_point}, __t2__.{expr_point}) AS 
__dist__
+                    FROM {border_table1} AS __t1__, {border_table2} AS 
__t2__)q1
+                WHERE __dist__ < {eps}
+                """.format(**locals())
+            plpy.execute(sql)
+
+            sql = """
+                CREATE TABLE {border_core_points_table} AS
+                SELECT * FROM (
+                    SELECT __src__ AS {id_column}, count(*) AS __count__
+                    FROM {border_edge_table} GROUP BY __src__) q1
+                WHERE __count__ >= {min_samples}
+                {dist_id_sql}
+                """.format(**locals())
+            plpy.execute(sql)
+
+            # Build common tables
+            sql = """
+                CREATE TABLE {distance_table} AS
+                SELECT * FROM {rt_edge_table}
+                UNION
+                SELECT * FROM {border_edge_table}
+                """.format(**locals())
+            plpy.execute(sql)
+            sql = """
+                CREATE TABLE {core_points_table} AS
+                SELECT {id_column} FROM {border_core_points_table}
+                UNION
+                SELECT {id_column} FROM {rt_core_points_table}
+                """.format(**locals())
+            plpy.execute(sql)
+
+            sql = """
+                CREATE TABLE {core_edge_table} AS
+                SELECT __t1__.__src__, __t1__.__dest__
+                FROM {rt_edge_table} __t1__ ,
+                    (SELECT array_agg({id_column}) AS arr
+                     FROM {core_points_table}) __t2__
+                WHERE  __t1__.__dest__ = ANY(arr)
+                UNION
+                SELECT __t1__.__src__, __t1__.__dest__
+                FROM {border_edge_table} __t1__ ,
+                    (SELECT array_agg({id_column}) AS arr
+                     FROM {core_points_table}) __t2__
+                WHERE  __t1__.__src__ = ANY(arr) AND __t1__.__dest__ = ANY(arr)
+                """.format(**locals())
+            plpy.execute(sql)
+
+            plpy.execute("DROP TABLE IF EXISTS {0}, {1}, {2}, {3}, {4}, {5}, 
{6}, {7}".format(
+                find_core_points_table, rt_edge_table, rt_core_points_table,
+                border_core_points_table, border_edge_table,
+                cur_source_table, border_table1, border_table2))
+
+        else:
+
+            # Calculate pairwise distances
+            sql = """
+                CREATE TABLE {distance_table} AS
+                SELECT __src__, __dest__ FROM (
+                    SELECT  __t1__.{id_column} AS __src__,
+                            __t2__.{id_column} AS __dest__,
+                            {schema_madlib}.{metric}(
+                                __t1__.{expr_point}, __t2__.{expr_point}) AS 
__dist__
+                    FROM {source_view} AS __t1__, {source_view} AS __t2__)q1
+                WHERE __dist__ < {eps}
+                {dist_src_sql}
+                """.format(**locals())
+            plpy.execute(sql)
+
+            # Find core points
+            sql = """
+                CREATE TABLE {core_points_table} AS
+                SELECT * FROM (
+                    SELECT __src__ AS {id_column}, count(*) AS __count__
+                    FROM {distance_table} GROUP BY __src__) q1
+                WHERE __count__ >= {min_samples}
+                {dist_id_sql}
+                """.format(**locals())
+            plpy.execute(sql)
+
+            # Find the connections between core points to form the clusters
+            sql = """
+                CREATE TABLE {core_edge_table} AS
+                SELECT __src__, __dest__
+                FROM {distance_table} AS __t1__, (SELECT 
array_agg({id_column}) AS arr
+                                                  FROM {core_points_table}) 
__t2__
+                WHERE __t1__.__src__ = ANY(arr) AND __t1__.__dest__ = ANY(arr)
+                {dist_src_sql}
+            """.format(**locals())
+            plpy.execute(sql)
 
-        # Find core points
-        # We use __count__ + 1 because we have to add the point itself to the 
count
-        core_points_table = unique_string(desp='core_points_table')
-        plpy.execute("DROP TABLE IF EXISTS {0}".format(core_points_table))
         sql = """
-            CREATE TABLE {core_points_table} AS
-            SELECT * FROM (SELECT __src__ AS {id_column}, count(*) AS __count__
-                           FROM {distance_table} GROUP BY __src__) q1
-            WHERE __count__ + 1 >= {min_samples}
-            {dist_id_sql}
+            SELECT count(*) FROM {core_points_table}
             """.format(**locals())
+        core_count = plpy.execute(sql)[0]['count']
+        _assert(core_count != 0, "DBSCAN: Cannot find any core 
points/clusters.")
+
+        # Start snowflake creation
+        if is_platform_pg():
+            sql = """
+                SELECT count(*) FROM {core_edge_table}
+                """.format(**locals())
+            count = plpy.execute(sql)[0]['count']
+
+            counts_list = [int(count)]
+            seg_id = 0
+            group_by_clause = ''
+            dist_clause = ''
+
+        else:
+            sql = """
+                SELECT count(*), gp_segment_id FROM {core_edge_table} GROUP BY 
gp_segment_id
+                """.format(**locals())
+            count_result = plpy.execute(sql)
+            seg_num = get_seg_number()
+            counts_list = [0]*seg_num
+            for i in count_result:
+                counts_list[int(i['gp_segment_id'])] = int(i['count'])
+            seg_id = 'gp_segment_id'
+            group_by_clause = 'GROUP BY gp_segment_id'
+            dist_clause = 'DISTRIBUTED BY (seg_id)'
+
+        snowflake_table = unique_string(desp='snowflake_table')
+        sf_edge_table = unique_string(desp='sf_edge_table')
+
+        plpy.execute("DROP TABLE IF EXISTS {0}, {1}".format(
+            snowflake_table, sf_edge_table))
+        sql = """
+            CREATE TABLE {snowflake_table} AS
+            SELECT {seg_id}::INTEGER AS seg_id,
+                   {schema_madlib}.build_snowflake_table( __src__,
+                                            __dest__,
+                                            ARRAY{counts_list},
+                                            {seg_id}
+                                           ) AS __sf__
+            FROM {core_edge_table} {group_by_clause}
+            {dist_clause}
+        """.format(**locals())
         plpy.execute(sql)
 
-        # Find the connections between core points to form the clusters
-        core_edge_table = unique_string(desp='core_edge_table')
-        plpy.execute("DROP TABLE IF EXISTS {0}".format(core_edge_table))
         sql = """
-            CREATE TABLE {core_edge_table} AS
-            SELECT __src__, __dest__
-            FROM {distance_table} AS __t1__, (SELECT array_agg({id_column}) AS 
arr
-                                              FROM {core_points_table}) __t2__
-            WHERE __t1__.__src__ = ANY(arr) AND __t1__.__dest__ = ANY(arr)
-            {dist_src_sql}
-        """.format(**locals())
+            CREATE TABLE {sf_edge_table} AS
+            SELECT seg_id, (unpacked_2d).src AS __src__, (unpacked_2d).dest AS 
__dest__
+            FROM (
+                SELECT seg_id,
+                       {schema_madlib}.unpack_2d(__sf__) AS unpacked_2d
+                FROM {snowflake_table}
+                ) q1
+            {dist_clause}
+            """.format(**locals())
         plpy.execute(sql)
 
         # Run wcc to get the min id for each cluster
-        wcc(schema_madlib, core_points_table, id_column, core_edge_table, 
'src=__src__, dest=__dest__',
-            output_table, None)
+        wcc(schema_madlib, core_points_table, id_column, sf_edge_table,
+            'src=__src__, dest=__dest__', output_table, None)
+
         plpy.execute("""
             ALTER TABLE {0}
             ADD COLUMN is_core_point BOOLEAN,
@@ -147,7 +359,7 @@ def dbscan(schema_madlib, source_table, output_table, 
id_column, expr_point, eps
         sql = """
             UPDATE {output_table} AS __t1__
             SET __points__ = {expr_point}
-            FROM {source_table} AS __t2__
+            FROM {source_view} AS __t2__
             WHERE __t1__.{id_column} = __t2__.{id_column}
         """.format(**locals())
         plpy.execute(sql)
@@ -165,8 +377,9 @@ def dbscan(schema_madlib, source_table, output_table, 
id_column, expr_point, eps
         plpy.execute(sql)
 
         output_summary_table = add_postfix(output_table, '_summary')
-        plpy.execute("DROP TABLE IF EXISTS {0}".format(output_summary_table))
 
+        # Drop the summary table from wcc
+        plpy.execute("DROP TABLE IF EXISTS {0}".format(output_summary_table))
         sql = """
             CREATE TABLE {output_summary_table} AS
             SELECT  '{id_column}'::VARCHAR AS id_column,
@@ -175,9 +388,114 @@ def dbscan(schema_madlib, source_table, output_table, 
id_column, expr_point, eps
             """.format(**locals())
         plpy.execute(sql)
 
-        plpy.execute("DROP TABLE IF EXISTS {0}, {1}, {2}, {3}".format(
+        plpy.execute("DROP VIEW IF EXISTS {0}".format(source_view))
+        plpy.execute("DROP TABLE IF EXISTS {0}, {1}, {2}, {3}, {4}, 
{5}".format(
                      distance_table, core_points_table, core_edge_table,
-                     reachable_points_table))
+                     reachable_points_table, snowflake_table, sf_edge_table))
+
+def dbscan_kd(schema_madlib, source_table, id_column, expr_point, eps,
+              min_samples, metric, depth):
+
+    n_features = num_features(source_table, expr_point)
+
+    # If squared_dist_norm2 is used, we assume eps is set for the squared 
distance
+    # That means the border only needs to be sqrt(eps) wide
+    local_eps = sqrt(eps) if metric == DEFAULT_METRIC else eps
+
+    kd_array, case_when_clause, border_cl1, border_cl2 = build_kd_tree(
+        schema_madlib, source_table, expr_point, depth, n_features, local_eps)
+
+    kd_source_table = unique_string(desp='kd_source_table')
+    kd_border_table1 = unique_string(desp='kd_border_table1')
+    kd_border_table2 = unique_string(desp='kd_border_table2')
+
+    dist_leaf_sql = ''  if is_platform_pg() else 'DISTRIBUTED BY (__leaf_id__)'
+    plpy.execute("DROP TABLE IF EXISTS {0}, {1}, {2}".format(kd_source_table, 
kd_border_table1, kd_border_table2))
+
+    output_sql = """
+        CREATE TABLE {kd_source_table} AS
+            SELECT *,
+                   CASE {case_when_clause} END AS __leaf_id__
+            FROM {source_table}
+            {dist_leaf_sql}
+        """.format(**locals())
+    plpy.execute(output_sql)
+
+    border_sql = """
+        CREATE TABLE {kd_border_table1} AS
+            SELECT *
+            FROM {source_table}
+            WHERE {border_cl1}
+        """.format(**locals())
+    plpy.execute(border_sql)
+
+    border_sql = """
+        CREATE TABLE {kd_border_table2} AS
+            SELECT *
+            FROM {source_table}
+            WHERE {border_cl2}
+        """.format(**locals())
+    plpy.execute(border_sql)
+
+    return kd_source_table, kd_border_table1, kd_border_table2
+
+
+def build_kd_tree(schema_madlib, source_table, expr_point,
+                  depth, n_features, eps, **kwargs):
+    """
+        KD-tree function to create a partitioning
+        Args:
+            @param schema_madlib        Name of the Madlib Schema
+            @param source_table         Training data table
+            @param output_table         Name of the table to store kd tree
+            @param expr_point           Name of the column with training data
+                                        or expression that evaluates to a
+                                        numeric array
+            @param depth                Depth of the kd tree
+            @param n_features           Number of features
+            @param eps                  The eps value defined by the user
+    """
+    with MinWarning("error"):
+
+        clauses = [' TRUE ']
+        border_cl1 = ' FALSE '
+        border_cl2 = ' FALSE '
+        clause_counter = 0
+        kd_array = []
+        for curr_level in range(depth):
+            curr_feature = (curr_level % n_features) + 1
+            for curr_leaf in range(pow(2,curr_level)):
+                clause = clauses[clause_counter]
+                cutoff_sql = """
+                    SELECT percentile_disc(0.5)
+                           WITHIN GROUP (
+                            ORDER BY ({expr_point})[{curr_feature}]
+                           ) AS cutoff
+                    FROM {source_table}
+                    WHERE {clause}
+                    """.format(**locals())
+
+                cutoff = plpy.execute(cutoff_sql)[0]['cutoff']
+                cutoff = "NULL" if cutoff is None else cutoff
+                kd_array.append(cutoff)
+                clause_counter += 1
+                clauses.append(clause +
+                               "AND ({expr_point})[{curr_feature}] < {cutoff} 
".
+                               format(**locals()))
+                clauses.append(clause +
+                               "AND ({expr_point})[{curr_feature}] >= {cutoff} 
".
+                               format(**locals()))
+                border_cl1 = border_cl1 + """ OR 
(({expr_point})[{curr_feature}] >= {cutoff} - {eps}
+                                            AND ({expr_point})[{curr_feature}] 
<= {cutoff} + {eps})
+                                        """.format(**locals())
+                border_cl2 = border_cl2 + """ OR 
(({expr_point})[{curr_feature}] >= {cutoff} - (2*{eps})
+                                            AND ({expr_point})[{curr_feature}] 
<= {cutoff} + (2*{eps}))
+                                        """.format(**locals())
+
+        n_leaves = pow(2, depth)
+        case_when_clause = '\n'.join(["WHEN {0} THEN 
{1}::INTEGER".format(cond, i)
+                                     for i, cond in 
enumerate(clauses[-n_leaves:])])
+        return kd_array, case_when_clause, border_cl1, border_cl2
 
 
 def dbscan_predict(schema_madlib, dbscan_table, source_table, id_column,
@@ -209,8 +527,189 @@ def dbscan_predict(schema_madlib, dbscan_table, 
source_table, id_column,
             """.format(**locals())
         result = plpy.execute(sql)
 
+def find_core_points_transition(state, id_in, expr_points, eps, min_samples, 
metric, n_rows, leaf_id, **kwargs):
+
+    SD = kwargs['SD']
+    if not state:
+        data = {}
+        SD['counter{0}'.format(leaf_id)] = 0
+    else:
+        data = SD['data{0}'.format(leaf_id)]
+
+    data[id_in] = expr_points
+    SD['counter{0}'.format(leaf_id)] = SD['counter{0}'.format(leaf_id)]+1
+    SD['data{0}'.format(leaf_id)] = data
+    ret = [[-1,-1],[-1,-1]]
+
+    my_n_rows = n_rows[leaf_id]
+
+    if SD['counter{0}'.format(leaf_id)] == my_n_rows:
+
+        core_counts = {}
+        core_lists = {}
+        p = index.Property()
+        p.dimension = len(expr_points)
+        idx = index.Index(properties=p)
+        ret = []
+
+        if metric == 'dist_norm1':
+            fn_dist = distance.cityblock
+        elif metric == 'dist_norm2':
+            fn_dist = distance.euclidean
+        else:
+            fn_dist = distance.sqeuclidean
+
+        for key1, value1 in data.items():
+            idx.add(key1,value1+value1,key1)
+
+        for key1, value1 in data.items():
+
+            v1 = []
+            v2 = []
+            for coor in value1:
+                v1.append(coor-eps)
+                v2.append(coor+eps)
+
+            # Array concat
+            v = v1+v2
+            hits = idx.intersection(v)
+
+            if key1 not in core_counts:
+                core_counts[key1] = 0
+                core_lists[key1] = []
+
+            for key2 in hits:
+                value2 = data[key2]
+                dist = fn_dist(value1, value2)
+                if dist <= eps:
+                    core_counts[key1] += 1
+                    core_lists[key1].append(key2)
+
+        for key1, value1 in core_counts.items():
+            if value1 >= min_samples:
+                for key2 in core_lists[key1]:
+                    ret.append([key1,key2])
+
+    return ret
+
+def find_core_points_merge(state1, state2, **kwargs):
+
+    if not state1:
+        return state2
+    elif not state2:
+        return state1
+    else:
+        plpy.error("dbscan Error: A kd-tree leaf should be on a single 
segment.")
+
+
+def find_core_points_final(state, **kwargs):
+
+    return state
+
+
+# The snowflake table is used to reduce the size of the edge table.
+# Note that the sole purpose of the edge table is finding the connected
+# components. Which means removing some of the edges is fine as long as the
+# component is intact.
+
+# We call it snowflake because the end result will look like a point in the
+# middle with a bunch of edges coming out of it to the other connected points.
+
+# Example:
+# Edges: [1,2] [1,3] [1,4] [2,3] [2,4] [3,1] [3,4] [5,6] [6,7] [7,8] [7,5]
+# Result: 1 and 5 are snowflake cores
+# Edges: [1,2] [1,3] [1,4] [5,6] [5,7] [5,8]
+
+# This is a proto-wcc operation (creates connected components) but we still
+# need to run the actual wcc to combine snowflakes from different segments.
+
+def sf_transition(state, src, dest, n_rows, gp_segment_id, **kwargs):
+
+    SD = kwargs['SD']
+    if not state:
+        data = []
+        SD['counter'] = 0
+    else:
+        data = SD['data']
+
+    counter = SD['counter']
+
+    data.append([src,dest])
+    ret = [[-1,-1],[-1,-1]]
+
+    my_n_rows = n_rows[gp_segment_id]
+
+    if len(data) == my_n_rows:
+
+        cl_ids = {}
+        clid_counter = 1
+        cl_counts = {}
+        for i in data:
+
+            key1 = i[0]
+            key2 = i[1]
+
+            cl_id_k1 = cl_ids[key1] if key1 in cl_ids else None
+            cl_id_k2 = cl_ids[key2] if key2 in cl_ids else None
+
+            if not cl_id_k1 and not cl_id_k2:
+                cl_ids[key1] = clid_counter
+                cl_ids[key2] = clid_counter
+                cl_counts[clid_counter] = 2
+                clid_counter += 1
+            elif cl_id_k1 and not cl_id_k2:
+                cl_ids[key2] = cl_id_k1
+                cl_counts[cl_ids[key1]] += 1
+            elif not cl_id_k1 and cl_id_k2:
+                cl_ids[key1] = cl_id_k2
+                cl_counts[cl_ids[key2]] += 1
+            else:
+                if cl_id_k1 != cl_id_k2:
+                    if cl_counts[cl_id_k1] > cl_counts[cl_id_k2]:
+                        for chkey,chvalue in cl_ids.items():
+                            if chvalue == cl_id_k2:
+                                cl_ids[chkey] = cl_id_k1
+                        cl_counts[cl_id_k1] += cl_counts[cl_id_k2]
+                        cl_counts[cl_id_k2] = 0
+                    else:
+                        for chkey,chvalue in cl_ids.items():
+                            if chvalue == cl_id_k1:
+                                cl_ids[chkey] = cl_id_k2
+                        cl_counts[cl_id_k2] += cl_counts[cl_id_k1]
+                        cl_counts[cl_id_k1] = 0
+
+        if cl_ids:
+
+            running_cl_id = -1
+            running_sf_center = -1
+            ret = []
+            for vertex_id, vertex_cl in sorted(cl_ids.items(), key=lambda 
item: item[1]):
+
+                # Check if we are still in the same snowflake
+                if vertex_cl != running_cl_id:
+
+                    running_sf_center = vertex_id
+                    running_cl_id = vertex_cl
+                else:
+                    ret.append([running_sf_center,vertex_id])
+
+    SD['data'] = data
+
+    return ret
+
+def sf_merge(state1, state2, **kwargs):
+
+    if state1:
+        return state1
+    else:
+        return state2
+
+def sf_final(state, **kwargs):
+
+    return state
+
 def _validate_dbscan(schema_madlib, source_table, output_table, id_column,
-    expr_point, eps, min_samples, metric, algorithm):
+    expr_point, eps, min_samples, metric, algorithm, depth):
 
     input_tbl_valid(source_table, 'dbscan')
     output_tbl_valid(output_table, 'dbscan')
@@ -235,6 +734,10 @@ def _validate_dbscan(schema_madlib, source_table, 
output_table, id_column,
     fn_dist_list = ['dist_norm1', 'dist_norm2', 'squared_dist_norm2', 
'dist_angle', 'dist_tanimoto']
     _assert(metric in fn_dist_list, "dbscan Error: metric has to be one of the 
madlib defined distance functions")
 
+    _assert(algorithm == BRUTE_FORCE or RTREE_ENABLED == 1,
+        "dbscan Error: Cannot use kd_tree without the necessary python module: 
rtree")
+    _assert(depth > 0, "dbscan Error: depth has to be a positive number")
+
 def _validate_dbscan_predict(schema_madlib, dbscan_table, source_table,
     id_column, expr_point, output_table):
 
@@ -282,7 +785,8 @@ SELECT {schema_madlib}.dbscan(
     min_samples,        -- The minimum size of a cluster
     metric,             -- The name of the function to use to calculate the
                         -- distance
-    algorithm           -- The algorithm to use for dbscan.
+    algorithm,          -- The algorithm to use for dbscan: brute or kd_tree
+    depth               -- The depth for the kdtree algorithm
     );
 
 -----------------------------------------------------------------------
diff --git a/src/ports/postgres/modules/dbscan/dbscan.sql_in 
b/src/ports/postgres/modules/dbscan/dbscan.sql_in
index f4539b2..7e1df05 100644
--- a/src/ports/postgres/modules/dbscan/dbscan.sql_in
+++ b/src/ports/postgres/modules/dbscan/dbscan.sql_in
@@ -377,7 +377,8 @@ CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.dbscan(
     eps                         DOUBLE PRECISION,
     min_samples                 INTEGER,
     metric                      VARCHAR,
-    algorithm                   VARCHAR
+    algorithm                   VARCHAR,
+    depth                       INTEGER
 ) RETURNS VOID AS $$
     PythonFunction(dbscan, dbscan, dbscan)
 $$ LANGUAGE plpythonu VOLATILE
@@ -390,9 +391,23 @@ CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.dbscan(
     expr_point                  VARCHAR,
     eps                         DOUBLE PRECISION,
     min_samples                 INTEGER,
+    metric                      VARCHAR,
+    algorithm                   VARCHAR
+) RETURNS VOID AS $$
+    SELECT MADLIB_SCHEMA.dbscan($1, $2, $3, $4, $5, $6, $7, $8, NULL);
+$$ LANGUAGE sql VOLATILE
+m4_ifdef(`\_\_HAS_FUNCTION_PROPERTIES\_\_', `MODIFIES SQL DATA', `');
+
+CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.dbscan(
+    source_table                VARCHAR,
+    output_table                VARCHAR,
+    id_column                   VARCHAR,
+    expr_point                  VARCHAR,
+    eps                         DOUBLE PRECISION,
+    min_samples                 INTEGER,
     metric                      VARCHAR
 ) RETURNS VOID AS $$
-    SELECT MADLIB_SCHEMA.dbscan($1, $2, $3, $4, $5, $6, $7, NULL);
+    SELECT MADLIB_SCHEMA.dbscan($1, $2, $3, $4, $5, $6, $7, NULL, NULL);
 $$ LANGUAGE sql VOLATILE
 m4_ifdef(`\_\_HAS_FUNCTION_PROPERTIES\_\_', `MODIFIES SQL DATA', `');
 
@@ -404,7 +419,7 @@ CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.dbscan(
     eps                         DOUBLE PRECISION,
     min_samples                 INTEGER
 ) RETURNS VOID AS $$
-    SELECT MADLIB_SCHEMA.dbscan($1, $2, $3, $4, $5, $6, NULL, NULL);
+    SELECT MADLIB_SCHEMA.dbscan($1, $2, $3, $4, $5, $6, NULL, NULL, NULL);
 $$ LANGUAGE sql VOLATILE
 m4_ifdef(`\_\_HAS_FUNCTION_PROPERTIES\_\_', `MODIFIES SQL DATA', `');
 
@@ -415,7 +430,7 @@ CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.dbscan(
     expr_point                  VARCHAR,
     eps                         DOUBLE PRECISION
 ) RETURNS VOID AS $$
-    SELECT MADLIB_SCHEMA.dbscan($1, $2, $3, $4, $5, NULL, NULL, NULL);
+    SELECT MADLIB_SCHEMA.dbscan($1, $2, $3, $4, $5, NULL, NULL, NULL, NULL);
 $$ LANGUAGE sql VOLATILE
 m4_ifdef(`\_\_HAS_FUNCTION_PROPERTIES\_\_', `MODIFIES SQL DATA', `');
 
@@ -456,3 +471,119 @@ CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.dbscan_predict(
     PythonFunction(dbscan, dbscan, dbscan_predict_help)
 $$ LANGUAGE plpythonu VOLATILE
 m4_ifdef(`\_\_HAS_FUNCTION_PROPERTIES\_\_', `MODIFIES SQL DATA', `');
+
+CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.sf_merge(
+    state1          INTEGER[][],
+    state2          INTEGER[][]
+) RETURNS INTEGER[][] AS $$
+PythonFunctionBodyOnlyNoSchema(`dbscan', `dbscan')
+    return dbscan.sf_merge(**globals())
+$$ LANGUAGE plpythonu
+m4_ifdef(`__HAS_FUNCTION_PROPERTIES__', `NO SQL', `');
+
+CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.sf_final(
+    state INTEGER[][]
+) RETURNS INTEGER[][] AS $$
+PythonFunctionBodyOnlyNoSchema(`dbscan', `dbscan')
+    return dbscan.sf_final(**globals())
+$$ LANGUAGE plpythonu
+m4_ifdef(`__HAS_FUNCTION_PROPERTIES__', `NO SQL', `');
+
+CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.sf_transition(
+    state                       INTEGER[],
+    src                         BIGINT,
+    dest                        BIGINT,
+    n_rows                      INTEGER[],
+    gp_segment_id               INTEGER
+
+) RETURNS INTEGER[][] AS $$
+PythonFunctionBodyOnlyNoSchema(`dbscan', `dbscan')
+    return dbscan.sf_transition(**globals())
+$$ LANGUAGE plpythonu
+m4_ifdef(`__HAS_FUNCTION_PROPERTIES__', `NO SQL', `');
+
+DROP AGGREGATE IF EXISTS MADLIB_SCHEMA.build_snowflake_table(
+    BIGINT,
+    BIGINT,
+    INTEGER[],
+    INTEGER);
+CREATE AGGREGATE MADLIB_SCHEMA.build_snowflake_table(
+    BIGINT,
+    BIGINT,
+    INTEGER[],
+    INTEGER
+)(
+    STYPE=INTEGER[][],
+    SFUNC=MADLIB_SCHEMA.sf_transition,
+    m4_ifdef(`__POSTGRESQL__', `', `prefunc=MADLIB_SCHEMA.sf_merge,')
+    FINALFUNC=MADLIB_SCHEMA.sf_final
+);
+
+CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.find_core_points_transition(
+    state                       INTEGER[],
+    id_in                         BIGINT,
+    expr_points DOUBLE PRECISION[],
+    eps     DOUBLE PRECISION,
+    min_samples INTEGER,
+    metric  VARCHAR,
+    n_rows  INTEGER[],
+    leaf_id     INTEGER
+
+) RETURNS INTEGER[][] AS $$
+PythonFunctionBodyOnlyNoSchema(`dbscan', `dbscan')
+    return dbscan.find_core_points_transition(**globals())
+$$ LANGUAGE plpythonu
+m4_ifdef(`__HAS_FUNCTION_PROPERTIES__', `NO SQL', `');
+
+CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.find_core_points_merge(
+    state1          INTEGER[][],
+    state2          INTEGER[][]
+) RETURNS INTEGER[][] AS $$
+PythonFunctionBodyOnlyNoSchema(`dbscan', `dbscan')
+    return dbscan.find_core_points_merge(**globals())
+$$ LANGUAGE plpythonu
+m4_ifdef(`__HAS_FUNCTION_PROPERTIES__', `NO SQL', `');
+
+CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.find_core_points_final(
+    state INTEGER[][]
+) RETURNS INTEGER[] AS $$
+PythonFunctionBodyOnlyNoSchema(`dbscan', `dbscan')
+    return dbscan.find_core_points_final(**globals())
+$$ LANGUAGE plpythonu
+m4_ifdef(`__HAS_FUNCTION_PROPERTIES__', `NO SQL', `');
+
+--id_in, expr_points, eps, min_samples, metric, n_rows, leaf_id,
+DROP AGGREGATE IF EXISTS MADLIB_SCHEMA.find_core_points(
+    BIGINT,
+    DOUBLE PRECISION[],
+    DOUBLE PRECISION,
+    INTEGER,
+    VARCHAR,
+    INTEGER[],
+    INTEGER);
+CREATE AGGREGATE MADLIB_SCHEMA.find_core_points(
+    BIGINT,
+    DOUBLE PRECISION[],
+    DOUBLE PRECISION,
+    INTEGER,
+    VARCHAR,
+    INTEGER[],
+    INTEGER
+)(
+    STYPE=INTEGER[][],
+    SFUNC=MADLIB_SCHEMA.find_core_points_transition,
+    m4_ifdef(`__POSTGRESQL__', `', 
`prefunc=MADLIB_SCHEMA.find_core_points_merge,')
+    FINALFUNC=MADLIB_SCHEMA.find_core_points_final
+);
+
+DROP TYPE IF EXISTS MADLIB_SCHEMA.unpacked_2d CASCADE;
+CREATE TYPE MADLIB_SCHEMA.unpacked_2d AS (
+    src BIGINT,
+    dest BIGINT);
+
+CREATE OR REPLACE FUNCTION MADLIB_SCHEMA.unpack_2d (packed INTEGER[][])
+  RETURNS SETOF MADLIB_SCHEMA.unpacked_2d
+AS $$
+    return packed
+$$ LANGUAGE plpythonu VOLATILE
+m4_ifdef(`\_\_HAS_FUNCTION_PROPERTIES\_\_', `MODIFIES SQL DATA', `');
diff --git a/src/ports/postgres/modules/dbscan/test/dbscan.sql_in 
b/src/ports/postgres/modules/dbscan/test/dbscan.sql_in
index 621251a..0e79e98 100644
--- a/src/ports/postgres/modules/dbscan/test/dbscan.sql_in
+++ b/src/ports/postgres/modules/dbscan/test/dbscan.sql_in
@@ -51,6 +51,12 @@ SELECT dbscan_predict('out1', 'dbscan_train_data', 'id_in', 
'data', 'out1_predic
 
 SELECT assert(count(DISTINCT cluster_id) = 2, 'Incorrect cluster count') FROM 
out1_predict;
 
+DROP TABLE IF EXISTS out1, out1_summary, out1_predict;
+SELECT 
dbscan('dbscan_train_data','out1','id_in','data',20,4,'dist_norm2','brute');
+
+DROP TABLE IF EXISTS out1, out1_summary, out1_predict;
+SELECT 
dbscan('dbscan_train_data','out1','id_in','data',20,4,'dist_norm1','brute');
+
 DROP TABLE IF EXISTS dbscan_train_data2;
 CREATE TABLE dbscan_train_data2 (pid int, points double precision[]);
 INSERT INTO dbscan_train_data2 VALUES
@@ -107,3 +113,1084 @@ SELECT assert(count(DISTINCT cluster_id) = 3, 'Incorrect 
cluster count') FROM db
 SELECT dbscan_predict('dbscan_result', 'dbscan_test_data2', 'pid', 'points', 
'dbscan_predict_out');
 
 SELECT assert(count(DISTINCT cluster_id) = 3, 'Incorrect cluster count') FROM 
dbscan_predict_out;
+
+DROP TABLE IF EXISTS dbscan_train_data3;
+CREATE TABLE dbscan_train_data3 (
+id_in integer,
+data0 integer,
+data1 integer);
+copy dbscan_train_data3 (id_in, data0, data1) FROM stdin delimiter '|';
+1|1|1
+2|2|2
+3|3|3
+4|4|4
+5|4|8
+6|17|8
+7|19|8
+8|19|9
+9|19|10
+10|3|111
+11|3|112
+12|3|113
+13|8|113
+\.
+
+DROP TABLE IF EXISTS out1, out1_summary;
+SELECT 
dbscan('dbscan_train_data3','out1','id_in','ARRAY[data0,data1]',20,4,'squared_dist_norm2','brute');
+
+DROP TABLE IF EXISTS dbscan_result, dbscan_result_summary, dbscan_predict_out;
+SELECT dbscan(
+'dbscan_train_data2',    -- source table
+'dbscan_result',        -- output table
+'pid',                  -- point id column
+'points',             -- data point
+ 1.75,                  -- epsilon
+ 4,                     -- min samples
+'dist_norm2',           -- metric
+'brute_force');               -- algorithm
+
+-- Test kd-tree
+
+DROP TABLE IF EXISTS dbscan_result, dbscan_result_summary;
+SELECT dbscan(
+'dbscan_train_data2',   -- source table
+'dbscan_result',        -- output table
+'pid',                  -- point id column
+'points',               -- data point
+ 1.75,                  -- epsilon
+ 4,                     -- min samples
+'dist_norm2',           -- metric
+'kd_tree',              -- algorithm
+2);                     -- depth
+
+SELECT * FROM dbscan_result ORDER BY pid;
+
+
+CREATE TABLE dbscan_kd_test_data (
+    row_id integer,
+    row_vec double precision[]
+);
+COPY dbscan_kd_test_data (row_id, row_vec) FROM stdin DELIMITER '|';
+1 | {-123.932588658681,312.674262022751,24.5140517556683}
+2 | {-1011.71837586596,294.857038270306,-596.339561040807}
+3 | {51.8496080463916,-392.173152861615,188.5097494329}
+4 | {799.127036569478,607.197217229762,-273.651240009801}
+5 | {382.754942347373,-730.542867226352,-16.353747006894}
+6 | {-184.468870153144,-221.332003547174,-591.35952658191}
+7 | {726.513984734646,156.985561437554,248.190787632731}
+8 | {-513.344896244817,310.773265404381,104.080456390778}
+9 | {868.906770040562,81.5471578992944,133.417529135344}
+10 | {304.7305636991,-465.597195927162,-692.38269341934}
+11 | {146.202397090308,643.426041147766,251.311062888657}
+12 | {613.839242557284,243.372738740541,-344.926416003253}
+13 | {-279.919780960877,830.688490188374,1106.70854171082}
+14 | {-239.941313060048,-150.729513285438,-644.486977709577}
+15 | {864.059606049879,229.761999686115,98.6393846787974}
+16 | {137.027023944766,-872.639896518612,-484.121630800583}
+17 | {-93.3532574414556,-2.83052851435212,-156.831074125122}
+18 | {386.756957145158,252.495312534386,-639.330396910652}
+19 | {210.12451616637,197.83940894125,121.228834110555}
+20 | {786.057957303133,-78.1737488754506,-293.537312267519}
+21 | {-338.401548335188,-500.885224226621,259.552625635757}
+22 | {-1037.65134435009,354.929835928489,-523.731062929561}
+23 | {649.665190518576,-201.744043329729,-383.714123375157}
+24 | {807.715272986264,527.719510934861,-238.925155968575}
+25 | {268.191717463272,288.453191375757,-292.660016634391}
+26 | {-767.046262696949,1054.28570038854,186.785685210919}
+27 | {603.597270228634,-426.717913313373,324.192854724388}
+28 | {-790.646063944855,613.794909920021,550.899642687981}
+29 | {-358.978123286781,-213.107177187055,297.623829646779}
+30 | {649.852832534744,-122.747853286041,-338.070280521321}
+31 | {289.975381104444,52.5492690561587,615.373688551293}
+32 | {219.2134287942,640.352925517431,-795.284581991311}
+33 | {14.9784107468498,-88.4494781406069,-123.072306109659}
+34 | {612.022594601429,-185.543004824301,-420.069777109188}
+35 | {-610.292592609088,239.789613935357,-673.520045033667}
+36 | {597.003206042794,197.782402998881,-376.678467551101}
+37 | {-56.8423068877579,232.889540939775,-233.929448140762}
+38 | {-933.879560389274,243.309855746838,-898.095254476934}
+39 | {340.465735117938,-578.405302034855,183.553117253541}
+40 | {-246.878364743862,-291.805796246968,-436.136662769966}
+41 | {993.304698488409,346.890205803306,17.6842057114629}
+42 | {172.133516798735,333.3442277846,-242.843618338439}
+43 | {747.733954731529,-356.07131864678,179.589467597229}
+44 | {679.345380618229,-2.68044668758962,-120.148391244667}
+45 | {488.894449728494,24.5452925443855,775.17536040857}
+46 | {260.731153450907,-868.175147049231,6.81574295503747}
+47 | {228.857968968147,70.8801925885408,369.851765182293}
+48 | {252.759302911726,153.249295308294,-638.37262532948}
+49 | {269.683805099097,51.0370679682875,712.091481303516}
+50 | {-269.268132926522,746.729882052773,195.702023539655}
+51 | {71.874274025531,759.649148831518,491.611002746426}
+52 | {-1663.96137606517,90.8133823647075,158.780074279377}
+53 | {-257.774920797097,-1030.22503446527,438.046739208405}
+54 | {752.215292509861,146.919523983795,-147.598607179812}
+55 | {177.381727903258,-818.519450864293,193.311594615948}
+56 | {166.741559741122,-163.118628708694,-404.900637540065}
+57 | {-1323.27537778783,-10.4967284230377,294.326001051292}
+58 | {549.909097625585,-596.903824692408,-301.850694344547}
+59 | {-269.916901585881,-627.64977878287,750.090432540072}
+60 | {775.624254458252,748.972003078806,-203.81676894634}
+61 | {-436.226974986994,-437.004875894216,-30.9748235460759}
+62 | {613.909837635605,48.4334189055091,-283.321027026525}
+63 | {-278.298037765777,378.436027806079,-257.853892045902}
+64 | {-1202.56287057624,259.014693113529,-200.71700430311}
+65 | {-576.994841464984,-523.386659017049,-230.554592593487}
+66 | {590.512151325561,398.065888838862,-347.231929394332}
+67 | {-242.822744756977,234.108323204393,-553.377918691274}
+68 | {943.6070753902,527.274570985321,-139.520854405628}
+69 | {-206.014801247102,528.178478241241,486.935420937639}
+70 | {-1231.88609517802,153.935968150265,190.638604939725}
+71 | {226.418944290168,514.65522108823,153.940917502731}
+72 | {406.242837506357,-413.34465423586,-431.049640725801}
+73 | {926.165945865784,292.497866770696,-32.5080388329608}
+74 | {98.0573027347866,141.647767296212,-689.77208317581}
+75 | {56.0959548637969,352.466952034086,850.195823590721}
+76 | {-845.263692780283,416.03103508814,-172.788128651063}
+77 | {342.859916548665,913.856416969682,685.48442034131}
+78 | {870.402457998673,726.676162243837,-182.971969280292}
+79 | {902.084234723884,490.477847071824,-337.824139748125}
+80 | {448.69419403979,-265.157573745948,-475.977013656683}
+81 | {-514.145412080225,413.261678856384,423.685005329721}
+82 | {-1066.01057897006,223.132531695535,-377.510836697104}
+83 | {-1021.50193347937,562.090253650298,-115.416784003661}
+84 | {-214.665103199501,32.8354197166855,-655.755578142951}
+85 | {278.049567196342,-697.129051950011,331.983924953855}
+86 | {179.724335008794,372.581626915806,-69.9806579428635}
+87 | {267.843845960723,21.7937867013737,700.60305984757}
+88 | {155.422832917542,-918.852392503608,-563.676251121922}
+89 | {-1177.23091561181,341.471135494987,636.07808560086}
+90 | {-67.9351579896963,-883.142952530624,-700.374252405142}
+91 | {-553.619481648191,-211.465410119479,-521.307252570533}
+92 | {145.858752083652,-658.631751181041,-353.373990023229}
+93 | {185.193850071249,-561.658346810616,296.077972874417}
+94 | {-163.225970575355,229.836951327697,-595.344875329231}
+95 | {203.55885535432,149.585670575698,-364.601983510134}
+96 | {-1235.84629981963,185.498736396646,-355.528834988744}
+97 | {658.286726835515,-592.635303407991,-50.7071498078878}
+98 | {136.841617763444,143.047237706469,-404.61280361507}
+99 | {158.811144857791,449.009048224879,1005.07753726789}
+100 | {927.860188757477,398.986993124426,-107.630193699315}
+101 | {312.494434150204,-144.984356964967,-106.847512834076}
+102 | {41.6384585803,-721.261854728639,-104.693423095052}
+103 | {866.942830053234,299.976907656981,18.1379345809771}
+104 | {316.089900119565,-798.289231608766,-187.177103552882}
+105 | {717.715398540749,33.5083707168619,146.963160971621}
+106 | {877.89816548212,823.178371173346,-335.256678447164}
+107 | {-470.579659565495,103.395254167218,139.275891941627}
+108 | {-256.599091223439,643.184223168957,118.114881878856}
+109 | {-448.281040797465,416.581677353488,367.177889179331}
+110 | {185.696099582541,-170.62108058618,-52.5855121896673}
+111 | {-64.6644999941366,-596.187534494299,503.632110740989}
+112 | {-747.037751783091,-212.088537754028,640.399855603188}
+113 | {995.797175264268,275.696395459126,-2.6031804563241}
+114 | {781.811070203258,901.88973031654,-342.469279381926}
+115 | {-1371.06472904863,0.254753126924469,28.1590955689618}
+116 | {161.638509349012,-826.99415058209,-415.359079451205}
+117 | {60.2440795907806,-1031.48434296339,129.80450714383}
+118 | {-632.754078578421,595.954722549919,-52.1965000148961}
+119 | {-792.275358264915,273.965086375479,31.4263876136518}
+120 | {-1371.55837611464,350.041174628846,-849.606882071275}
+121 | {-577.31215410232,-218.006790696498,459.303941542333}
+122 | {-1541.60323702349,782.610438668886,403.995959184054}
+123 | {341.476380596273,499.411480471099,-269.729378826935}
+124 | {173.23061521224,-721.191535815391,-414.764004342643}
+125 | {928.779761572521,355.688704910061,46.5382786373071}
+126 | {209.724942648269,560.612581086384,-197.447200573214}
+127 | {-313.973068774299,31.0882298202861,33.9370433613805}
+128 | {251.221805066445,-319.825003528556,-727.848057451017}
+129 | {923.27529093008,452.927865196981,-46.3175300513252}
+130 | {94.1001366827434,424.393803256897,-696.672177048828}
+131 | {290.777077215229,386.875089358842,705.238902783636}
+132 | {78.3104427987785,-593.999773769646,-693.89660403862}
+133 | {530.165597614355,303.568827698029,21.8307597880683}
+134 | {745.989685666099,-350.914161320458,-1.63452735343602}
+135 | {230.625054830844,-151.878827428637,719.675931469744}
+136 | {-4.88204686109166,632.429483520486,-10.3403865486221}
+137 | {347.376793331603,466.684675808313,1034.79903899528}
+138 | {555.922207006474,522.362209958336,-508.643912795811}
+139 | {-554.897605831554,670.651132833643,138.629544715331}
+140 | {238.571508235037,-537.280329277,-636.905112765466}
+141 | {676.103107062802,12.4799973194849,121.540894483029}
+142 | {518.526499029932,-68.0671987185693,-419.148640255824}
+143 | {275.950298846445,-658.732193051554,122.825937733362}
+144 | {248.543173029081,-118.945164499253,26.9352643130976}
+145 | {337.688653144682,297.961306208333,564.011462067362}
+146 | {382.85599052658,-6.85388913061074,-562.125695427939}
+147 | {-52.4475318190431,644.109608617153,-483.134594336694}
+148 | {149.841502399512,-205.951679756533,-658.23250424851}
+149 | {404.217286019655,-311.921368597925,-218.012332084879}
+150 | {-329.815477358164,574.875649505225,1073.31425379999}
+151 | {330.611675991508,-151.669569164386,-347.609167073519}
+152 | {-297.017584051949,-222.609475176527,-624.101650686011}
+153 | {877.728101693063,599.633343871781,-95.7768256174856}
+154 | {691.095600886416,-227.89716818059,-142.628393241196}
+155 | {64.222822214952,-708.804914430627,559.913142173742}
+156 | {-154.985650210882,5.57199213748102,-800.386729632886}
+157 | {-769.054471295342,55.2932504282974,-50.5244199234928}
+158 | {-222.466780840431,467.825974075828,-282.72979171046}
+159 | {26.7916368906677,-368.256797039103,437.004516621924}
+160 | {-196.978340559212,52.6103592942436,-307.534384366573}
+161 | {310.51748558234,-104.166567060538,358.418634774962}
+162 | {-623.271552287036,946.354651233934,-89.9314144559954}
+163 | {289.402893273542,-721.731189379299,547.006877006168}
+164 | {421.5331349382,-254.457678817215,-448.030003277579}
+165 | {-612.061742515849,-657.322569404473,390.311217087443}
+166 | {-121.306790800733,138.125351085248,-654.746049688596}
+167 | {368.719660673043,-559.117366768125,-453.662659305552}
+168 | {119.812637986994,-771.28373582792,-406.250648502737}
+169 | {674.236906483308,130.687745297242,-76.6053685891544}
+170 | {-1022.79757969621,311.718315630086,-183.127390880452}
+171 | {202.338403452415,-860.307354892633,424.079844438539}
+172 | {-387.045718709377,232.529857640182,-265.836636378781}
+173 | {18.2125290669655,-906.569503580754,217.379400495216}
+174 | {686.03338296649,249.199448995659,-318.359885821749}
+175 | {980.894478237984,374.335582889807,33.9180784285859}
+176 | {324.922851226244,204.369620341049,-509.807691878582}
+177 | {530.081614602026,-415.709203779453,440.205908751018}
+178 | {925.363130959933,580.025078187357,-364.204601054662}
+179 | {-373.610131759552,-0.718393149850969,-603.127464106215}
+180 | {-687.995822021562,301.193093975771,284.730186445072}
+181 | {717.48690071556,357.084745476417,49.2505276798374}
+182 | {-254.244391140987,-441.008028494252,-65.0781968354926}
+183 | {-559.738812937771,592.422666536987,850.671057509993}
+184 | {313.921519762085,-953.05201460759,-38.196695019816}
+185 | {822.255207617371,212.696989807625,152.074132442933}
+186 | {-57.1317994970976,-795.313449329732,-450.834310152401}
+187 | {80.1667539451294,103.965773324679,293.437236704695}
+188 | {-635.575347681524,1145.31289171765,-375.223593239241}
+189 | {-159.541032167873,-196.203058439151,209.473048650684}
+190 | {-138.287980061376,67.8680801733663,-143.560064410083}
+191 | {-72.2155149533544,716.065722326693,-169.735463109537}
+192 | {407.908223041248,75.1313796781743,-528.873217927684}
+193 | {-1221.59208595602,138.061907094,-364.082619035294}
+194 | {13.3646914711573,-710.166502673975,-440.617088418329}
+195 | {-334.036655083391,-909.653603753016,425.174731451375}
+196 | {350.182349053338,-275.273598905502,-365.819929719917}
+197 | {-335.440697739989,-974.019605374756,797.236889156825}
+198 | {434.72369147896,511.559583754292,-491.097670859027}
+199 | {195.605291564488,645.61974621394,907.884362939621}
+200 | {-35.1737342414631,-398.968243796021,-373.632014143764}
+201 | {702.533333143816,120.535262693352,45.6569065767321}
+202 | {752.813997164527,487.750382767263,-290.896418153567}
+203 | {252.82093856,315.982951678391,-594.328951375259}
+204 | {-180.523516820434,-100.062549981327,28.0950313606483}
+205 | {286.858685263634,-301.422784988896,-501.539242778488}
+206 | {767.039697015571,780.113526935977,-236.554781739303}
+207 | {-1100.92518978373,377.745584580959,501.617939247464}
+208 | {-198.275146379448,-230.174105952237,-311.191311266055}
+209 | {986.975527136794,480.742909440388,-204.500898956358}
+210 | {-1322.09260615232,674.524354191798,660.109907134343}
+211 | {-278.385549939677,83.3551636151832,381.828965334432}
+212 | {964.905378912989,639.312856721136,-199.672820275921}
+213 | {594.413326401024,-307.924682001146,340.886251028796}
+214 | {-389.516990600771,312.961545355854,-443.264875424668}
+215 | {-210.28684651554,-888.474814698773,644.54276721249}
+216 | {-298.281566534085,872.747832807321,356.881877402889}
+217 | {-606.672716216157,9.20886088515567,473.597430667058}
+218 | {53.3306046761978,-682.815972379101,-153.981938157605}
+219 | {-166.164426700657,87.9674705749968,36.356362610877}
+220 | {-244.670274987449,224.989819322353,-369.881804224672}
+221 | {-381.347893104584,732.3849554511,-179.712174063503}
+222 | {-757.726594354378,-319.322511141291,-423.767627054307}
+223 | {-713.348202375684,-471.709482641208,488.018014589984}
+224 | {118.736181360627,-839.71877329138,-390.068049479781}
+225 | {10.8784288145688,-87.4897784701692,-143.365488561671}
+226 | {-293.506303853627,438.241052243718,-508.269091008473}
+227 | {394.34862896922,-671.585113656735,646.208496624259}
+228 | {484.593179470938,-532.272038085391,-58.2469420293706}
+229 | {224.398148966515,-121.32531002782,567.759078846576}
+230 | {-1054.65585735268,173.204339573606,690.673929900579}
+231 | {405.421717506233,-540.089300175202,-323.715660127428}
+232 | {947.953934214412,697.910938365625,-155.603791955845}
+233 | {-112.61761811219,-76.7013995353192,43.330990838962}
+234 | {-110.300872290683,146.621004555421,-836.233454271909}
+235 | {-895.507800234031,129.053214877903,216.127565778741}
+236 | {-228.738512691034,687.73671096529,67.1144690673398}
+237 | {28.9333252900687,161.537483761917,-235.717915596879}
+238 | {56.9141559972235,-865.972027912909,-103.487553156228}
+239 | {-182.441743993424,-287.49201467789,-25.1443406587628}
+240 | {10.1173252174403,260.10641628891,-330.763664296458}
+241 | {730.370055906335,151.824642697818,172.980422573513}
+242 | {-791.96924586964,-486.685916440648,58.5737220929955}
+243 | {-651.740131262894,950.198601442712,382.29610037216}
+244 | {84.5978038634731,-1120.86404693992,87.3944812524254}
+245 | {-942.198864996848,119.970703442216,714.344958413139}
+246 | {-59.3495582750651,509.613622506798,-413.669293188925}
+247 | {-187.47239025722,301.309716611758,507.378137262389}
+248 | {449.207763462859,-462.637114567643,30.3798672941953}
+249 | {610.456778807888,198.519818741946,333.300259367063}
+250 | {-927.933637336285,422.103101959924,605.889195417107}
+251 | {131.419211797127,-225.686366734672,325.392488111269}
+252 | {1005.50237913565,514.951885957742,-169.477238517533}
+253 | {365.992584121997,794.437829227493,207.335684319584}
+254 | {-647.725418987958,206.031579182044,-834.994754871832}
+255 | {-149.519090037423,618.58797362626,589.222469754598}
+256 | {-81.6208935759235,677.326281876909,-158.853684654441}
+257 | {704.581717074611,460.268957486136,-312.431299453742}
+258 | {136.760600914922,-761.315656574841,-565.773388934528}
+259 | {542.792534835575,-343.614858427223,136.395307493572}
+260 | {109.99160427489,-56.7054394290818,-610.381077528243}
+261 | {90.3449573058456,-130.001349478866,-81.4654506128783}
+262 | {-512.665406165849,-366.304920154402,-682.481809089526}
+263 | {-22.1796104306038,348.788172041955,-361.757908670545}
+264 | {76.241248309695,-1059.8098209341,-76.7624576831485}
+265 | {174.156857158579,-408.43442275703,550.26449991691}
+266 | {-178.515270017749,151.704124192512,-483.205858719553}
+267 | {-305.456786757011,769.044055844025,642.100602670379}
+268 | {476.698119462161,-517.298391047742,351.767173123356}
+269 | {510.895510245851,-395.70285923919,-75.6995982438582}
+270 | {910.31363595359,753.438810488909,-201.073732609609}
+271 | {872.315398041446,361.200236795391,-25.1554621518617}
+272 | {420.635022939196,-498.313838582602,-363.494121244324}
+273 | {-177.269675345942,-910.590607973394,273.616506061199}
+274 | {76.3436012931424,249.507611236412,-311.616111454328}
+275 | {181.02110956064,502.001710756496,-45.9732382995407}
+276 | {-63.0962293442747,-780.625207934631,-400.56043160221}
+277 | {981.770911041083,343.643026129044,-45.3128733900144}
+278 | {-584.10315976854,179.10155610122,-634.497970435302}
+279 | {-269.063997536307,607.785493848366,-138.82045235882}
+280 | {-143.842051235726,267.439579485124,391.680207380163}
+281 | {519.947653297392,-640.07913909545,-65.9119266364107}
+282 | {-302.825650890623,613.330438844525,323.149242956132}
+283 | {232.67661526524,-451.996375814583,525.372103301078}
+284 | {-1724.69142566052,469.14069830831,210.787459128411}
+285 | {-432.91731598958,507.186190366737,896.327861511646}
+286 | {512.803901693912,-389.637573788805,93.5784757999194}
+287 | {-593.69828108371,63.8644494977606,-137.746169721704}
+288 | {471.074607299921,108.754186932717,-411.435753246054}
+289 | {452.773319967048,-770.671241689298,317.087466913385}
+290 | {305.932511972531,-651.098205568271,-316.141242583735}
+291 | {977.054737633027,618.644320281761,-115.25507944907}
+292 | {216.268672749371,759.996507131981,-126.711772022954}
+293 | {412.330061730791,-272.453268307611,-28.7773794667325}
+294 | {-1953.04707833322,476.159268458845,-234.938228291953}
+295 | {-513.985270392564,-631.995115846413,-90.3506179653199}
+296 | {268.866635204195,543.6049855296,-505.242368473939}
+297 | {-1138.74636015926,172.469648648152,-16.9395066721298}
+298 | {59.5415772641756,-776.895893581939,-493.697596549044}
+299 | {-229.255970499488,661.44241505883,1197.43170214103}
+300 | {-522.949598745089,-342.317003623386,-484.998256429935}
+301 | {624.989850092813,357.393515363269,-94.9682976203501}
+302 | {224.794668460843,-913.97461218077,-287.470841579596}
+303 | {21.5354377920639,106.440907559115,-838.753380401963}
+304 | {-1709.19699654119,494.920585053875,-633.078748065034}
+305 | {473.307143995647,-404.502856191805,515.309755581454}
+306 | {345.507875665702,-683.330311742503,-391.63805296806}
+307 | {-575.126012646451,203.543730284027,322.43512177424}
+308 | {449.965321262723,-643.208033329854,-172.754076264661}
+309 | {-477.666830107041,322.555087605572,-617.20061990134}
+310 | {859.269255540004,742.191701261763,-115.601622432398}
+311 | {953.084751563622,311.086906299598,117.197429899017}
+312 | {-533.95874304058,-333.343383145402,-547.428511085023}
+313 | {-99.0047559561135,-22.3743009879742,390.158118403224}
+314 | {554.003376594728,-333.462614002376,-86.9157912182443}
+315 | {-204.918957237642,-553.862611836255,305.328359992685}
+316 | {904.176359288498,674.81720881397,-284.096063512534}
+317 | {48.3170123014491,181.072994538896,965.858917574502}
+318 | {-402.646920810498,71.7321514794017,-905.891618668266}
+319 | {316.071778240667,414.467710167676,-24.4071264681002}
+320 | {420.375999198925,-394.580048143078,-531.063040785746}
+321 | {-1143.2405560093,192.170059526702,10.2142627272553}
+322 | {-82.1574799683772,549.18794975444,-66.7873090337897}
+323 | {-86.6843680661668,-533.742046243743,520.800757467371}
+324 | {-732.462588539979,-511.317061626004,-129.023018378612}
+325 | {211.61766491859,-879.400072352874,18.8060794504145}
+326 | {-618.251764970305,167.834857909278,-751.837053445814}
+327 | {-932.19298572634,112.642111591201,-113.205670720154}
+328 | {217.799288140231,649.51363123181,-298.262130770787}
+329 | {-579.729436575179,-107.261563652181,-182.81984256084}
+330 | {10.9853516640163,-933.859056652432,-572.992269231245}
+331 | {-259.487025812284,621.876082410585,606.326130070292}
+332 | {-226.420065155714,-467.174708376193,-592.015255267082}
+333 | {-234.275753696963,263.682256649359,710.748924089039}
+334 | {-435.690010838885,364.232806735142,410.974538778665}
+335 | {-104.446304939002,-684.723255984144,492.257736613582}
+336 | {169.599760568186,-94.4527184243665,-498.79464837813}
+337 | {389.455569029528,-540.058098966027,147.307024243665}
+338 | {53.3633506608499,-917.061067048708,-209.881780422017}
+339 | {84.3340525251708,-846.534146760375,23.1895795357894}
+340 | {-487.01079563553,186.842869085594,-610.373270842639}
+341 | {-138.978436313878,-442.490101205001,317.106236958948}
+342 | {-328.79647787375,676.604732005631,32.9361799414727}
+343 | {526.271176162738,-224.808938453964,63.0636831011326}
+344 | {272.268287296183,515.44547331077,-720.103805154437}
+345 | {131.198118009859,-729.164107511484,435.135916675743}
+346 | {879.680977081302,611.871374709576,-298.732567622536}
+347 | {102.955964016661,-83.4479454272387,77.9817162387275}
+348 | {-575.575141974943,148.489645623754,-875.842674004145}
+349 | {54.1594086939664,454.948215813284,-273.072469733803}
+350 | {3.29900166719316,-807.146134342838,-60.5812027037688}
+351 | {210.898908783065,-782.763118712499,328.505060726155}
+352 | {814.917941505118,580.830644674071,-374.931026234055}
+353 | {-567.184556693858,502.395568338215,591.607469728492}
+354 | {274.352219545012,-533.679758619067,-148.910928356681}
+355 | {475.770029556265,-487.421606475358,442.470787421646}
+356 | {793.326603478998,747.726036145896,-334.901159161955}
+357 | {397.160150860322,247.792371866122,973.010172900265}
+358 | {912.08162203896,587.750642399583,-407.569115619358}
+359 | {757.407515416379,111.429168144541,88.3346513127937}
+360 | {-1147.80574236177,347.732758888107,-134.171075165781}
+361 | {426.476473600079,1006.08974535188,320.548644203402}
+362 | {-80.181270006828,388.473752328192,78.4836479169393}
+363 | {296.389241896439,-795.635604972469,-1.32765192613692}
+364 | {585.725104788188,-286.221043364657,-449.181824894238}
+365 | {468.46375035446,-465.050290975735,-47.9302751309938}
+366 | {-407.274360878147,286.087533292467,-416.990898155993}
+367 | {896.800820591847,355.332196656719,-74.6670347455514}
+368 | {-770.916334777547,-331.128740178432,-489.506461194702}
+369 | {487.23346870336,395.98491362138,-89.7939429685944}
+370 | {159.802388147014,-910.373448564408,-156.768640472894}
+371 | {811.799760343949,179.615268597205,252.11012706447}
+372 | {-394.462406985416,-892.850886240543,213.72266953489}
+373 | {427.651272074627,-550.933926519887,363.911287477545}
+374 | {349.468856169766,-658.095359107168,-395.182358571423}
+375 | {91.8942379383517,-714.650026866811,386.740787696337}
+376 | {-612.80795859733,124.624448066035,-757.303297464803}
+377 | {51.9458649009602,339.111132036119,515.418968673663}
+378 | {-182.372929416926,-942.634599879938,-353.210262722814}
+379 | {-3.00264985984875,422.419995813511,-277.327479182083}
+380 | {558.363569929053,-323.733119319969,-421.920371621665}
+381 | {-457.242915381044,-689.899338664912,175.336585993796}
+382 | {-372.570961209771,146.202955799612,-933.650070181235}
+383 | {805.930772865,375.884310411012,-138.901130087469}
+384 | {85.9160249656279,-813.107718860409,-186.936525847028}
+385 | {488.780876269051,-535.473552091837,118.208058872337}
+386 | {-586.255389057148,252.421345038034,-822.097168743349}
+387 | {-178.470004161705,406.653950902725,870.019014572561}
+388 | {244.203984199115,-773.270794074194,-59.2940071964743}
+389 | {637.898975584093,-33.550813018246,-264.250861402128}
+390 | {466.616403072951,-520.1278750033,-397.050714489564}
+391 | {-71.2259285094823,779.563308372759,122.848856111467}
+392 | {-281.896058287109,120.399971301,-610.201579225824}
+393 | {114.895391715427,557.80021416049,1015.71679522396}
+394 | {170.697354824239,376.763871054044,-755.908649127931}
+395 | {608.284681616519,222.796273143494,133.371130885525}
+396 | {-621.486736924824,-132.213765473724,-656.097082118266}
+397 | {60.0945474374745,257.403209911627,124.871604057035}
+398 | {813.165419335817,709.576424661116,-187.723055815793}
+399 | {819.084753114636,261.847855944848,119.519599599984}
+400 | {-1591.11699504515,451.765744812528,-1084.07381389854}
+401 | {100.836235499064,45.1692677870091,6.71665535390334}
+402 | {71.9987686033297,-301.119651193113,-491.952724311063}
+403 | {-21.4854801941939,-856.538863769626,-139.635850856623}
+404 | {39.0185291983767,105.225584647705,-582.08435334705}
+405 | {-341.551324381422,553.102678536304,820.957715337237}
+406 | {-238.676040878439,596.475062006686,227.261170544244}
+407 | {677.263400995669,170.262695934152,145.576375698915}
+408 | {9.12547146156543,180.359657967512,-474.355893841514}
+409 | {974.819226687325,354.914104145955,54.4817971994358}
+410 | {402.484050808457,-540.802516168123,-511.981328227048}
+411 | {248.703388970545,1025.1097083336,-327.058213775947}
+412 | {384.936221959771,-577.775139993188,-467.827571287516}
+413 | {723.903716050944,-238.987348866713,153.015184484788}
+414 | {311.735600106838,-697.566335453892,-433.343668594933}
+415 | {142.965482466729,-52.6980268671412,-260.243588842275}
+416 | {160.100840549061,284.92268295551,-1039.92792558982}
+417 | {1037.91913083568,380.670628839144,-296.113412462725}
+418 | {598.781959566029,74.6568609444189,-327.133335149884}
+419 | {630.543696264649,13.7310638518728,-104.839389382329}
+420 | {279.455929621923,-530.179625285714,-295.21631527422}
+421 | {240.524050325161,-129.052470215072,59.8165377851838}
+422 | {-365.39673502932,-344.301581001411,-809.503633420073}
+423 | {757.022813817618,-343.907582190206,114.039518486389}
+424 | {448.695736428793,-388.271140062866,-405.061996855199}
+425 | {-22.9058631343533,-316.126520650765,940.560436920855}
+426 | {-285.944464167464,222.898868288105,-260.716629656138}
+427 | {-252.422413155871,-957.087166358264,340.821797196074}
+428 | {-1882.99623836936,283.860272366386,-949.66481016547}
+429 | {297.317599060629,-495.565601466887,818.961050509237}
+430 | {-1751.20369603674,528.424487540286,307.132966165625}
+431 | {-37.4735324946073,278.70780796336,-53.5821730245203}
+432 | {-297.250461444078,-464.538273141524,-525.056551841106}
+433 | {21.0822385591843,37.6738929377201,-364.606782787189}
+434 | {106.79125804557,663.62222729765,-102.002776618213}
+435 | {317.675657314467,-448.737176823445,513.396745496883}
+436 | {-1408.19500897142,321.700273685132,-54.2088816106459}
+437 | {304.198522094964,-576.206616646692,198.669138225353}
+438 | {356.951292873271,9.85404025181337,-407.831348441305}
+439 | {-221.909854717695,-841.613794951758,403.942929593813}
+440 | {218.042303966432,178.348618480596,-817.881655620234}
+441 | {-1281.52525297999,484.907607886733,479.550559714883}
+442 | {436.772103628282,-447.130350411483,16.7184813389559}
+443 | {302.501938748731,-664.783713642657,140.078437332494}
+444 | {797.719888084189,652.632396688113,-231.580100785436}
+445 | {671.021288192126,43.8037318602946,364.996137996503}
+446 | {78.0541237451137,192.503273867577,-523.868141832414}
+447 | {-507.766602721028,-794.065415138739,334.15690710198}
+448 | {775.369058341192,724.559087933035,-214.452129063896}
+449 | {41.1337435670084,85.9255709310885,-707.848065045515}
+450 | {-327.416878634682,-444.05722199435,-753.713951362636}
+451 | {853.338544612389,505.014033820954,-205.624425866576}
+452 | {-1002.36407430723,-132.22009219999,-285.390713565312}
+453 | {-567.409601190117,563.747137108595,266.524796551915}
+454 | {-938.006315891387,297.032920646306,-675.158621015533}
+455 | {961.818194458522,187.035646668105,80.7153213265519}
+456 | {920.262135557397,626.854702922905,-201.949915458484}
+457 | {391.18639216917,369.816664878082,-51.4420798649837}
+458 | {-103.084166591067,235.213055217911,-871.466065470742}
+459 | {-1731.3593866932,428.340451016334,601.81251144488}
+460 | {156.258981912923,-405.204302714197,757.312436486708}
+461 | {172.798332662283,-781.412726975732,43.4703643500116}
+462 | {-124.682756051884,-921.427433695609,-298.206171901951}
+463 | {-1558.28885353475,259.291320165662,-140.708674430952}
+464 | {587.588583579763,16.0557213142602,-477.32466112425}
+465 | {-1135.48391353965,530.957523534048,509.033267407822}
+466 | {-500.467763528005,82.7787061301719,-797.6607116279}
+467 | {934.490371788019,413.428351079979,-62.0748950410273}
+468 | {-149.852971522378,-491.939622292473,-257.656477147412}
+469 | {163.894796836653,-246.14078953069,421.152391826798}
+470 | {-76.3779189268282,-132.503316507637,-797.355624915202}
+471 | {543.14213671445,174.569465816672,52.6134105626089}
+472 | {314.937627269956,-264.234359593651,-590.479129807736}
+473 | {546.15063150275,547.633844305612,181.384651896172}
+474 | {-944.106446131244,164.389365176612,-809.484208317295}
+475 | {-276.186861195802,-9.8405419754517,199.633793865809}
+476 | {791.364382250026,548.002078881368,-110.412431254201}
+477 | {284.644372998784,-365.707960764279,-131.729414070876}
+478 | {-374.09830051264,271.232542687002,-686.163451202625}
+479 | {601.222343461796,-547.2063133236,202.274933700423}
+480 | {-280.427483990363,311.020571420288,711.439299780444}
+481 | {-436.855918970441,-118.961280113762,411.287720459391}
+482 | {0.0619362573841019,-613.411489239433,-741.524353321055}
+483 | {470.436747536129,-524.083028409058,-125.386248166657}
+484 | {-67.4736991006526,-202.700377449104,-276.265738206689}
+485 | {862.866782034975,235.414401144183,90.0181307538263}
+486 | {-100.271266503223,65.685905648072,-758.606729986432}
+487 | {-131.352342426162,373.824056926479,915.740636292545}
+488 | {403.639716730026,-238.171790881127,-467.416652344398}
+489 | {102.819198484512,-28.2337285940254,199.513494729884}
+490 | {-534.968448925461,-154.668908660873,-431.786355519681}
+491 | {-268.801488941952,1011.0360953516,713.340221161693}
+492 | {612.854737665278,293.81451879556,-234.987108685438}
+493 | {868.39687689543,625.312718569827,-261.927933097171}
+494 | {-115.841061090447,145.44978761855,-629.960180769146}
+495 | {-742.716726862378,-110.944154329836,243.862343855349}
+496 | {-696.765398498195,513.604507873028,286.655758708002}
+497 | {-348.559217543603,417.992832411208,40.2621854458962}
+498 | {255.442132148284,-109.825759789379,-514.155335068317}
+499 | {240.668020929643,96.5495834371956,-193.529205276749}
+500 | {161.778520522418,493.088434044576,-415.078841990247}
+501 | {537.98282866316,-416.190864455458,405.687115218173}
+502 | {519.603416047613,30.4761366609198,-431.26608235468}
+503 | {379.990921318087,104.636868379266,565.272268175705}
+504 | {507.378152405232,537.168306367709,-646.204900798443}
+505 | {-827.448932617718,890.035438895891,594.183981629764}
+506 | {83.4049722059482,-862.535852280356,-408.232510081187}
+507 | {-88.5778015857495,-369.962514991262,60.5695210709452}
+508 | {781.529266195112,544.422561791657,-220.789887426653}
+509 | {768.33208089506,539.461342013196,-159.147489128926}
+510 | {242.566667445008,-193.691582116201,-673.81650400549}
+511 | {873.8500235162,642.0637612669,-95.3078907020974}
+512 | {500.47906726696,-42.5019390689841,-405.327641941716}
+513 | {-327.281911677433,-370.293512759609,696.43168522638}
+514 | {345.913420985134,47.6687163480349,-598.305161489094}
+515 | {162.823315690785,261.505008975881,181.135923306452}
+516 | {-667.951715009782,253.179770306784,-758.191348300609}
+517 | {-144.000312618501,-73.8748237749109,716.232140026597}
+518 | {-51.1239346934185,282.01033219148,-448.488895854982}
+519 | {716.442507138459,-353.272640285148,0.443485880870652}
+520 | {-1266.6496127886,-15.4913605859475,-1147.2759930605}
+521 | {-172.372321032924,-786.417609286374,608.963496121262}
+522 | {-229.223699918035,-684.823473695096,-532.459739108545}
+523 | {95.9540356019813,-968.302794194312,137.230124614101}
+524 | {644.338916226092,85.7056872485926,-353.626433977497}
+525 | {-1085.0326373211,485.199014490097,643.874796083576}
+526 | {305.972671419631,-523.323561583324,-538.083491743727}
+527 | {-1539.36566040091,368.706699637933,521.6721072456}
+528 | {-283.79977934535,-176.434892817145,-900.179421139601}
+529 | {-392.692039150683,-72.3569901055291,202.550453384228}
+530 | {476.253376286256,-144.448740562,-626.750123205185}
+531 | {121.504776321947,76.9880186866473,-123.922361323709}
+532 | {164.31720052523,-532.616811055046,-587.148108120281}
+533 | {189.21160216675,66.4197423705866,603.289923857094}
+534 | {620.334559053582,218.041698198341,-223.167941440271}
+535 | {-151.181758164764,-58.9804039718364,197.258913841692}
+536 | {541.904120938559,472.889622044987,-394.007556797846}
+537 | {102.068131652237,-380.50094602482,-205.112730226358}
+538 | {362.573820900631,228.483864225012,-604.170006608295}
+539 | {892.026322790356,622.444621838802,-70.78996087841}
+540 | {-245.491308288221,122.437932642964,-207.19244860805}
+541 | {455.21945753944,87.3752982445442,296.452478803704}
+542 | {189.210285587865,-827.273441606126,-516.926833028529}
+543 | {-1080.50965146605,400.010887102887,-161.403863951123}
+544 | {419.67278649724,279.050060280213,-600.145701661747}
+545 | {-635.403768062795,710.357930936592,295.55997918132}
+546 | {284.767887286605,399.97078868494,-609.565262903434}
+547 | {15.4369450522308,810.696689083784,346.147980467543}
+548 | {350.252936960078,-193.907568404985,-590.246954499399}
+549 | {-880.535141733092,275.790767050874,-376.306447239928}
+550 | {-143.624447544623,390.542314777442,-191.504791342555}
+551 | {-44.2351327705973,-799.988624102526,427.653063975339}
+552 | {-359.949943608878,434.261049905488,-761.041522532282}
+553 | {592.852886718428,616.724334583424,-375.859331006478}
+554 | {768.894080748696,683.047728137268,-186.417419854248}
+555 | {-932.417227755271,30.0983878468402,975.507600704357}
+556 | {-196.781214442567,272.187097606848,-820.059937123286}
+557 | {916.575634088825,331.944654183797,2.94581689808976}
+558 | {-211.699689990266,433.823723117561,-159.713657489908}
+559 | {519.56407444555,-16.4489680356125,217.180095325832}
+560 | {69.6819731557514,548.628981797799,-852.744844880397}
+561 | {716.796845017736,224.501803508143,58.6507020762773}
+562 | {-55.4873029253347,428.667926264752,-544.267802973001}
+563 | {42.1691649583717,-655.321187917958,442.111719673265}
+564 | {-114.923557727401,474.423304534672,-646.673741072495}
+565 | {-125.155294086934,-978.827178305164,225.037143184466}
+566 | {-283.797668333994,-241.229670724137,-943.620033014558}
+567 | {311.803888967382,-506.350589153477,498.592374114051}
+568 | {310.157702231061,-451.223824584861,-322.721772262693}
+569 | {73.172226020791,152.324006296347,-638.157632986994}
+570 | {73.2048252209965,-455.190348427268,-783.707221698963}
+571 | {-652.205359408259,-271.591926409946,-372.664778800866}
+572 | {168.630394011291,329.796662312575,-499.223812655727}
+573 | {902.585638275337,443.550682752982,174.906371961038}
+574 | {864.166281484128,538.882040992784,-241.260029947795}
+575 | {626.628828899153,347.093925018816,296.301832291863}
+576 | {356.184473030271,547.540311323295,-567.516424889957}
+577 | {434.175160350566,-352.936727898286,703.319606290386}
+578 | {-1179.33300404813,-72.9249566249252,-886.393226272171}
+579 | {14.4366417012208,58.2560787766943,-97.179167162361}
+580 | {319.365469319519,76.034719757136,-659.110975504371}
+581 | {168.067665284458,-698.35706526995,345.948025535119}
+582 | {119.79527723948,291.812910699053,-355.599233074834}
+583 | {-671.32415880786,-101.04836562843,375.269981539745}
+584 | {579.396901651801,62.0264748285509,-415.011790800176}
+585 | {241.259250503728,-134.321622200352,-199.885018060763}
+586 | {234.790251106492,-850.587058438898,-423.02582781811}
+587 | {402.300909938414,-66.5243376369359,-99.6767397592658}
+588 | {687.596000761602,526.495642838798,-151.187027928361}
+589 | {734.993041120701,493.081698503795,306.176167242505}
+590 | {362.497646598856,-292.746392935886,-751.895132621691}
+591 | {481.976228753779,-397.65977354182,-61.7242242520729}
+592 | {-780.849606659576,-6.55058676896115,-1026.60390190804}
+593 | {181.554573317411,-346.432972594644,135.995327805793}
+594 | {822.524571998913,532.479269835051,-227.39554858261}
+595 | {-26.1135089403867,387.467605133604,559.269025361976}
+596 | {638.215359001091,-195.763675261362,-334.062405460751}
+597 | {-1053.62194519563,-146.889657978386,566.123017500278}
+598 | {-47.0908770819274,476.926617435656,-838.351806127496}
+599 | {-516.408268920451,-166.287519847036,-716.48533236721}
+600 | {266.828765182438,-545.370380487119,-338.096407854331}
+601 | {784.812170844362,-221.534631768067,87.1486330234546}
+602 | {388.218868725628,-104.829926926614,-568.288701181723}
+603 | {-144.055382809268,799.623188763903,553.582513888885}
+604 | {-705.22746697173,252.154856527435,-993.137266274926}
+605 | {935.253861523358,256.737752022904,93.8523017887398}
+606 | {317.418671889213,-138.973266427214,-537.60563491324}
+607 | {9.98943341635767,-478.954820713277,316.059280490539}
+608 | {-136.498177249765,152.48287607055,-817.33346796983}
+609 | {-355.261578923076,-969.811576443126,643.170832468971}
+610 | {824.437684399933,602.482632546088,-255.1756670267}
+611 | {-72.9385008739971,-670.202440329946,233.109091415797}
+612 | {591.240557923025,290.729136389335,-197.369073840949}
+613 | {-1074.94603124509,58.2913119590264,429.287996929066}
+614 | {150.920765799806,-73.6431193715648,58.6089460822138}
+615 | {232.290394286259,-340.119423732257,-592.82850239833}
+616 | {109.653464304067,37.3029779953881,-419.243598328699}
+617 | {-652.504745214437,-987.526892705446,323.914882376124}
+618 | {264.615957112295,-472.904126366273,-399.750835650877}
+619 | {815.549180760655,148.08362510736,118.905440162934}
+620 | {-42.2369658686152,176.290856364818,-807.550749955292}
+621 | {-1305.46780276718,331.959554837604,1058.70300291939}
+622 | {397.139401171252,-452.116560520818,-410.163804552289}
+623 | {-811.438572693068,48.9950683406568,504.519394078102}
+624 | {-73.4798216205107,60.1879926733432,-1046.41562557193}
+625 | {427.416896622735,-572.772583952523,78.1069832603096}
+626 | {-151.426509894568,-14.3770624539062,-173.044994438471}
+627 | {-420.658631781462,-875.581235328708,-572.54625221803}
+628 | {170.425884573121,-346.466270310908,-253.854556756585}
+629 | {221.60371416113,305.692687248867,638.555373900102}
+630 | {-53.1605460066107,-186.793092079822,-232.220196593119}
+631 | {537.45725190819,309.891778396188,236.506277619151}
+632 | {409.472863726917,-72.3260802521748,-698.92456093397}
+633 | {-68.2226238081198,-825.259486138194,138.337246394617}
+634 | {-684.934478334922,83.4352251708351,-834.557557263317}
+635 | {756.687776375493,-61.9810724479211,195.300148360529}
+636 | {-18.1980211150009,-298.399790012271,-927.544605943432}
+637 | {589.899222804789,-521.15724346812,-111.779359536369}
+638 | {672.50092941365,564.632471347428,-141.919435493338}
+639 | {850.072560959019,179.958657275544,85.7574750717463}
+640 | {-1202.24227712896,213.36832280069,-698.745581906982}
+641 | {-183.866583149882,-619.207802320413,447.87514669701}
+642 | {765.930843449081,232.031668223761,-307.656667402113}
+643 | {-422.227382847039,382.409792839879,-755.339309453088}
+644 | {-310.872796387575,341.413463046036,-118.22262020165}
+645 | {847.859975754991,318.748242107191,-13.8509459045839}
+646 | {-268.792451270834,-39.5950404580584,152.133441084019}
+647 | {510.025687513682,864.236887871042,264.156891394987}
+648 | {364.29190490714,-306.549352604555,-367.667181633398}
+649 | {980.323805826002,482.727516410779,-50.8174976674377}
+650 | {-53.6085395989408,235.683783881355,-978.480310144991}
+651 | {606.807080667966,83.5420838482681,447.125989172952}
+652 | {133.36095080274,-502.239955876612,-645.385743063501}
+653 | {78.8677299944965,356.130556273136,-39.0674768056663}
+654 | {543.794216994294,126.84171828776,-425.249900318297}
+655 | {565.527784820136,-716.294427538868,173.578904473763}
+656 | {6.25408752941046,-718.077776940994,-666.503014358111}
+657 | {-1148.38202263509,33.4190780083834,-809.975305028368}
+658 | {-142.953067220135,137.596935126068,-642.88461331632}
+659 | {-212.3298852913,-8.99210991934292,197.29318808034}
+660 | {99.9197771633391,-80.1004027328411,-533.527819938508}
+661 | {290.890368407894,240.064301938402,121.287239561789}
+662 | {541.98446051682,-5.2586800218402,-402.055947256177}
+663 | {-676.02810341811,131.867540879266,112.162519576817}
+664 | {130.808634311517,-507.487002988064,-642.374475800659}
+665 | {230.511443444686,-475.989615308983,-78.6672792418933}
+666 | {-312.826818314322,88.5513216611647,-982.152462768792}
+667 | {-583.786134806007,-142.089948693085,-502.918921526684}
+668 | {-406.319373183606,24.0486825245026,-915.160311122739}
+669 | {-920.553065615433,79.0309534902843,721.788326979133}
+670 | {-1347.37298222037,423.414750857922,-668.297146789149}
+671 | {-687.112861773791,639.896116590061,206.990978842629}
+672 | {873.036351521083,631.924117317063,-417.413348435987}
+673 | {-157.929675154414,220.32998320163,-576.998603206917}
+674 | {-193.192084722822,144.301182380499,-875.534778194533}
+675 | {622.425970210891,573.360398504796,28.9427662213165}
+676 | {-225.789041133218,779.484688230156,243.825632340858}
+677 | {870.23020451346,517.378407842839,-93.3498265927583}
+678 | {-515.292383322542,-851.548304832859,259.778494416528}
+679 | {941.995812677439,270.522798292907,39.5112635374139}
+680 | {-514.475849467379,420.892107324055,-216.346757649015}
+681 | {77.8091171185693,-547.749875213624,-407.684655854756}
+682 | {-999.013057056375,-290.809640190755,-714.911869544184}
+683 | {343.931090477165,-218.681721463089,440.473475328689}
+684 | {52.8931788156299,-775.543035090581,-391.01255115995}
+685 | {-260.24437390078,275.141250622516,-587.08278394113}
+686 | {330.151255242658,695.85048680871,-670.728887945738}
+687 | {252.579864974913,-507.057264597949,581.153041533289}
+688 | {35.7077393105128,-845.186376441932,-184.623989603058}
+689 | {471.502381368152,382.226516888085,416.236017746721}
+690 | {-1352.6742744236,285.043215022361,-557.029447359714}
+691 | {-241.355218830733,-50.5611936819873,514.280315326515}
+692 | {836.944818583833,626.358814163905,-385.611731065728}
+693 | {-280.302346926537,-685.967164035308,663.905944365916}
+694 | {-52.7660340318387,148.269546359626,-325.585027466883}
+695 | {502.514274951011,135.897659564071,489.441550225534}
+696 | {-3.23854682060494,588.530864519724,-136.494564217061}
+697 | {330.527332906732,665.393326585891,-191.038661109675}
+698 | {-301.896721929371,-700.362043815007,-140.726279234646}
+699 | {849.34063463065,820.217015726992,-305.643780780746}
+700 | {-686.61349685413,257.518777413837,52.1904650850033}
+701 | {165.751209975237,-560.405630309918,-411.794888343739}
+702 | {-897.932437471349,-387.185026921975,-779.129775707054}
+703 | {-1181.85964399957,-36.9622139018686,35.3508143390579}
+704 | {-144.506348435805,-954.273219254946,-482.486557339571}
+705 | {-451.756404847454,-382.60111247033,-32.3856430898621}
+706 | {393.565542269047,582.761232010015,-747.281110333803}
+707 | {205.730676244875,-400.458764232173,580.859857848137}
+708 | {298.206411081284,-625.672285062076,-216.619405912087}
+709 | {498.863479062784,275.163704984379,462.253241420261}
+710 | {-1186.37217306509,82.3606492857581,-376.467796190978}
+711 | {845.02999773937,373.87265698297,10.3796397683702}
+712 | {795.04197516292,694.915027748931,-299.88696724297}
+713 | {-511.622098904875,316.846316270853,-1384.49445355605}
+714 | {-581.81089604332,357.259403713998,-957.772699715654}
+715 | {308.448199880885,652.345576911107,-33.1282016213211}
+716 | {-88.6037239955771,585.735601135617,-237.722614167953}
+717 | {-412.501356004621,240.133803027536,-836.395897149958}
+718 | {-616.84406667083,-985.056004919844,-662.414669713013}
+719 | {127.439524819779,-454.907582594598,752.271005359503}
+720 | {-1115.98999337498,385.788081909961,459.486723919306}
+721 | {615.950589945837,20.6099165311995,284.060935582237}
+722 | {-654.616709512482,-43.9888758160641,-971.276073443275}
+723 | {-483.363036514028,-225.901535084434,870.570080144211}
+724 | {15.5127891588682,-727.654464198775,-585.406431665874}
+725 | {-108.895403268249,-1002.81721594152,642.275809392752}
+726 | {262.702883934253,724.002541453453,-813.419207177602}
+727 | {257.737789859145,288.979192735909,687.435830001004}
+728 | {98.8219054912978,-806.398092393137,-266.701901658543}
+729 | {637.635535498514,-17.7691751827907,32.6506795138847}
+730 | {-617.156026381906,-754.558488512803,98.1616560565632}
+731 | {285.047854385652,-794.187493636105,90.5510094938665}
+732 | {83.2758119407126,550.529799987187,-280.905970549652}
+733 | {24.4925798814172,1227.35235066648,-38.4237799816534}
+734 | {-1152.92006498557,507.53052098479,-594.957470722601}
+735 | {-15.4945103647813,-5.77437150457105,503.793283640471}
+736 | {-368.193131051375,-111.508621653183,-964.369749111357}
+737 | {457.15100444115,299.279944999075,553.256854803106}
+738 | {-465.399915191364,150.427169904131,-1097.95507143817}
+739 | {772.687044980818,17.0199025757928,177.923243789027}
+740 | {-551.126454679043,-252.875869022386,-1062.41906777601}
+741 | {65.9393677048679,312.055119776043,334.452215630698}
+742 | {-16.3614135946173,-802.4918909632,-361.175794842372}
+743 | {-382.233558269935,-657.056744533811,-134.585071893798}
+744 | {-1401.1123493289,399.290432560378,-820.911944814856}
+745 | {743.902166961853,-376.714040322118,269.244510415971}
+746 | {-976.256716514129,181.839400567698,-1122.33831481136}
+747 | {729.962207685051,-121.25141589894,-78.4837511980305}
+748 | {801.689359824297,604.461825017797,-242.934405720971}
+749 | {-239.714128974731,197.488491360865,1003.01572679399}
+750 | {-121.902399042688,-521.194746597473,-500.249880848591}
+751 | {352.028771288608,-712.233479485391,433.021021277982}
+752 | {-522.211711994086,267.082447110575,-137.043444503691}
+753 | {-256.228167670777,914.120589620596,928.363155345899}
+754 | {186.111427787902,-593.492154603143,-704.515472245221}
+755 | {687.765442368681,-248.842544571159,101.105517234741}
+756 | {-7.42215276099878,-744.132553934954,-636.020896987028}
+757 | {-993.315463432948,-350.287272288958,-85.0065021188535}
+758 | {-704.341587412894,320.787633047779,-477.60988673104}
+759 | {-192.064982976327,-135.328070114122,341.026049507009}
+760 | {-555.972900391362,-182.67671553349,-975.641553501842}
+761 | {122.211910109399,-48.9825658356263,851.724538113818}
+762 | {72.2527997728746,264.748618302844,-514.781773594674}
+763 | {111.093738759592,-395.130232029224,-67.998855943799}
+764 | {-476.981054473196,-1142.41969610554,-133.222701166732}
+765 | {-118.162616796479,-707.521161723014,680.261680101437}
+766 | {791.028323185663,976.480079330705,-333.548621084679}
+767 | {-170.543885705758,421.096199519982,288.609974276743}
+768 | {-75.2824499514624,784.158847868556,66.8720078123507}
+769 | {-312.477081141435,529.778345922751,506.153960642743}
+770 | {-140.467271951247,275.892172821479,-698.739305199582}
+771 | {327.525759644703,-254.103115043571,-90.0624029970732}
+772 | {-202.273021165821,294.670621410991,-328.422396870613}
+773 | {-370.983717005777,-987.686513955396,-417.594575703646}
+774 | {386.639139253936,-842.887739495258,-33.7093551788008}
+775 | {-715.109031931664,389.232185871099,-817.910232558157}
+776 | {439.598558801408,63.4120835293936,-287.269430332954}
+777 | {-517.269564843596,-163.803517616261,-313.545369849965}
+778 | {-93.2423558504878,162.931117753995,-498.917189573652}
+779 | {431.466149138303,-210.753036330402,249.148236840926}
+780 | {755.738214073895,833.095167459982,-244.414770051082}
+781 | {865.668825803853,235.730351689529,8.22940980015358}
+782 | {-1331.45147377059,420.494408119913,-844.064968417156}
+783 | {92.8194184688646,-530.619250597068,-86.5579646450627}
+784 | {752.802841215897,803.482610490353,-237.917922864814}
+785 | {325.375964963828,270.670712052602,-60.9043182736513}
+786 | {-148.723674487471,-136.252224264115,-934.145292001353}
+787 | {473.173576488087,-602.785719296802,446.416142889058}
+788 | {-1279.12955693586,368.317395497577,-599.132199035168}
+789 | {78.4758787000064,-240.368252057123,-383.954951457365}
+790 | {-62.9989644262882,605.718701660693,-171.708857473058}
+791 | {-1055.90134525364,706.075357842101,262.254734291975}
+792 | {98.5705676011739,429.057476164339,-691.955630771352}
+793 | {17.7646160632793,413.968515813905,653.365092467441}
+794 | {-206.556254279997,-887.717201375341,-146.305604840786}
+795 | {-164.119805076113,228.689386401408,-772.219111291815}
+796 | {414.248920457798,-211.836657183096,-273.847838906537}
+797 | {338.623154294465,114.329759373197,-59.3491523129584}
+798 | {-86.9895503405777,-843.567781385826,-298.192245121943}
+799 | {424.706576259844,-768.389009352601,452.867425214894}
+800 | {-312.619017620356,225.162607884703,-296.576173741536}
+801 | {70.3225512804919,-746.928196744248,-26.1678616717768}
+802 | {130.801906106136,-7.41785193427119,-315.669873580279}
+803 | {-208.937565082164,-324.734430451578,-175.705864485773}
+804 | {269.209174957207,-413.31409696441,-379.32979391202}
+805 | {-146.440433429308,-746.566644697438,169.681141217744}
+806 | {-98.1865592862545,293.178114737507,-231.781813698896}
+807 | {55.2409071909099,-980.167902848414,209.647072309194}
+808 | {253.333886601357,313.824682925761,-85.5189109115365}
+809 | {506.226185285863,-40.1959802389049,464.373336772295}
+810 | {809.577892263547,861.45971643036,-215.839866118099}
+811 | {-127.501838813553,13.7606419092008,9.81166198036121}
+812 | {100.649385158365,535.402445153727,-297.696857369913}
+813 | {274.27129108479,-840.14978195816,246.183017694331}
+814 | {-46.2854105687034,-199.715277217276,-358.904601912}
+815 | {-317.612426215132,1013.27995833572,-195.972353125572}
+816 | {-592.669748005772,570.312717322712,-39.6696864647711}
+817 | {-183.32093275676,1.31431239038281,-432.592777277904}
+818 | {337.833279730974,528.211136254314,-623.328289554593}
+819 | {-735.395548062007,86.9211483220829,119.9815847401}
+820 | {65.8498260682315,-757.562619642572,2.39793582114106}
+821 | {-76.1270117276792,-704.420087008563,870.195269195728}
+822 | {673.153753463738,639.88193206633,-454.003688426263}
+823 | {734.963353258147,-34.0943104718832,-74.3105177344389}
+824 | {0.175356462046616,160.857688018079,-580.427435426889}
+825 | {670.296349323709,-417.040492813342,441.61009990097}
+826 | {-1056.41357483245,126.609182567558,-1312.0391473084}
+827 | {-424.180333321663,-643.25483835206,233.423144627609}
+828 | {466.898896326053,-420.08074245939,-579.250884083244}
+829 | {-74.7189473935087,15.034277211623,118.290747416653}
+830 | {-324.941424696505,137.908224611397,-1105.19424274368}
+831 | {-93.7013380576266,1114.26230555569,538.566075969567}
+832 | {-242.933097992659,262.532078370072,-527.82237064497}
+833 | {106.773554853935,400.863266839726,-32.8800302899553}
+834 | {810.486836855214,924.031574914851,-302.673020708143}
+835 | {194.118465939629,-646.730924029044,-190.873544856654}
+836 | {764.351771602829,871.295219387592,-256.666991280228}
+837 | {-338.12393509507,283.55802623005,311.726627547816}
+838 | {-466.40177076557,545.407979381489,-287.408360798957}
+839 | {968.504615554743,327.954827223635,31.7681441869798}
+840 | {391.707569107936,-532.380674206322,-275.86406390686}
+841 | {65.7021231446265,843.151077104383,559.67404061885}
+842 | {-567.177823811457,98.4930770461428,-875.98951292245}
+843 | {-240.118585971328,-300.838521730066,445.233509109663}
+844 | {418.201360145544,399.889097018551,-537.915494854176}
+845 | {-651.15101193177,150.372810155743,-564.664638540817}
+846 | {-330.583751126287,380.381840532071,-372.133597890078}
+847 | {-551.85634649832,154.054657421656,274.758631759727}
+848 | {-371.455048621029,-1102.69056338744,-430.917808715693}
+849 | {-130.47529038127,-394.838231074815,275.057811972018}
+850 | {-1060.26324641447,286.611706602539,-815.603364284879}
+851 | {62.1746241751155,-415.284270989495,-411.3867298436}
+852 | {-405.331291358957,-1075.09449084042,-148.726367084367}
+853 | {451.897766908103,125.320647911046,-739.30584198508}
+854 | {283.59004204622,-641.73340366849,-529.832523011986}
+855 | {-66.8087280568449,-840.483138170074,3.59682564469361}
+856 | {-263.963709783891,-997.851945054542,-210.813156805968}
+857 | {431.241623806907,283.397130411643,639.432948267423}
+858 | {-168.275986538782,505.612344383228,-553.775573458303}
+859 | {460.068764110312,154.42154916592,-100.25663764682}
+860 | {-1239.90429918057,-182.442148510248,-971.337478998153}
+861 | {-1218.44650382102,418.694861236223,-782.376852814258}
+862 | {-77.9133518017871,545.018440272665,-194.294222758201}
+863 | {229.294001177911,407.767136271776,26.0827164658991}
+864 | {412.914188924056,-83.6439104970493,1.54359214999199}
+865 | {790.718365913513,59.7384836228951,28.7154465281105}
+866 | {-5.48507964432512,291.654543970409,-811.098733254866}
+867 | {-165.919947526991,289.875243524126,292.364754837454}
+868 | {-167.375722479374,613.092100689566,-278.683456284648}
+869 | {341.718962754608,-548.242683325337,309.72519454169}
+870 | {-675.579998628862,333.467479469418,-646.112261936097}
+871 | {512.019063790617,-5.54559146504928,479.898401608288}
+872 | {836.651491393135,742.2026583897,-226.399785349779}
+873 | {-1108.11349044238,-147.062239095242,609.157329950429}
+874 | {-725.62791705556,72.1425852058642,-580.773478663935}
+875 | {-566.224825353205,1309.33346974762,594.256064249778}
+876 | {-126.79487393261,487.707611051203,731.290882155773}
+877 | {546.40534615193,213.385563958301,611.992597498893}
+878 | {-366.243550951903,-910.866981589853,159.443519482858}
+879 | {569.496838307133,149.961653997492,356.685198146741}
+880 | {507.716765551222,7.34702805600455,-355.359084765486}
+881 | {394.123751555144,-671.462063714369,-366.14868086596}
+882 | {-38.6488071929475,-154.286438280787,-880.504141292461}
+883 | {-589.557305555902,-512.229022221349,231.881620454893}
+884 | {-316.658657884754,-1013.33752998367,-6.41567293990953}
+885 | {841.851655717041,348.141475648719,-39.5611426262697}
+886 | {10.7432309312682,577.299299960637,-890.492189606733}
+887 | {-448.226731715486,706.879476744785,765.890898316826}
+888 | {-231.049430727141,-999.575788984889,-439.126313777873}
+889 | {513.233149842738,-557.336687924548,215.462476662483}
+890 | {-422.569036066866,-92.7411934690458,-1019.06978581727}
+891 | {-354.713381991472,593.353157871147,665.111737111218}
+892 | {829.459986815253,716.972654975171,-217.808641883368}
+893 | {659.464235144996,179.788802266547,-152.07574399922}
+894 | {-678.466506006046,-96.4996475975826,-1004.7768490759}
+895 | {319.837097695739,-359.256061382087,-183.770586453406}
+896 | {-980.392695556109,529.977607638414,593.525985290871}
+897 | {-281.215470593302,-761.594803054393,-582.170711897283}
+898 | {-144.252902903399,-827.468036490074,-427.449281249836}
+899 | {470.986531363633,-728.97316952651,18.0771610026469}
+900 | {567.403835752947,-76.2941909526237,-444.947095107438}
+901 | {706.979063088001,-122.990116697311,169.916170692656}
+902 | {-169.355845797254,-69.6519275347317,-935.629625907535}
+903 | {-1095.1881777081,-40.1076699026012,-28.4895306711101}
+904 | {-325.425033010691,-37.9773679477537,-1137.74021773175}
+905 | {121.144734585384,-973.551137332752,550.435525948308}
+906 | {764.358362457224,542.318707634552,-349.470962164379}
+907 | {69.5829273901727,-339.32260636416,452.352573883547}
+908 | {-753.559120382882,255.578645615065,-1100.81919687931}
+909 | {24.0516335176814,-633.271597059063,504.249377001862}
+910 | {-925.308321496954,816.479919860698,187.4210905229}
+911 | {-649.12384284262,110.462925525243,498.103866666845}
+912 | {53.3954042803347,-589.227962634671,-651.474095127237}
+913 | {61.5231729618868,42.7462095939035,204.207659943438}
+914 | {-109.593266644938,222.110495510898,-1121.18101363853}
+915 | {-368.777444792403,-850.791777272896,247.704255588821}
+916 | {609.904271854888,102.798032373011,-367.975183926057}
+917 | {-615.567316414998,347.783538492958,464.041756094257}
+918 | {-418.550308335065,-6.39308583179094,-942.942306601689}
+919 | {593.29782987191,7.14428259594825,-129.452424838944}
+920 | {766.282831800326,777.418680818332,-229.98945439678}
+921 | {910.871465093998,691.556240536251,-278.163103269148}
+922 | {363.756713117458,-103.646264143244,-629.877819008462}
+923 | {684.85729898907,99.9670312067313,-157.903680092398}
+924 | {-60.3358824480383,252.675035153166,-871.167796865807}
+925 | {-405.985302220507,575.81044293744,939.65456899225}
+926 | {-135.492919910059,169.769233366734,-644.970382666706}
+927 | {-612.826318036153,-618.352794635246,312.997041550876}
+928 | {-1150.29163503576,251.710635211856,-810.50261115671}
+929 | {203.904368544106,-704.306352186695,-195.539824379942}
+930 | {-211.543711079523,-759.836014744651,-389.405291990594}
+931 | {-561.969825889784,-920.051467685321,-247.450057350689}
+932 | {-228.056291514799,-134.809348798831,-829.240640688308}
+933 | {80.4099326615248,-585.118027311074,547.355539704245}
+934 | {407.895377434929,-337.774506937713,-230.945335874607}
+935 | {-251.725881007582,-450.146025443781,147.229648756053}
+936 | {443.595739248501,277.803085645711,-459.216384421689}
+937 | {768.266603164227,-179.654780125608,381.223855323766}
+938 | {166.465493793124,-591.925555021857,-543.426085751739}
+939 | {254.441140212917,1003.751364105,373.243684882407}
+940 | {502.105685500678,139.011252682558,-490.769761272359}
+941 | {-173.81499182828,-836.919014533662,230.344091140366}
+942 | {831.790172041626,508.080813733292,-41.0500303912222}
+943 | {-443.91709126997,-709.639129724882,-125.214642690726}
+944 | {8.5224803747858,19.6415647013969,-854.790815607621}
+945 | {-779.385468123768,-517.967219232449,-117.675516357504}
+946 | {393.199145881257,-591.144506833762,-481.538620710119}
+947 | {-419.171453919274,463.336246952451,-232.771166907718}
+948 | {562.330851272274,-21.8805888541834,-395.909966818373}
+949 | {508.714577280977,47.952079022558,-371.758551504665}
+950 | {-1168.98687052981,-177.933528879234,-1023.41481902235}
+951 | {230.705173537601,-542.41757534927,-101.617919339639}
+952 | {771.190402165315,643.151079698103,-173.623387841553}
+953 | {-703.379699970943,-157.293618533999,178.264693772853}
+954 | {-19.8131229865762,-37.4224645789017,607.509596117244}
+955 | {30.1536483473821,-820.597428853329,-512.659298974536}
+956 | {442.38951618994,55.6176726462379,-679.162185031076}
+957 | {-392.956221476116,823.541061780575,115.475096991448}
+958 | {-313.03909612127,27.8869991039227,-705.934651444036}
+959 | {-418.066387660349,133.826725499763,211.307502089971}
+960 | {673.057151986376,821.903487354505,-553.916942551824}
+961 | {-628.095524032583,183.871294980084,838.730359337866}
+962 | {787.6017509309,631.591626340498,-81.140155017458}
+963 | {456.240130031983,-788.155327129433,70.5708912206144}
+964 | {407.537028743856,488.887145778841,-701.490756029475}
+965 | {296.178266153442,415.296860360723,-540.197723478973}
+966 | {-454.486872247235,137.017262747375,-703.369464286931}
+967 | {12.0533578650523,-20.3279556880276,436.832189495814}
+968 | {-247.610468001537,-920.35322321143,-590.866819710663}
+969 | {622.537777303333,-360.736203031848,264.654366201194}
+970 | {-348.329085994667,-631.821666405665,-134.669630661862}
+971 | {-375.038237944054,495.640789505547,778.400549391501}
+972 | {428.593147056118,-378.744763158628,-332.730134314595}
+973 | {-468.388921819793,-361.345703589705,46.0774374659003}
+974 | {166.597168427257,132.896266718624,-839.80238946382}
+975 | {-32.6131648090045,70.054597422148,797.079234432721}
+976 | {-404.939656433141,935.044983762568,429.472443689896}
+977 | {51.2459618889678,36.4230119431423,58.7371984585452}
+978 | {237.03156256896,52.9828961935876,-709.931077483125}
+979 | {729.431809942643,54.8817088875761,187.494175010928}
+980 | {-544.166598323061,60.6414836687122,-1017.20817237116}
+981 | {611.011402727611,-315.690616609018,155.838302306684}
+982 | {-410.903183807051,-817.431881448712,-498.941051349443}
+983 | {584.942305350026,436.336379432393,140.18491884502}
+984 | {-62.2576273119263,-43.2935301637635,688.864964048942}
+985 | {-443.659607538172,45.6100114145606,604.826905400621}
+986 | {469.538524301448,33.8965252398074,-472.681333118967}
+987 | {-739.771306341606,-628.667466347046,154.43004104212}
+988 | {6.16975042506824,-814.046180611434,-564.054638448502}
+989 | {-412.718322652751,-810.953990028575,111.578197740358}
+990 | {-371.788909598942,131.714123700478,-899.574111680826}
+991 | {-496.135296949346,-830.199985428607,233.364349557065}
+992 | {-432.547935230597,-506.358842797757,265.069576509392}
+993 | {-287.340085685258,817.939734530848,249.620152949832}
+994 | {794.082931481233,529.711329496276,-43.9666712275483}
+995 | {-51.9104170952357,-938.769614779545,235.101660342489}
+996 | {-182.538603056643,-655.29591247686,-311.602065173997}
+997 | {155.382900972205,130.042892062227,-22.6827886036227}
+998 | {-524.406687025992,-14.7069741427976,-1160.50595918993}
+999 | {-255.495117367138,744.521374636333,446.551897876606}
+1000 | {306.590358490976,149.581073128642,-724.558878908178}
+\.
+
+DROP TABLE IF EXISTS dbscan_result, dbscan_result_summary;
+SELECT dbscan('dbscan_kd_test_data','dbscan_result','row_id','row_vec',50, 3, 
'dist_norm2', 'br');
+
+DROP TABLE IF EXISTS dbscan_result_kd, dbscan_result_kd_summary;
+SELECT dbscan('dbscan_kd_test_data','dbscan_result_kd','row_id','row_vec',50, 
3, 'dist_norm2', 'kd', 2);
+
+SELECT assert(count(row_id) = 50 AND count(distinct cluster_id) = 11, 
'Incorrect cluster counts for brute')
+FROM dbscan_result;
+
+SELECT assert(count(row_id) = 50 AND count(distinct cluster_id) = 11, 
'Incorrect cluster counts for kd')
+FROM dbscan_result_kd;
+
+CREATE TABLE temp_result AS
+SELECT row_id
+FROM dbscan_result
+WHERE cluster_id = (SELECT cluster_id FROM dbscan_result WHERE row_id = 4);
+
+SELECT assert(count(*) = 6, 'Clusters do not match')
+FROM dbscan_result_kd
+WHERE cluster_id = (SELECT cluster_id FROM dbscan_result_kd WHERE row_id = 4)
+      AND row_id IN (SELECT row_id FROM temp_result);
diff --git 
a/src/ports/postgres/modules/dbscan/test/unit_tests/test_dbscan.py_in 
b/src/ports/postgres/modules/dbscan/test/unit_tests/test_dbscan.py_in
index e04de94..1aa0681 100644
--- a/src/ports/postgres/modules/dbscan/test/unit_tests/test_dbscan.py_in
+++ b/src/ports/postgres/modules/dbscan/test/unit_tests/test_dbscan.py_in
@@ -55,7 +55,8 @@ class DBSCANTestCase(unittest.TestCase):
             'eps' : 20,
             'min_samples' : 4,
             'metric' : 'squared_dist_norm2',
-            'algorithm' : 'brute'
+            'algorithm' : 'brute',
+            'depth': 2
         }
         import dbscan.dbscan
         self.module = dbscan.dbscan

Reply via email to