diff --git a/regression-tests/daaltkregtests/lib/daaltk_test.py b/regression-tests/daaltkregtests/lib/daaltk_test.py index d523af9..2948f13 100644 --- a/regression-tests/daaltkregtests/lib/daaltk_test.py +++ b/regression-tests/daaltkregtests/lib/daaltk_test.py @@ -55,10 +55,6 @@ def get_context(): 'spark.eventLog.enabled': 'false', 'spark.sql.shuffle.partitions': '6'} if config.run_mode: - print "initializing context" - print "" - print "" - print "" global_tc = stk.TkContext(master='yarn-client', other_libs=[daaltk]) else: diff --git a/regression-tests/daaltkregtests/lib/score_utils.py b/regression-tests/daaltkregtests/lib/score_utils.py new file mode 100644 index 0000000..16e659d --- /dev/null +++ b/regression-tests/daaltkregtests/lib/score_utils.py @@ -0,0 +1,84 @@ +# vim: set encoding=utf-8 + +# Copyright (c) 2016 Intel Corporation  +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +#       http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +""" Library to support scoring in TAP for the ATK service """ +import subprocess as sp +import requests +import time +import signal +import os +import config +from ConfigParser import SafeConfigParser + +class scorer(object): + + def __init__(self, model_path, port_id, host=config.scoring_engine_host): + """Set up the server location, port and model file""" + self.hdfs_path = model_path + self.name = host.split('.')[0] + self.host = host + #set port + config = SafeConfigParser() + filepath = os.path.abspath(os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "port.ini")) + config.read(filepath) + self.port = config.get('port', port_id) + self.scoring_process = None + + def __enter__(self): + """Activate the Server""" + #change current working directory to point at scoring_engine dir + run_path = os.path.abspath(os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "..", "..", "..", "scoring", "scoring_engine")) + + #keep track of cwd for future + test_dir = os.getcwd() + os.chdir(run_path) + # make a new process group + self.scoring_process = sp.Popen( + ["./bin/model-scoring.sh", "-Dtrustedanalytics.scoring-engine.archive-mar=%s" % self.hdfs_path, + "-Dtrustedanalytics.scoring.port=%s" % self.port], + preexec_fn=os.setsid) + + #restore cwd + os.chdir(test_dir) + + # wait for server to start + time.sleep(20) + return self + + def __exit__(self, *args): + """Teardown the server""" + # Get the process group to kill all of the suprocesses + pgrp = os.getpgid(self.scoring_process.pid) + os.killpg(pgrp, signal.SIGKILL) + + def score(self, data_val): + """score the json set data_val""" + + # Magic headers to make the server respond appropriately + # Ask the head of scoring why these + headers = {'Content-type': 'application/json', + 'Accept': 'application/json,text/plain'} + + scoring_host = self.host + ":" + self.port + submit_string = 'http://'+scoring_host+'/v2/score' + response = requests.post(submit_string, json={"records":data_val}, headers=headers) + return response diff --git a/regression-tests/daaltkregtests/testcases/models/daal_covariance_test.py b/regression-tests/daaltkregtests/testcases/models/daal_covariance_test.py deleted file mode 100644 index 87c496c..0000000 --- a/regression-tests/daaltkregtests/testcases/models/daal_covariance_test.py +++ /dev/null @@ -1,47 +0,0 @@ -# vim: set encoding=utf-8 - -# Copyright (c) 2016 Intel Corporation  -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -#       http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -"""tests daal covariance code""" -import unittest -from daaltkregtests.lib import daaltk_test - - -class DaalCovarianceTest(daaltk_test.DaalTKTestCase): - - def test_covar_matrix(self): - """test daal covariance matrix""" - data_in = self.get_file("covariance_correlation.csv") - base_frame = self.context.frame.import_csv(data_in) - - # use daaltk to calc expected result - # we will use sparktk result for reference - # since the algorithm should essentially be the same - # we expect the daaltk/sparktk results to match - covar_matrix = self.context.daaltk.operations.covariance_matrix(base_frame) - sparktk_result = base_frame.covariance_matrix(base_frame.column_names) - - # flatten the expected and actual result for ease of comparison - covar_flat = [item for sublist in covar_matrix.take(covar_matrix.count()) for item in sublist] - sparktk_res_flat = [item for sublist in sparktk_result.take(sparktk_result.count()) for item in sublist] - - # compare expected and actual result - for daal_value, spark_value in zip(covar_flat, sparktk_res_flat): - self.assertAlmostEqual(daal_value, spark_value, 7) - - -if __name__ == "__main__": - unittest.main() diff --git a/regression-tests/daaltkregtests/testcases/models/daal_kmeans_test.py b/regression-tests/daaltkregtests/testcases/models/daal_kmeans_test.py deleted file mode 100644 index 81171c1..0000000 --- a/regression-tests/daaltkregtests/testcases/models/daal_kmeans_test.py +++ /dev/null @@ -1,258 +0,0 @@ -# vim: set encoding=utf-8 - -# Copyright (c) 2016 Intel Corporation  -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -#       http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -""" test cases for the Daal kmeans clustering algorithm """ -import unittest -import daaltk -from daaltkregtests.lib import daaltk_test - - -class KMeansClustering(daaltk_test.DaalTKTestCase): - - def setUp(self): - """Import the files to test against.""" - super(KMeansClustering, self).setUp() - schema = [("Vec1", float), - ("Vec2", float), - ("Vec3", float), - ("Vec4", float), - ("Vec5", float), - ("term", str)] - #self.context = TkContext(other_libs=[daaltk]) - #self.train_dataset = "hdfs://master.organa.cluster.gao:8020/user/hadoop/qa_data/kmeans_train.csv" - #self.test_dataset = "hdfs://master.organa.cluster.gao:8020/user/hadoop/qa_data/kmeans_test.csv" - self.train_dataset = self.get_file("kmeans_train.csv") - self.test_dataset = self.get_file("kmeans_test.csv") - self.frame_train = self.context.frame.import_csv( - self.train_dataset, schema=schema) - self.frame_test = self.context.frame.import_csv( - self.test_dataset, schema=schema) - - def test_kmeans_standard(self): - """Tests standard usage of the kmeans cluster algorithm.""" - model = self.context.daaltk.models.clustering.kmeans.train( - self.frame_train, ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], k=5) - - names = [u'Cluster:1', u'Cluster:0', u'Cluster:3', - u'Cluster:2', u'Cluster:4'] - - centroids = sorted([[10.0, 10.0, 10.0, 10.0, 10.0], - [60.0, -70.0, -40.0, 30.0, 600.0], - [-10.0, -10.0, -10.0, -20.0, 10.0], - [-50.0, 70.0, -30.0, 90.0, 20.0], - [0.0, 0.0, 0.0, 0.0, 0.0]]) - - for i in range(5): - self.assertEqual(model.cluster_sizes['Cluster:'+str(i)], 10000) - - for i1, i2 in zip(sorted(model.centroids.values()), centroids): - for j1, j2 in zip(i1, i2): - self.assertAlmostEqual(j1, j2, delta=.2) - - self.assertItemsEqual(model.centroids.keys(), names) - - test_frame = model.predict(self.frame_test) - test_take = test_frame.to_pandas(test_frame.count()) - grouped = test_take.groupby(['predicted_cluster', 'term']) - for i in grouped.size(): - self.assertEqual(10000, i) - - def test_column_weights(self): - """Tests kmeans cluster algorithm with weighted values.""" - model = self.context.daaltk.models.clustering.kmeans.train( - self.frame_train, ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], - [0.01, 0.01, 0.01, 0.01, 0.01], 5) - - names = ['Cluster:1', u'Cluster:0', u'Cluster:3', - u'Cluster:2', u'Cluster:4'] - - centroids = sorted([[.10, .10, .10, .10, .10], - [.60, -.70, -.40, .30, 6.0], - [-.10, -.10, -.10, -.20, .1], - [-.50, .70, -.30, .90, .20], - [0.0, 0.0, 0.0, 0.0, 0.0]]) - - for i in range(5): - self.assertEqual(model.cluster_sizes['Cluster:'+str(i)], 10000) - - for i1, i2 in zip(sorted(model.centroids.values()), centroids): - for j1, j2 in zip(i1, i2): - self.assertAlmostEqual(j1, j2, delta=.2) - - self.assertItemsEqual(model.centroids.keys(), names) - - test_frame = model.predict(self.frame_test) - test_take = test_frame.to_pandas(test_frame.count()) - grouped = test_take.groupby(['predicted_cluster', 'term']) - for i in grouped.size(): - self.assertEqual(10000, i) - - def test_max_iterations(self): - """Tests kmeans cluster algorithm with more iterations.""" - model = self.context.daaltk.models.clustering.kmeans.train( - self.frame_train, ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], - k=5, max_iterations=300) - - names = ['Cluster:1', u'Cluster:0', u'Cluster:3', - u'Cluster:2', u'Cluster:4'] - - centroids = sorted([[10.0, 10.0, 10.0, 10.0, 10.0], - [60.0, -70.0, -40.0, 30.0, 600.0], - [-10.0, -10.0, -10.0, -20.0, 10.0], - [-50.0, 70.0, -30.0, 90.0, 20.0], - [0.0, 0.0, 0.0, 0.0, 0.0]]) - - for i in range(5): - self.assertEqual(model.cluster_sizes['Cluster:'+str(i)], 10000) - - for i1, i2 in zip(sorted(model.centroids.values()), centroids): - for j1, j2 in zip(i1, i2): - self.assertAlmostEqual(j1, j2, delta=.2) - - self.assertItemsEqual(model.centroids.keys(), names) - - test_frame = model.predict(self.frame_test) - test_take = test_frame.to_pandas(test_frame.count()) - grouped = test_take.groupby(['predicted_cluster', 'term']) - for i in grouped.size(): - self.assertEqual(10000, i) - - def test_different_columns(self): - """Tests kmeans cluster algorithm with more iterations.""" - model = self.context.daaltk.models.clustering.kmeans.train( - self.frame_train, ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], - k=5, max_iterations=300) - - names = ['Cluster:1', u'Cluster:0', u'Cluster:3', - u'Cluster:2', u'Cluster:4'] - - centroids = sorted([[10.0, 10.0, 10.0, 10.0, 10.0], - [60.0, -70.0, -40.0, 30.0, 600.0], - [-10.0, -10.0, -10.0, -20.0, 10.0], - [-50.0, 70.0, -30.0, 90.0, 20.0], - [0.0, 0.0, 0.0, 0.0, 0.0]]) - - for i in range(5): - self.assertEqual(model.cluster_sizes['Cluster:'+str(i)], 10000) - - for i1, i2 in zip(sorted(model.centroids.values()), centroids): - for j1, j2 in zip(i1, i2): - self.assertAlmostEqual(j1, j2, delta=.2) - - self.assertItemsEqual(model.centroids.keys(), names) - - self.frame_test.rename_columns( - {"Vec1": 'Dim1', "Vec2": 'Dim2', "Vec3": "Dim3", - "Vec4": "Dim4", "Vec5": 'Dim5'}) - test_frame = model.predict( - self.frame_test, ['Dim1', 'Dim2', 'Dim3', 'Dim4', 'Dim5']) - test_take = test_frame.to_pandas(test_frame.count()) - grouped = test_take.groupby(['predicted_cluster', 'term']) - for i in grouped.size(): - self.assertEqual(10000, i) - - def test_predict_column(self): - """Tests kmeans cluster algorithm with more iterations.""" - model = self.context.daaltk.models.clustering.kmeans.train( - self.frame_train, ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], - k=5, max_iterations=300) - - names = ['Cluster:1', u'Cluster:0', u'Cluster:3', - u'Cluster:2', u'Cluster:4'] - - centroids = sorted([[10.0, 10.0, 10.0, 10.0, 10.0], - [60.0, -70.0, -40.0, 30.0, 600.0], - [-10.0, -10.0, -10.0, -20.0, 10.0], - [-50.0, 70.0, -30.0, 90.0, 20.0], - [0.0, 0.0, 0.0, 0.0, 0.0]]) - - for i in range(5): - self.assertEqual(model.cluster_sizes['Cluster:'+str(i)], 10000) - - for i1, i2 in zip(sorted(model.centroids.values()), centroids): - for j1, j2 in zip(i1, i2): - self.assertAlmostEqual(j1, j2, delta=.2) - - self.assertItemsEqual(model.centroids.keys(), names) - - test_frame = model.predict( - self.frame_test, label_column="predict_cluster") - test_take = test_frame.to_pandas(test_frame.count()) - grouped = test_take.groupby(['predict_cluster', 'term']) - for i in grouped.size(): - self.assertEqual(10000, i) - - def test_publish(self): - """Tests kmeans cluster algorithm with random seeds.""" - model = self.context.daaltk.models.clustering.kmeans.train(self.frame_train, - ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], - k=5) - path = self.get_export_file(self.get_name("daal_kmeans")) - model.export_to_mar(path) - - self.assertIn("hdfs", path) - - def test_max_iterations_negative(self): - """Check error on negative number of iterations.""" - with(self.assertRaisesRegexp(Exception, "max iterations must be a positive value")): - model = self.context.daaltk.models.clustering.kmeans.train(self.frame_train, - ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], - k=5, max_iterations=-3) - - def test_max_iterations_bad_type(self): - """Check error on a floating point number of iterations.""" - with(self.assertRaisesRegexp(Exception, "max_iterations must be an int")): - model = self.context.daaltk.models.clustering.kmeans.train(self.frame_train, - ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], max_iterations=[]) - - def test_invalid_columns_predict(self): - """Check error on a floating point number of iterations.""" - with(self.assertRaisesRegexp(Exception, "Invalid column name Vec1")): - model = self.context.daaltk.models.clustering.kmeans.train(self.frame_train, - ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"]) - self.frame_test.rename_columns( - {"Vec1": 'Dim1', "Vec2": 'Dim2', - "Vec3": "Dim3", "Vec4": "Dim4", "Vec5": 'Dim5'}) - model.predict(self.frame_test) - - def test_too_few_columns(self): - """Check error on a floating point number of iterations.""" - with(self.assertRaisesRegexp(Exception, "Number of columns for train and predict should be same")): - model = self.context.daaltk.models.clustering.kmeans.train(self.frame_train, - ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"]) - model.predict(self.frame_test, ["Vec1", "Vec2"]) - - def test_k_negative(self): - """Check error on negative number of clusters.""" - with(self.assertRaisesRegexp(Exception, "k must be at least 1")): - model = self.context.daaltk.models.clustering.kmeans.train(self.frame_train, - ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], k=-5) - - def test_k_bad_type(self): - """Check error on float number of clusters.""" - with(self.assertRaisesRegexp(Exception, "k must be an int")): - model = self.context.daaltk.models.clustering.kmeans.train(self.frame_train, - ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], k=[]) - - def test_null_frame(self): - """Check error on null frame.""" - with(self.assertRaisesRegexp(Exception, "received: ")): - model = self.context.daaltk.models.clustering.kmeans.train(None, ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], k=5) - - -if __name__ == '__main__': - unittest.main() diff --git a/regression-tests/daaltkregtests/testcases/models/daal_linear_regression_test.py b/regression-tests/daaltkregtests/testcases/models/daal_linear_regression_test.py deleted file mode 100644 index c7331d1..0000000 --- a/regression-tests/daaltkregtests/testcases/models/daal_linear_regression_test.py +++ /dev/null @@ -1,74 +0,0 @@ -# vim: set encoding=utf-8 - -# Copyright (c) 2016 Intel Corporation  -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -#       http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -""" Tests Linear Regression Model, with known coefficents """ -import unittest -from daaltkregtests.lib import daaltk_test - - -class DaalLinearRegression(daaltk_test.DaalTKTestCase): - - def setUp(self): - super(DaalLinearRegression, self).setUp() - dataset = self.get_file("linear_regression_gen.csv") - schema = [("c1", float), - ("c2", float), - ("c3", float), - ("c4", float), - ("label", float)] - - self.frame = self.context.frame.import_csv(dataset, schema=schema) - - def test_model_test(self): - """Test daal linear regression publish""" - model = self.context.daaltk.models.regression.linear_regression.train(self.frame, "label", ['c1', 'c2', 'c3', 'c4']) - baseline = { u'mean_squared_error': 0, - u'r_2': 1.0, - u'mean_absolute_error': 0, - u'explained_variance': 3, - u'root_mean_squared_error': 0} - - res = model.test(self.frame) - self.assertAlmostEqual(res.mean_squared_error, 0) - self.assertAlmostEqual(res.r2, 1.0) - self.assertAlmostEqual(res.mean_absolute_error, 0) - self.assertAlmostEqual(res.explained_variance, 3.0, delta=0.1) - self.assertAlmostEqual(res.root_mean_squared_error, 0) - - def test_model_publish(self): - """Test daal linear regression publish""" - model = self.context.daaltk.models.regression.linear_regression.train(self.frame, "label", ['c1', 'c2', 'c3', 'c4']) - path = self.get_export_file(self.get_name("daaltk_linear_regression")) - model.export_to_mar(path) - self.assertIn("hdfs", path) - - def test_model_predict_output(self): - """Test daal linear regression output and predict is correct""" - model = self.context.daaltk.models.regression.linear_regression.train( - self.frame, "label", ['c1', 'c2', 'c3', 'c4']) - - for i, j in zip([0.5, -0.7, -0.24, 0.4], model.weights): - self.assertAlmostEqual(i, j, places=4) - - res = model.predict(self.frame, ['c1', 'c2', 'c3', 'c4']) - pd_res = res.to_pandas(res.count()) - for _, i in pd_res.iterrows(): - self.assertAlmostEqual(i["label"], i["predict_label"]) - - -if __name__ == '__main__': - unittest.main() diff --git a/regression-tests/daaltkregtests/testcases/models/daal_naive_bayes_test.py b/regression-tests/daaltkregtests/testcases/models/daal_naive_bayes_test.py deleted file mode 100644 index 4afdc05..0000000 --- a/regression-tests/daaltkregtests/testcases/models/daal_naive_bayes_test.py +++ /dev/null @@ -1,89 +0,0 @@ -# vim: set encoding=utf-8 - -# Copyright (c) 2016 Intel Corporation  -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -#       http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -"""Tests Naive Bayes Model against known values""" -import unittest -import math -from daaltkregtests.lib import daaltk_test - - -class DaalNaiveBayes(daaltk_test.DaalTKTestCase): - - def setUp(self): - """Build the frames needed for the tests.""" - super(DaalNaiveBayes, self).setUp() - - dataset = self.get_file("naive_bayes.csv") - schema = [("label", int), - ("count", int), - ("f1", int), - ("f2", int), - ("f3", int)] - self.frame = self.context.frame.import_csv(dataset, schema=schema) - - def test_model_train_empty_feature(self): - """Test empty string for training features throws errors.""" - with(self.assertRaisesRegexp(Exception, "observation_columns must be a list of strings")): - self.context.daaltk.models.classification.naive_bayes.train(self.frame, "label", "") - - def test_model_train_empty_label_coloum(self): - """Test empty string for label coloum throws error.""" - with(self.assertRaisesRegexp(Exception, "label column must not be null nor empty")): - self.context.daaltk.models.classification.naive_bayes.train(self.frame, "", ['f1', 'f2', 'f3']) - - def test_model_test(self): - """Test training intializes theta, pi and labels""" - model = self.context.daaltk.models.classification.naive_bayes.train(self.frame, "label", ['f1', 'f2', 'f3'], 3) - values = sorted(map( - lambda x: map(math.exp, x), models.feature_log_prob)) - baseline = sorted([[0.3, 0.2, 0.5], [0.7, 0.0, 0.3], [0.2, 0.6, 0.2]]) - for i, j in zip(values, baseline): - for k, l in zip(i, j): - self.assertAlmostEqual(k, l, delta=.05) - - # This is hacky, should really train on another - # dataset - res = model.test(self.frame, "label", ['f1', 'f2', 'f3']) - self.assertGreater(res.precision, 0.9) - self.assertGreater(res.recall, 0.9) - self.assertGreater(res.accuracy, 0.9) - self.assertGreater(res.f_measure, 0.9) - - def test_model_publish_bayes(self): - """Test training intializes theta, pi and labels""" - model = self.context.daaltk.models.classification.naive_bayes.train(self.frame, "label", ['f1', 'f2', 'f3']) - path = self.get_export_file(self.get_name("daal_naive_bayes")) - model.export_to_mar(path) - self.assertIn("hdfs", path) - - def test_model_test_paramater_initiation(self): - """Test training intializes theta, pi and labels""" - model = self.context.daaltk.models.classification.naive_bayes.train(self.frame, "label", ['f1', 'f2', 'f3'], 3) - - # This is hacky, should really train on another - # dataset - res = model.predict(self.frame, ['f1', 'f2', 'f3']) - res.add_columns(lambda x: [int(x['predicted_class'])], [("pc", int)]) - res_2 = res.classification_metrics('label', 'pc') - self.assertGreater(res_2.precision, 0.9) - self.assertGreater(res_2.recall, 0.9) - self.assertGreater(res_2.accuracy, 0.9) - self.assertGreater(res_2.f_measure, 0.9) - - -if __name__ == '__main__': - unittest.main() diff --git a/regression-tests/daaltkregtests/testcases/models/daal_pca_test.py b/regression-tests/daaltkregtests/testcases/models/daal_pca_test.py deleted file mode 100644 index 2b21f59..0000000 --- a/regression-tests/daaltkregtests/testcases/models/daal_pca_test.py +++ /dev/null @@ -1,225 +0,0 @@ -# vim: set encoding=utf-8 - -# Copyright (c) 2016 Intel Corporation  -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -#       http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -"""Test DAAL PCA implementation. It is unknown what this functionality does""" -import unittest -from daaltkregtests.lib import daaltk_test - - -class DaalPrincipalComponent(daaltk_test.DaalTKTestCase): - # expected singular values - expected_singular_val = [3373.70412657, 594.11385671, - 588.713470217, 584.157023124, - 579.433395835, 576.659495077, - 572.267630461, 568.224352464, - 567.328732759, 560.882281619] - # expected right-singular vectors V - expected_R_singular_vec = \ - [[0.315533916, -0.3942771, 0.258362247, -0.0738539198, - -0.460673735, 0.0643077298, -0.0837131184, 0.0257963888, - 0.00376728499, 0.669876972], - [0.316500921, -0.165508013, -0.131017612, 0.581988787, - -0.0863507191, 0.160473134, 0.53134635, 0.41199152, - 0.0823770991, -0.156517367], - [0.316777341, 0.244415549, 0.332413311, -0.377379981, - 0.149653873, 0.0606339992, -0.163748261, 0.699502817, - -0.171189721, -0.124509149], - [0.318988109, -0.171520719, -0.250278714, 0.335635209, - 0.580901954, 0.160427725, -0.531610364, -0.0304943121, - -0.0785743304, 0.201591811], - [0.3160833, 0.000386702461, -0.108022985, 0.167086405, - -0.470855879, -0.256296677, -0.318727111, -0.155621638, - -0.521547782, -0.418681224], - [0.316721742, 0.288319245, 0.499514144, 0.267566455, - -0.0338341451, -0.134086469, -0.184724393, -0.246523528, - 0.593753078, -0.169969303], - [0.315335647, -0.258529064, 0.374780341, -0.169762381, - 0.416093803, -0.118232778, 0.445019707, -0.395962728, - -0.337229123, -0.0937071881], - [0.314899154, -0.0294147958, -0.447870311, -0.258339192, - 0.0794841625, -0.71141762, 0.110951688, 0.102784186, - 0.292018251, 0.109836478], - [0.315542865, -0.236497774, -0.289051199, -0.452795684, - -0.12175352, 0.5265342, -0.0312645934, -0.180142504, - 0.318334436, -0.359303747], - [0.315875856, 0.72196434, -0.239088332, -0.0259999274, - -0.0579153559, 0.244335633, 0.232808362, -0.233600306, - -0.181191102, 0.3413174]] - - def setUp(self): - super(DaalPrincipalComponent, self).setUp() - schema = [("X1", int), - ("X2", int), - ("X3", int), - ("X4", int), - ("X5", int), - ("X6", int), - ("X7", int), - ("X8", int), - ("X9", int), - ("X10", int)] - training_data = self.get_file("pcadata.csv") - self.frame = self.context.frame.import_csv(training_data, schema=schema) - - @unittest.skip("daaltk: pca_model has no singular vectors") - def test_daal_principal_components_train_mean(self): - """Test the train functionality with mean centering""" - pca_train_out = self.context.daaltk.models.dimreduction.pca.train(self.frame, - ["X1", "X2", "X3", "X4", "X5", - "X6", "X7", "X8", "X9", "X10"], - True, 10) - - # actual right-singular vectors - actual_R_singular_vec = pca_train_out.right_singular_vectors - - # actual singular values - actual_singular_val = pca_train_out.singular_values - for c in self.frame.column_names: - mean = self.frame.column_summary_statistics(c)["mean"] - self.frame.add_columns( - lambda x: x[c] - mean, (c+"_n", float)) - - pcamodelmean = self.context.daaltk.models.dimreduction.pca.train( - self.frame, - ["X1_n", "X2_n", "X3_n", "X4_n", "X5_n", - "X6_n", "X7_n", "X8_n", "X9_n", "X10_n"], - False, 10) - - # actual right-singular vectors - actual_R_singular_vec_mean = pca_train_out.right_singular_vectors - # actual singular values - actual_singular_val_mean = pca_train_out.singular_values - - expected_actual = zip(actual_singular_val, actual_singular_val_mean) - for expected, actual in expected_actual: - self.assertAlmostEqual(expected, actual, 8) - - expected_actual = zip( - actual_R_singular_vec, actual_R_singular_vec_mean) - for expected, actual in expected_actual: - for f1, f2 in zip(expected, actual): - self.assertAlmostEqual(f1, f2, 4) - - @unittest.skip("daaltk: pca_model has no singular vectors") - def test_daal_pca_predict(self): - """Test the train functionality""" - pca_train_out = self.context.daaltk.models.dimreduction.pca.train(self.frame, - ["X1", "X2", "X3", "X4", "X5", - "X6", "X7", "X8", "X9", "X10"], - False, 10) - - pca_train_out.predict(self.frame, False) - pd_frame = self.frame.to_pandas(self.frame.count()) - actual_R_singular_vec = map( - list, zip(*pca_train_out.right_singular_vectors)) - for _, i in pd_frame.iterrows(): - vec1 = i[0:10] - vec2 = i[10:] - dot_product = [sum([(r1)*(r2) for r1, r2 in zip(vec1, k)]) - for k in actual_R_singular_vec] - for i, j in zip(vec2, dot_product): - self.assertAlmostEqual(i, j) - - @unittest.skip("daaltk: pca_model has no singular vectors") - def test_daal_pca_train(self): - """Test the train functionality""" - pca_train_out = self.context.daaltk.models.dimreduction.pca.train(self.frame, - ["X1", "X2", "X3", "X4", "X5", - "X6", "X7", "X8", "X9", "X10"], - False, 10) - - # actual right-singular vectors - actual_R_singular_vec = pca_train_out.right_singular_vectors - - # actual singular values - actual_singular_val = pca_train_out.singular_values - - expected_actual = zip(self.expected_singular_val, actual_singular_val) - for expected, actual in expected_actual: - self.assertAlmostEqual(expected, actual, 8) - - expected_actual = zip(actual_R_singular_vec, - self.expected_R_singular_vec) - for expected, actual in expected_actual: - for f1, f2 in zip(expected, actual): - self.assertAlmostEqual(abs(f1), abs(f2), 4) - - def test_daal_pca_publish(self): - """Test the publish functionality""" - pcamodel = self.context.daaltk.models.dimreduction.pca.train( - self.frame, - ["X1", "X2", "X3", "X4", "X5", "X6", "X7", "X8", "X9", "X10"], - False, 10) - path = self.get_export_file(self.get_name("daaltk_pca")) - pcamodel.export_to_mar(path) - self.assertIn("hdfs", path) - - @unittest.skip("daaltk: pca_model has no singular vectors") - def test_daal_pca_default(self): - """Test default no. of k""" - pca_train_out = self.context.daaltk.models.dimreduction.pca.train( - self.frame, - ["X1", "X2", "X3", "X4", "X5", "X6", "X7", "X8", "X9", "X10"], - False) - # actual right-singular vectors - actual_R_singular_vec = pca_train_out.right_singular_vectors - - # actual singular values - actual_singular_val = pca_train_out.singular_values - - for ind in xrange(0, len(actual_singular_val)): - self.assertAlmostEqual(round(actual_singular_val[ind], 8), - self.expected_singular_val[ind]) - - for ind in xrange(0, len(actual_R_singular_vec)): - for ind2 in xrange(0, len(actual_R_singular_vec[ind])): - self.assertEqual( - abs(round(actual_R_singular_vec[ind][ind2], 6)), - abs(round(self.expected_R_singular_vec[ind][ind2], 6))) - - def test_daal_pca_bad_no_of_k(self): - """Test invalid k value in train""" - with self.assertRaisesRegexp(Exception, "k must be less than or equal to number of observation columns"): - self.context.daaltk.models.dimreduction.pca.train(self.frame, - ["X1", "X2", "X3", "X4", "X5", - "X6", "X7", "X8", "X9", "X10"], - k=11) - - def test_daal_pca_invalid_k(self): - """Test k < 1 in train""" - with self.assertRaisesRegexp(Exception, "k must be smaller than the number of observation columns"): - self.context.daaltk.models.dimreduction.pca.train(self.frame, - ["X1", "X2", "X3", "X4", "X5", - "X6", "X7", "X8", "X9", "X10"], - k=0) - - def test_daal_pca_bad_column_name(self): - """Test bad feature column name""" - with self.assertRaisesRegexp(Exception, "column ERR was not found"): - self.context.daaltk.models.dimreduction.pca.train(self.frame, - ["ERR", "X2", "X3", "X4", "X5", - "X6", "X7", "X8", "X9", "X10"], - k=10) - - def test_daal_pca_bad_column_type(self): - """Test bad feature column name type""" - with self.assertRaisesRegexp(Exception, "columns must be a list of strings"): - self.context.daaltk.models.dimreduction.pca.train(self.frame, 10, k=10) - - -if __name__ == '__main__': - unittest.main() diff --git a/regression-tests/daaltkregtests/testcases/scoretests/kmeans_test.py b/regression-tests/daaltkregtests/testcases/scoretests/kmeans_test.py new file mode 100644 index 0000000..fbccfc8 --- /dev/null +++ b/regression-tests/daaltkregtests/testcases/scoretests/kmeans_test.py @@ -0,0 +1,65 @@ +# vim: set encoding=utf-8 + +# Copyright (c) 2016 Intel Corporation  +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +#       http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +import unittest +import time +import os +from daaltkregtests.lib import daaltk_test +from daaltkregtests.lib import score_utils +from daaltkregtests.lib import config + +""" test cases for the kmeans clustering algorithm""" +class KMeansClustering(daaltk_test.DaalTKTestCase): + + def setUp(self): + """Import the files to test against.""" + super(KMeansClustering, self).setUp() + schema = [("Vec1", float), + ("Vec2", float), + ("Vec3", float), + ("Vec4", float), + ("Vec5", float), + ("term", str)] + + self.frame_train = self.context.frame.import_csv( + self.get_file("kmeans_train.csv"), schema=schema) + self.frame_test = self.context.frame.import_csv( + self.get_file("kmeans_test.csv"), schema=schema) + + @unittest.skip("daaltk: kmeans scoring engine produces different result than predict") + def test_model_scoring(self): + """Tests standard usage of the kmeans cluster algorithm.""" + kmodel = self.context.daaltk.models.clustering.kmeans.train( + self.frame_train, ["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], k=5) + + result_frame = kmodel.predict(self.frame_test) + test_rows = result_frame.to_pandas(50) + result = kmodel.export_to_mar(self.get_export_file(self.get_name("daaltk_kmeans"))) + + with score_utils.scorer( + result, self.id()) as scorer: + for index, row in test_rows.iterrows(): + res = scorer.score( + [dict(zip(["Vec1", "Vec2", "Vec3", "Vec4", "Vec5"], + list(row[0:5])))]) + + self.assertEqual(row.predicted_cluster, res.json()["data"][0]['score']) + + +if __name__ == '__main__': + unittest.main() diff --git a/regression-tests/daaltkregtests/testcases/scoretests/linear_regression_test.py b/regression-tests/daaltkregtests/testcases/scoretests/linear_regression_test.py new file mode 100644 index 0000000..a3ab8d1 --- /dev/null +++ b/regression-tests/daaltkregtests/testcases/scoretests/linear_regression_test.py @@ -0,0 +1,62 @@ +# vim: set encoding=utf-8 + +# Copyright (c) 2016 Intel Corporation  +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +#       http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +""" Tests Linear Regression scoring engine """ +import unittest +import os +from daaltkregtests.lib import daaltk_test +from daaltkregtests.lib import score_utils + + +class LinearRegression(daaltk_test.DaalTKTestCase): + + def setUp(self): + """Build test frame""" + super(LinearRegression, self).setUp() + dataset = self.get_file("linear_regression_gen.csv") + schema = [("c1", float), + ("c2", float), + ("c3", float), + ("c4", float), + ("label", float)] + + self.frame = self.context.frame.import_csv( + dataset, schema=schema) + + def test_model_scoring(self): + """Test publishing a linear regression model""" + model = self.context.daaltk.models.regression.linear_regression.train(self.frame, "label", ['c1', 'c2', 'c3', 'c4']) + + predict = model.predict(self.frame, ['c1', 'c2', 'c3', 'c4']) + test_rows = predict.to_pandas(predict.count()) + + file_name = self.get_name("linear_regression") + model_path = model.export_to_mar(self.get_export_file(file_name)) + with score_utils.scorer( + model_path, self.id()) as scorer: + for index, row in test_rows.iterrows(): + res = scorer.score( + [dict(zip(["c1", "c2", "c3", "c4"], list(row[0:4])))]) + self.assertAlmostEqual( + row["predict_label"], res.json()["data"][0]['score']) + + + + +if __name__ == '__main__': + unittest.main() diff --git a/regression-tests/daaltkregtests/testcases/scoretests/naive_bayes_test.py b/regression-tests/daaltkregtests/testcases/scoretests/naive_bayes_test.py new file mode 100644 index 0000000..bbe7d97 --- /dev/null +++ b/regression-tests/daaltkregtests/testcases/scoretests/naive_bayes_test.py @@ -0,0 +1,59 @@ +# vim: set encoding=utf-8 + +# Copyright (c) 2016 Intel Corporation  +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +#       http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +""" Tests Naive Bayes Model against known values. """ +import unittest +import os +from daaltkregtests.lib import daaltk_test +from daaltkregtests.lib import score_utils + + +class NaiveBayes(daaltk_test.DaalTKTestCase): + + def setUp(self): + """Build the frames needed for the tests.""" + super(NaiveBayes, self).setUp() + + dataset = self.get_file("naive_bayes.csv") + schema = [("label", int), + ("f1", int), + ("f2", int), + ("f3", int)] + self.frame = self.context.frame.import_csv(dataset, schema=schema) + + def test_model_scoring(self): + """Test training intializes theta, pi and labels""" + model = self.context.daaltk.models.classification.naive_bayes.train(self.frame, "label", ['f1', 'f2', 'f3']) + + res = model.predict(self.frame, ['f1', 'f2', 'f3']) + + analysis = res.to_pandas() + file_name = self.get_name("daal_naive_bayes") + model_path = model.export_to_mar(self.get_export_file(file_name)) + with score_utils.scorer( + model_path, self.id()) as scorer: + for index, row in analysis.iterrows(): + r = scorer.score( + [dict(zip(['f1', 'f2', 'f3'], + map(lambda x: int(x), (row[1:4]))))]) + self.assertEqual( + r.json()["data"][0]['score'], row.predicted_class) + + +if __name__ == '__main__': + unittest.main() diff --git a/regression-tests/daaltkregtests/testcases/scoretests/pca_test.py b/regression-tests/daaltkregtests/testcases/scoretests/pca_test.py new file mode 100644 index 0000000..a0d6c77 --- /dev/null +++ b/regression-tests/daaltkregtests/testcases/scoretests/pca_test.py @@ -0,0 +1,70 @@ +# vim: set encoding=utf-8 + +# Copyright (c) 2016 Intel Corporation  +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +#       http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +import unittest +from daaltkregtests.lib import daaltk_test +from daaltkregtests.lib import score_utils + + +class PrincipalComponent(daaltk_test.DaalTKTestCase): + + def setUp(self): + super(PrincipalComponent, self).setUp() + schema = [("X1", int), + ("X2", int), + ("X3", int), + ("X4", int), + ("X5", int), + ("X6", int), + ("X7", int), + ("X8", int), + ("X9", int), + ("X10", int)] + pca_traindata = self.get_file("pcadata.csv") + self.frame = self.context.frame.import_csv(pca_traindata, schema=schema) + + @unittest.skip("daaltk: pca produces different result for daaltk than predict") + def test_model_scoring(self): + """Test pca scoring""" + model = self.context.daaltk.models.dimensionality_reduction.principal_components.train( + self.frame, + ["X1", "X2", "X3", "X4", "X5", + "X6", "X7", "X8", "X9", "X10"], + mean_centered=False, k=10) + + file_name = self.get_name("pca") + model_path = model.export_to_mar(self.get_export_file(file_name)) + + with score_utils.scorer( + model_path, self.id()) as scorer: + baseline = model.predict(self.frame, mean_centered=False) + testvals = baseline.to_pandas(50) + + for index, row in testvals.iterrows(): + r = scorer.score( + [dict(zip(["X1", "X2", "X3", "X4", "X5", + "X6", "X7", "X8", "X9", "X10"], + map(lambda x: x, row[0:10])))]) + print "results: " + str(r.json()["data"][-1]["principal_components"]) + print "row: " + str(row[10:]) + map(lambda x, y: self.assertAlmostEqual(float(x),float(y)), + r.json()["data"][-1]["principal_components"], row[10:]) + + +if __name__ == '__main__': + unittest.main() diff --git a/regression-tests/datasets/naive_bayes.csv b/regression-tests/datasets/naive_bayes.csv new file mode 100644 index 0000000..37bac49 --- /dev/null +++ b/regression-tests/datasets/naive_bayes.csv @@ -0,0 +1,3000 @@ +0,0,1,1,4 +0,1,3,3,4 +0,2,3,2,9 +0,3,4,1,4 +0,4,7,2,5 +0,5,4,3,4 +0,6,4,1,5 +0,7,0,2,6 +0,8,1,1,8 +0,9,1,1,6 +0,10,2,3,8 +0,11,3,1,8 +0,12,5,2,7 +0,13,4,0,7 +0,14,4,3,6 +0,15,3,2,9 +0,16,0,2,3 +0,17,2,1,4 +0,18,1,1,4 +0,19,5,1,8 +0,20,3,0,5 +0,21,1,0,6 +0,22,2,4,5 +0,23,3,1,2 +0,24,4,1,2 +0,25,4,1,4 +0,26,4,3,6 +0,27,6,0,3 +0,28,7,2,1 +0,29,2,2,7 +0,30,5,2,6 +0,31,2,2,4 +0,32,6,3,5 +0,33,1,1,4 +0,34,2,1,5 +0,35,4,2,8 +0,36,2,1,3 +0,37,2,5,5 +0,38,4,2,3 +0,39,4,0,6 +0,40,5,2,2 +0,41,2,0,4 +0,42,7,2,5 +0,43,3,3,8 +0,44,1,3,4 +0,45,1,3,3 +0,46,2,1,6 +0,47,5,0,2 +0,48,3,1,8 +0,49,3,0,6 +0,50,2,2,5 +0,51,4,0,5 +0,52,2,0,3 +0,53,2,1,3 +0,54,4,3,5 +0,55,5,2,6 +0,56,5,1,5 +0,57,3,2,6 +0,58,1,1,8 +0,59,2,1,2 +0,60,2,3,5 +0,61,4,1,5 +0,62,2,7,3 +0,63,3,1,5 +0,64,6,0,6 +0,65,3,1,2 +0,66,3,0,2 +0,67,4,2,4 +0,68,3,3,4 +0,69,5,2,3 +0,70,1,1,4 +0,71,8,3,3 +0,72,3,4,2 +0,73,3,2,6 +0,74,4,2,2 +0,75,1,2,6 +0,76,6,2,5 +0,77,0,2,8 +0,78,1,3,6 +0,79,5,4,2 +0,80,7,3,1 +0,81,5,1,7 +0,82,1,2,4 +0,83,0,0,5 +0,84,1,2,5 +0,85,4,4,3 +0,86,2,1,6 +0,87,3,1,1 +0,88,3,5,4 +0,89,2,1,6 +0,90,2,4,8 +0,91,2,3,1 +0,92,1,1,7 +0,93,2,1,6 +0,94,2,1,4 +0,95,3,2,7 +0,96,2,0,4 +0,97,4,4,3 +0,98,3,1,4 +0,99,2,2,3 +0,100,3,5,5 +0,101,0,2,3 +0,102,5,0,3 +0,103,2,2,4 +0,104,3,0,3 +0,105,4,2,2 +0,106,4,1,2 +0,107,5,2,7 +0,108,3,1,4 +0,109,5,1,8 +0,110,3,3,8 +0,111,4,0,5 +0,112,3,4,1 +0,113,5,3,5 +0,114,3,2,4 +0,115,2,4,4 +0,116,2,2,8 +0,117,2,0,3 +0,118,3,1,6 +0,119,2,2,2 +0,120,3,2,1 +0,121,2,0,9 +0,122,4,1,7 +0,123,4,2,5 +0,124,1,1,6 +0,125,2,1,5 +0,126,1,1,4 +0,127,3,5,2 +0,128,4,5,4 +0,129,2,3,1 +0,130,2,1,4 +0,131,5,1,1 +0,132,5,4,2 +0,133,3,1,3 +0,134,0,1,5 +0,135,6,0,4 +0,136,2,5,3 +0,137,1,1,3 +0,138,3,5,6 +0,139,5,1,5 +0,140,3,0,6 +0,141,4,4,4 +0,142,5,0,2 +0,143,2,3,3 +0,144,2,3,5 +0,145,2,0,3 +0,146,1,4,3 +0,147,1,4,7 +0,148,0,2,3 +0,149,2,2,5 +0,150,2,1,2 +0,151,2,3,5 +0,152,4,1,6 +0,153,2,1,6 +0,154,2,3,0 +0,155,1,3,4 +0,156,1,2,4 +0,157,1,3,6 +0,158,2,3,5 +0,159,6,2,6 +0,160,2,3,0 +0,161,3,2,6 +0,162,1,3,7 +0,163,1,2,4 +0,164,3,3,6 +0,165,2,0,8 +0,166,1,2,2 +0,167,4,0,7 +0,168,4,1,2 +0,169,3,3,5 +0,170,3,2,8 +0,171,2,0,4 +0,172,1,4,1 +0,173,5,3,5 +0,174,2,0,7 +0,175,5,2,7 +0,176,0,3,10 +0,177,1,0,9 +0,178,0,1,4 +0,179,2,1,5 +0,180,3,3,6 +0,181,3,3,5 +0,182,3,0,3 +0,183,2,2,3 +0,184,6,0,8 +0,185,3,3,2 +0,186,2,2,1 +0,187,4,0,7 +0,188,1,2,5 +0,189,3,0,2 +0,190,4,3,6 +0,191,4,3,7 +0,192,0,2,5 +0,193,2,3,3 +0,194,5,0,7 +0,195,3,0,5 +0,196,3,2,7 +0,197,3,0,2 +0,198,2,0,7 +0,199,3,0,10 +0,200,1,2,3 +0,201,3,2,3 +0,202,4,1,2 +0,203,1,2,2 +0,204,5,1,4 +0,205,4,0,2 +0,206,6,3,2 +0,207,5,1,5 +0,208,1,6,2 +0,209,2,2,5 +0,210,3,2,9 +0,211,0,2,5 +0,212,3,1,6 +0,213,5,2,7 +0,214,0,1,5 +0,215,0,2,3 +0,216,4,0,8 +0,217,3,0,3 +0,218,2,0,4 +0,219,2,2,6 +0,220,6,2,5 +0,221,1,1,5 +0,222,1,2,2 +0,223,5,2,1 +0,224,1,0,6 +0,225,2,0,5 +0,226,1,1,4 +0,227,1,3,4 +0,228,2,0,5 +0,229,4,3,3 +0,230,3,0,3 +0,231,3,1,2 +0,232,4,2,6 +0,233,0,0,6 +0,234,2,2,1 +0,235,0,0,6 +0,236,5,4,5 +0,237,2,2,4 +0,238,2,3,4 +0,239,3,1,3 +0,240,2,1,2 +0,241,4,4,4 +0,242,4,3,7 +0,243,5,3,5 +0,244,0,1,7 +0,245,3,1,10 +0,246,1,2,7 +0,247,4,4,3 +0,248,1,1,3 +0,249,4,1,2 +0,250,1,1,9 +0,251,3,2,8 +0,252,2,2,9 +0,253,2,4,3 +0,254,4,4,3 +0,255,6,2,6 +0,256,5,3,6 +0,257,1,2,5 +0,258,3,3,6 +0,259,3,0,3 +0,260,3,2,6 +0,261,6,0,7 +0,262,4,2,8 +0,263,0,4,5 +0,264,4,2,6 +0,265,4,4,4 +0,266,2,2,4 +0,267,2,0,5 +0,268,1,3,1 +0,269,3,2,8 +0,270,4,2,6 +0,271,4,4,2 +0,272,2,1,5 +0,273,0,3,2 +0,274,6,2,5 +0,275,1,2,3 +0,276,1,3,7 +0,277,5,1,3 +0,278,4,2,3 +0,279,5,5,3 +0,280,3,2,3 +0,281,2,4,6 +0,282,3,2,4 +0,283,3,2,8 +0,284,3,4,3 +0,285,4,1,9 +0,286,3,0,6 +0,287,2,3,6 +0,288,4,1,5 +0,289,5,2,6 +0,290,3,1,5 +0,291,4,3,3 +0,292,1,0,6 +0,293,4,1,6 +0,294,2,1,5 +0,295,2,2,1 +0,296,2,3,2 +0,297,3,2,5 +0,298,1,3,5 +0,299,8,1,5 +0,300,2,0,4 +0,301,2,1,7 +0,302,4,2,8 +0,303,3,3,8 +0,304,9,1,3 +0,305,3,2,2 +0,306,5,4,5 +0,307,4,3,7 +0,308,9,2,3 +0,309,0,1,6 +0,310,3,1,3 +0,311,5,1,5 +0,312,0,3,6 +0,313,2,1,6 +0,314,0,1,5 +0,315,2,3,4 +0,316,4,3,7 +0,317,2,1,6 +0,318,3,2,6 +0,319,4,1,4 +0,320,3,2,1 +0,321,1,5,4 +0,322,4,1,4 +0,323,1,2,4 +0,324,2,1,6 +0,325,2,1,3 +0,326,2,1,3 +0,327,2,1,5 +0,328,4,3,4 +0,329,3,2,6 +0,330,1,0,4 +0,331,0,2,3 +0,332,5,1,6 +0,333,2,6,6 +0,334,2,1,2 +0,335,4,0,6 +0,336,5,2,4 +0,337,3,1,3 +0,338,0,1,4 +0,339,8,1,5 +0,340,0,0,5 +0,341,6,1,7 +0,342,2,1,2 +0,343,3,3,6 +0,344,0,1,6 +0,345,3,5,5 +0,346,3,2,6 +0,347,2,2,10 +0,348,5,1,5 +0,349,2,4,8 +0,350,3,1,5 +0,351,3,1,3 +0,352,1,0,4 +0,353,5,2,4 +0,354,1,1,10 +0,355,3,4,7 +0,356,1,0,4 +0,357,4,2,7 +0,358,2,1,7 +0,359,1,2,4 +0,360,1,1,5 +0,361,1,0,4 +0,362,3,3,3 +0,363,3,1,2 +0,364,5,0,7 +0,365,2,1,5 +0,366,4,1,6 +0,367,4,0,4 +0,368,2,1,2 +0,369,2,5,4 +0,370,2,1,2 +0,371,1,1,5 +0,372,3,3,6 +0,373,4,2,1 +0,374,5,3,6 +0,375,3,2,4 +0,376,3,0,3 +0,377,1,1,4 +0,378,1,2,4 +0,379,3,1,7 +0,380,1,1,3 +0,381,7,4,3 +0,382,2,2,8 +0,383,3,2,7 +0,384,2,0,5 +0,385,3,3,4 +0,386,5,1,2 +0,387,4,2,6 +0,388,3,3,4 +0,389,3,2,6 +0,390,2,2,8 +0,391,7,1,3 +0,392,3,2,7 +0,393,5,4,2 +0,394,1,1,6 +0,395,5,2,7 +0,396,4,1,1 +0,397,1,2,2 +0,398,4,2,1 +0,399,4,2,8 +0,400,1,2,6 +0,401,1,1,4 +0,402,5,3,5 +0,403,5,0,9 +0,404,2,3,3 +0,405,4,2,7 +0,406,2,2,5 +0,407,4,2,8 +0,408,2,1,2 +0,409,3,2,8 +0,410,3,1,6 +0,411,2,3,4 +0,412,2,2,5 +0,413,2,1,2 +0,414,2,1,7 +0,415,4,2,5 +0,416,1,2,4 +0,417,2,2,2 +0,418,1,2,5 +0,419,1,4,6 +0,420,5,0,6 +0,421,1,3,7 +0,422,0,0,5 +0,423,3,2,6 +0,424,0,3,3 +0,425,4,0,4 +0,426,0,4,8 +0,427,2,1,7 +0,428,1,1,3 +0,429,3,5,4 +0,430,2,6,6 +0,431,4,3,5 +0,432,2,1,4 +0,433,3,0,3 +0,434,1,1,3 +0,435,5,1,3 +0,436,3,0,3 +0,437,4,2,6 +0,438,3,2,2 +0,439,1,3,4 +0,440,6,1,5 +0,441,0,4,9 +0,442,0,5,3 +0,443,6,1,4 +0,444,1,2,3 +0,445,1,2,7 +0,446,3,2,2 +0,447,6,2,6 +0,448,1,2,9 +0,449,3,1,6 +0,450,1,1,5 +0,451,6,2,5 +0,452,2,4,2 +0,453,4,1,4 +0,454,4,2,4 +0,455,4,3,5 +0,456,2,1,7 +0,457,1,4,3 +0,458,3,2,4 +0,459,3,1,2 +0,460,1,2,5 +0,461,2,2,2 +0,462,3,0,3 +0,463,1,1,7 +0,464,4,3,1 +0,465,2,2,1 +0,466,1,3,6 +0,467,3,0,3 +0,468,1,4,6 +0,469,1,1,4 +0,470,3,0,3 +0,471,7,1,6 +0,472,2,1,7 +0,473,3,2,8 +0,474,1,3,7 +0,475,2,3,1 +0,476,3,1,7 +0,477,2,4,6 +0,478,1,0,5 +0,479,2,1,2 +0,480,1,1,12 +0,481,2,2,6 +0,482,2,3,5 +0,483,5,1,3 +0,484,2,3,4 +0,485,3,1,3 +0,486,2,2,6 +0,487,3,3,2 +0,488,2,0,5 +0,489,1,0,5 +0,490,2,0,3 +0,491,2,2,1 +0,492,3,1,5 +0,493,4,2,2 +0,494,1,1,7 +0,495,5,1,2 +0,496,3,2,6 +0,497,1,1,5 +0,498,1,2,11 +0,499,2,3,5 +0,500,3,2,7 +0,501,3,3,8 +0,502,1,2,3 +0,503,2,0,5 +0,504,3,4,4 +0,505,5,1,6 +0,506,2,1,4 +0,507,5,3,5 +0,508,2,4,8 +0,509,5,4,5 +0,510,1,3,4 +0,511,4,2,4 +0,512,2,1,5 +0,513,0,1,8 +0,514,3,3,5 +0,515,1,0,5 +0,516,4,0,6 +0,517,1,1,11 +0,518,4,2,7 +0,519,4,0,3 +0,520,2,0,8 +0,521,3,0,6 +0,522,2,2,3 +0,523,3,1,2 +0,524,5,3,5 +0,525,1,2,3 +0,526,0,2,3 +0,527,2,2,7 +0,528,0,0,5 +0,529,2,2,4 +0,530,3,1,5 +0,531,1,0,4 +0,532,4,1,9 +0,533,0,0,7 +0,534,3,2,5 +0,535,4,0,5 +0,536,5,3,2 +0,537,6,0,2 +0,538,4,2,8 +0,539,2,4,6 +0,540,1,4,9 +0,541,1,1,3 +0,542,2,0,9 +0,543,4,1,3 +0,544,1,2,6 +0,545,2,1,3 +0,546,3,0,2 +0,547,1,0,4 +0,548,2,2,5 +0,549,1,0,10 +0,550,2,3,5 +0,551,2,3,4 +0,552,2,4,8 +0,553,4,5,2 +0,554,4,1,4 +0,555,2,1,8 +0,556,1,1,3 +0,557,8,2,2 +0,558,1,3,1 +0,559,3,2,6 +0,560,3,5,4 +0,561,0,0,5 +0,562,5,2,7 +0,563,5,2,7 +0,564,3,1,4 +0,565,0,1,5 +0,566,1,1,5 +0,567,8,1,4 +0,568,2,3,6 +0,569,4,2,3 +0,570,2,3,4 +0,571,5,2,2 +0,572,3,1,6 +0,573,3,1,1 +0,574,3,2,5 +0,575,5,2,6 +0,576,3,2,4 +0,577,2,1,4 +0,578,4,2,6 +0,579,5,2,5 +0,580,0,1,7 +0,581,2,4,5 +0,582,1,2,7 +0,583,3,1,2 +0,584,3,1,6 +0,585,2,2,7 +0,586,5,1,5 +0,587,1,1,6 +0,588,4,1,8 +0,589,5,1,7 +0,590,5,4,5 +0,591,0,1,4 +0,592,3,1,6 +0,593,2,1,3 +0,594,3,2,6 +0,595,8,0,5 +0,596,1,1,6 +0,597,1,1,5 +0,598,5,3,6 +0,599,4,3,5 +0,600,5,2,7 +0,601,1,2,6 +0,602,4,3,3 +0,603,3,2,4 +0,604,5,2,5 +0,605,0,2,7 +0,606,3,2,6 +0,607,3,1,2 +0,608,2,4,8 +0,609,1,1,4 +0,610,1,2,5 +0,611,3,0,11 +0,612,3,3,5 +0,613,3,1,2 +0,614,5,3,4 +0,615,2,1,3 +0,616,2,3,6 +0,617,3,1,1 +0,618,2,2,2 +0,619,3,0,2 +0,620,2,2,4 +0,621,0,1,8 +0,622,2,2,4 +0,623,3,1,9 +0,624,2,2,6 +0,625,5,3,5 +0,626,3,3,4 +0,627,4,1,4 +0,628,1,1,3 +0,629,4,3,5 +0,630,5,3,1 +0,631,4,2,8 +0,632,3,1,7 +0,633,4,2,6 +0,634,2,1,8 +0,635,4,2,8 +0,636,1,2,8 +0,637,2,1,2 +0,638,3,3,5 +0,639,3,1,2 +0,640,3,0,2 +0,641,1,3,3 +0,642,3,2,3 +0,643,4,1,7 +0,644,5,0,8 +0,645,4,2,6 +0,646,6,1,1 +0,647,2,0,3 +0,648,2,1,4 +0,649,4,1,9 +0,650,2,1,4 +0,651,4,2,8 +0,652,4,1,3 +0,653,1,2,3 +0,654,5,4,4 +0,655,2,3,7 +0,656,2,2,8 +0,657,1,1,6 +0,658,1,2,2 +0,659,3,2,4 +0,660,3,0,6 +0,661,2,4,6 +0,662,4,0,1 +0,663,0,3,10 +0,664,2,3,8 +0,665,2,2,8 +0,666,2,0,4 +0,667,3,3,6 +0,668,4,0,6 +0,669,2,1,8 +0,670,2,1,5 +0,671,4,2,3 +0,672,2,3,0 +0,673,2,0,4 +0,674,3,1,3 +0,675,3,1,3 +0,676,2,0,4 +0,677,4,0,4 +0,678,3,1,2 +0,679,2,4,8 +0,680,4,2,7 +0,681,3,2,8 +0,682,2,0,6 +0,683,0,2,4 +0,684,3,1,8 +0,685,0,3,3 +0,686,3,1,6 +0,687,1,0,5 +0,688,2,1,5 +0,689,4,0,5 +0,690,2,4,5 +0,691,3,2,7 +0,692,1,5,8 +0,693,5,3,5 +0,694,3,1,1 +0,695,3,0,9 +0,696,5,0,4 +0,697,3,2,4 +0,698,4,3,6 +0,699,4,1,3 +0,700,3,2,7 +0,701,4,0,4 +0,702,3,2,2 +0,703,2,0,6 +0,704,4,6,4 +0,705,3,0,4 +0,706,4,0,1 +0,707,1,1,4 +0,708,1,0,4 +0,709,1,2,4 +0,710,2,2,3 +0,711,2,1,9 +0,712,4,2,6 +0,713,3,5,4 +0,714,3,1,4 +0,715,5,1,8 +0,716,4,3,4 +0,717,3,3,8 +0,718,2,0,5 +0,719,2,3,3 +0,720,3,1,3 +0,721,2,1,5 +0,722,2,1,5 +0,723,1,1,6 +0,724,3,6,3 +0,725,1,1,7 +0,726,0,4,2 +0,727,3,0,2 +0,728,3,1,5 +0,729,3,0,6 +0,730,1,1,5 +0,731,4,1,7 +0,732,5,2,2 +0,733,1,3,4 +0,734,5,0,9 +0,735,3,2,3 +0,736,4,3,3 +0,737,3,1,6 +0,738,4,0,9 +0,739,0,1,9 +0,740,3,4,6 +0,741,4,1,2 +0,742,1,0,5 +0,743,1,0,4 +0,744,4,2,5 +0,745,0,2,4 +0,746,1,1,3 +0,747,2,1,3 +0,748,1,3,7 +0,749,3,0,3 +0,750,1,0,13 +0,751,7,2,3 +0,752,3,3,3 +0,753,3,3,8 +0,754,1,5,7 +0,755,2,2,2 +0,756,3,1,6 +0,757,3,3,0 +0,758,1,2,4 +0,759,1,0,6 +0,760,2,2,2 +0,761,1,1,5 +0,762,3,1,1 +0,763,4,3,5 +0,764,6,4,2 +0,765,3,1,5 +0,766,1,1,3 +0,767,2,1,3 +0,768,1,2,4 +0,769,1,3,3 +0,770,5,0,1 +0,771,2,1,9 +0,772,4,2,7 +0,773,4,2,4 +0,774,3,2,1 +0,775,4,2,7 +0,776,2,1,2 +0,777,2,0,3 +0,778,3,1,5 +0,779,7,3,3 +0,780,1,2,3 +0,781,2,2,3 +0,782,3,1,3 +0,783,3,0,3 +0,784,3,4,4 +0,785,0,2,4 +0,786,2,2,5 +0,787,4,1,5 +0,788,4,0,7 +0,789,5,2,3 +0,790,2,1,5 +0,791,1,2,3 +0,792,2,2,6 +0,793,2,1,3 +0,794,0,1,4 +0,795,1,2,2 +0,796,2,4,6 +0,797,0,2,3 +0,798,3,2,6 +0,799,5,0,1 +0,800,2,4,6 +0,801,2,1,2 +0,802,2,0,3 +0,803,4,2,7 +0,804,2,1,3 +0,805,2,2,7 +0,806,0,2,7 +0,807,2,2,4 +0,808,0,1,4 +0,809,5,4,3 +0,810,2,2,1 +0,811,6,3,2 +0,812,1,0,4 +0,813,4,1,7 +0,814,1,0,5 +0,815,2,2,5 +0,816,0,2,6 +0,817,4,3,4 +0,818,3,6,1 +0,819,4,3,5 +0,820,3,1,3 +0,821,4,3,7 +0,822,3,3,7 +0,823,3,2,2 +0,824,2,4,7 +0,825,4,3,6 +0,826,3,1,8 +0,827,4,1,9 +0,828,1,2,3 +0,829,1,3,7 +0,830,1,2,3 +0,831,2,3,1 +0,832,4,1,5 +0,833,4,2,3 +0,834,3,1,2 +0,835,2,0,4 +0,836,1,3,7 +0,837,5,1,3 +0,838,6,0,7 +0,839,5,2,4 +0,840,1,0,5 +0,841,5,1,8 +0,842,4,3,4 +0,843,2,2,4 +0,844,4,3,5 +0,845,2,1,2 +0,846,4,2,5 +0,847,1,4,5 +0,848,2,1,4 +0,849,1,3,6 +0,850,5,3,1 +0,851,4,4,6 +0,852,4,1,5 +0,853,6,1,6 +0,854,4,1,5 +0,855,3,3,6 +0,856,0,3,2 +0,857,1,2,6 +0,858,2,2,9 +0,859,3,1,3 +0,860,4,1,5 +0,861,4,1,6 +0,862,4,1,5 +0,863,1,3,5 +0,864,7,1,6 +0,865,2,3,5 +0,866,3,0,4 +0,867,3,3,7 +0,868,2,2,2 +0,869,4,1,4 +0,870,3,4,3 +0,871,1,2,2 +0,872,2,2,3 +0,873,3,1,6 +0,874,3,2,9 +0,875,2,2,2 +0,876,2,3,6 +0,877,6,1,3 +0,878,2,1,5 +0,879,2,0,4 +0,880,1,2,4 +0,881,2,5,1 +0,882,3,2,3 +0,883,2,3,1 +0,884,0,1,5 +0,885,2,1,2 +0,886,2,2,1 +0,887,1,0,7 +0,888,3,1,2 +0,889,2,1,4 +0,890,2,4,5 +0,891,0,0,5 +0,892,3,3,4 +0,893,2,1,6 +0,894,6,3,1 +0,895,2,1,6 +0,896,0,3,6 +0,897,5,2,1 +0,898,4,2,3 +0,899,2,4,3 +0,900,6,3,2 +0,901,3,4,6 +0,902,1,0,4 +0,903,2,2,7 +0,904,3,1,1 +0,905,1,1,3 +0,906,2,2,8 +0,907,1,1,5 +0,908,2,2,5 +0,909,2,1,5 +0,910,5,2,1 +0,911,2,0,4 +0,912,3,3,5 +0,913,6,1,7 +0,914,2,4,5 +0,915,3,2,7 +0,916,4,0,2 +0,917,4,4,6 +0,918,2,5,7 +0,919,4,2,6 +0,920,3,5,6 +0,921,2,3,2 +0,922,3,4,3 +0,923,3,0,6 +0,924,2,1,4 +0,925,3,5,4 +0,926,5,2,1 +0,927,5,0,5 +0,928,2,3,4 +0,929,8,2,4 +0,930,5,4,5 +0,931,1,4,0 +0,932,3,4,6 +0,933,1,3,1 +0,934,3,4,6 +0,935,2,1,6 +0,936,6,4,4 +0,937,5,3,4 +0,938,3,4,4 +0,939,2,3,1 +0,940,3,4,6 +0,941,1,0,5 +0,942,1,2,6 +0,943,4,1,5 +0,944,3,1,3 +0,945,2,4,7 +0,946,3,0,2 +0,947,7,0,7 +0,948,2,3,6 +0,949,4,4,4 +0,950,3,1,4 +0,951,2,4,2 +0,952,4,1,9 +0,953,1,0,4 +0,954,0,2,9 +0,955,5,2,7 +0,956,3,3,5 +0,957,4,2,8 +0,958,3,1,3 +0,959,5,4,5 +0,960,1,0,5 +0,961,1,5,3 +0,962,1,2,4 +0,963,2,2,2 +0,964,5,1,8 +0,965,5,2,3 +0,966,3,0,9 +0,967,2,2,5 +0,968,3,2,2 +0,969,5,1,5 +0,970,4,1,5 +0,971,3,2,4 +0,972,2,1,3 +0,973,1,0,9 +0,974,3,2,1 +0,975,2,0,4 +0,976,4,2,5 +0,977,3,2,6 +0,978,2,0,8 +0,979,3,2,5 +0,980,2,3,1 +0,981,4,3,7 +0,982,0,4,5 +0,983,1,2,8 +0,984,1,2,5 +0,985,2,1,6 +0,986,2,0,3 +0,987,2,5,4 +0,988,2,2,2 +0,989,4,1,4 +0,990,1,2,4 +0,991,4,2,4 +0,992,4,3,4 +0,993,3,2,9 +0,994,2,4,5 +0,995,3,0,2 +0,996,4,1,2 +0,997,6,2,6 +0,998,1,1,6 +0,999,2,1,5 +1,0,13,0,0 +1,1,4,0,3 +1,2,6,0,2 +1,3,2,0,3 +1,4,5,0,4 +1,5,10,0,3 +1,6,7,0,1 +1,7,5,0,0 +1,8,4,0,2 +1,9,6,0,2 +1,10,7,0,3 +1,11,5,0,0 +1,12,2,0,3 +1,13,7,0,3 +1,14,6,0,7 +1,15,3,0,2 +1,16,8,0,3 +1,17,4,0,2 +1,18,8,0,2 +1,19,6,0,1 +1,20,4,0,3 +1,21,6,0,4 +1,22,7,0,3 +1,23,8,0,3 +1,24,6,0,4 +1,25,5,0,3 +1,26,10,0,3 +1,27,7,0,2 +1,28,5,0,7 +1,29,10,0,3 +1,30,7,0,3 +1,31,3,0,2 +1,32,6,0,1 +1,33,11,0,1 +1,34,7,0,7 +1,35,6,0,7 +1,36,5,0,2 +1,37,8,0,1 +1,38,9,0,3 +1,39,4,0,3 +1,40,8,0,3 +1,41,9,0,5 +1,42,7,0,3 +1,43,11,0,3 +1,44,4,0,2 +1,45,9,0,3 +1,46,4,0,1 +1,47,7,0,2 +1,48,6,0,3 +1,49,6,0,1 +1,50,5,0,2 +1,51,11,0,3 +1,52,4,0,5 +1,53,6,0,3 +1,54,6,0,3 +1,55,8,0,5 +1,56,8,0,5 +1,57,8,0,4 +1,58,9,0,3 +1,59,6,0,2 +1,60,2,0,6 +1,61,5,0,1 +1,62,9,0,1 +1,63,9,0,1 +1,64,9,0,4 +1,65,5,0,1 +1,66,9,0,1 +1,67,4,0,4 +1,68,8,0,4 +1,69,8,0,4 +1,70,7,0,1 +1,71,7,0,1 +1,72,8,0,5 +1,73,7,0,2 +1,74,8,0,2 +1,75,6,0,2 +1,76,7,0,1 +1,77,3,0,5 +1,78,5,0,2 +1,79,6,0,1 +1,80,9,0,2 +1,81,8,0,0 +1,82,3,0,2 +1,83,6,0,2 +1,84,4,0,1 +1,85,9,0,4 +1,86,4,0,4 +1,87,4,0,5 +1,88,8,0,4 +1,89,11,0,1 +1,90,5,0,2 +1,91,10,0,0 +1,92,9,0,2 +1,93,8,0,6 +1,94,7,0,3 +1,95,6,0,3 +1,96,6,0,4 +1,97,8,0,4 +1,98,3,0,2 +1,99,8,0,2 +1,100,8,0,4 +1,101,6,0,4 +1,102,6,0,5 +1,103,8,0,5 +1,104,9,0,1 +1,105,7,0,4 +1,106,7,0,2 +1,107,8,0,4 +1,108,4,0,2 +1,109,6,0,3 +1,110,3,0,4 +1,111,5,0,1 +1,112,5,0,0 +1,113,7,0,3 +1,114,4,0,1 +1,115,9,0,5 +1,116,5,0,0 +1,117,3,0,3 +1,118,8,0,2 +1,119,7,0,4 +1,120,5,0,0 +1,121,4,0,3 +1,122,3,0,4 +1,123,6,0,3 +1,124,11,0,2 +1,125,4,0,3 +1,126,6,0,1 +1,127,12,0,2 +1,128,8,0,4 +1,129,11,0,2 +1,130,3,0,7 +1,131,8,0,3 +1,132,12,0,1 +1,133,7,0,3 +1,134,5,0,2 +1,135,2,0,5 +1,136,7,0,3 +1,137,6,0,3 +1,138,6,0,3 +1,139,11,0,3 +1,140,4,0,4 +1,141,5,0,3 +1,142,7,0,0 +1,143,9,0,5 +1,144,13,0,1 +1,145,7,0,3 +1,146,11,0,3 +1,147,10,0,3 +1,148,4,0,1 +1,149,7,0,2 +1,150,4,0,1 +1,151,7,0,7 +1,152,4,0,2 +1,153,7,0,3 +1,154,4,0,2 +1,155,8,0,1 +1,156,2,0,3 +1,157,8,0,2 +1,158,12,0,1 +1,159,9,0,0 +1,160,6,0,8 +1,161,10,0,3 +1,162,9,0,0 +1,163,10,0,4 +1,164,8,0,4 +1,165,2,0,3 +1,166,6,0,3 +1,167,8,0,4 +1,168,9,0,3 +1,169,5,0,3 +1,170,10,0,4 +1,171,7,0,1 +1,172,3,0,6 +1,173,5,0,2 +1,174,4,0,1 +1,175,8,0,2 +1,176,6,0,2 +1,177,3,0,2 +1,178,9,0,5 +1,179,7,0,3 +1,180,11,0,2 +1,181,2,0,5 +1,182,5,0,2 +1,183,5,0,1 +1,184,11,0,1 +1,185,3,0,3 +1,186,8,0,2 +1,187,4,0,2 +1,188,6,0,0 +1,189,9,0,5 +1,190,4,0,2 +1,191,5,0,2 +1,192,6,0,3 +1,193,4,0,2 +1,194,3,0,2 +1,195,2,0,5 +1,196,7,0,3 +1,197,10,0,4 +1,198,8,0,3 +1,199,7,0,3 +1,200,7,0,2 +1,201,11,0,2 +1,202,10,0,1 +1,203,5,0,3 +1,204,6,0,8 +1,205,5,0,6 +1,206,7,0,0 +1,207,4,0,3 +1,208,6,0,3 +1,209,6,0,4 +1,210,5,0,3 +1,211,6,0,5 +1,212,9,0,1 +1,213,6,0,1 +1,214,8,0,4 +1,215,8,0,1 +1,216,5,0,4 +1,217,5,0,4 +1,218,6,0,2 +1,219,4,0,1 +1,220,8,0,3 +1,221,9,0,2 +1,222,7,0,2 +1,223,6,0,3 +1,224,3,0,2 +1,225,5,0,3 +1,226,5,0,2 +1,227,7,0,4 +1,228,9,0,1 +1,229,8,0,5 +1,230,4,0,5 +1,231,4,0,5 +1,232,2,0,3 +1,233,6,0,7 +1,234,8,0,4 +1,235,4,0,2 +1,236,10,0,3 +1,237,5,0,9 +1,238,3,0,2 +1,239,10,0,2 +1,240,2,0,3 +1,241,6,0,7 +1,242,7,0,6 +1,243,7,0,1 +1,244,8,0,2 +1,245,9,0,5 +1,246,8,0,4 +1,247,5,0,2 +1,248,4,0,1 +1,249,9,0,4 +1,250,4,0,1 +1,251,10,0,1 +1,252,7,0,4 +1,253,10,0,4 +1,254,7,0,2 +1,255,9,0,4 +1,256,7,0,1 +1,257,2,0,4 +1,258,8,0,3 +1,259,8,0,2 +1,260,4,0,2 +1,261,6,0,1 +1,262,7,0,1 +1,263,3,0,6 +1,264,11,0,0 +1,265,4,0,2 +1,266,6,0,4 +1,267,7,0,5 +1,268,7,0,4 +1,269,8,0,6 +1,270,8,0,4 +1,271,6,0,0 +1,272,6,0,6 +1,273,7,0,4 +1,274,2,0,5 +1,275,4,0,2 +1,276,7,0,0 +1,277,7,0,2 +1,278,9,0,3 +1,279,10,0,4 +1,280,4,0,4 +1,281,7,0,3 +1,282,7,0,4 +1,283,5,0,3 +1,284,7,0,2 +1,285,3,0,3 +1,286,6,0,3 +1,287,5,0,5 +1,288,9,0,2 +1,289,4,0,1 +1,290,8,0,6 +1,291,7,0,2 +1,292,6,0,5 +1,293,12,0,1 +1,294,8,0,2 +1,295,10,0,2 +1,296,8,0,3 +1,297,6,0,0 +1,298,8,0,5 +1,299,5,0,1 +1,300,7,0,4 +1,301,5,0,3 +1,302,5,0,4 +1,303,4,0,2 +1,304,5,0,1 +1,305,7,0,3 +1,306,7,0,3 +1,307,6,0,1 +1,308,10,0,4 +1,309,5,0,2 +1,310,5,0,1 +1,311,6,0,7 +1,312,7,0,5 +1,313,2,0,3 +1,314,7,0,1 +1,315,6,0,6 +1,316,12,0,2 +1,317,8,0,5 +1,318,10,0,3 +1,319,4,0,1 +1,320,9,0,1 +1,321,3,0,2 +1,322,6,0,3 +1,323,9,0,3 +1,324,9,0,3 +1,325,9,0,5 +1,326,6,0,3 +1,327,10,0,4 +1,328,5,0,2 +1,329,9,0,1 +1,330,10,0,4 +1,331,4,0,3 +1,332,8,0,6 +1,333,4,0,2 +1,334,7,0,1 +1,335,6,0,2 +1,336,4,0,1 +1,337,4,0,2 +1,338,9,0,5 +1,339,4,0,2 +1,340,9,0,4 +1,341,7,0,6 +1,342,4,0,2 +1,343,9,0,4 +1,344,8,0,5 +1,345,10,0,0 +1,346,5,0,0 +1,347,7,0,3 +1,348,3,0,4 +1,349,7,0,4 +1,350,6,0,5 +1,351,4,0,2 +1,352,4,0,2 +1,353,5,0,4 +1,354,4,0,6 +1,355,10,0,3 +1,356,5,0,7 +1,357,6,0,2 +1,358,4,0,2 +1,359,10,0,3 +1,360,10,0,3 +1,361,10,0,2 +1,362,4,0,1 +1,363,10,0,2 +1,364,8,0,5 +1,365,6,0,2 +1,366,7,0,6 +1,367,9,0,4 +1,368,12,0,2 +1,369,5,0,3 +1,370,9,0,0 +1,371,10,0,1 +1,372,4,0,5 +1,373,8,0,1 +1,374,4,0,2 +1,375,8,0,4 +1,376,8,0,3 +1,377,6,0,2 +1,378,4,0,1 +1,379,6,0,6 +1,380,4,0,8 +1,381,6,0,5 +1,382,11,0,1 +1,383,6,0,3 +1,384,12,0,0 +1,385,5,0,1 +1,386,3,0,4 +1,387,6,0,5 +1,388,6,0,2 +1,389,5,0,2 +1,390,6,0,2 +1,391,8,0,4 +1,392,8,0,3 +1,393,6,0,1 +1,394,8,0,2 +1,395,10,0,3 +1,396,8,0,3 +1,397,7,0,7 +1,398,5,0,1 +1,399,4,0,1 +1,400,5,0,4 +1,401,10,0,1 +1,402,11,0,2 +1,403,7,0,7 +1,404,8,0,1 +1,405,11,0,2 +1,406,4,0,3 +1,407,4,0,2 +1,408,5,0,0 +1,409,9,0,2 +1,410,12,0,1 +1,411,7,0,2 +1,412,8,0,3 +1,413,7,0,4 +1,414,11,0,2 +1,415,3,0,3 +1,416,3,0,2 +1,417,6,0,2 +1,418,3,0,4 +1,419,9,0,0 +1,420,7,0,3 +1,421,10,0,1 +1,422,11,0,2 +1,423,5,0,2 +1,424,5,0,2 +1,425,8,0,3 +1,426,9,0,5 +1,427,4,0,1 +1,428,8,0,3 +1,429,9,0,4 +1,430,6,0,4 +1,431,8,0,4 +1,432,6,0,2 +1,433,3,0,3 +1,434,4,0,5 +1,435,10,0,0 +1,436,7,0,5 +1,437,8,0,4 +1,438,8,0,3 +1,439,8,0,3 +1,440,5,0,1 +1,441,2,0,3 +1,442,7,0,7 +1,443,10,0,4 +1,444,4,0,3 +1,445,5,0,4 +1,446,6,0,2 +1,447,6,0,2 +1,448,11,0,1 +1,449,4,0,3 +1,450,6,0,4 +1,451,7,0,3 +1,452,9,0,4 +1,453,8,0,2 +1,454,8,0,6 +1,455,4,0,9 +1,456,7,0,5 +1,457,11,0,2 +1,458,6,0,3 +1,459,8,0,4 +1,460,4,0,3 +1,461,5,0,2 +1,462,11,0,2 +1,463,3,0,3 +1,464,4,0,3 +1,465,8,0,6 +1,466,6,0,3 +1,467,3,0,2 +1,468,9,0,3 +1,469,9,0,3 +1,470,7,0,2 +1,471,8,0,3 +1,472,11,0,3 +1,473,9,0,1 +1,474,9,0,3 +1,475,9,0,5 +1,476,4,0,3 +1,477,4,0,5 +1,478,7,0,7 +1,479,11,0,3 +1,480,9,0,4 +1,481,4,0,3 +1,482,6,0,1 +1,483,7,0,5 +1,484,5,0,1 +1,485,6,0,2 +1,486,6,0,2 +1,487,7,0,4 +1,488,11,0,2 +1,489,9,0,3 +1,490,3,0,6 +1,491,8,0,6 +1,492,6,0,3 +1,493,7,0,5 +1,494,9,0,5 +1,495,7,0,3 +1,496,7,0,0 +1,497,8,0,3 +1,498,5,0,2 +1,499,4,0,1 +1,500,5,0,2 +1,501,3,0,5 +1,502,7,0,1 +1,503,5,0,4 +1,504,13,0,1 +1,505,7,0,2 +1,506,8,0,4 +1,507,2,0,3 +1,508,7,0,2 +1,509,6,0,5 +1,510,7,0,1 +1,511,11,0,2 +1,512,7,0,4 +1,513,5,0,9 +1,514,6,0,3 +1,515,8,0,4 +1,516,9,0,2 +1,517,7,0,2 +1,518,10,0,3 +1,519,5,0,2 +1,520,4,0,3 +1,521,4,0,4 +1,522,4,0,1 +1,523,4,0,3 +1,524,6,0,2 +1,525,3,0,2 +1,526,4,0,1 +1,527,4,0,1 +1,528,10,0,1 +1,529,9,0,3 +1,530,5,0,3 +1,531,5,0,3 +1,532,11,0,3 +1,533,6,0,3 +1,534,5,0,3 +1,535,3,0,2 +1,536,9,0,2 +1,537,3,0,2 +1,538,7,0,1 +1,539,9,0,4 +1,540,5,0,0 +1,541,7,0,4 +1,542,7,0,2 +1,543,2,0,4 +1,544,6,0,4 +1,545,4,0,4 +1,546,3,0,2 +1,547,3,0,2 +1,548,3,0,5 +1,549,7,0,6 +1,550,8,0,3 +1,551,3,0,2 +1,552,8,0,3 +1,553,7,0,3 +1,554,5,0,1 +1,555,13,0,1 +1,556,11,0,3 +1,557,8,0,6 +1,558,12,0,2 +1,559,7,0,5 +1,560,11,0,1 +1,561,2,0,3 +1,562,6,0,4 +1,563,6,0,2 +1,564,9,0,2 +1,565,3,0,2 +1,566,5,0,1 +1,567,6,0,3 +1,568,6,0,5 +1,569,10,0,3 +1,570,6,0,4 +1,571,7,0,2 +1,572,7,0,4 +1,573,8,0,5 +1,574,9,0,2 +1,575,5,0,1 +1,576,5,0,0 +1,577,7,0,1 +1,578,8,0,1 +1,579,7,0,3 +1,580,8,0,0 +1,581,10,0,4 +1,582,8,0,5 +1,583,8,0,1 +1,584,9,0,1 +1,585,11,0,3 +1,586,6,0,2 +1,587,7,0,5 +1,588,4,0,4 +1,589,7,0,3 +1,590,8,0,6 +1,591,10,0,3 +1,592,9,0,5 +1,593,6,0,2 +1,594,5,0,1 +1,595,5,0,0 +1,596,4,0,1 +1,597,7,0,1 +1,598,8,0,0 +1,599,6,0,1 +1,600,6,0,1 +1,601,9,0,2 +1,602,4,0,3 +1,603,8,0,3 +1,604,5,0,5 +1,605,4,0,1 +1,606,4,0,2 +1,607,10,0,4 +1,608,11,0,3 +1,609,7,0,2 +1,610,8,0,4 +1,611,7,0,0 +1,612,6,0,4 +1,613,11,0,2 +1,614,8,0,5 +1,615,5,0,2 +1,616,4,0,1 +1,617,8,0,1 +1,618,8,0,6 +1,619,9,0,3 +1,620,7,0,3 +1,621,6,0,3 +1,622,8,0,2 +1,623,2,0,3 +1,624,8,0,4 +1,625,3,0,2 +1,626,11,0,2 +1,627,7,0,5 +1,628,7,0,0 +1,629,4,0,4 +1,630,4,0,1 +1,631,5,0,4 +1,632,6,0,0 +1,633,6,0,1 +1,634,9,0,2 +1,635,11,0,2 +1,636,5,0,2 +1,637,8,0,4 +1,638,10,0,4 +1,639,4,0,1 +1,640,4,0,2 +1,641,3,0,4 +1,642,5,0,3 +1,643,7,0,1 +1,644,9,0,5 +1,645,7,0,0 +1,646,7,0,0 +1,647,9,0,4 +1,648,8,0,3 +1,649,9,0,5 +1,650,5,0,7 +1,651,4,0,3 +1,652,8,0,3 +1,653,7,0,0 +1,654,6,0,2 +1,655,5,0,2 +1,656,4,0,3 +1,657,5,0,1 +1,658,8,0,3 +1,659,7,0,3 +1,660,5,0,3 +1,661,4,0,1 +1,662,10,0,4 +1,663,6,0,3 +1,664,4,0,1 +1,665,7,0,0 +1,666,7,0,4 +1,667,7,0,0 +1,668,6,0,4 +1,669,9,0,4 +1,670,12,0,2 +1,671,8,0,2 +1,672,5,0,3 +1,673,7,0,2 +1,674,11,0,1 +1,675,3,0,6 +1,676,10,0,3 +1,677,4,0,1 +1,678,6,0,4 +1,679,4,0,2 +1,680,6,0,3 +1,681,7,0,4 +1,682,9,0,2 +1,683,8,0,4 +1,684,11,0,2 +1,685,4,0,5 +1,686,3,0,3 +1,687,8,0,3 +1,688,11,0,1 +1,689,8,0,3 +1,690,3,0,2 +1,691,7,0,3 +1,692,7,0,5 +1,693,8,0,0 +1,694,10,0,4 +1,695,5,0,3 +1,696,7,0,1 +1,697,12,0,2 +1,698,7,0,0 +1,699,9,0,2 +1,700,11,0,3 +1,701,5,0,2 +1,702,5,0,5 +1,703,4,0,4 +1,704,11,0,3 +1,705,12,0,1 +1,706,7,0,3 +1,707,6,0,4 +1,708,7,0,2 +1,709,6,0,0 +1,710,7,0,6 +1,711,7,0,2 +1,712,10,0,2 +1,713,9,0,4 +1,714,7,0,4 +1,715,9,0,4 +1,716,7,0,3 +1,717,4,0,3 +1,718,11,0,3 +1,719,6,0,7 +1,720,7,0,7 +1,721,7,0,3 +1,722,8,0,6 +1,723,4,0,2 +1,724,11,0,3 +1,725,8,0,4 +1,726,3,0,2 +1,727,10,0,3 +1,728,5,0,7 +1,729,7,0,7 +1,730,4,0,2 +1,731,4,0,3 +1,732,6,0,2 +1,733,7,0,7 +1,734,5,0,3 +1,735,6,0,5 +1,736,6,0,1 +1,737,4,0,3 +1,738,5,0,4 +1,739,7,0,5 +1,740,5,0,1 +1,741,7,0,3 +1,742,8,0,3 +1,743,7,0,6 +1,744,4,0,1 +1,745,6,0,2 +1,746,5,0,0 +1,747,11,0,2 +1,748,3,0,4 +1,749,6,0,1 +1,750,6,0,2 +1,751,2,0,3 +1,752,10,0,0 +1,753,4,0,4 +1,754,7,0,5 +1,755,8,0,1 +1,756,6,0,1 +1,757,7,0,2 +1,758,5,0,1 +1,759,9,0,1 +1,760,6,0,0 +1,761,3,0,6 +1,762,7,0,1 +1,763,7,0,3 +1,764,9,0,0 +1,765,12,0,1 +1,766,4,0,2 +1,767,7,0,2 +1,768,4,0,5 +1,769,7,0,1 +1,770,5,0,2 +1,771,6,0,5 +1,772,6,0,4 +1,773,5,0,3 +1,774,8,0,4 +1,775,5,0,1 +1,776,6,0,5 +1,777,7,0,2 +1,778,8,0,4 +1,779,3,0,8 +1,780,7,0,1 +1,781,7,0,3 +1,782,8,0,1 +1,783,6,0,4 +1,784,4,0,3 +1,785,8,0,4 +1,786,7,0,6 +1,787,4,0,1 +1,788,9,0,5 +1,789,4,0,3 +1,790,10,0,3 +1,791,6,0,0 +1,792,8,0,2 +1,793,7,0,2 +1,794,9,0,5 +1,795,4,0,6 +1,796,7,0,2 +1,797,9,0,2 +1,798,6,0,3 +1,799,7,0,2 +1,800,6,0,2 +1,801,7,0,5 +1,802,5,0,2 +1,803,4,0,2 +1,804,11,0,3 +1,805,3,0,2 +1,806,8,0,4 +1,807,4,0,3 +1,808,5,0,3 +1,809,5,0,0 +1,810,11,0,2 +1,811,6,0,2 +1,812,7,0,6 +1,813,10,0,4 +1,814,7,0,1 +1,815,5,0,3 +1,816,5,0,3 +1,817,8,0,1 +1,818,10,0,3 +1,819,4,0,2 +1,820,8,0,5 +1,821,11,0,3 +1,822,7,0,1 +1,823,4,0,4 +1,824,5,0,1 +1,825,9,0,2 +1,826,3,0,2 +1,827,8,0,0 +1,828,7,0,5 +1,829,7,0,2 +1,830,7,0,3 +1,831,6,0,2 +1,832,5,0,0 +1,833,2,0,3 +1,834,6,0,4 +1,835,10,0,4 +1,836,7,0,5 +1,837,11,0,2 +1,838,10,0,1 +1,839,6,0,2 +1,840,5,0,4 +1,841,6,0,4 +1,842,8,0,2 +1,843,11,0,2 +1,844,4,0,1 +1,845,10,0,3 +1,846,7,0,3 +1,847,6,0,4 +1,848,4,0,2 +1,849,10,0,4 +1,850,3,0,2 +1,851,5,0,0 +1,852,9,0,4 +1,853,7,0,5 +1,854,5,0,3 +1,855,9,0,3 +1,856,5,0,1 +1,857,3,0,2 +1,858,5,0,2 +1,859,8,0,1 +1,860,4,0,1 +1,861,7,0,6 +1,862,5,0,2 +1,863,7,0,4 +1,864,7,0,2 +1,865,7,0,3 +1,866,9,0,4 +1,867,3,0,2 +1,868,4,0,4 +1,869,10,0,3 +1,870,7,0,1 +1,871,6,0,4 +1,872,6,0,4 +1,873,5,0,5 +1,874,5,0,2 +1,875,5,0,3 +1,876,8,0,6 +1,877,4,0,3 +1,878,11,0,1 +1,879,6,0,4 +1,880,3,0,3 +1,881,12,0,2 +1,882,9,0,5 +1,883,11,0,0 +1,884,11,0,0 +1,885,10,0,2 +1,886,5,0,5 +1,887,8,0,6 +1,888,2,0,4 +1,889,6,0,0 +1,890,3,0,3 +1,891,8,0,4 +1,892,9,0,3 +1,893,4,0,5 +1,894,6,0,4 +1,895,4,0,3 +1,896,9,0,2 +1,897,9,0,2 +1,898,7,0,5 +1,899,9,0,1 +1,900,4,0,2 +1,901,5,0,1 +1,902,10,0,3 +1,903,4,0,4 +1,904,5,0,3 +1,905,6,0,4 +1,906,6,0,2 +1,907,8,0,4 +1,908,5,0,5 +1,909,9,0,4 +1,910,6,0,6 +1,911,6,0,2 +1,912,5,0,1 +1,913,8,0,4 +1,914,9,0,3 +1,915,7,0,5 +1,916,8,0,2 +1,917,4,0,4 +1,918,3,0,2 +1,919,5,0,2 +1,920,4,0,3 +1,921,4,0,2 +1,922,4,0,1 +1,923,10,0,4 +1,924,3,0,2 +1,925,7,0,5 +1,926,9,0,2 +1,927,5,0,9 +1,928,6,0,1 +1,929,4,0,1 +1,930,5,0,1 +1,931,4,0,1 +1,932,10,0,2 +1,933,7,0,4 +1,934,11,0,3 +1,935,8,0,4 +1,936,5,0,2 +1,937,5,0,0 +1,938,5,0,4 +1,939,9,0,4 +1,940,7,0,1 +1,941,3,0,3 +1,942,7,0,1 +1,943,4,0,3 +1,944,8,0,3 +1,945,7,0,0 +1,946,5,0,0 +1,947,5,0,2 +1,948,5,0,1 +1,949,11,0,3 +1,950,5,0,4 +1,951,7,0,7 +1,952,5,0,1 +1,953,5,0,3 +1,954,8,0,1 +1,955,11,0,2 +1,956,3,0,3 +1,957,4,0,5 +1,958,8,0,4 +1,959,10,0,2 +1,960,7,0,2 +1,961,7,0,5 +1,962,5,0,3 +1,963,10,0,3 +1,964,4,0,6 +1,965,9,0,2 +1,966,8,0,2 +1,967,9,0,5 +1,968,9,0,3 +1,969,5,0,1 +1,970,10,0,3 +1,971,8,0,4 +1,972,9,0,1 +1,973,8,0,1 +1,974,7,0,4 +1,975,8,0,5 +1,976,3,0,3 +1,977,9,0,5 +1,978,8,0,4 +1,979,8,0,2 +1,980,4,0,1 +1,981,6,0,5 +1,982,5,0,0 +1,983,3,0,2 +1,984,7,0,3 +1,985,5,0,1 +1,986,6,0,2 +1,987,3,0,3 +1,988,8,0,3 +1,989,5,0,2 +1,990,8,0,5 +1,991,5,0,0 +1,992,4,0,2 +1,993,7,0,5 +1,994,11,0,2 +1,995,9,0,3 +1,996,10,0,1 +1,997,8,0,3 +1,998,7,0,5 +1,999,3,0,3 +2,0,2,3,0 +2,1,2,6,3 +2,2,3,8,2 +2,3,0,7,0 +2,4,1,9,3 +2,5,1,8,3 +2,6,2,5,2 +2,7,1,6,1 +2,8,1,4,1 +2,9,2,4,2 +2,10,2,9,1 +2,11,1,5,2 +2,12,2,8,2 +2,13,4,7,0 +2,14,2,1,2 +2,15,0,3,2 +2,16,3,3,1 +2,17,0,2,4 +2,18,1,1,3 +2,19,3,8,1 +2,20,3,5,3 +2,21,6,3,1 +2,22,0,6,1 +2,23,0,5,1 +2,24,4,1,1 +2,25,2,7,3 +2,26,2,4,0 +2,27,3,5,4 +2,28,0,8,6 +2,29,2,5,1 +2,30,4,7,0 +2,31,2,5,1 +2,32,1,6,2 +2,33,0,7,1 +2,34,3,9,2 +2,35,2,4,0 +2,36,2,10,1 +2,37,3,3,0 +2,38,0,5,0 +2,39,1,11,2 +2,40,2,4,0 +2,41,1,8,2 +2,42,2,9,2 +2,43,1,8,3 +2,44,4,7,2 +2,45,0,3,2 +2,46,2,7,1 +2,47,1,5,3 +2,48,4,7,3 +2,49,3,7,3 +2,50,2,5,5 +2,51,5,4,4 +2,52,4,4,1 +2,53,3,5,2 +2,54,5,7,2 +2,55,1,4,1 +2,56,1,5,1 +2,57,3,2,4 +2,58,0,3,2 +2,59,4,6,2 +2,60,2,8,1 +2,61,2,7,0 +2,62,0,6,2 +2,63,2,5,2 +2,64,1,5,2 +2,65,4,7,0 +2,66,0,7,4 +2,67,3,9,2 +2,68,2,7,2 +2,69,3,1,3 +2,70,1,4,0 +2,71,2,7,3 +2,72,3,10,0 +2,73,1,3,3 +2,74,2,2,3 +2,75,1,4,2 +2,76,1,5,2 +2,77,2,10,1 +2,78,1,8,4 +2,79,1,5,1 +2,80,3,4,0 +2,81,3,6,3 +2,82,2,4,1 +2,83,1,7,5 +2,84,4,7,1 +2,85,4,1,0 +2,86,0,6,0 +2,87,5,3,2 +2,88,2,8,2 +2,89,4,4,2 +2,90,1,5,4 +2,91,3,9,1 +2,92,1,8,3 +2,93,2,8,2 +2,94,0,3,3 +2,95,2,9,1 +2,96,0,4,1 +2,97,1,5,4 +2,98,1,7,2 +2,99,0,3,3 +2,100,4,4,0 +2,101,2,4,1 +2,102,3,5,2 +2,103,2,8,2 +2,104,2,4,1 +2,105,0,8,1 +2,106,3,1,1 +2,107,2,9,3 +2,108,3,1,1 +2,109,2,7,4 +2,110,2,7,1 +2,111,1,2,2 +2,112,1,2,2 +2,113,3,8,1 +2,114,1,7,4 +2,115,1,5,0 +2,116,1,4,0 +2,117,2,8,3 +2,118,1,4,0 +2,119,3,5,2 +2,120,4,4,1 +2,121,0,4,1 +2,122,2,8,1 +2,123,1,5,1 +2,124,4,3,0 +2,125,1,10,3 +2,126,2,2,7 +2,127,2,3,4 +2,128,2,9,3 +2,129,2,6,4 +2,130,2,7,2 +2,131,2,4,3 +2,132,1,4,2 +2,133,0,8,6 +2,134,0,8,6 +2,135,1,4,0 +2,136,3,6,3 +2,137,1,4,0 +2,138,1,6,3 +2,139,4,6,1 +2,140,4,3,2 +2,141,3,4,3 +2,142,1,7,1 +2,143,3,3,2 +2,144,2,8,3 +2,145,2,3,1 +2,146,3,4,6 +2,147,1,8,4 +2,148,3,2,1 +2,149,2,9,3 +2,150,3,6,2 +2,151,3,5,4 +2,152,2,3,1 +2,153,0,5,4 +2,154,4,4,2 +2,155,2,6,5 +2,156,2,6,3 +2,157,2,6,1 +2,158,2,9,1 +2,159,1,7,1 +2,160,3,3,1 +2,161,0,3,3 +2,162,1,3,1 +2,163,2,7,1 +2,164,2,9,0 +2,165,0,8,3 +2,166,1,6,1 +2,167,1,4,1 +2,168,0,5,2 +2,169,5,5,3 +2,170,3,7,1 +2,171,1,10,3 +2,172,3,5,4 +2,173,2,6,3 +2,174,3,6,3 +2,175,3,7,3 +2,176,1,9,0 +2,177,5,6,3 +2,178,2,11,1 +2,179,3,2,0 +2,180,0,6,0 +2,181,1,4,1 +2,182,3,8,2 +2,183,4,7,2 +2,184,2,5,1 +2,185,3,9,1 +2,186,1,10,3 +2,187,1,10,1 +2,188,3,3,2 +2,189,2,1,2 +2,190,1,4,1 +2,191,2,6,3 +2,192,5,5,1 +2,193,1,9,1 +2,194,2,2,1 +2,195,4,9,1 +2,196,0,11,3 +2,197,1,5,0 +2,198,1,9,4 +2,199,2,3,1 +2,200,2,9,2 +2,201,2,7,2 +2,202,2,11,1 +2,203,2,7,1 +2,204,4,3,0 +2,205,1,7,1 +2,206,1,4,1 +2,207,1,5,0 +2,208,1,3,2 +2,209,2,9,2 +2,210,3,8,3 +2,211,4,8,1 +2,212,3,6,5 +2,213,2,5,2 +2,214,0,10,4 +2,215,1,10,3 +2,216,0,6,2 +2,217,2,5,1 +2,218,1,2,4 +2,219,1,8,1 +2,220,0,8,3 +2,221,1,4,5 +2,222,1,3,1 +2,223,1,3,2 +2,224,2,9,1 +2,225,2,4,0 +2,226,1,7,3 +2,227,3,7,3 +2,228,0,8,1 +2,229,3,7,3 +2,230,2,5,0 +2,231,3,7,3 +2,232,2,3,1 +2,233,1,5,2 +2,234,1,3,2 +2,235,2,8,2 +2,236,1,3,3 +2,237,0,5,1 +2,238,0,6,1 +2,239,1,3,2 +2,240,2,5,4 +2,241,2,5,4 +2,242,4,5,4 +2,243,4,5,3 +2,244,5,3,4 +2,245,3,8,1 +2,246,0,8,3 +2,247,0,8,1 +2,248,1,4,3 +2,249,1,3,1 +2,250,1,9,2 +2,251,3,5,2 +2,252,3,8,1 +2,253,1,7,1 +2,254,3,2,1 +2,255,2,5,2 +2,256,1,7,3 +2,257,4,6,3 +2,258,2,4,2 +2,259,0,4,2 +2,260,1,4,0 +2,261,3,6,2 +2,262,4,5,2 +2,263,2,3,0 +2,264,1,10,3 +2,265,1,6,2 +2,266,0,9,4 +2,267,0,4,1 +2,268,4,5,4 +2,269,5,4,0 +2,270,1,9,1 +2,271,3,3,3 +2,272,2,4,3 +2,273,1,7,2 +2,274,3,3,1 +2,275,1,4,1 +2,276,1,4,4 +2,277,0,4,2 +2,278,1,4,1 +2,279,4,6,1 +2,280,1,4,3 +2,281,2,2,4 +2,282,0,3,3 +2,283,1,8,2 +2,284,4,7,2 +2,285,1,2,2 +2,286,2,6,5 +2,287,1,5,2 +2,288,2,3,2 +2,289,1,7,0 +2,290,2,4,2 +2,291,1,2,2 +2,292,3,2,1 +2,293,3,8,2 +2,294,1,5,2 +2,295,3,3,1 +2,296,5,6,2 +2,297,3,6,3 +2,298,4,7,0 +2,299,5,5,2 +2,300,1,7,0 +2,301,3,4,1 +2,302,1,8,3 +2,303,2,6,0 +2,304,0,11,2 +2,305,1,6,4 +2,306,3,9,2 +2,307,2,4,3 +2,308,1,7,1 +2,309,1,6,1 +2,310,1,8,4 +2,311,2,2,1 +2,312,0,9,3 +2,313,2,11,1 +2,314,2,7,2 +2,315,2,7,2 +2,316,2,4,1 +2,317,2,5,1 +2,318,1,4,1 +2,319,0,9,1 +2,320,4,4,6 +2,321,0,8,1 +2,322,3,6,2 +2,323,0,12,1 +2,324,1,5,1 +2,325,1,3,4 +2,326,2,7,2 +2,327,0,4,1 +2,328,3,3,2 +2,329,0,5,2 +2,330,1,3,6 +2,331,3,5,2 +2,332,2,7,3 +2,333,2,8,3 +2,334,3,2,3 +2,335,1,9,1 +2,336,0,5,0 +2,337,5,4,1 +2,338,1,6,0 +2,339,2,9,2 +2,340,2,7,4 +2,341,5,5,1 +2,342,4,6,3 +2,343,2,4,2 +2,344,3,6,1 +2,345,1,3,2 +2,346,0,3,3 +2,347,3,7,2 +2,348,3,6,4 +2,349,1,3,2 +2,350,3,8,3 +2,351,0,5,5 +2,352,3,5,1 +2,353,4,5,0 +2,354,2,5,5 +2,355,2,4,1 +2,356,4,4,1 +2,357,1,5,1 +2,358,1,5,1 +2,359,0,7,1 +2,360,3,8,3 +2,361,1,3,3 +2,362,0,5,2 +2,363,0,2,3 +2,364,3,2,4 +2,365,1,8,4 +2,366,2,7,1 +2,367,1,11,2 +2,368,2,6,1 +2,369,1,8,1 +2,370,0,5,0 +2,371,4,3,2 +2,372,1,3,1 +2,373,0,5,1 +2,374,2,12,0 +2,375,3,5,2 +2,376,2,3,0 +2,377,2,8,2 +2,378,3,3,1 +2,379,3,7,4 +2,380,0,6,1 +2,381,0,7,2 +2,382,2,10,2 +2,383,2,7,3 +2,384,2,5,3 +2,385,4,1,1 +2,386,0,10,4 +2,387,2,10,0 +2,388,0,4,4 +2,389,1,7,3 +2,390,3,6,2 +2,391,3,10,1 +2,392,2,6,6 +2,393,3,5,1 +2,394,3,4,1 +2,395,2,7,0 +2,396,1,3,1 +2,397,2,7,2 +2,398,3,6,2 +2,399,1,7,4 +2,400,2,7,1 +2,401,5,5,3 +2,402,4,8,2 +2,403,2,6,2 +2,404,0,4,1 +2,405,1,5,1 +2,406,1,7,2 +2,407,2,7,0 +2,408,2,8,4 +2,409,3,9,0 +2,410,0,7,1 +2,411,3,6,3 +2,412,0,6,1 +2,413,4,4,4 +2,414,5,4,3 +2,415,2,3,0 +2,416,3,2,0 +2,417,2,9,2 +2,418,1,6,0 +2,419,3,5,3 +2,420,2,7,4 +2,421,1,6,1 +2,422,1,4,4 +2,423,5,5,1 +2,424,1,6,1 +2,425,1,7,2 +2,426,1,6,5 +2,427,5,7,2 +2,428,0,5,2 +2,429,3,9,1 +2,430,3,7,1 +2,431,4,5,1 +2,432,0,6,1 +2,433,1,5,2 +2,434,1,3,1 +2,435,3,3,2 +2,436,1,5,4 +2,437,4,6,4 +2,438,2,6,0 +2,439,6,7,0 +2,440,4,7,3 +2,441,2,9,1 +2,442,3,9,1 +2,443,1,4,0 +2,444,0,6,1 +2,445,4,8,1 +2,446,0,5,1 +2,447,0,4,1 +2,448,2,4,3 +2,449,2,7,4 +2,450,1,8,5 +2,451,4,4,4 +2,452,0,7,0 +2,453,2,6,1 +2,454,2,7,2 +2,455,0,5,1 +2,456,5,7,0 +2,457,0,6,4 +2,458,1,7,0 +2,459,1,4,2 +2,460,4,5,3 +2,461,0,2,3 +2,462,2,8,3 +2,463,2,8,4 +2,464,1,5,0 +2,465,2,4,4 +2,466,1,6,6 +2,467,3,9,0 +2,468,1,3,5 +2,469,3,5,1 +2,470,2,8,3 +2,471,0,10,1 +2,472,2,9,3 +2,473,1,7,1 +2,474,1,10,3 +2,475,2,6,2 +2,476,2,8,3 +2,477,0,3,3 +2,478,0,6,1 +2,479,1,8,3 +2,480,1,3,2 +2,481,1,3,1 +2,482,2,5,0 +2,483,3,6,3 +2,484,5,2,1 +2,485,2,4,3 +2,486,2,10,2 +2,487,1,3,1 +2,488,2,3,1 +2,489,5,4,2 +2,490,3,6,2 +2,491,2,6,5 +2,492,1,9,0 +2,493,2,4,1 +2,494,3,6,1 +2,495,0,4,2 +2,496,1,3,2 +2,497,0,6,0 +2,498,2,6,0 +2,499,0,5,0 +2,500,1,7,2 +2,501,4,3,1 +2,502,2,6,6 +2,503,1,4,0 +2,504,2,9,1 +2,505,2,5,1 +2,506,0,5,3 +2,507,4,7,2 +2,508,1,4,4 +2,509,5,5,2 +2,510,0,3,2 +2,511,3,3,1 +2,512,3,3,2 +2,513,1,8,0 +2,514,3,9,0 +2,515,2,4,1 +2,516,1,5,3 +2,517,3,4,1 +2,518,3,5,2 +2,519,2,8,0 +2,520,0,6,1 +2,521,2,5,0 +2,522,5,7,1 +2,523,2,9,3 +2,524,4,5,1 +2,525,2,9,2 +2,526,0,11,3 +2,527,1,5,4 +2,528,2,8,2 +2,529,0,3,3 +2,530,4,6,0 +2,531,2,8,1 +2,532,1,4,2 +2,533,2,4,3 +2,534,1,5,2 +2,535,0,8,2 +2,536,1,2,2 +2,537,0,8,5 +2,538,2,5,0 +2,539,3,3,2 +2,540,2,2,2 +2,541,1,11,1 +2,542,2,4,2 +2,543,2,8,3 +2,544,1,3,1 +2,545,2,9,1 +2,546,2,6,2 +2,547,1,6,4 +2,548,2,4,1 +2,549,1,3,2 +2,550,3,5,2 +2,551,1,2,6 +2,552,2,3,1 +2,553,1,10,3 +2,554,4,3,1 +2,555,1,2,2 +2,556,1,3,2 +2,557,1,9,4 +2,558,0,3,2 +2,559,2,5,1 +2,560,1,8,4 +2,561,2,8,1 +2,562,5,5,0 +2,563,1,11,2 +2,564,1,9,4 +2,565,2,6,1 +2,566,0,4,2 +2,567,2,8,4 +2,568,2,2,4 +2,569,3,9,1 +2,570,3,6,1 +2,571,2,8,0 +2,572,4,6,2 +2,573,0,6,3 +2,574,2,7,3 +2,575,3,1,1 +2,576,1,8,2 +2,577,1,7,0 +2,578,0,7,2 +2,579,1,8,1 +2,580,0,4,2 +2,581,2,7,1 +2,582,1,7,5 +2,583,4,5,1 +2,584,1,4,5 +2,585,2,8,1 +2,586,3,2,0 +2,587,1,6,0 +2,588,0,12,0 +2,589,2,5,0 +2,590,5,3,2 +2,591,1,6,4 +2,592,1,5,1 +2,593,2,3,1 +2,594,3,9,2 +2,595,2,5,2 +2,596,3,2,0 +2,597,2,4,3 +2,598,2,3,3 +2,599,2,4,3 +2,600,1,9,1 +2,601,2,5,2 +2,602,3,5,2 +2,603,2,3,1 +2,604,1,8,2 +2,605,0,7,0 +2,606,0,5,0 +2,607,3,4,1 +2,608,1,9,4 +2,609,2,3,2 +2,610,2,9,2 +2,611,1,6,0 +2,612,0,6,0 +2,613,1,3,2 +2,614,2,5,7 +2,615,2,4,1 +2,616,3,10,1 +2,617,2,5,3 +2,618,1,2,2 +2,619,3,1,1 +2,620,4,3,5 +2,621,6,5,2 +2,622,1,4,3 +2,623,7,5,2 +2,624,1,8,0 +2,625,0,6,2 +2,626,2,5,1 +2,627,1,7,2 +2,628,0,3,2 +2,629,4,7,2 +2,630,1,6,7 +2,631,3,7,3 +2,632,2,5,5 +2,633,1,5,3 +2,634,2,7,3 +2,635,3,5,1 +2,636,1,7,3 +2,637,2,6,3 +2,638,2,7,4 +2,639,2,10,2 +2,640,3,5,3 +2,641,1,9,2 +2,642,0,7,3 +2,643,3,6,3 +2,644,1,3,2 +2,645,1,6,0 +2,646,1,6,1 +2,647,2,8,3 +2,648,0,6,1 +2,649,1,6,2 +2,650,3,3,0 +2,651,0,3,3 +2,652,2,8,2 +2,653,0,6,0 +2,654,4,2,2 +2,655,1,9,0 +2,656,0,2,3 +2,657,0,4,5 +2,658,3,6,4 +2,659,4,4,1 +2,660,3,7,1 +2,661,2,5,2 +2,662,1,4,1 +2,663,3,8,1 +2,664,1,12,1 +2,665,0,7,0 +2,666,1,11,1 +2,667,0,5,3 +2,668,6,5,3 +2,669,3,4,3 +2,670,1,3,3 +2,671,3,6,2 +2,672,1,3,2 +2,673,4,3,5 +2,674,0,5,2 +2,675,2,6,2 +2,676,0,4,2 +2,677,3,5,4 +2,678,2,6,5 +2,679,0,6,2 +2,680,0,6,0 +2,681,2,8,0 +2,682,2,11,1 +2,683,3,5,6 +2,684,3,3,1 +2,685,5,7,2 +2,686,0,12,2 +2,687,2,7,2 +2,688,0,7,0 +2,689,3,8,1 +2,690,1,9,3 +2,691,1,3,2 +2,692,4,9,1 +2,693,1,5,2 +2,694,0,6,3 +2,695,0,6,1 +2,696,1,5,1 +2,697,1,9,1 +2,698,4,3,1 +2,699,1,8,2 +2,700,1,6,3 +2,701,1,5,2 +2,702,1,8,2 +2,703,1,9,3 +2,704,3,5,4 +2,705,1,7,1 +2,706,1,6,1 +2,707,1,5,2 +2,708,2,4,1 +2,709,1,8,1 +2,710,1,9,3 +2,711,1,1,4 +2,712,1,6,2 +2,713,2,1,2 +2,714,3,2,0 +2,715,4,9,1 +2,716,1,5,5 +2,717,2,6,4 +2,718,0,4,1 +2,719,2,3,0 +2,720,0,5,1 +2,721,1,5,0 +2,722,2,5,3 +2,723,4,3,2 +2,724,5,7,1 +2,725,1,5,5 +2,726,1,5,0 +2,727,3,8,2 +2,728,3,6,2 +2,729,5,6,2 +2,730,1,4,2 +2,731,0,9,0 +2,732,4,1,3 +2,733,1,8,2 +2,734,3,6,0 +2,735,2,5,0 +2,736,4,7,3 +2,737,1,5,0 +2,738,4,6,4 +2,739,0,7,2 +2,740,1,9,2 +2,741,2,5,4 +2,742,2,7,5 +2,743,0,13,0 +2,744,1,3,2 +2,745,0,11,2 +2,746,1,8,3 +2,747,5,1,0 +2,748,1,5,1 +2,749,2,9,1 +2,750,3,4,1 +2,751,2,6,1 +2,752,1,4,1 +2,753,1,4,1 +2,754,0,5,2 +2,755,1,4,0 +2,756,3,6,2 +2,757,3,6,1 +2,758,1,5,1 +2,759,0,9,2 +2,760,1,2,2 +2,761,3,4,7 +2,762,2,7,4 +2,763,6,5,1 +2,764,5,6,2 +2,765,0,6,0 +2,766,2,8,0 +2,767,1,3,2 +2,768,1,5,1 +2,769,0,6,1 +2,770,3,4,6 +2,771,2,4,1 +2,772,1,2,2 +2,773,1,4,1 +2,774,2,6,2 +2,775,4,9,1 +2,776,4,5,0 +2,777,3,5,0 +2,778,0,5,4 +2,779,3,7,1 +2,780,2,7,3 +2,781,3,6,3 +2,782,0,7,4 +2,783,1,6,2 +2,784,2,7,5 +2,785,1,7,4 +2,786,0,11,1 +2,787,5,6,2 +2,788,1,10,3 +2,789,0,4,1 +2,790,0,7,1 +2,791,2,4,2 +2,792,2,7,1 +2,793,2,3,2 +2,794,1,3,2 +2,795,3,6,2 +2,796,3,7,4 +2,797,3,5,2 +2,798,1,3,3 +2,799,1,5,5 +2,800,2,8,1 +2,801,1,3,1 +2,802,1,3,2 +2,803,1,4,1 +2,804,1,11,2 +2,805,0,6,1 +2,806,1,7,3 +2,807,3,3,4 +2,808,3,7,4 +2,809,2,8,4 +2,810,0,4,1 +2,811,1,7,4 +2,812,5,3,4 +2,813,3,2,1 +2,814,3,3,1 +2,815,1,7,0 +2,816,0,5,1 +2,817,5,8,1 +2,818,4,4,1 +2,819,3,6,1 +2,820,2,4,2 +2,821,1,4,2 +2,822,2,5,2 +2,823,3,4,4 +2,824,3,6,2 +2,825,1,7,3 +2,826,2,7,0 +2,827,4,2,1 +2,828,0,7,1 +2,829,3,5,3 +2,830,2,7,3 +2,831,3,9,1 +2,832,2,6,0 +2,833,2,6,6 +2,834,2,6,4 +2,835,0,6,1 +2,836,0,5,1 +2,837,0,4,2 +2,838,2,8,0 +2,839,0,4,2 +2,840,2,4,0 +2,841,2,2,3 +2,842,3,5,6 +2,843,2,6,0 +2,844,1,7,3 +2,845,2,9,3 +2,846,0,5,0 +2,847,1,3,1 +2,848,1,6,0 +2,849,5,7,2 +2,850,0,8,1 +2,851,0,8,2 +2,852,0,3,2 +2,853,1,4,3 +2,854,5,6,2 +2,855,2,6,2 +2,856,1,3,3 +2,857,3,3,2 +2,858,2,4,1 +2,859,0,5,1 +2,860,2,6,1 +2,861,0,8,2 +2,862,1,9,2 +2,863,2,4,3 +2,864,1,5,2 +2,865,0,10,1 +2,866,2,4,3 +2,867,1,6,1 +2,868,2,8,2 +2,869,3,8,1 +2,870,4,7,3 +2,871,0,5,2 +2,872,4,8,1 +2,873,6,8,0 +2,874,2,8,1 +2,875,2,7,0 +2,876,1,4,2 +2,877,3,2,0 +2,878,0,8,1 +2,879,0,3,2 +2,880,1,5,1 +2,881,1,10,1 +2,882,2,8,3 +2,883,0,7,4 +2,884,0,7,2 +2,885,2,8,3 +2,886,1,5,4 +2,887,0,5,1 +2,888,0,4,1 +2,889,3,7,0 +2,890,1,2,2 +2,891,4,9,0 +2,892,3,7,4 +2,893,1,6,3 +2,894,1,4,0 +2,895,1,3,2 +2,896,2,6,6 +2,897,4,7,3 +2,898,3,5,1 +2,899,2,8,2 +2,900,4,8,1 +2,901,2,2,3 +2,902,0,2,3 +2,903,1,4,3 +2,904,1,7,1 +2,905,3,6,1 +2,906,1,7,4 +2,907,0,10,1 +2,908,2,3,1 +2,909,1,6,1 +2,910,3,4,2 +2,911,5,7,2 +2,912,3,8,2 +2,913,3,6,4 +2,914,0,8,1 +2,915,2,6,2 +2,916,3,4,2 +2,917,2,8,2 +2,918,3,1,2 +2,919,1,7,4 +2,920,2,6,0 +2,921,1,7,3 +2,922,0,8,5 +2,923,6,4,0 +2,924,4,7,1 +2,925,3,5,1 +2,926,2,4,0 +2,927,2,2,2 +2,928,2,8,4 +2,929,1,3,1 +2,930,2,7,2 +2,931,4,3,1 +2,932,4,8,1 +2,933,2,12,0 +2,934,1,9,3 +2,935,0,8,2 +2,936,3,1,2 +2,937,5,4,2 +2,938,2,5,1 +2,939,1,4,0 +2,940,1,3,4 +2,941,3,2,0 +2,942,1,6,1 +2,943,3,1,2 +2,944,0,5,2 +2,945,5,6,2 +2,946,4,8,2 +2,947,0,10,4 +2,948,3,6,1 +2,949,1,9,3 +2,950,5,9,0 +2,951,3,7,3 +2,952,3,6,0 +2,953,1,7,2 +2,954,1,11,1 +2,955,2,3,1 +2,956,1,11,2 +2,957,2,6,4 +2,958,1,4,3 +2,959,1,6,1 +2,960,0,7,1 +2,961,0,5,3 +2,962,0,6,0 +2,963,3,11,0 +2,964,2,7,1 +2,965,2,8,2 +2,966,3,9,1 +2,967,2,10,0 +2,968,2,8,3 +2,969,1,7,3 +2,970,0,5,1 +2,971,3,8,3 +2,972,1,4,2 +2,973,2,5,0 +2,974,2,2,1 +2,975,3,5,0 +2,976,1,5,1 +2,977,2,6,3 +2,978,1,3,3 +2,979,1,5,0 +2,980,1,2,2 +2,981,2,2,3 +2,982,1,5,0 +2,983,1,4,0 +2,984,4,8,2 +2,985,2,4,0 +2,986,2,4,0 +2,987,3,8,2 +2,988,2,2,3 +2,989,3,6,4 +2,990,3,2,1 +2,991,1,3,3 +2,992,1,8,2 +2,993,3,4,2 +2,994,1,5,0 +2,995,2,6,4 +2,996,3,7,3 +2,997,4,4,3 +2,998,1,4,5 +2,999,2,3,0