From 2a8b573afdc0c68afe1f9d3d6516bfd863ac7a38 Mon Sep 17 00:00:00 2001 From: Cebere Bogdan Date: Wed, 2 Dec 2020 11:29:26 +0200 Subject: [PATCH 01/10] add power support for ckks tensor --- tenseal/binding.cpp | 16 ++++--- tenseal/cpp/tensors/ckkstensor.cpp | 26 ++++++++++- tenseal/cpp/tensors/plain_tensor.h | 9 ++++ tests/cpp/tensors/ckkstensor_test.cpp | 16 +++++++ .../tenseal/tensors/test_ckks_tensor.py | 43 +++++++++++++++++++ 5 files changed, 103 insertions(+), 7 deletions(-) diff --git a/tenseal/binding.cpp b/tenseal/binding.cpp index d915503f..91ae4317 100644 --- a/tenseal/binding.cpp +++ b/tenseal/binding.cpp @@ -452,6 +452,12 @@ PYBIND11_MODULE(_tenseal_cpp, m) { [](const shared_ptr &ctx, const std::string &data) { return CKKSTensor::Create(ctx, data); })) + .def("decrypt", + [](shared_ptr obj) { return obj->decrypt(); }) + .def("decrypt", + [](shared_ptr obj, const shared_ptr &sk) { + return obj->decrypt(sk); + }) .def("sum", &CKKSTensor::sum, py::arg("axis") = 0) .def("sum_", &CKKSTensor::sum_inplace, py::arg("axis") = 0) .def("sum_batch", &CKKSTensor::sum_batch) @@ -460,12 +466,8 @@ PYBIND11_MODULE(_tenseal_cpp, m) { .def("neg_", &CKKSTensor::negate_inplace) .def("square", &CKKSTensor::square) .def("square_", &CKKSTensor::square_inplace) - .def("decrypt", - [](shared_ptr obj) { return obj->decrypt(); }) - .def("decrypt", - [](shared_ptr obj, const shared_ptr &sk) { - return obj->decrypt(sk); - }) + .def("pow", &CKKSTensor::power) + .def("pow_", &CKKSTensor::power_inplace) .def("add", &CKKSTensor::add) .def("add_", &CKKSTensor::add_inplace) .def("sub", &CKKSTensor::sub) @@ -558,6 +560,8 @@ PYBIND11_MODULE(_tenseal_cpp, m) { .def("__isub__", py::overload_cast &>( &CKKSTensor::sub_plain_inplace)) .def("__neg__", &CKKSTensor::negate) + .def("__pow__", &CKKSTensor::power) + .def("__ipow__", &CKKSTensor::power_inplace) .def("context", [](shared_ptr obj) { return obj->tenseal_context(); }) .def("serialize", diff --git a/tenseal/cpp/tensors/ckkstensor.cpp b/tenseal/cpp/tensors/ckkstensor.cpp index 502d262d..3a96c651 100644 --- a/tenseal/cpp/tensors/ckkstensor.cpp +++ b/tenseal/cpp/tensors/ckkstensor.cpp @@ -125,7 +125,31 @@ shared_ptr CKKSTensor::square_inplace() { } shared_ptr CKKSTensor::power_inplace(unsigned int power) { - // TODO + if (power == 0) { + auto ones = PlainTensor::repeat_value(1, this->shape()); + *this = CKKSTensor(this->tenseal_context(), ones, this->_init_scale, + _batch_size.has_value()); + return shared_from_this(); + } + + if (power == 1) { + return shared_from_this(); + } + + if (power == 2) { + this->square_inplace(); + return shared_from_this(); + } + + int closest_power_of_2 = 1 << static_cast(floor(log2(power))); + power -= closest_power_of_2; + if (power == 0) { + this->power_inplace(closest_power_of_2 / 2)->square_inplace(); + } else { + auto closest_pow2_vector = this->power(closest_power_of_2); + this->power_inplace(power)->mul_inplace(closest_pow2_vector); + } + return shared_from_this(); } diff --git a/tenseal/cpp/tensors/plain_tensor.h b/tenseal/cpp/tensors/plain_tensor.h index 9fe5f336..8966de5c 100644 --- a/tenseal/cpp/tensors/plain_tensor.h +++ b/tenseal/cpp/tensors/plain_tensor.h @@ -318,6 +318,15 @@ class PlainTensor { _shape = {_data.size()}; } + static PlainTensor repeat_value(plain_t value, + vector shape) { + size_t size = 1; + for (auto& dim : shape) size *= dim; + + vector repeated(size, value); + return PlainTensor(repeated, shape); + } + private: vector _data; vector _shape; diff --git a/tests/cpp/tensors/ckkstensor_test.cpp b/tests/cpp/tensors/ckkstensor_test.cpp index 4cb3116d..6b489d58 100644 --- a/tests/cpp/tensors/ckkstensor_test.cpp +++ b/tests/cpp/tensors/ckkstensor_test.cpp @@ -152,6 +152,22 @@ TEST_F(CKKSTensorTest, TestCKKSSumBatching) { ASSERT_TRUE(are_close(decr.data(), {6, 15})); } +TEST_F(CKKSTensorTest, TestCKKSPower) { + auto ctx = + TenSEALContext::Create(scheme_type::ckks, 8192, -1, {60, 40, 40, 60}); + ASSERT_TRUE(ctx != nullptr); + ctx->generate_galois_keys(); + + auto data = + PlainTensor(vector({1, 2, 3, 4, 5, 6}), vector({2, 3})); + auto l = CKKSTensor::Create(ctx, data, std::pow(2, 40), true); + + auto res = l->power(2); + ASSERT_THAT(res->shape(), ElementsAreArray({2, 3})); + auto decr = res->decrypt(); + ASSERT_TRUE(are_close(decr.data(), {1, 4, 9, 16, 25, 36})); +} + TEST_F(CKKSTensorTest, TestCreateCKKSFail) { auto ctx = TenSEALContext::Create(scheme_type::ckks, 8192, -1, {60, 40, 40, 60}); diff --git a/tests/python/tenseal/tensors/test_ckks_tensor.py b/tests/python/tenseal/tensors/test_ckks_tensor.py index b165c9f5..f714eac6 100644 --- a/tests/python/tenseal/tensors/test_ckks_tensor.py +++ b/tests/python/tenseal/tensors/test_ckks_tensor.py @@ -318,3 +318,46 @@ def test_add_sub_mul_scalar(context, shape, op): right_result = np.array(ts.tolist(right.decrypt())) assert right_result.shape == expected_result.shape assert np.allclose(right_result, expected_result, rtol=0, atol=0.01) + + +@pytest.mark.parametrize( + "plain,power", + [ + (ts.plain_tensor([6]), 2), + (ts.plain_tensor([1, 2, 3]), 4), + (ts.plain_tensor([1, 2, 3, 4], [2, 2]), 6), + ], +) +def test_power(context, plain, power, precision): + context = ts.context(ts.SCHEME_TYPE.CKKS, 16384, coeff_mod_bit_sizes=[60, 40, 40, 40, 40, 60]) + context.global_scale = pow(2, 40) + + tensor = ts.ckks_tensor(context, plain) + expected = np.array([np.power(v, power) for v in plain.data()]).reshape(plain.shape()).tolist() + + new_tensor = tensor ** power + decrypted_result = ts.tolist(new_tensor.decrypt()) + assert _almost_equal(decrypted_result, expected, precision), "Decryption of tensor is incorrect" + assert _almost_equal( + ts.tolist(tensor.decrypt()), ts.tolist(plain), precision + ), "Something went wrong in memory." + + +@pytest.mark.parametrize( + "plain,power", + [ + (ts.plain_tensor([6]), 2), + (ts.plain_tensor([1, 2, 3]), 4), + (ts.plain_tensor([1, 2, 3, 4], [2, 2]), 6), + ], +) +def test_power_inplace(context, plain, power, precision): + context = ts.context(ts.SCHEME_TYPE.CKKS, 16384, coeff_mod_bit_sizes=[60, 40, 40, 40, 40, 60]) + context.global_scale = pow(2, 40) + + tensor = ts.ckks_tensor(context, plain) + expected = np.array([np.power(v, power) for v in plain.data()]).reshape(plain.shape()).tolist() + + tensor **= power + decrypted_result = ts.tolist(tensor.decrypt()) + assert _almost_equal(decrypted_result, expected, precision), "Decryption of tensor is incorrect" From 16fcb3f6ebbbbec121ef20f94253f2aa6f612a2b Mon Sep 17 00:00:00 2001 From: Cebere Bogdan Date: Wed, 2 Dec 2020 12:18:47 +0200 Subject: [PATCH 02/10] add polyval for ckktensor --- tenseal/binding.cpp | 2 + tenseal/cpp/tensors/ckkstensor.cpp | 47 ++++++++++++++- tests/cpp/tensors/ckkstensor_test.cpp | 16 ++++++ .../tenseal/tensors/test_ckks_tensor.py | 57 +++++++++++++++++++ 4 files changed, 121 insertions(+), 1 deletion(-) diff --git a/tenseal/binding.cpp b/tenseal/binding.cpp index 91ae4317..d97baa9d 100644 --- a/tenseal/binding.cpp +++ b/tenseal/binding.cpp @@ -498,6 +498,8 @@ PYBIND11_MODULE(_tenseal_cpp, m) { py::overload_cast(&CKKSTensor::mul_plain_inplace)) .def("mul_plain_", py::overload_cast &>( &CKKSTensor::mul_plain_inplace)) + .def("polyval", &CKKSTensor::polyval) + .def("polyval_", &CKKSTensor::polyval_inplace) // python arithmetic .def("__add__", &CKKSTensor::add) .def("__add__", py::overload_cast( diff --git a/tenseal/cpp/tensors/ckkstensor.cpp b/tenseal/cpp/tensors/ckkstensor.cpp index 3a96c651..e1f9bfc7 100644 --- a/tenseal/cpp/tensors/ckkstensor.cpp +++ b/tenseal/cpp/tensors/ckkstensor.cpp @@ -444,7 +444,52 @@ shared_ptr CKKSTensor::sum_batch_inplace() { shared_ptr CKKSTensor::polyval_inplace( const vector& coefficients) { - // TODO + if (coefficients.size() == 0) { + throw invalid_argument( + "the coefficients vector need to have at least one element"); + } + + int degree = static_cast(coefficients.size()) - 1; + while (degree >= 0) { + if (coefficients[degree] == 0.0) + degree--; + else + break; + } + + if (degree == -1) { + auto zeros = PlainTensor::repeat_value(0, this->shape()); + *this = CKKSTensor(this->tenseal_context(), zeros, this->_init_scale, + _batch_size.has_value()); + return shared_from_this(); + } + + // pre-compute squares of x + auto x = this->copy(); + + int max_square = static_cast(floor(log2(degree))); + vector> x_squares; + x_squares.reserve(max_square + 1); + x_squares.push_back(x->copy()); // x + for (int i = 1; i <= max_square; i++) { + x->square_inplace(); + x_squares.push_back(x->copy()); // x^(2^i) + } + + auto cst_coeff = + PlainTensor::repeat_value(coefficients[0], this->shape()); + auto result = + CKKSTensor::Create(this->tenseal_context(), cst_coeff, + this->_init_scale, _batch_size.has_value()); + + // coefficients[1] * x + ... + coefficients[degree] * x^(degree) + for (int i = 1; i <= degree; i++) { + if (coefficients[i] == 0.0) continue; + x = compute_polynomial_term(i, coefficients[i], x_squares); + result->add_inplace(x); + } + + this->_data = result->data(); return shared_from_this(); } diff --git a/tests/cpp/tensors/ckkstensor_test.cpp b/tests/cpp/tensors/ckkstensor_test.cpp index 6b489d58..5deac4e9 100644 --- a/tests/cpp/tensors/ckkstensor_test.cpp +++ b/tests/cpp/tensors/ckkstensor_test.cpp @@ -13,6 +13,7 @@ bool are_close(const std::vector& l, const std::vector& r) { return false; } for (size_t idx = 0; idx < l.size(); ++idx) { + fprintf(stderr, "data %lf %ld \n", l[idx], r[idx]); if (std::abs(l[idx] - r[idx]) > 0.5) return false; } return true; @@ -168,6 +169,21 @@ TEST_F(CKKSTensorTest, TestCKKSPower) { ASSERT_TRUE(are_close(decr.data(), {1, 4, 9, 16, 25, 36})); } +TEST_F(CKKSTensorTest, TestCKKSTensorPolyval) { + auto ctx = + TenSEALContext::Create(scheme_type::ckks, 8192, -1, {60, 40, 40, 60}); + ASSERT_TRUE(ctx != nullptr); + + auto data = + PlainTensor(vector({1, 2, 3, 4, 5, 6}), vector({2, 3})); + auto l = CKKSTensor::Create(ctx, data, std::pow(2, 40), true); + + auto res = l->polyval({1, 1, 1}); + ASSERT_THAT(res->shape(), ElementsAreArray({2, 3})); + auto decr = res->decrypt(); + ASSERT_TRUE(are_close(decr.data(), {3, 7, 13, 21, 31, 43})); +} + TEST_F(CKKSTensorTest, TestCreateCKKSFail) { auto ctx = TenSEALContext::Create(scheme_type::ckks, 8192, -1, {60, 40, 40, 60}); diff --git a/tests/python/tenseal/tensors/test_ckks_tensor.py b/tests/python/tenseal/tensors/test_ckks_tensor.py index f714eac6..8f2d4f34 100644 --- a/tests/python/tenseal/tensors/test_ckks_tensor.py +++ b/tests/python/tenseal/tensors/test_ckks_tensor.py @@ -323,6 +323,7 @@ def test_add_sub_mul_scalar(context, shape, op): @pytest.mark.parametrize( "plain,power", [ + (ts.plain_tensor([6]), 0), (ts.plain_tensor([6]), 2), (ts.plain_tensor([1, 2, 3]), 4), (ts.plain_tensor([1, 2, 3, 4], [2, 2]), 6), @@ -349,6 +350,7 @@ def test_power(context, plain, power, precision): (ts.plain_tensor([6]), 2), (ts.plain_tensor([1, 2, 3]), 4), (ts.plain_tensor([1, 2, 3, 4], [2, 2]), 6), + (ts.plain_tensor([1, 2, 3, 4], [2, 2]), 0), ], ) def test_power_inplace(context, plain, power, precision): @@ -361,3 +363,58 @@ def test_power_inplace(context, plain, power, precision): tensor **= power decrypted_result = ts.tolist(tensor.decrypt()) assert _almost_equal(decrypted_result, expected, precision), "Decryption of tensor is incorrect" + + +@pytest.mark.parametrize( + "data, polynom", + [ + (ts.plain_tensor([1, 2, 3]), [0, 0, 0,]), + (ts.plain_tensor([1, 2, 3, 4], [2, 2]), [1, 1]), + (ts.plain_tensor([1, 2, 3, 4], [2, 2]), [1, 1, 1]), + (ts.plain_tensor([1, 2, 3, 4, 5, 6], [2, 3]), [3, 2, 4, 5]), + ], +) +def test_polynomial(context, data, polynom): + ct = ts.ckks_tensor(context, data) + expected = ( + np.array([np.polyval(polynom[::-1], x) for x in data.data()]).reshape(data.shape()).tolist() + ) + result = ct.polyval(polynom) + + if len(polynom) >= 13: + precision = -1 + else: + precision = 1 + + decrypted_result = ts.tolist(result.decrypt()) + assert _almost_equal( + decrypted_result, expected, precision + ), "Polynomial evaluation is incorrect." + + +@pytest.mark.parametrize( + "data, polynom", + [(ts.plain_tensor([1, 2, 3, 4]), [0, 1, 1]), (ts.plain_tensor([1, 2, 3, 4]), [0, 1, 0, 1]),], +) +def test_polynomial_modswitch_off(context, data, polynom): + context = ts.context(ts.SCHEME_TYPE.CKKS, 8192, 0, [60, 40, 40, 60]) + context.global_scale = 2 ** 40 + context.auto_mod_switch = False + + ct = ts.ckks_tensor(context, data) + with pytest.raises(ValueError) as e: + result = ct.polyval(polynom) + + +@pytest.mark.parametrize( + "data, polynom", + [(ts.plain_tensor([1, 2, 3, 4]), [0, 1, 1]), (ts.plain_tensor([1, 2, 3, 4]), [0, 1, 0, 1]),], +) +def test_polynomial_rescale_off(context, data, polynom): + context = ts.context(ts.SCHEME_TYPE.CKKS, 8192, 0, [60, 40, 40, 60]) + context.global_scale = 2 ** 40 + context.auto_rescale = False + + ct = ts.ckks_tensor(context, data) + with pytest.raises(ValueError) as e: + result = ct.polyval(polynom) From adcf38f0a7c0934b99c8866c14a3c7bc65fa28d8 Mon Sep 17 00:00:00 2001 From: Cebere Bogdan Date: Wed, 2 Dec 2020 14:11:00 +0200 Subject: [PATCH 03/10] ckkstensor::dot product --- tenseal/binding.cpp | 10 ++ tenseal/cpp/tensors/ckkstensor.cpp | 42 +++++-- tests/cpp/tensors/ckkstensor_test.cpp | 8 ++ .../tenseal/tensors/test_ckks_tensor.py | 118 ++++++++++++++++++ 4 files changed, 165 insertions(+), 13 deletions(-) diff --git a/tenseal/binding.cpp b/tenseal/binding.cpp index d97baa9d..81d0fc27 100644 --- a/tenseal/binding.cpp +++ b/tenseal/binding.cpp @@ -500,6 +500,16 @@ PYBIND11_MODULE(_tenseal_cpp, m) { &CKKSTensor::mul_plain_inplace)) .def("polyval", &CKKSTensor::polyval) .def("polyval_", &CKKSTensor::polyval_inplace) + .def("dot", &CKKSTensor::dot_product) + .def("dot", + [](shared_ptr obj, const PlainTensor &other) { + return obj->dot_product_plain(other); + }) + .def("dot_", &CKKSTensor::dot_product_inplace) + .def("dot_", + [](shared_ptr obj, const PlainTensor &other) { + return obj->dot_product_plain_inplace(other); + }) // python arithmetic .def("__add__", &CKKSTensor::add) .def("__add__", py::overload_cast( diff --git a/tenseal/cpp/tensors/ckkstensor.cpp b/tenseal/cpp/tensors/ckkstensor.cpp index e1f9bfc7..47a1d527 100644 --- a/tenseal/cpp/tensors/ckkstensor.cpp +++ b/tenseal/cpp/tensors/ckkstensor.cpp @@ -184,8 +184,18 @@ void CKKSTensor::perform_plain_op(seal::Ciphertext& ct, seal::Plaintext other, this->tenseal_context()->evaluator->sub_plain_inplace(ct, other); break; case OP::MUL: - this->tenseal_context()->evaluator->multiply_plain_inplace(ct, - other); + try { + this->tenseal_context()->evaluator->multiply_plain_inplace( + ct, other); + } catch (const std::logic_error& e) { + if (strcmp(e.what(), "result ciphertext is transparent") == 0) { + // replace by encryption of zero + this->tenseal_context()->encryptor->encrypt_zero(ct); + ct.scale() = this->_init_scale; + } else { // Something else, need to be forwarded + throw; + } + } this->auto_relin(ct); this->auto_rescale(ct); break; @@ -223,16 +233,16 @@ shared_ptr CKKSTensor::op_inplace( std::min((i + 1) * batch_size, this->_data.size()))); } // waiting - std::optional fail; + optional fail; for (size_t i = 0; i < futures.size(); i++) { try { futures[i].get(); } catch (std::exception& e) { - fail = e; + fail = e.what(); } } - if (fail) throw invalid_argument(fail.value().what()); + if (fail) throw invalid_argument(fail.value()); } return shared_from_this(); @@ -272,16 +282,18 @@ shared_ptr CKKSTensor::op_plain_inplace( std::min((i + 1) * batch_size, this->_data.size()))); } // waiting - std::optional fail; + optional fail; for (size_t i = 0; i < futures.size(); i++) { try { futures[i].get(); } catch (std::exception& e) { - fail = e; + fail = e.what(); } } - if (fail) throw invalid_argument(fail.value().what()); + if (fail) { + throw invalid_argument(fail.value()); + } } return shared_from_this(); @@ -313,16 +325,16 @@ shared_ptr CKKSTensor::op_plain_inplace(const double& operand, std::min((i + 1) * batch_size, this->_data.size()))); } // waiting - std::optional fail; + std::optional fail; for (size_t i = 0; i < futures.size(); i++) { try { futures[i].get(); } catch (std::exception& e) { - fail = e; + fail = e.what(); } } - if (fail) throw invalid_argument(fail.value().what()); + if (fail) throw invalid_argument(fail.value()); } return shared_from_this(); @@ -345,7 +357,8 @@ shared_ptr CKKSTensor::mul_inplace( shared_ptr CKKSTensor::dot_product_inplace( const shared_ptr& to_mul) { - // TODO + this->mul_inplace(to_mul); + for (auto& dim : _shape) this->sum_inplace(); return shared_from_this(); } @@ -366,7 +379,8 @@ shared_ptr CKKSTensor::mul_plain_inplace( shared_ptr CKKSTensor::dot_product_plain_inplace( const PlainTensor& to_mul) { - // TODO + this->mul_plain_inplace(to_mul); + for (auto& dim : _shape) this->sum_inplace(); return shared_from_this(); } @@ -426,6 +440,8 @@ shared_ptr CKKSTensor::sum_inplace(size_t axis) { // reinsert the batched axis new_shape.insert(new_shape.begin(), *_batch_size); } + if (new_shape.size() == 0) new_shape = {1}; + _data = new_data; _shape = new_shape; return shared_from_this(); diff --git a/tests/cpp/tensors/ckkstensor_test.cpp b/tests/cpp/tensors/ckkstensor_test.cpp index 5deac4e9..703c2e5b 100644 --- a/tests/cpp/tensors/ckkstensor_test.cpp +++ b/tests/cpp/tensors/ckkstensor_test.cpp @@ -122,6 +122,14 @@ TEST_F(CKKSTensorTest, TestCKKSSumNoBatching) { decr = l->decrypt(); ASSERT_TRUE(are_close(decr.data(), {6, 15})); + data = PlainTensor(vector({1, 2, 3, 4, 5, 6}), vector({6})); + l = CKKSTensor::Create(ctx, data, std::pow(2, 40), false); + + l->sum_inplace(); + ASSERT_THAT(l->shape(), ElementsAreArray({1})); + decr = l->decrypt(); + ASSERT_TRUE(are_close(decr.data(), {21})); + data = PlainTensor(vector({1, 2, 3, 4, 5, 6, 7, 8}), vector({2, 2, 2})); l = CKKSTensor::Create(ctx, data, std::pow(2, 40), false); diff --git a/tests/python/tenseal/tensors/test_ckks_tensor.py b/tests/python/tenseal/tensors/test_ckks_tensor.py index 8f2d4f34..a49f3dd5 100644 --- a/tests/python/tenseal/tensors/test_ckks_tensor.py +++ b/tests/python/tenseal/tensors/test_ckks_tensor.py @@ -418,3 +418,121 @@ def test_polynomial_rescale_off(context, data, polynom): ct = ts.ckks_tensor(context, data) with pytest.raises(ValueError) as e: result = ct.polyval(polynom) + + +@pytest.mark.parametrize( + "t1, t2", + [ + (ts.plain_tensor([0]), ts.plain_tensor([0])), + (ts.plain_tensor([1, 2, 3, 4], [2, 2]), ts.plain_tensor([6, 7, 8, 9], [2, 2])), + ( + ts.plain_tensor([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2]), + ts.plain_tensor([6, 7, 8, 9, 10, 11, 12, 13], [2, 2, 2]), + ), + ], +) +def test_dot_product(context, t1, t2, precision): + context.generate_galois_keys() + tensor1 = ts.ckks_tensor(context, t1) + tensor2 = ts.ckks_tensor(context, t2) + + result = tensor1.dot(tensor2) + + expected = [sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())])] + + # Decryption + decrypted_result = ts.tolist(result.decrypt()) + assert _almost_equal( + decrypted_result, expected, precision + ), "Dot product of tensors is incorrect." + assert _almost_equal( + ts.tolist(tensor1.decrypt()), ts.tolist(t1), precision + ), "Something went wrong in memory." + assert _almost_equal( + ts.tolist(tensor2.decrypt()), ts.tolist(t2), precision + ), "Something went wrong in memory." + + +@pytest.mark.parametrize( + "t1, t2", + [ + (ts.plain_tensor([0]), ts.plain_tensor([0])), + (ts.plain_tensor([1, 2, 3, 4], [2, 2]), ts.plain_tensor([6, 7, 8, 9], [2, 2])), + ( + ts.plain_tensor([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2]), + ts.plain_tensor([6, 7, 8, 9, 10, 11, 12, 13], [2, 2, 2]), + ), + ], +) +def test_dot_product_inplace(context, t1, t2, precision): + context.generate_galois_keys() + tensor1 = ts.ckks_tensor(context, t1) + tensor2 = ts.ckks_tensor(context, t2) + + tensor1.dot_(tensor2) + + expected = [sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())])] + + # Decryption + decrypted_result = ts.tolist(tensor1.decrypt()) + assert _almost_equal( + decrypted_result, expected, precision + ), "Dot product of tensors is incorrect." + assert _almost_equal( + ts.tolist(tensor2.decrypt()), ts.tolist(t2), precision + ), "Something went wrong in memory." + + +@pytest.mark.parametrize( + "t1, t2", + [ + (ts.plain_tensor([0]), ts.plain_tensor([0])), + (ts.plain_tensor([1, 2, 3, 4], [2, 2]), ts.plain_tensor([6, 7, 8, 9], [2, 2])), + ( + ts.plain_tensor([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2]), + ts.plain_tensor([6, 7, 8, 9, 10, 11, 12, 13], [2, 2, 2]), + ), + ], +) +def test_dot_product_plain(context, t1, t2, precision): + context.generate_galois_keys() + tensor1 = ts.ckks_tensor(context, t1) + + result = tensor1.dot(t2) + + expected = [sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())])] + + # Decryption + decrypted_result = ts.tolist(result.decrypt()) + assert _almost_equal( + decrypted_result, expected, precision + ), "Dot product of tensors is incorrect." + assert _almost_equal( + ts.tolist(tensor1.decrypt()), ts.tolist(t1), precision + ), "Something went wrong in memory." + + +@pytest.mark.parametrize( + "t1, t2", + [ + (ts.plain_tensor([0]), ts.plain_tensor([0])), + (ts.plain_tensor([1, 2, 3, 4], [2, 2]), ts.plain_tensor([6, 7, 8, 9], [2, 2])), + ( + ts.plain_tensor([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2]), + ts.plain_tensor([6, 7, 8, 9, 10, 11, 12, 13], [2, 2, 2]), + ), + ], +) +def test_dot_product_plain_inplace(context, t1, t2, precision): + context.generate_galois_keys() + tensor1 = ts.ckks_tensor(context, t1) + + tensor1.dot_(t2) + + expected = [sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())])] + + # Decryption + decrypted_result = ts.tolist(tensor1.decrypt()) + assert _almost_equal( + decrypted_result, expected, precision + ), "Dot product of tensors is incorrect." From c2bb22f676a747c1f7d29cf91245dedd8616299b Mon Sep 17 00:00:00 2001 From: Cebere Bogdan Date: Wed, 2 Dec 2020 14:12:49 +0200 Subject: [PATCH 04/10] cleanup --- tests/cpp/tensors/ckkstensor_test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/cpp/tensors/ckkstensor_test.cpp b/tests/cpp/tensors/ckkstensor_test.cpp index 703c2e5b..83a90864 100644 --- a/tests/cpp/tensors/ckkstensor_test.cpp +++ b/tests/cpp/tensors/ckkstensor_test.cpp @@ -13,7 +13,6 @@ bool are_close(const std::vector& l, const std::vector& r) { return false; } for (size_t idx = 0; idx < l.size(); ++idx) { - fprintf(stderr, "data %lf %ld \n", l[idx], r[idx]); if (std::abs(l[idx] - r[idx]) > 0.5) return false; } return true; From 0ee2bfabaa1c14fb84aa2abfce56b5316cfb20d8 Mon Sep 17 00:00:00 2001 From: Cebere Bogdan Date: Wed, 2 Dec 2020 14:31:56 +0200 Subject: [PATCH 05/10] cleanup --- tenseal/cpp/tensors/ckkstensor.cpp | 1 - tests/cpp/tensors/ckkstensor_test.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tenseal/cpp/tensors/ckkstensor.cpp b/tenseal/cpp/tensors/ckkstensor.cpp index 47a1d527..88a2b15f 100644 --- a/tenseal/cpp/tensors/ckkstensor.cpp +++ b/tenseal/cpp/tensors/ckkstensor.cpp @@ -440,7 +440,6 @@ shared_ptr CKKSTensor::sum_inplace(size_t axis) { // reinsert the batched axis new_shape.insert(new_shape.begin(), *_batch_size); } - if (new_shape.size() == 0) new_shape = {1}; _data = new_data; _shape = new_shape; diff --git a/tests/cpp/tensors/ckkstensor_test.cpp b/tests/cpp/tensors/ckkstensor_test.cpp index 83a90864..604eadb5 100644 --- a/tests/cpp/tensors/ckkstensor_test.cpp +++ b/tests/cpp/tensors/ckkstensor_test.cpp @@ -125,7 +125,7 @@ TEST_F(CKKSTensorTest, TestCKKSSumNoBatching) { l = CKKSTensor::Create(ctx, data, std::pow(2, 40), false); l->sum_inplace(); - ASSERT_THAT(l->shape(), ElementsAreArray({1})); + ASSERT_THAT(l->shape(), ElementsAreArray({})); decr = l->decrypt(); ASSERT_TRUE(are_close(decr.data(), {21})); From 32b450e262959fd315d952527de723f035a3c0ed Mon Sep 17 00:00:00 2001 From: Cebere Bogdan Date: Wed, 2 Dec 2020 14:39:05 +0200 Subject: [PATCH 06/10] bbreak --- tests/cpp/tensors/ckkstensor_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cpp/tensors/ckkstensor_test.cpp b/tests/cpp/tensors/ckkstensor_test.cpp index 604eadb5..b3866e22 100644 --- a/tests/cpp/tensors/ckkstensor_test.cpp +++ b/tests/cpp/tensors/ckkstensor_test.cpp @@ -125,7 +125,7 @@ TEST_F(CKKSTensorTest, TestCKKSSumNoBatching) { l = CKKSTensor::Create(ctx, data, std::pow(2, 40), false); l->sum_inplace(); - ASSERT_THAT(l->shape(), ElementsAreArray({})); + ASSERT_THAT(l->shape(), ElementsAreArray(vector({}))); decr = l->decrypt(); ASSERT_TRUE(are_close(decr.data(), {21})); From 45fa9757e35da7e2631d7f2d4a255fb8943edddf Mon Sep 17 00:00:00 2001 From: Cebere Bogdan Date: Wed, 2 Dec 2020 14:55:46 +0200 Subject: [PATCH 07/10] dot product updates --- tests/python/tenseal/tensors/test_ckks_tensor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/python/tenseal/tensors/test_ckks_tensor.py b/tests/python/tenseal/tensors/test_ckks_tensor.py index a49f3dd5..0818f3da 100644 --- a/tests/python/tenseal/tensors/test_ckks_tensor.py +++ b/tests/python/tenseal/tensors/test_ckks_tensor.py @@ -438,7 +438,7 @@ def test_dot_product(context, t1, t2, precision): result = tensor1.dot(tensor2) - expected = [sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())])] + expected = sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())]) # Decryption decrypted_result = ts.tolist(result.decrypt()) @@ -471,7 +471,7 @@ def test_dot_product_inplace(context, t1, t2, precision): tensor1.dot_(tensor2) - expected = [sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())])] + expected = sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())]) # Decryption decrypted_result = ts.tolist(tensor1.decrypt()) @@ -500,7 +500,7 @@ def test_dot_product_plain(context, t1, t2, precision): result = tensor1.dot(t2) - expected = [sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())])] + expected = sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())]) # Decryption decrypted_result = ts.tolist(result.decrypt()) @@ -529,7 +529,7 @@ def test_dot_product_plain_inplace(context, t1, t2, precision): tensor1.dot_(t2) - expected = [sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())])] + expected = sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())]) # Decryption decrypted_result = ts.tolist(tensor1.decrypt()) From d3830283bef027730143c524d66b48c12ab4bdbf Mon Sep 17 00:00:00 2001 From: Cebere Bogdan Date: Thu, 3 Dec 2020 22:02:04 +0200 Subject: [PATCH 08/10] drop dot product --- tenseal/binding.cpp | 20 +-- tenseal/cpp/tensors/ckkstensor.cpp | 6 +- .../tenseal/tensors/test_ckks_tensor.py | 118 ------------------ 3 files changed, 4 insertions(+), 140 deletions(-) diff --git a/tenseal/binding.cpp b/tenseal/binding.cpp index 81d0fc27..7b427fe8 100644 --- a/tenseal/binding.cpp +++ b/tenseal/binding.cpp @@ -272,15 +272,9 @@ PYBIND11_MODULE(_tenseal_cpp, m) { // because dot doesn't have a magic function like __add__ // we prefer to overload it instead of having dot_plain functions .def("dot", &CKKSVector::dot_product) - .def("dot", - [](shared_ptr obj, const vector &other) { - return obj->dot_product_plain(other); - }) + .def("dot", &CKKSVector::dot_product_plain) .def("dot_", &CKKSVector::dot_product_inplace) - .def("dot_", - [](shared_ptr obj, const vector &other) { - return obj->dot_product_plain_inplace(other); - }) + .def("dot_", &CKKSVector::dot_product_plain_inplace) .def("sum", &CKKSVector::sum, py::arg("axis") = 0) .def("sum_", &CKKSVector::sum_inplace, py::arg("axis") = 0) .def( @@ -500,16 +494,6 @@ PYBIND11_MODULE(_tenseal_cpp, m) { &CKKSTensor::mul_plain_inplace)) .def("polyval", &CKKSTensor::polyval) .def("polyval_", &CKKSTensor::polyval_inplace) - .def("dot", &CKKSTensor::dot_product) - .def("dot", - [](shared_ptr obj, const PlainTensor &other) { - return obj->dot_product_plain(other); - }) - .def("dot_", &CKKSTensor::dot_product_inplace) - .def("dot_", - [](shared_ptr obj, const PlainTensor &other) { - return obj->dot_product_plain_inplace(other); - }) // python arithmetic .def("__add__", &CKKSTensor::add) .def("__add__", py::overload_cast( diff --git a/tenseal/cpp/tensors/ckkstensor.cpp b/tenseal/cpp/tensors/ckkstensor.cpp index 88a2b15f..6aa5931b 100644 --- a/tenseal/cpp/tensors/ckkstensor.cpp +++ b/tenseal/cpp/tensors/ckkstensor.cpp @@ -357,8 +357,7 @@ shared_ptr CKKSTensor::mul_inplace( shared_ptr CKKSTensor::dot_product_inplace( const shared_ptr& to_mul) { - this->mul_inplace(to_mul); - for (auto& dim : _shape) this->sum_inplace(); + // TODO return shared_from_this(); } @@ -379,8 +378,7 @@ shared_ptr CKKSTensor::mul_plain_inplace( shared_ptr CKKSTensor::dot_product_plain_inplace( const PlainTensor& to_mul) { - this->mul_plain_inplace(to_mul); - for (auto& dim : _shape) this->sum_inplace(); + // TODO return shared_from_this(); } diff --git a/tests/python/tenseal/tensors/test_ckks_tensor.py b/tests/python/tenseal/tensors/test_ckks_tensor.py index 0818f3da..8f2d4f34 100644 --- a/tests/python/tenseal/tensors/test_ckks_tensor.py +++ b/tests/python/tenseal/tensors/test_ckks_tensor.py @@ -418,121 +418,3 @@ def test_polynomial_rescale_off(context, data, polynom): ct = ts.ckks_tensor(context, data) with pytest.raises(ValueError) as e: result = ct.polyval(polynom) - - -@pytest.mark.parametrize( - "t1, t2", - [ - (ts.plain_tensor([0]), ts.plain_tensor([0])), - (ts.plain_tensor([1, 2, 3, 4], [2, 2]), ts.plain_tensor([6, 7, 8, 9], [2, 2])), - ( - ts.plain_tensor([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2]), - ts.plain_tensor([6, 7, 8, 9, 10, 11, 12, 13], [2, 2, 2]), - ), - ], -) -def test_dot_product(context, t1, t2, precision): - context.generate_galois_keys() - tensor1 = ts.ckks_tensor(context, t1) - tensor2 = ts.ckks_tensor(context, t2) - - result = tensor1.dot(tensor2) - - expected = sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())]) - - # Decryption - decrypted_result = ts.tolist(result.decrypt()) - assert _almost_equal( - decrypted_result, expected, precision - ), "Dot product of tensors is incorrect." - assert _almost_equal( - ts.tolist(tensor1.decrypt()), ts.tolist(t1), precision - ), "Something went wrong in memory." - assert _almost_equal( - ts.tolist(tensor2.decrypt()), ts.tolist(t2), precision - ), "Something went wrong in memory." - - -@pytest.mark.parametrize( - "t1, t2", - [ - (ts.plain_tensor([0]), ts.plain_tensor([0])), - (ts.plain_tensor([1, 2, 3, 4], [2, 2]), ts.plain_tensor([6, 7, 8, 9], [2, 2])), - ( - ts.plain_tensor([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2]), - ts.plain_tensor([6, 7, 8, 9, 10, 11, 12, 13], [2, 2, 2]), - ), - ], -) -def test_dot_product_inplace(context, t1, t2, precision): - context.generate_galois_keys() - tensor1 = ts.ckks_tensor(context, t1) - tensor2 = ts.ckks_tensor(context, t2) - - tensor1.dot_(tensor2) - - expected = sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())]) - - # Decryption - decrypted_result = ts.tolist(tensor1.decrypt()) - assert _almost_equal( - decrypted_result, expected, precision - ), "Dot product of tensors is incorrect." - assert _almost_equal( - ts.tolist(tensor2.decrypt()), ts.tolist(t2), precision - ), "Something went wrong in memory." - - -@pytest.mark.parametrize( - "t1, t2", - [ - (ts.plain_tensor([0]), ts.plain_tensor([0])), - (ts.plain_tensor([1, 2, 3, 4], [2, 2]), ts.plain_tensor([6, 7, 8, 9], [2, 2])), - ( - ts.plain_tensor([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2]), - ts.plain_tensor([6, 7, 8, 9, 10, 11, 12, 13], [2, 2, 2]), - ), - ], -) -def test_dot_product_plain(context, t1, t2, precision): - context.generate_galois_keys() - tensor1 = ts.ckks_tensor(context, t1) - - result = tensor1.dot(t2) - - expected = sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())]) - - # Decryption - decrypted_result = ts.tolist(result.decrypt()) - assert _almost_equal( - decrypted_result, expected, precision - ), "Dot product of tensors is incorrect." - assert _almost_equal( - ts.tolist(tensor1.decrypt()), ts.tolist(t1), precision - ), "Something went wrong in memory." - - -@pytest.mark.parametrize( - "t1, t2", - [ - (ts.plain_tensor([0]), ts.plain_tensor([0])), - (ts.plain_tensor([1, 2, 3, 4], [2, 2]), ts.plain_tensor([6, 7, 8, 9], [2, 2])), - ( - ts.plain_tensor([1, 2, 3, 4, 5, 6, 7, 8], [2, 2, 2]), - ts.plain_tensor([6, 7, 8, 9, 10, 11, 12, 13], [2, 2, 2]), - ), - ], -) -def test_dot_product_plain_inplace(context, t1, t2, precision): - context.generate_galois_keys() - tensor1 = ts.ckks_tensor(context, t1) - - tensor1.dot_(t2) - - expected = sum([v1 * v2 for v1, v2 in zip(t1.data(), t2.data())]) - - # Decryption - decrypted_result = ts.tolist(tensor1.decrypt()) - assert _almost_equal( - decrypted_result, expected, precision - ), "Dot product of tensors is incorrect." From e07f7a8f96ee90e3fbf1bf8e35ffe4f00f8f0b20 Mon Sep 17 00:00:00 2001 From: Cebere Bogdan Date: Thu, 3 Dec 2020 22:57:00 +0200 Subject: [PATCH 09/10] handle SEAL re-release --- tenseal/deps.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tenseal/deps.bzl b/tenseal/deps.bzl index d446463c..af76197d 100644 --- a/tenseal/deps.bzl +++ b/tenseal/deps.bzl @@ -27,7 +27,7 @@ def tenseal_deps(): http_archive( name = "com_microsoft_seal", build_file = "//third_party:seal.BUILD", - sha256 = "7751b57c0c66c1e81bb25cdddeaca6340e4475e11ab04faa27f3e0dc7526c236", + sha256 = "79c0e45bf301f4577a7633b14e8b26e37eefc89fd4f6a29d13f87e5f22a372ad", strip_prefix = "SEAL-3.6.0", urls = ["https://github.com/microsoft/SEAL/archive/v3.6.0.tar.gz"], ) From 2658b0f91f6aa0785697e34d507d8039c3d7baae Mon Sep 17 00:00:00 2001 From: Cebere Bogdan Date: Thu, 3 Dec 2020 23:15:20 +0200 Subject: [PATCH 10/10] update dot tests --- tests/python/tenseal/tensors/test_ckks_vector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/python/tenseal/tensors/test_ckks_vector.py b/tests/python/tenseal/tensors/test_ckks_vector.py index a9241f27..e1b22c2e 100644 --- a/tests/python/tenseal/tensors/test_ckks_vector.py +++ b/tests/python/tenseal/tensors/test_ckks_vector.py @@ -797,7 +797,7 @@ def test_dot_product_inplace(context, vec1, vec2, precision): def test_dot_product_plain(context, vec1, vec2, precision): context.generate_galois_keys() first_vec = ts.ckks_vector(context, vec1) - second_vec = vec2 + second_vec = ts.plain_tensor(vec2) result = first_vec.dot(second_vec) expected = [sum([v1 * v2 for v1, v2 in zip(vec1, vec2)])] @@ -831,7 +831,7 @@ def test_dot_product_plain(context, vec1, vec2, precision): def test_dot_product_plain_inplace(context, vec1, vec2, precision): context.generate_galois_keys() first_vec = ts.ckks_vector(context, vec1) - second_vec = vec2 + second_vec = ts.plain_tensor(vec2) first_vec.dot_(second_vec) expected = [sum([v1 * v2 for v1, v2 in zip(vec1, vec2)])]