chore: Move to the mono repo layout

This commit is contained in:
Quentin Bourgerie
2023-03-08 11:23:21 +01:00
parent 4fb476aaec
commit ce7eddc22d
201 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
"""
Tests of `concrete.numpy` namespace.
"""

View File

@@ -0,0 +1,3 @@
"""
Tests of `concrete.numpy.compilation` namespace.
"""

View File

@@ -0,0 +1,63 @@
"""
Tests of `DebugArtifacts` class.
"""
import tempfile
from pathlib import Path
import numpy as np
from concrete.numpy import DebugArtifacts, compiler
def test_artifacts_export(helpers):
"""
Test `export` method of `DebugArtifacts` class.
"""
with tempfile.TemporaryDirectory() as path:
tmpdir = Path(path)
configuration = helpers.configuration()
artifacts = DebugArtifacts(tmpdir)
@compiler({"x": "encrypted"})
def f(x):
a = ((np.sin(x) ** 2) + (np.cos(x) ** 2)).astype(np.int64)
b = np.where(x < 5, x * 10, x + 10)
return a + b
inputset = range(10)
f.compile(inputset, configuration, artifacts)
artifacts.export()
assert (tmpdir / "environment.txt").exists()
assert (tmpdir / "requirements.txt").exists()
assert (tmpdir / "function.txt").exists()
assert (tmpdir / "parameters.txt").exists()
assert (tmpdir / "1.initial.graph.txt").exists()
assert (tmpdir / "2.after-fusing.graph.txt").exists()
assert (tmpdir / "3.after-fusing.graph.txt").exists()
assert (tmpdir / "4.final.graph.txt").exists()
assert (tmpdir / "mlir.txt").exists()
assert (tmpdir / "client_parameters.json").exists()
artifacts.export()
assert (tmpdir / "environment.txt").exists()
assert (tmpdir / "requirements.txt").exists()
assert (tmpdir / "function.txt").exists()
assert (tmpdir / "parameters.txt").exists()
assert (tmpdir / "1.initial.graph.txt").exists()
assert (tmpdir / "2.after-fusing.graph.txt").exists()
assert (tmpdir / "3.after-fusing.graph.txt").exists()
assert (tmpdir / "4.final.graph.txt").exists()
assert (tmpdir / "mlir.txt").exists()
assert (tmpdir / "client_parameters.json").exists()

View File

@@ -0,0 +1,345 @@
"""
Tests of `Circuit` class.
"""
import tempfile
from pathlib import Path
import numpy as np
import pytest
from concrete.numpy import Client, ClientSpecs, EvaluationKeys, LookupTable, Server, compiler
def test_circuit_str(helpers):
"""
Test `__str__` method of `Circuit` class.
"""
configuration = helpers.configuration()
@compiler({"x": "encrypted", "y": "encrypted"})
def f(x, y):
return x + y
inputset = [(np.random.randint(0, 2**4), np.random.randint(0, 2**5)) for _ in range(100)]
circuit = f.compile(inputset, configuration.fork(p_error=6e-5))
assert str(circuit) == circuit.graph.format()
def test_circuit_feedback(helpers):
"""
Test feedback properties of `Circuit` class.
"""
configuration = helpers.configuration()
p_error = 0.1
global_p_error = 0.05
@compiler({"x": "encrypted", "y": "encrypted"})
def f(x, y):
return np.sqrt(((x + y) ** 2) + 10).astype(np.int64)
inputset = [(np.random.randint(0, 2**2), np.random.randint(0, 2**2)) for _ in range(100)]
circuit = f.compile(inputset, configuration, p_error=p_error, global_p_error=global_p_error)
assert isinstance(circuit.complexity, float)
assert isinstance(circuit.size_of_secret_keys, int)
assert isinstance(circuit.size_of_bootstrap_keys, int)
assert isinstance(circuit.size_of_keyswitch_keys, int)
assert isinstance(circuit.size_of_inputs, int)
assert isinstance(circuit.size_of_outputs, int)
assert isinstance(circuit.p_error, float)
assert isinstance(circuit.global_p_error, float)
assert circuit.p_error <= p_error
assert circuit.global_p_error <= global_p_error
def test_circuit_bad_run(helpers):
"""
Test `run` method of `Circuit` class with bad parameters.
"""
configuration = helpers.configuration()
@compiler({"x": "encrypted", "y": "encrypted"})
def f(x, y):
return x + y
inputset = [(np.random.randint(0, 2**4), np.random.randint(0, 2**5)) for _ in range(100)]
circuit = f.compile(inputset, configuration)
# with 1 argument
# ---------------
with pytest.raises(ValueError) as excinfo:
circuit.encrypt_run_decrypt(1)
assert str(excinfo.value) == "Expected 2 inputs but got 1"
# with 3 arguments
# ----------------
with pytest.raises(ValueError) as excinfo:
circuit.encrypt_run_decrypt(1, 2, 3)
assert str(excinfo.value) == "Expected 2 inputs but got 3"
# with negative argument 0
# ------------------------
with pytest.raises(ValueError) as excinfo:
circuit.encrypt_run_decrypt(-1, 11)
assert str(excinfo.value) == (
"Expected argument 0 to be EncryptedScalar<uint6> but it's EncryptedScalar<int1>"
)
# with negative argument 1
# ------------------------
with pytest.raises(ValueError) as excinfo:
circuit.encrypt_run_decrypt(1, -11)
assert str(excinfo.value) == (
"Expected argument 1 to be EncryptedScalar<uint6> but it's EncryptedScalar<int5>"
)
# with large argument 0
# ---------------------
with pytest.raises(ValueError) as excinfo:
circuit.encrypt_run_decrypt(100, 10)
assert str(excinfo.value) == (
"Expected argument 0 to be EncryptedScalar<uint6> but it's EncryptedScalar<uint7>"
)
# with large argument 1
# ---------------------
with pytest.raises(ValueError) as excinfo:
circuit.encrypt_run_decrypt(1, 100)
assert str(excinfo.value) == (
"Expected argument 1 to be EncryptedScalar<uint6> but it's EncryptedScalar<uint7>"
)
def test_client_server_api(helpers):
"""
Test client/server API.
"""
configuration = helpers.configuration()
@compiler({"x": "encrypted"})
def function(x):
return x + 42
inputset = [np.random.randint(0, 10, size=(3,)) for _ in range(10)]
circuit = function.compile(inputset, configuration.fork(jit=False))
# for coverage
circuit.keygen()
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_dir_path = Path(tmp_dir)
server_path = tmp_dir_path / "server.zip"
circuit.server.save(server_path)
client_path = tmp_dir_path / "client.zip"
circuit.client.save(client_path)
circuit.cleanup()
server = Server.load(server_path)
serialized_client_specs = server.client_specs.serialize()
client_specs = ClientSpecs.unserialize(serialized_client_specs)
clients = [
Client(client_specs, configuration.insecure_key_cache_location),
Client.load(client_path, configuration.insecure_key_cache_location),
]
for client in clients:
args = client.encrypt([3, 8, 1])
serialized_args = client.specs.serialize_public_args(args)
serialized_evaluation_keys = client.evaluation_keys.serialize()
unserialized_args = server.client_specs.unserialize_public_args(serialized_args)
unserialized_evaluation_keys = EvaluationKeys.unserialize(serialized_evaluation_keys)
result = server.run(unserialized_args, unserialized_evaluation_keys)
serialized_result = server.client_specs.serialize_public_result(result)
unserialized_result = client.specs.unserialize_public_result(serialized_result)
output = client.decrypt(unserialized_result)
assert np.array_equal(output, [45, 50, 43])
with pytest.raises(RuntimeError) as excinfo:
server.save("UNUSED", via_mlir=True)
assert str(excinfo.value) == "Loaded server objects cannot be saved again via MLIR"
server.cleanup()
def test_client_server_api_via_mlir(helpers):
"""
Test client/server API.
"""
configuration = helpers.configuration()
@compiler({"x": "encrypted"})
def function(x):
return x + 42
inputset = [np.random.randint(0, 10, size=(3,)) for _ in range(10)]
circuit = function.compile(inputset, configuration.fork(jit=False))
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_dir_path = Path(tmp_dir)
server_path = tmp_dir_path / "server.zip"
circuit.server.save(server_path, via_mlir=True)
client_path = tmp_dir_path / "client.zip"
circuit.client.save(client_path)
circuit.cleanup()
server = Server.load(server_path)
serialized_client_specs = server.client_specs.serialize()
client_specs = ClientSpecs.unserialize(serialized_client_specs)
clients = [
Client(client_specs, configuration.insecure_key_cache_location),
Client.load(client_path, configuration.insecure_key_cache_location),
]
for client in clients:
args = client.encrypt([3, 8, 1])
serialized_args = client.specs.serialize_public_args(args)
serialized_evaluation_keys = client.evaluation_keys.serialize()
unserialized_args = server.client_specs.unserialize_public_args(serialized_args)
unserialized_evaluation_keys = EvaluationKeys.unserialize(serialized_evaluation_keys)
result = server.run(unserialized_args, unserialized_evaluation_keys)
serialized_result = server.client_specs.serialize_public_result(result)
unserialized_result = client.specs.unserialize_public_result(serialized_result)
output = client.decrypt(unserialized_result)
assert np.array_equal(output, [45, 50, 43])
server.cleanup()
def test_bad_server_save(helpers):
"""
Test `save` method of `Server` class with bad parameters.
"""
configuration = helpers.configuration()
@compiler({"x": "encrypted"})
def function(x):
return x + 42
inputset = range(10)
circuit = function.compile(inputset, configuration)
with pytest.raises(RuntimeError) as excinfo:
circuit.server.save("test.zip")
assert str(excinfo.value) == "Just-in-Time compilation cannot be saved"
@pytest.mark.parametrize("p_error", [0.75, 0.5, 0.4, 0.25, 0.2, 0.1, 0.01, 0.001])
@pytest.mark.parametrize("bit_width", [5])
@pytest.mark.parametrize("sample_size", [1_000_000])
@pytest.mark.parametrize("tolerance", [0.075])
def test_p_error_simulation(p_error, bit_width, sample_size, tolerance, helpers):
"""
Test p_error simulation.
"""
configuration = helpers.configuration().fork(global_p_error=None)
table = LookupTable([0] + [x - 1 for x in range(1, 2**bit_width)])
@compiler({"x": "encrypted"})
def function(x):
return table[x + 1]
inputset = [np.random.randint(0, (2**bit_width) - 1, size=(sample_size,)) for _ in range(100)]
circuit = function.compile(inputset, configuration=configuration, p_error=p_error)
assert circuit.p_error < p_error
sample = np.random.randint(0, (2**bit_width) - 1, size=(sample_size,))
output = circuit.simulate(sample)
errors = np.sum(output != sample)
expected_number_of_errors_on_average = sample_size * circuit.p_error
tolerated_difference = expected_number_of_errors_on_average * tolerance
acceptable_number_of_errors = [
round(expected_number_of_errors_on_average - tolerated_difference),
round(expected_number_of_errors_on_average + tolerated_difference),
]
assert acceptable_number_of_errors[0] <= errors <= acceptable_number_of_errors[1]
def test_circuit_run_with_unused_arg(helpers):
"""
Test `encrypt_run_decrypt` method of `Circuit` class with unused arguments.
"""
configuration = helpers.configuration()
@compiler({"x": "encrypted", "y": "encrypted"})
def f(x, y): # pylint: disable=unused-argument
return x + 10
inputset = [
(np.random.randint(2**3, 2**4), np.random.randint(2**4, 2**5)) for _ in range(100)
]
circuit = f.compile(inputset, configuration)
with pytest.raises(ValueError, match="Expected 2 inputs but got 1"):
circuit.encrypt_run_decrypt(10)
assert circuit.encrypt_run_decrypt(10, 0) == 20
assert circuit.encrypt_run_decrypt(10, 10) == 20
assert circuit.encrypt_run_decrypt(10, 20) == 20
def test_dataflow_circuit(helpers):
"""
Test execution with dataflow_parallelize=True.
"""
configuration = helpers.configuration().fork(dataflow_parallelize=True)
@compiler({"x": "encrypted", "y": "encrypted"})
def f(x, y):
return (x**2) + (y // 2)
inputset = [(np.random.randint(0, 2**3), np.random.randint(0, 2**3)) for _ in range(100)]
circuit = f.compile(inputset, configuration)
assert circuit.encrypt_run_decrypt(5, 6) == 28

View File

@@ -0,0 +1,332 @@
"""
Tests of `Compiler` class.
"""
import numpy as np
import pytest
from concrete.numpy.compilation import Compiler
def test_compiler_bad_init():
"""
Test `__init__` method of `Compiler` class with bad parameters.
"""
def f(x, y, z):
return x + y + z
# missing all
# -----------
with pytest.raises(ValueError) as excinfo:
Compiler(f, {})
assert str(excinfo.value) == (
"Encryption statuses of parameters 'x', 'y' and 'z' of function 'f' are not provided"
)
# missing x and y
# ---------------
with pytest.raises(ValueError) as excinfo:
Compiler(f, {"z": "clear"})
assert str(excinfo.value) == (
"Encryption statuses of parameters 'x' and 'y' of function 'f' are not provided"
)
# missing x
# ---------
with pytest.raises(ValueError) as excinfo:
Compiler(f, {"y": "encrypted", "z": "clear"})
assert str(excinfo.value) == (
"Encryption status of parameter 'x' of function 'f' is not provided"
)
# additional a, b, c
# ------------------
with pytest.raises(ValueError) as excinfo:
Compiler(
f,
{
"x": "encrypted",
"y": "encrypted",
"z": "encrypted",
"a": "encrypted",
"b": "encrypted",
"c": "encrypted",
},
)
assert str(excinfo.value) == (
"Encryption statuses of 'a', 'b' and 'c' are provided "
"but they are not a parameter of function 'f'"
)
# additional a and b
# ------------------
with pytest.raises(ValueError) as excinfo:
Compiler(
f,
{
"x": "encrypted",
"y": "encrypted",
"z": "encrypted",
"a": "encrypted",
"b": "encrypted",
},
)
assert str(excinfo.value) == (
"Encryption statuses of 'a' and 'b' are provided "
"but they are not a parameter of function 'f'"
)
# additional a
# ------------
with pytest.raises(ValueError) as excinfo:
Compiler(
f,
{
"x": "encrypted",
"y": "encrypted",
"z": "encrypted",
"a": "encrypted",
},
)
assert str(excinfo.value) == (
"Encryption status of 'a' is provided but it is not a parameter of function 'f'"
)
def test_compiler_bad_call():
"""
Test `__call__` method of `Compiler` class with bad parameters.
"""
def f(x, y, z):
return x + y + z
with pytest.raises(RuntimeError) as excinfo:
compiler = Compiler(f, {"x": "encrypted", "y": "encrypted", "z": "clear"})
compiler(1, 2, 3, invalid=4)
assert str(excinfo.value) == "Calling function 'f' with kwargs is not supported"
def test_compiler_bad_trace(helpers):
"""
Test `trace` method of `Compiler` class with bad parameters.
"""
configuration = helpers.configuration()
# without inputset
# ----------------
def f(x, y, z):
return x + y + z
with pytest.raises(RuntimeError) as excinfo:
compiler = Compiler(
f,
{"x": "encrypted", "y": "encrypted", "z": "clear"},
)
compiler.trace(configuration=configuration)
assert str(excinfo.value) == "Tracing function 'f' without an inputset is not supported"
# bad return
# ----------
def g():
return np.array([{}, ()], dtype=object)
with pytest.raises(ValueError) as excinfo:
compiler = Compiler(g, {})
compiler.trace(inputset=[()], configuration=configuration)
assert str(excinfo.value) == "Function 'g' returned '[{} ()]', which is not supported"
def test_compiler_bad_compile(helpers):
"""
Test `compile` method of `Compiler` class with bad parameters.
"""
configuration = helpers.configuration()
def f(x, y, z):
return x + y + z
# without inputset
# ----------------
with pytest.raises(RuntimeError) as excinfo:
compiler = Compiler(
f,
{"x": "encrypted", "y": "encrypted", "z": "clear"},
)
compiler.compile(configuration=configuration)
assert str(excinfo.value) == "Compiling function 'f' without an inputset is not supported"
# with bad inputset at the first input
# ------------------------------------
with pytest.raises(ValueError) as excinfo:
compiler = Compiler(
f,
{"x": "encrypted", "y": "encrypted", "z": "clear"},
)
inputset = [1]
compiler.compile(inputset, configuration=configuration)
assert str(excinfo.value) == (
"Input #0 of your inputset is not well formed "
"(expected a tuple of 3 values got a single value)"
)
# with bad inputset at the second input
# -------------------------------------
with pytest.raises(ValueError) as excinfo:
compiler = Compiler(
f,
{"x": "encrypted", "y": "encrypted", "z": "clear"},
)
inputset = [(1, 2, 3), (1, 2)]
compiler.compile(inputset, configuration=configuration)
assert str(excinfo.value) == (
"Input #1 of your inputset is not well formed "
"(expected a tuple of 3 values got a tuple of 2 values)"
)
def test_compiler_compile_bad_inputset(helpers):
"""
Test `compile` method of `Compiler` class with bad inputset.
"""
configuration = helpers.configuration()
# with inf
# --------
def f(x):
return (x + np.inf).astype(np.int64)
with pytest.raises(RuntimeError) as excinfo:
compiler = Compiler(f, {"x": "encrypted"})
compiler.compile(range(10), configuration=configuration)
assert str(excinfo.value) == "Bound measurement using inputset[0] failed"
helpers.check_str(
"""
Evaluation of the graph failed
%0 = x # EncryptedScalar<uint1>
%1 = subgraph(%0) # EncryptedScalar<uint1>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of this node failed
return %1
Subgraphs:
%1 = subgraph(%0):
%0 = input # EncryptedScalar<uint1>
%1 = inf # ClearScalar<float64>
%2 = add(%0, %1) # EncryptedScalar<float64>
%3 = astype(%2, dtype=int_) # EncryptedScalar<uint1>
return %3
""".strip(),
str(excinfo.value.__cause__).strip(),
)
helpers.check_str(
"""
Evaluation of the graph failed
%0 = input # EncryptedScalar<uint1>
%1 = inf # ClearScalar<float64>
%2 = add(%0, %1) # EncryptedScalar<float64>
%3 = astype(%2, dtype=int_) # EncryptedScalar<uint1>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of this node failed
return %3
""".strip(),
str(excinfo.value.__cause__.__cause__).strip(),
)
assert (
str(excinfo.value.__cause__.__cause__.__cause__)
== "An `Inf` value is tried to be converted to integer"
)
# with nan
# --------
def g(x):
return (x + np.nan).astype(np.int64)
with pytest.raises(RuntimeError) as excinfo:
compiler = Compiler(g, {"x": "encrypted"})
compiler.compile(range(10), configuration=configuration)
assert str(excinfo.value) == "Bound measurement using inputset[0] failed"
helpers.check_str(
"""
Evaluation of the graph failed
%0 = x # EncryptedScalar<uint1>
%1 = subgraph(%0) # EncryptedScalar<uint1>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of this node failed
return %1
Subgraphs:
%1 = subgraph(%0):
%0 = input # EncryptedScalar<uint1>
%1 = nan # ClearScalar<float64>
%2 = add(%0, %1) # EncryptedScalar<float64>
%3 = astype(%2, dtype=int_) # EncryptedScalar<uint1>
return %3
""".strip(),
str(excinfo.value.__cause__).strip(),
)
helpers.check_str(
"""
Evaluation of the graph failed
%0 = input # EncryptedScalar<uint1>
%1 = nan # ClearScalar<float64>
%2 = add(%0, %1) # EncryptedScalar<float64>
%3 = astype(%2, dtype=int_) # EncryptedScalar<uint1>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of this node failed
return %3
""".strip(),
str(excinfo.value.__cause__.__cause__).strip(),
)
assert (
str(excinfo.value.__cause__.__cause__.__cause__)
== "A `NaN` value is tried to be converted to integer"
)

View File

@@ -0,0 +1,105 @@
"""
Tests of `Configuration` class.
"""
import pytest
from concrete.numpy.compilation import Configuration
@pytest.mark.parametrize(
"kwargs,expected_error,expected_message",
[
pytest.param(
{"enable_unsafe_features": False, "use_insecure_key_cache": True},
RuntimeError,
"Insecure key cache cannot be used without enabling unsafe features",
),
pytest.param(
{
"enable_unsafe_features": True,
"use_insecure_key_cache": True,
"insecure_key_cache_location": None,
},
RuntimeError,
"Insecure key cache cannot be enabled without specifying its location",
),
],
)
def test_configuration_bad_init(kwargs, expected_error, expected_message):
"""
Test `__init__` method of `Configuration` class with bad parameters.
"""
with pytest.raises(expected_error) as excinfo:
Configuration(**kwargs)
assert str(excinfo.value) == expected_message
def test_configuration_fork():
"""
Test `fork` method of `Configuration` class.
"""
config1 = Configuration(enable_unsafe_features=True, loop_parallelize=False)
config2 = config1.fork(enable_unsafe_features=False, loop_parallelize=True)
assert config1 is not config2
assert config1.enable_unsafe_features is True
assert config1.loop_parallelize is False
assert config2.enable_unsafe_features is False
assert config2.loop_parallelize is True
@pytest.mark.parametrize(
"kwargs,expected_error,expected_message",
[
pytest.param(
{"foo": False},
TypeError,
"Unexpected keyword argument 'foo'",
),
pytest.param(
{"dump_artifacts_on_unexpected_failures": "yes"},
TypeError,
"Unexpected type for keyword argument 'dump_artifacts_on_unexpected_failures' "
"(expected 'bool', got 'str')",
),
pytest.param(
{"insecure_key_cache_location": 3},
TypeError,
"Unexpected type for keyword argument 'insecure_key_cache_location' "
"(expected 'Optional[str]', got 'int')",
),
pytest.param(
{"p_error": "yes"},
TypeError,
"Unexpected type for keyword argument 'p_error' "
"(expected 'Optional[float]', got 'str')",
),
pytest.param(
{"global_p_error": "mamma mia"},
TypeError,
"Unexpected type for keyword argument 'global_p_error' "
"(expected 'Optional[float]', got 'str')",
),
pytest.param(
{"show_optimizer": "please"},
TypeError,
"Unexpected type for keyword argument 'show_optimizer' "
"(expected 'Optional[bool]', got 'str')",
),
],
)
def test_configuration_bad_fork(kwargs, expected_error, expected_message):
"""
Test `fork` method of `Configuration` class with bad parameters.
"""
with pytest.raises(expected_error) as excinfo:
Configuration().fork(**kwargs)
assert str(excinfo.value) == expected_message

View File

@@ -0,0 +1,260 @@
"""
Tests of `compiler` and `circuit` decorators.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
def test_compiler_call_and_compile(helpers):
"""
Test `__call__` and `compile` methods of `compiler` decorator back to back.
"""
configuration = helpers.configuration()
@cnp.compiler({"x": "encrypted"})
def function(x):
return x + 42
for i in range(10):
function(i)
circuit = function.compile(configuration=configuration)
sample = 5
helpers.check_execution(circuit, function, sample)
def test_compiler_verbose_trace(helpers, capsys):
"""
Test `trace` method of `compiler` decorator with verbose flag.
"""
configuration = helpers.configuration()
artifacts = cnp.DebugArtifacts()
@cnp.compiler({"x": "encrypted"})
def function(x):
return x + 42
inputset = range(10)
function.trace(inputset, configuration, artifacts, show_graph=True)
captured = capsys.readouterr()
assert captured.out.strip() == (
f"""
Computation Graph
------------------------------------------------------------------
{str(list(artifacts.textual_representations_of_graphs.values())[-1][-1])}
------------------------------------------------------------------
""".strip()
)
def test_compiler_verbose_compile(helpers, capsys):
"""
Test `compile` method of `compiler` decorator with verbose flag.
"""
configuration = helpers.configuration()
artifacts = cnp.DebugArtifacts()
@cnp.compiler({"x": "encrypted"})
def function(x):
return x + 42
inputset = range(10)
function.compile(inputset, configuration, artifacts, verbose=True)
captured = capsys.readouterr()
assert captured.out.strip().startswith(
f"""
Computation Graph
--------------------------------------------------------------------------------
{list(artifacts.textual_representations_of_graphs.values())[-1][-1]}
--------------------------------------------------------------------------------
MLIR
--------------------------------------------------------------------------------
{artifacts.mlir_to_compile}
--------------------------------------------------------------------------------
Optimizer
--------------------------------------------------------------------------------
""".strip()
)
def test_circuit(helpers):
"""
Test circuit decorator.
"""
@cnp.circuit({"x": "encrypted"}, helpers.configuration())
def circuit1(x: cnp.uint2):
return x + 42
helpers.check_str(
"""
%0 = x # EncryptedScalar<uint2>
%1 = 42 # ClearScalar<uint6>
%2 = add(%0, %1) # EncryptedScalar<uint6>
return %2
""".strip(),
str(circuit1),
)
# ======================================================================
@cnp.circuit({"x": "encrypted"}, helpers.configuration())
def circuit2(x: cnp.tensor[cnp.uint2, 3, 2]):
return x + 42
helpers.check_str(
"""
%0 = x # EncryptedTensor<uint2, shape=(3, 2)>
%1 = 42 # ClearScalar<uint6>
%2 = add(%0, %1) # EncryptedTensor<uint6, shape=(3, 2)>
return %2
""".strip(),
str(circuit2),
)
# ======================================================================
@cnp.circuit({"x": "encrypted"}, helpers.configuration())
def circuit3(x: cnp.uint3):
def square(x):
return x**2
return cnp.univariate(square, outputs=cnp.uint7)(x)
helpers.check_str(
"""
%0 = x # EncryptedScalar<uint3>
%1 = square(%0) # EncryptedScalar<uint7>
return %1
""".strip(),
str(circuit3),
)
# ======================================================================
@cnp.circuit({"x": "encrypted"}, helpers.configuration())
def circuit4(x: cnp.uint3):
return ((np.sin(x) ** 2) + (np.cos(x) ** 2)).astype(cnp.uint3)
helpers.check_str(
"""
%0 = x # EncryptedScalar<uint3>
%1 = subgraph(%0) # EncryptedScalar<uint3>
return %1
Subgraphs:
%1 = subgraph(%0):
%0 = input # EncryptedScalar<uint3>
%1 = sin(%0) # EncryptedScalar<float64>
%2 = 2 # ClearScalar<uint2>
%3 = power(%1, %2) # EncryptedScalar<float64>
%4 = cos(%0) # EncryptedScalar<float64>
%5 = 2 # ClearScalar<uint2>
%6 = power(%4, %5) # EncryptedScalar<float64>
%7 = add(%3, %6) # EncryptedScalar<float64>
%8 = astype(%7) # EncryptedScalar<uint3>
return %8
""".strip(),
str(circuit4),
)
# ======================================================================
@cnp.circuit({"x": "encrypted"}, helpers.configuration())
def circuit5(x: cnp.int2):
return x + 42
helpers.check_str(
"""
%0 = x # EncryptedScalar<int2>
%1 = 42 # ClearScalar<uint6>
%2 = add(%0, %1) # EncryptedScalar<int6>
return %2
""".strip(),
str(circuit5),
)
def test_bad_circuit(helpers):
"""
Test circuit decorator with bad parameters.
"""
# bad annotation
# --------------
with pytest.raises(ValueError) as excinfo:
@cnp.circuit({"x": "encrypted"}, helpers.configuration())
def circuit1(x: int):
return x + 42
assert str(excinfo.value) == (
f"Annotation {str(int)} for argument 'x' is not valid "
f"(please use a cnp type such as `cnp.uint4` or 'cnp.tensor[cnp.uint4, 3, 2]')"
)
# missing encryption status
# -------------------------
with pytest.raises(ValueError) as excinfo:
@cnp.circuit({}, helpers.configuration())
def circuit2(x: cnp.uint3):
return x + 42
assert str(excinfo.value) == (
"Encryption status of parameter 'x' of function 'circuit2' is not provided"
)
# bad astype
# ----------
with pytest.raises(ValueError) as excinfo:
@cnp.circuit({"x": "encrypted"}, helpers.configuration())
def circuit3(x: cnp.uint3):
return x.astype(np.int64)
assert str(excinfo.value) == (
"`astype` method must be called with a concrete.numpy type "
"for direct circuit definition (e.g., value.astype(cnp.uint4))"
)
# round
# -----
with pytest.raises(RuntimeError) as excinfo:
@cnp.circuit({"x": "encrypted"}, helpers.configuration())
def circuit4(x: cnp.uint3):
return round(x)
assert str(excinfo.value) == (
"'round(x)' cannot be used in direct definition (you may use np.around instead)"
)

View File

@@ -0,0 +1,331 @@
"""
Configuration of `pytest`.
"""
import json
import os
import random
from pathlib import Path
from typing import Any, Callable, Dict, List, Tuple, Union
import numpy as np
import pytest
import concrete.numpy as cnp
import tests
tests_directory = os.path.dirname(tests.__file__)
INSECURE_KEY_CACHE_LOCATION = None
def pytest_addoption(parser):
"""
Add CLI options.
"""
parser.addoption(
"--global-coverage",
type=str,
default=None,
action="store",
help="JSON file to dump pytest-cov terminal report.",
)
parser.addoption(
"--key-cache",
type=str,
default=None,
action="store",
help="Specify the location of the key cache",
)
def pytest_sessionstart(session):
"""
Initialize insecure key cache.
"""
# pylint: disable=global-statement
global INSECURE_KEY_CACHE_LOCATION
# pylint: enable=global-statement
key_cache_location = session.config.getoption("--key-cache", default=None)
if key_cache_location is not None:
if key_cache_location.lower() == "disable":
key_cache_location = None
else:
key_cache_location = Path(key_cache_location).expanduser().resolve()
else:
key_cache_location = Path.home().resolve() / ".cache" / "concrete-numpy" / "pytest"
if key_cache_location:
key_cache_location.mkdir(parents=True, exist_ok=True)
print(f"INSECURE_KEY_CACHE_LOCATION={str(key_cache_location)}")
INSECURE_KEY_CACHE_LOCATION = str(key_cache_location)
def pytest_sessionfinish(session, exitstatus): # pylint: disable=unused-argument
"""
Save global coverage info after testing is finished.
"""
# Hacked together from the source code, they don't have an option to export to file,
# and it's too much work to get a PR in for such a little thing.
# https://github.com/pytest-dev/pytest-cov/blob/ec344d8adf2d78238d8f07cb20ed2463d7536970/src/pytest_cov/plugin.py#L329
if session.config.pluginmanager.hasplugin("_cov"):
global_coverage_option = session.config.getoption("--global-coverage", default=None)
if global_coverage_option is not None:
coverage_plugin = session.config.pluginmanager.getplugin("_cov")
coverage_txt = coverage_plugin.cov_report.getvalue()
coverage_status = 0
if (
coverage_plugin.options.cov_fail_under is not None
and coverage_plugin.options.cov_fail_under > 0
and coverage_plugin.cov_total < coverage_plugin.options.cov_fail_under
):
coverage_status = 1
global_coverage_file_path = Path(global_coverage_option).resolve()
with open(global_coverage_file_path, "w", encoding="utf-8") as f:
json.dump({"exit_code": coverage_status, "content": coverage_txt}, f)
class Helpers:
"""
Helpers class, which provides various helpers to tests.
"""
@staticmethod
def configuration() -> cnp.Configuration:
"""
Get the test configuration to use during testing.
Returns:
cnp.Configuration:
test configuration
"""
return cnp.Configuration(
dump_artifacts_on_unexpected_failures=False,
enable_unsafe_features=True,
use_insecure_key_cache=True,
loop_parallelize=True,
dataflow_parallelize=False,
auto_parallelize=False,
jit=True,
insecure_key_cache_location=INSECURE_KEY_CACHE_LOCATION,
global_p_error=(1 / 10_000),
)
@staticmethod
def generate_encryption_statuses(parameters: Dict[str, Dict[str, Any]]) -> Dict[str, str]:
"""
Generate parameter encryption statuses accoring to a parameter specification.
Args:
parameters (Dict[str, Dict[str, Any]]):
parameter specification to use
e.g.,
{
"param1": {"range": [0, 10], "status": "clear"},
"param2": {"range": [3, 30], "status": "encrypted", "shape": (3,)},
}
Returns:
Dict[str, str]:
parameter encryption statuses
generated according to the given parameter specification
"""
return {
parameter: details["status"] if "status" in details else "encrypted"
for parameter, details in parameters.items()
}
@staticmethod
def generate_inputset(
parameters: Dict[str, Dict[str, Any]],
size: int = 128,
) -> List[Union[Tuple[Union[int, np.ndarray], ...], Union[int, np.ndarray]]]:
"""
Generate a random inputset of desired size accoring to a parameter specification.
Args:
parameters (Dict[str, Dict[str, Any]]):
parameter specification to use
e.g.,
{
"param1": {"range": [0, 10], "status": "clear"},
"param2": {"range": [3, 30], "status": "encrypted", "shape": (3,)},
}
size (int):
size of the resulting inputset
Returns:
List[Union[Tuple[Union[int, np.ndarray], ...], Union[int, np.ndarray]]]:
random inputset of desired size
generated according to the given parameter specification
"""
inputset = []
for _ in range(size):
sample = Helpers.generate_sample(parameters)
inputset.append(tuple(sample) if len(sample) > 1 else sample[0])
return inputset
@staticmethod
def generate_sample(parameters: Dict[str, Dict[str, Any]]) -> List[Union[int, np.ndarray]]:
"""
Generate a random sample accoring to a parameter specification.
Args:
parameters (Dict[str, Dict[str, Any]]):
parameter specification to use
e.g.,
{
"param1": {"range": [0, 10], "status": "clear"},
"param2": {"range": [3, 30], "status": "encrypted", "shape": (3,)},
}
Returns:
List[Union[int, np.ndarray]]:
random sample
generated according to the given parameter specification
"""
sample = []
for description in parameters.values():
minimum, maximum = description.get("range", [0, (2**16) - 1])
if "shape" in description:
shape = description["shape"]
sample.append(np.random.randint(minimum, maximum + 1, size=shape, dtype=np.int64))
else:
sample.append(np.int64(random.randint(minimum, maximum)))
return sample
@staticmethod
def check_execution(
circuit: cnp.Circuit,
function: Callable,
sample: Union[Any, List[Any]],
retries: int = 1,
simulate: bool = False,
):
"""
Assert that `circuit` is behaves the same as `function` on `sample`.
Args:
circuit (cnp.Circuit):
compiled circuit
function (Callable):
original function
sample (List[Any]):
inputs
retries (int, default = 1):
number of times to retry (for probabilistic execution)
simulate (bool, default = False):
whether to simulate instead of fhe execution
"""
if not isinstance(sample, list):
sample = [sample]
def sanitize(values):
if not isinstance(values, tuple):
values = (values,)
result = []
for value in values:
if isinstance(value, (bool, np.bool_)):
value = int(value)
elif isinstance(value, np.ndarray) and value.dtype == np.bool_:
value = value.astype(np.int64)
result.append(value)
return tuple(result)
for i in range(retries):
expected = sanitize(function(*sample))
actual = sanitize(
circuit.simulate(*sample) if simulate else circuit.encrypt_run_decrypt(*sample)
)
if all(np.array_equal(e, a) for e, a in zip(expected, actual)):
break
if i == retries - 1:
message = f"""
Expected Output
===============
{expected}
Actual Output
=============
{actual}
"""
raise AssertionError(message)
@staticmethod
def check_str(expected: str, actual: str):
"""
Assert that `circuit` is behaves the same as `function` on `sample`.
Args:
expected (str):
expected str
actual (str):
actual str
"""
# remove error line information
# there are explicit tests to make sure the line information is correct
# however, it would have been very hard to keep the other tests up to date
actual = "\n".join(
line for line in actual.splitlines() if not line.strip().startswith(tests_directory)
)
assert (
actual.strip() == expected.strip()
), f"""
Expected Output
===============
{expected}
Actual Output
=============
{actual}
"""
@pytest.fixture
def helpers():
"""
Fixture that provides `Helpers` class to tests.
"""
return Helpers

View File

@@ -0,0 +1,3 @@
"""
Tests of `concrete.numpy.dtypes` namespace.
"""

View File

@@ -0,0 +1,92 @@
"""
Tests of `Float` data type.
"""
import pytest
from concrete.numpy.dtypes import Float
@pytest.mark.parametrize(
"bit_width,expected_error,expected_message",
[
pytest.param(
128,
ValueError,
"Float(128) is not supported (bit width must be one of 16, 32 or 64)",
),
pytest.param(
"abc",
ValueError,
"Float('abc') is not supported (bit width must be one of 16, 32 or 64)",
),
],
)
def test_float_bad_init(bit_width, expected_error, expected_message):
"""
Test `__init__` method of `Float` data type with bad parameters.
"""
with pytest.raises(expected_error) as excinfo:
Float(bit_width)
assert str(excinfo.value) == expected_message
@pytest.mark.parametrize(
"lhs,rhs,expected_result",
[
pytest.param(
Float(32),
Float(32),
True,
),
pytest.param(
Float(32),
Float(64),
False,
),
pytest.param(
Float(32),
"Float(32)",
False,
),
pytest.param(
"Float(32)",
Float(32),
False,
),
],
)
def test_float_eq(lhs, rhs, expected_result):
"""
Test `__eq__` method of `Float` data type.
"""
assert (lhs == rhs) == expected_result
assert (rhs == lhs) == expected_result
@pytest.mark.parametrize(
"data_type,expected_result",
[
pytest.param(
Float(16),
"float16",
),
pytest.param(
Float(32),
"float32",
),
pytest.param(
Float(64),
"float64",
),
],
)
def test_float_str(data_type, expected_result):
"""
Test `__str__` method of `Float` data type.
"""
assert str(data_type) == expected_result

View File

@@ -0,0 +1,691 @@
"""
Tests of `Integer` data type.
"""
import numpy as np
import pytest
from concrete.numpy.dtypes import Integer, SignedInteger, UnsignedInteger
@pytest.mark.parametrize(
"value,force_signed,expected_result",
[
pytest.param(
-4,
False,
SignedInteger(3),
),
pytest.param(
-3,
False,
SignedInteger(3),
),
pytest.param(
-2,
False,
SignedInteger(2),
),
pytest.param(
-1,
False,
SignedInteger(1),
),
pytest.param(
0,
False,
UnsignedInteger(1),
),
pytest.param(
1,
False,
UnsignedInteger(1),
),
pytest.param(
2,
False,
UnsignedInteger(2),
),
pytest.param(
3,
False,
UnsignedInteger(2),
),
pytest.param(
4,
False,
UnsignedInteger(3),
),
pytest.param(
-4,
True,
SignedInteger(3),
),
pytest.param(
-3,
True,
SignedInteger(3),
),
pytest.param(
-2,
True,
SignedInteger(2),
),
pytest.param(
-1,
True,
SignedInteger(1),
),
pytest.param(
0,
True,
SignedInteger(1),
),
pytest.param(
1,
True,
SignedInteger(2),
),
pytest.param(
2,
True,
SignedInteger(3),
),
pytest.param(
3,
True,
SignedInteger(3),
),
pytest.param(
4,
True,
SignedInteger(4),
),
pytest.param(
np.array([0, 1]),
False,
UnsignedInteger(1),
),
pytest.param(
np.array([0, 1]),
True,
SignedInteger(2),
),
pytest.param(
[-1, 1],
False,
SignedInteger(2),
),
pytest.param(
[-1, 1],
True,
SignedInteger(2),
),
],
)
def test_integer_that_can_represent(value, force_signed, expected_result):
"""
Test `that_can_represent` function of `Integer` data type.
"""
assert Integer.that_can_represent(value, force_signed) == expected_result
@pytest.mark.parametrize(
"value,force_signed,expected_error,expected_message",
[
pytest.param(
"abc",
False,
ValueError,
"Integer cannot represent 'abc'",
),
pytest.param(
"abc",
True,
ValueError,
"Integer cannot represent 'abc'",
),
pytest.param(
4.2,
False,
ValueError,
"Integer cannot represent 4.2",
),
pytest.param(
4.2,
True,
ValueError,
"Integer cannot represent 4.2",
),
pytest.param(
np.array([2.2, 1.1]),
False,
ValueError,
"Integer cannot represent array([2.2, 1.1])",
),
pytest.param(
np.array([2.2, 1.1]),
True,
ValueError,
"Integer cannot represent array([2.2, 1.1])",
),
pytest.param(
[1, (), 3],
True,
ValueError,
"Integer cannot represent [1, (), 3]",
),
],
)
def test_integer_bad_that_can_represent(value, force_signed, expected_error, expected_message):
"""
Test `that_can_represent` function of `Integer` data type with bad parameters.
"""
with pytest.raises(expected_error) as excinfo:
Integer.that_can_represent(value, force_signed)
assert str(excinfo.value) == expected_message
@pytest.mark.parametrize(
"constructor,bit_width,expected_error,expected_message",
[
pytest.param(
SignedInteger,
0,
ValueError,
"SignedInteger(0) is not supported (bit width must be a positive integer)",
),
pytest.param(
UnsignedInteger,
0,
ValueError,
"UnsignedInteger(0) is not supported (bit width must be a positive integer)",
),
pytest.param(
SignedInteger,
-1,
ValueError,
"SignedInteger(-1) is not supported (bit width must be a positive integer)",
),
pytest.param(
UnsignedInteger,
-1,
ValueError,
"UnsignedInteger(-1) is not supported (bit width must be a positive integer)",
),
pytest.param(
SignedInteger,
"abc",
ValueError,
"SignedInteger('abc') is not supported (bit width must be a positive integer)",
),
pytest.param(
UnsignedInteger,
"abc",
ValueError,
"UnsignedInteger('abc') is not supported (bit width must be a positive integer)",
),
],
)
def test_integer_bad_init(constructor, bit_width, expected_error, expected_message):
"""
Test `__init__` method of `Integer` data type with bad parameters.
"""
with pytest.raises(expected_error) as excinfo:
constructor(bit_width)
assert str(excinfo.value) == expected_message
@pytest.mark.parametrize(
"lhs,rhs,expected_result",
[
pytest.param(
SignedInteger(5),
SignedInteger(5),
True,
),
pytest.param(
UnsignedInteger(5),
UnsignedInteger(5),
True,
),
pytest.param(
SignedInteger(5),
SignedInteger(6),
False,
),
pytest.param(
SignedInteger(6),
SignedInteger(5),
False,
),
pytest.param(
UnsignedInteger(5),
UnsignedInteger(6),
False,
),
pytest.param(
UnsignedInteger(6),
UnsignedInteger(5),
False,
),
pytest.param(
SignedInteger(5),
UnsignedInteger(5),
False,
),
pytest.param(
UnsignedInteger(5),
SignedInteger(5),
False,
),
pytest.param(
SignedInteger(5),
"SignedInteger(5)",
False,
),
pytest.param(
"SignedInteger(5)",
SignedInteger(5),
False,
),
],
)
def test_integer_eq(lhs, rhs, expected_result):
"""
Test `__eq__` method of `Integer` data type.
"""
assert (lhs == rhs) == expected_result
assert (rhs == lhs) == expected_result
@pytest.mark.parametrize(
"data_type,expected_result",
[
pytest.param(
UnsignedInteger(4),
"uint4",
),
pytest.param(
UnsignedInteger(7),
"uint7",
),
pytest.param(
SignedInteger(4),
"int4",
),
pytest.param(
SignedInteger(7),
"int7",
),
],
)
def test_integer_str(data_type, expected_result):
"""
Test `__str__` method of `Integer` data type.
"""
assert str(data_type) == expected_result
@pytest.mark.parametrize(
"data_type,expected_result",
[
pytest.param(
UnsignedInteger(1),
0,
),
pytest.param(
UnsignedInteger(3),
0,
),
pytest.param(
UnsignedInteger(5),
0,
),
pytest.param(
SignedInteger(1),
-1,
),
pytest.param(
SignedInteger(3),
-4,
),
pytest.param(
SignedInteger(5),
-16,
),
],
)
def test_integer_min(data_type, expected_result):
"""
Test `min` method of `Integer` data type.
"""
assert data_type.min() == expected_result
@pytest.mark.parametrize(
"data_type,expected_result",
[
pytest.param(
UnsignedInteger(1),
1,
),
pytest.param(
UnsignedInteger(3),
7,
),
pytest.param(
UnsignedInteger(5),
31,
),
pytest.param(
SignedInteger(1),
0,
),
pytest.param(
SignedInteger(3),
3,
),
pytest.param(
SignedInteger(5),
15,
),
],
)
def test_integer_max(data_type, expected_result):
"""
Test `max` method of `Integer` data type.
"""
assert data_type.max() == expected_result
@pytest.mark.parametrize(
"data_type,value,expected_result",
[
pytest.param(
UnsignedInteger(1),
-4,
False,
),
pytest.param(
UnsignedInteger(1),
-3,
False,
),
pytest.param(
UnsignedInteger(1),
-2,
False,
),
pytest.param(
UnsignedInteger(1),
-1,
False,
),
pytest.param(
UnsignedInteger(1),
0,
True,
),
pytest.param(
UnsignedInteger(1),
1,
True,
),
pytest.param(
UnsignedInteger(1),
2,
False,
),
pytest.param(
UnsignedInteger(1),
3,
False,
),
pytest.param(
UnsignedInteger(1),
4,
False,
),
pytest.param(
UnsignedInteger(2),
-4,
False,
),
pytest.param(
UnsignedInteger(2),
-3,
False,
),
pytest.param(
UnsignedInteger(2),
-2,
False,
),
pytest.param(
UnsignedInteger(2),
-1,
False,
),
pytest.param(
UnsignedInteger(2),
0,
True,
),
pytest.param(
UnsignedInteger(2),
1,
True,
),
pytest.param(
UnsignedInteger(2),
2,
True,
),
pytest.param(
UnsignedInteger(2),
3,
True,
),
pytest.param(
UnsignedInteger(2),
4,
False,
),
pytest.param(
UnsignedInteger(3),
-4,
False,
),
pytest.param(
UnsignedInteger(3),
-3,
False,
),
pytest.param(
UnsignedInteger(3),
-2,
False,
),
pytest.param(
UnsignedInteger(3),
-1,
False,
),
pytest.param(
UnsignedInteger(3),
0,
True,
),
pytest.param(
UnsignedInteger(3),
1,
True,
),
pytest.param(
UnsignedInteger(3),
2,
True,
),
pytest.param(
UnsignedInteger(3),
3,
True,
),
pytest.param(
UnsignedInteger(3),
4,
True,
),
pytest.param(
SignedInteger(1),
-4,
False,
),
pytest.param(
SignedInteger(1),
-3,
False,
),
pytest.param(
SignedInteger(1),
-2,
False,
),
pytest.param(
SignedInteger(1),
-1,
True,
),
pytest.param(
SignedInteger(1),
0,
True,
),
pytest.param(
SignedInteger(1),
1,
False,
),
pytest.param(
SignedInteger(1),
2,
False,
),
pytest.param(
SignedInteger(1),
3,
False,
),
pytest.param(
SignedInteger(1),
4,
False,
),
pytest.param(
SignedInteger(2),
-4,
False,
),
pytest.param(
SignedInteger(2),
-3,
False,
),
pytest.param(
SignedInteger(2),
-2,
True,
),
pytest.param(
SignedInteger(2),
-1,
True,
),
pytest.param(
SignedInteger(2),
0,
True,
),
pytest.param(
SignedInteger(2),
1,
True,
),
pytest.param(
SignedInteger(2),
2,
False,
),
pytest.param(
SignedInteger(2),
3,
False,
),
pytest.param(
SignedInteger(2),
4,
False,
),
pytest.param(
SignedInteger(3),
-4,
True,
),
pytest.param(
SignedInteger(3),
-3,
True,
),
pytest.param(
SignedInteger(3),
-2,
True,
),
pytest.param(
SignedInteger(3),
-1,
True,
),
pytest.param(
SignedInteger(3),
0,
True,
),
pytest.param(
SignedInteger(3),
1,
True,
),
pytest.param(
SignedInteger(3),
2,
True,
),
pytest.param(
SignedInteger(3),
3,
True,
),
pytest.param(
SignedInteger(3),
4,
False,
),
],
)
def test_integer_can_represent(data_type, value, expected_result):
"""
Test `can_represent` method of `Integer` data type.
"""
assert data_type.can_represent(value) == expected_result

View File

@@ -0,0 +1,77 @@
"""
Tests of utilities related to data types.
"""
import pytest
from concrete.numpy.dtypes import Float, SignedInteger, UnsignedInteger
from concrete.numpy.dtypes.utils import combine_dtypes
@pytest.mark.parametrize(
"dtypes,expected_result",
[
pytest.param(
[Float(64), Float(64)],
Float(64),
),
pytest.param(
[Float(32), Float(64)],
Float(64),
),
pytest.param(
[Float(16), Float(64)],
Float(64),
),
pytest.param(
[Float(32), Float(16)],
Float(32),
),
pytest.param(
[Float(16), Float(16)],
Float(16),
),
pytest.param(
[SignedInteger(5), Float(64)],
Float(64),
),
pytest.param(
[Float(32), SignedInteger(5)],
Float(32),
),
pytest.param(
[SignedInteger(5), Float(16)],
Float(16),
),
pytest.param(
[SignedInteger(5), SignedInteger(6)],
SignedInteger(6),
),
pytest.param(
[UnsignedInteger(5), UnsignedInteger(6)],
UnsignedInteger(6),
),
pytest.param(
[SignedInteger(5), UnsignedInteger(6)],
SignedInteger(7),
),
pytest.param(
[SignedInteger(5), UnsignedInteger(4)],
SignedInteger(5),
),
pytest.param(
[UnsignedInteger(6), SignedInteger(5)],
SignedInteger(7),
),
pytest.param(
[UnsignedInteger(4), SignedInteger(5)],
SignedInteger(5),
),
],
)
def test_combine_dtypes(dtypes, expected_result):
"""
Test `combine_dtypes` function.
"""
assert combine_dtypes(dtypes) == expected_result

View File

@@ -0,0 +1,3 @@
"""
Tests of execution.
"""

View File

@@ -0,0 +1,181 @@
"""
Tests of execution of add operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x: x + 42,
id="x + 42",
),
pytest.param(
lambda x: 42 + x,
id="42 + x",
),
pytest.param(
lambda x: x + np.array([1, 2, 3]),
id="x + [1, 2, 3]",
),
pytest.param(
lambda x: np.array([1, 2, 3]) + x,
id="[1, 2, 3] + x",
),
pytest.param(
lambda x: x + np.array([[1, 2, 3], [4, 5, 6]]),
id="x + [[1, 2, 3], [4, 5, 6]]",
),
pytest.param(
lambda x: np.array([[1, 2, 3], [4, 5, 6]]) + x,
id="[[1, 2, 3], [4, 5, 6]] + x",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 85], "status": "encrypted"},
},
{
"x": {"range": [0, 85], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 85], "status": "encrypted", "shape": (2, 3)},
},
{
"x": {"range": [-50, 10], "status": "encrypted"},
},
{
"x": {"range": [-50, 10], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [-50, 10], "status": "encrypted", "shape": (2, 3)},
},
{
"x": {"range": [0, 1000000], "status": "encrypted"},
},
{
"x": {"range": [0, 1000000], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 1000000], "status": "encrypted", "shape": (2, 3)},
},
],
)
def test_constant_add(function, parameters, helpers):
"""
Test add where one of the operators is a constant.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x, y: x + y,
id="x + y",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 60], "status": "clear"},
"y": {"range": [0, 60], "status": "encrypted"},
},
{
"x": {"range": [0, 60], "status": "encrypted"},
"y": {"range": [0, 60], "status": "clear"},
},
{
"x": {"range": [0, 60], "status": "encrypted"},
"y": {"range": [0, 60], "status": "encrypted"},
},
{
"x": {"range": [0, 60], "status": "clear", "shape": (3,)},
"y": {"range": [0, 60], "status": "encrypted"},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
"y": {"range": [0, 60], "status": "clear"},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
"y": {"range": [0, 60], "status": "encrypted"},
},
{
"x": {"range": [0, 60], "status": "clear"},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted"},
"y": {"range": [0, 60], "status": "clear", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted"},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "clear", "shape": (3,)},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
"y": {"range": [0, 60], "status": "clear", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "clear", "shape": (2, 1)},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (2, 1)},
"y": {"range": [0, 60], "status": "clear", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (2, 1)},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [-30, 30], "status": "encrypted", "shape": (3, 2)},
"y": {"range": [-30, 30], "status": "encrypted", "shape": (3, 2)},
},
],
)
def test_add(function, parameters, helpers):
"""
Test add where both of the operators are dynamic.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,61 @@
"""
Tests of execution of array operation.
"""
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function,parameters",
[
pytest.param(
lambda x: cnp.array([x, x + 1, 1]),
{
"x": {"range": [0, 10], "status": "encrypted", "shape": ()},
},
id="cnp.array([x, x + 1, 1])",
),
pytest.param(
lambda x, y: cnp.array([x, y]),
{
"x": {"range": [0, 10], "status": "encrypted", "shape": ()},
"y": {"range": [0, 10], "status": "clear", "shape": ()},
},
id="cnp.array([x, y])",
),
pytest.param(
lambda x, y: cnp.array([[x, y], [y, x]]),
{
"x": {"range": [0, 10], "status": "encrypted", "shape": ()},
"y": {"range": [0, 10], "status": "clear", "shape": ()},
},
id="cnp.array([[x, y], [y, x]])",
),
pytest.param(
lambda x, y, z: cnp.array([[x, 1], [y, 2], [z, 3]]),
{
"x": {"range": [0, 10], "status": "encrypted", "shape": ()},
"y": {"range": [0, 10], "status": "clear", "shape": ()},
"z": {"range": [0, 10], "status": "encrypted", "shape": ()},
},
id="cnp.array([[x, 1], [y, 2], [z, 3]])",
),
],
)
def test_array(function, parameters, helpers):
"""
Test array.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,62 @@
"""
Tests of execution of bitwise operations.
"""
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x, y: x & y,
id="x & y",
),
pytest.param(
lambda x, y: x | y,
id="x | y",
),
pytest.param(
lambda x, y: x ^ y,
id="x ^ y",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 255], "status": "encrypted"},
"y": {"range": [0, 255], "status": "encrypted"},
},
{
"x": {"range": [0, 7], "status": "encrypted"},
"y": {"range": [0, 7], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 7], "status": "encrypted", "shape": (3,)},
"y": {"range": [0, 7], "status": "encrypted"},
},
{
"x": {"range": [0, 7], "status": "encrypted", "shape": (3,)},
"y": {"range": [0, 7], "status": "encrypted", "shape": (3,)},
},
],
)
def test_bitwise(function, parameters, helpers):
"""
Test bitwise operations between encrypted integers.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,40 @@
"""
Tests of execution of broadcast to operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"from_shape,to_shape",
[
pytest.param((), (2,)),
pytest.param((), (2, 3)),
pytest.param((3,), (2, 3)),
pytest.param((3,), (4, 2, 3)),
pytest.param((1, 2), (4, 3, 2)),
pytest.param((3, 2), (4, 3, 2)),
pytest.param((3, 1), (4, 3, 5)),
pytest.param((3, 1, 4), (3, 2, 4)),
pytest.param((3, 1, 1), (5, 3, 1, 3)),
],
)
def test_broadcast_to(from_shape, to_shape, helpers):
"""
Test broadcast to.
"""
def function(x):
return np.broadcast_to(x, to_shape)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, {"x": "encrypted"})
inputset = [np.random.randint(0, 2**2, size=from_shape) for _ in range(100)]
circuit = compiler.compile(inputset, configuration)
sample = np.random.randint(0, 2**2, size=from_shape)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,161 @@
"""
Tests of execution of comparison operations.
"""
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x, y: x == y,
id="x == y",
),
pytest.param(
lambda x, y: x != y,
id="x != y",
),
pytest.param(
lambda x, y: x < y,
id="x < y",
),
pytest.param(
lambda x, y: x <= y,
id="x <= y",
),
pytest.param(
lambda x, y: x > y,
id="x > y",
),
pytest.param(
lambda x, y: x >= y,
id="x >= y",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 3], "status": "encrypted"},
"y": {"range": [0, 3], "status": "encrypted"},
},
{
"x": {"range": [0, 255], "status": "encrypted"},
"y": {"range": [0, 255], "status": "encrypted"},
},
{
"x": {"range": [-128, 127], "status": "encrypted"},
"y": {"range": [-128, 127], "status": "encrypted"},
},
{
"x": {"range": [-128, 127], "status": "encrypted"},
"y": {"range": [0, 255], "status": "encrypted"},
},
{
"x": {"range": [0, 255], "status": "encrypted"},
"y": {"range": [-128, 127], "status": "encrypted"},
},
{
"x": {"range": [-8, 7], "status": "encrypted"},
"y": {"range": [-8, 7], "status": "encrypted", "shape": (2,)},
},
{
"x": {"range": [-8, 7], "status": "encrypted", "shape": (2,)},
"y": {"range": [-8, 7], "status": "encrypted"},
},
{
"x": {"range": [-8, 7], "status": "encrypted", "shape": (2,)},
"y": {"range": [-8, 7], "status": "encrypted", "shape": (2,)},
},
],
)
def test_comparison(function, parameters, helpers):
"""
Test comparison operations between encrypted integers.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x, y: (x == y) + 200,
id="(x == y) + 200",
),
pytest.param(
lambda x, y: (x != y) + 200,
id="(x != y) + 200",
),
pytest.param(
lambda x, y: (x < y) + 200,
id="(x < y) + 200",
),
pytest.param(
lambda x, y: (x <= y) + 200,
id="(x <= y) + 200",
),
pytest.param(
lambda x, y: (x > y) + 200,
id="(x > y) + 200",
),
pytest.param(
lambda x, y: (x >= y) + 200,
id="(x >= y) + 200",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 15], "status": "encrypted"},
"y": {"range": [0, 15], "status": "encrypted"},
},
{
"x": {"range": [-8, 7], "status": "encrypted"},
"y": {"range": [-8, 7], "status": "encrypted"},
},
{
"x": {"range": [0, 15], "status": "encrypted"},
"y": {"range": [0, 15], "status": "encrypted", "shape": (2,)},
},
{
"x": {"range": [-8, 7], "status": "encrypted", "shape": (2,)},
"y": {"range": [-8, 7], "status": "encrypted"},
},
{
"x": {"range": [-10, 10], "status": "encrypted", "shape": (2,)},
"y": {"range": [-10, 10], "status": "encrypted", "shape": (2,)},
},
],
)
def test_optimized_comparison(function, parameters, helpers):
"""
Test comparison operations between encrypted integers with a single TLU.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,176 @@
"""
Tests of execution of concatenate operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function,parameters",
[
pytest.param(
lambda x, y: np.concatenate((x, y)),
{
"x": {"shape": (4, 2)},
"y": {"shape": (3, 2)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=0),
{
"x": {"shape": (4, 2)},
"y": {"shape": (3, 2)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=1),
{
"x": {"shape": (2, 4)},
"y": {"shape": (2, 3)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=-1),
{
"x": {"shape": (2, 4)},
"y": {"shape": (2, 3)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=-2),
{
"x": {"shape": (4, 2)},
"y": {"shape": (3, 2)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=None),
{
"x": {"shape": (3, 4)},
"y": {"shape": (2, 3)},
},
),
pytest.param(
lambda x, y, z: np.concatenate((x, y, z)),
{
"x": {"shape": (4, 2)},
"y": {"shape": (3, 2)},
"z": {"shape": (5, 2)},
},
),
pytest.param(
lambda x, y, z: np.concatenate((x, y, z), axis=0),
{
"x": {"shape": (4, 2)},
"y": {"shape": (3, 2)},
"z": {"shape": (5, 2)},
},
),
pytest.param(
lambda x, y, z: np.concatenate((x, y, z), axis=1),
{
"x": {"shape": (2, 4)},
"y": {"shape": (2, 3)},
"z": {"shape": (2, 5)},
},
),
pytest.param(
lambda x, y, z: np.concatenate((x, y, z), axis=-1),
{
"x": {"shape": (2, 4)},
"y": {"shape": (2, 3)},
"z": {"shape": (2, 5)},
},
),
pytest.param(
lambda x, y, z: np.concatenate((x, y, z), axis=-2),
{
"x": {"shape": (4, 2)},
"y": {"shape": (3, 2)},
"z": {"shape": (5, 2)},
},
),
pytest.param(
lambda x, y, z: np.concatenate((x, y, z), axis=None),
{
"x": {"shape": (3, 4)},
"y": {"shape": (2, 3)},
"z": {"shape": (5, 1)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y)),
{
"x": {"shape": (3, 4, 2)},
"y": {"shape": (5, 4, 2)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=0),
{
"x": {"shape": (3, 4, 2)},
"y": {"shape": (5, 4, 2)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=1),
{
"x": {"shape": (2, 4, 5)},
"y": {"shape": (2, 3, 5)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=2),
{
"x": {"shape": (2, 3, 4)},
"y": {"shape": (2, 3, 5)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=-1),
{
"x": {"shape": (2, 3, 4)},
"y": {"shape": (2, 3, 5)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=-2),
{
"x": {"shape": (2, 4, 5)},
"y": {"shape": (2, 3, 5)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=-3),
{
"x": {"shape": (3, 4, 2)},
"y": {"shape": (5, 4, 2)},
},
),
pytest.param(
lambda x, y: np.concatenate((x, y), axis=None),
{
"x": {"shape": (3, 4, 5)},
"y": {"shape": (5, 2, 3)},
},
),
],
)
def test_concatenate(function, parameters, helpers):
"""
Test concatenate.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,504 @@
"""
Tests of execution of convolution operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
import concrete.onnx as connx
from concrete.numpy.representation.node import Node
from concrete.numpy.tracing.tracer import Tracer
@pytest.mark.parametrize(
"input_shape,weight_shape, group",
[
pytest.param(
(1, 1, 4, 4),
(1, 1, 2, 2),
1,
),
pytest.param(
(4, 3, 4, 4),
(2, 3, 2, 2),
1,
),
pytest.param(
(1, 6, 4, 4),
(6, 1, 2, 2),
6,
),
],
)
@pytest.mark.parametrize(
"strides",
[
(2, 2),
],
)
@pytest.mark.parametrize(
"dilations",
[
(1, 1),
],
)
@pytest.mark.parametrize(
"has_bias",
[
True,
False,
],
)
def test_conv2d(input_shape, weight_shape, group, strides, dilations, has_bias, helpers):
"""
Test conv2d.
"""
configuration = helpers.configuration()
weight = np.random.randint(0, 4, size=weight_shape)
if has_bias:
bias = np.random.randint(0, 4, size=(weight_shape[0],))
else:
bias = None
@cnp.compiler({"x": "encrypted"})
def function(x):
return connx.conv(x, weight, bias, strides=strides, dilations=dilations, group=group)
inputset = [np.random.randint(0, 4, size=input_shape) for i in range(100)]
circuit = function.compile(inputset, configuration)
sample = np.random.randint(0, 4, size=input_shape)
helpers.check_execution(circuit, function, sample)
@pytest.mark.parametrize(
"input_shape,weight_shape,bias_shape,pads,strides,dilations,kernel_shape,group,auto_pad,"
"expected_error,expected_message",
[
pytest.param(
(1, 1, 4, 4),
(1, 1, 2, 2),
(1,),
(0, 0, 0, 0),
(1, 1),
(1, 1),
None,
1,
"VALID",
ValueError,
"auto_pad should be in {'NOTSET'}, but got 'VALID'",
),
pytest.param(
(1, 1, 1, 4),
(1, 1, 2, 2),
(1,),
(1, 0, 2, 0),
(1, 1),
(1, 1),
None,
1,
"NOTSET",
RuntimeError,
"padding should be the same for the beginning of the dimension and its end, but got "
"1 in the beginning, and 2 at the end for dimension 0",
),
pytest.param(
(1, 1, 4),
(1, 1, 2),
(1,),
(),
(1,),
(1,),
None,
1,
"NOTSET",
ValueError,
"pads should be of form (D_begin_pad, D_end_pad) when performing 1D convolution, "
"but it's ()",
),
pytest.param(
(1, 1, 4),
(1, 1, 2),
(1,),
(0, 0),
(),
(1,),
None,
1,
"NOTSET",
ValueError,
"strides should be of form (D_stride,) when performing 1D " "convolution, but it's ()",
),
pytest.param(
(1, 1, 4),
(1, 1, 2),
(1,),
(0, 0),
(1,),
(),
None,
1,
"NOTSET",
ValueError,
"dilations should be of form (D_dilation,) when performing 1D "
"convolution, but it's ()",
),
pytest.param(
(1, 1, 4, 4),
(1, 1, 2, 2),
(1,),
(),
(1, 1),
(1, 1),
None,
1,
"NOTSET",
ValueError,
"pads should be of form (height_begin_pad, width_begin_pad, "
"height_end_pad, width_end_pad) when performing 2D convolution, "
"but it's ()",
),
pytest.param(
(1, 1, 4, 4),
(1, 1, 2, 2),
(1,),
(0, 0, 0, 0),
(),
(1, 1),
None,
1,
"NOTSET",
ValueError,
"strides should be of form (height_stride, width_stride) when performing 2D "
"convolution, but it's ()",
),
pytest.param(
(1, 1, 4, 4),
(1, 1, 2, 2),
(1,),
(0, 0, 0, 0),
(1, 1),
(),
None,
1,
"NOTSET",
ValueError,
"dilations should be of form (height_dilation, width_dilation) when performing 2D "
"convolution, but it's ()",
),
pytest.param(
(1, 1, 4, 4, 4),
(1, 1, 2, 2, 4),
(1,),
(),
(1, 1, 1),
(1, 1, 1),
None,
1,
"NOTSET",
ValueError,
"pads should be of form (D_begin_pad, height_begin_pad, width_begin_pad, "
"D_end_pad, height_end_pad, width_end_pad) when performing 3D convolution, "
"but it's ()",
),
pytest.param(
(1, 1, 4, 4, 4),
(1, 1, 2, 2, 2),
(1,),
(0, 0, 0, 0, 0, 0),
(),
(1, 1, 1),
None,
1,
"NOTSET",
ValueError,
"strides should be of form (D_stride, height_stride, width_stride) when performing 3D "
"convolution, but it's ()",
),
pytest.param(
(1, 1, 4, 4, 4),
(1, 1, 2, 2, 2),
(1,),
(0, 0, 0, 0, 0, 0),
(1, 1, 1),
(),
None,
1,
"NOTSET",
ValueError,
"dilations should be of form (D_dilation, height_dilation, width_dilation) when "
"performing 3D convolution, but it's ()",
),
pytest.param(
(),
(1, 1, 2, 2),
(1,),
(0, 0, 0, 0),
(1, 1),
(1, 1),
None,
1,
"NOTSET",
ValueError,
"expected input x to have at least 3 dimensions (N, C, D1, ...), but got 0",
),
pytest.param(
(1, 1, 4, 4),
(),
(1,),
(0, 0, 0, 0),
(1, 1),
(1, 1),
None,
1,
"NOTSET",
ValueError,
"expected weight to have at least 3 dimensions (F, C / group, K1, ...), but got 0",
),
pytest.param(
(1, 1, 4, 4),
(1, 1, 2, 2),
(),
(0, 0, 0, 0),
(1, 1),
(1, 1),
None,
1,
"NOTSET",
ValueError,
"expected bias to have a single dimension (F,), but got 0",
),
pytest.param(
(1, 1, 4, 4),
(1, 1, 2, 2),
(1,),
(0, 0, 0, 0),
(1, 1),
(1, 1),
(1, 2),
1,
"NOTSET",
ValueError,
"expected kernel_shape to be (2, 2), but got (1, 2)",
),
pytest.param(
(1, 1, 4, 4),
(1, 1, 2, 2),
(1,),
(0, 0, 0, 0),
(1, 1),
(1, 1),
None,
None,
"NOTSET",
ValueError,
"expected group to be an integer > 0, but got None",
),
pytest.param(
(1, 1, 4, 4),
(1, 2, 2, 2),
(1,),
(0, 0, 0, 0),
(1, 1),
(1, 1),
None,
1,
"NOTSET",
ValueError,
"expected number of channel in weight to be 1.0 (C / group), but got 2",
),
pytest.param(
(1, 1, 4),
(1, 1, 2),
(1,),
(0, 0),
(1,),
(1,),
None,
1,
"NOTSET",
NotImplementedError,
"conv1d conversion to MLIR is not yet implemented",
),
pytest.param(
(1, 1, 4, 4, 4),
(1, 1, 2, 2, 2),
(1,),
(0, 0, 0, 0, 0, 0),
(1, 1, 1),
(1, 1, 1),
None,
1,
"NOTSET",
NotImplementedError,
"conv3d conversion to MLIR is not yet implemented",
),
pytest.param(
(1, 1, 4, 4, 4, 4),
(1, 1, 2, 2, 2, 2),
(1,),
(0, 0, 0, 0, 0, 0, 0, 0),
(1, 1, 1, 1),
(1, 1, 1, 1),
None,
1,
"NOTSET",
NotImplementedError,
"only 1D, 2D, and 3D convolutions are supported",
),
pytest.param(
(1, 2, 4, 4),
(1, 1, 2, 2),
(1,),
(0, 0, 0, 0),
(1, 1),
(1, 1),
None,
2,
"NOTSET",
ValueError,
"expected number of feature maps (1) to be a multiple of group (2)",
),
],
)
# pylint: disable=too-many-arguments
def test_bad_conv_compilation(
input_shape,
weight_shape,
bias_shape,
pads,
strides,
dilations,
kernel_shape,
group,
auto_pad,
expected_error,
expected_message,
helpers,
):
# pylint: enable=too-many-arguments
"""
Test conv with bad parameters.
"""
configuration = helpers.configuration()
weight = np.random.randint(0, 4, size=weight_shape)
if bias_shape is not None:
bias = np.random.randint(0, 4, size=bias_shape)
else:
bias = None
@cnp.compiler({"x": "encrypted"})
def function(x):
return connx.conv(
x,
weight,
bias=bias,
pads=pads,
strides=strides,
dilations=dilations,
kernel_shape=kernel_shape,
group=group,
auto_pad=auto_pad,
)
inputset = [np.random.randint(0, 4, size=input_shape) for i in range(100)]
with pytest.raises(expected_error) as excinfo:
function.compile(inputset, configuration)
# Get the root cause error
current_error = excinfo.value
cause = current_error.__cause__
while cause:
current_error = cause
cause = current_error.__cause__
assert str(current_error) == expected_message
@pytest.mark.parametrize(
"conv_func_name",
[
"conv",
223,
None,
],
)
@pytest.mark.parametrize(
"func",
[
# pylint: disable=protected-access
connx.convolution._evaluate_conv,
connx.convolution._trace_conv,
# pylint: enable=protected-access
],
)
def test_bad_conv_func_name(conv_func_name, func):
"""
Test invalid conv function name.
"""
with pytest.raises(AssertionError) as excinfo:
func(None, None, None, None, None, None, None, conv_func_name)
assert (
str(excinfo.value) == f"expected conv_func to be one of ['conv1d', 'conv2d', 'conv3d'], "
f"but got {conv_func_name}"
)
@pytest.mark.parametrize(
"x,weight,bias,expected_error,expected_message",
[
pytest.param(
np.array([1, 2, 3]),
"not same type as x",
None,
TypeError,
"expected weight to be of same type as x",
),
pytest.param(
np.array([1, 2, 3]),
np.array([1, 2, 3]),
"not same type as x",
TypeError,
"expected bias to be of same type as x",
),
pytest.param(
Tracer(Node.constant(np.array([1, 2, 3])), []),
"not same type as x",
None,
TypeError,
"expected weight to be of type Tracer or ndarray",
),
pytest.param(
Tracer(Node.constant(np.array([1, 2, 3])), []),
np.array([1, 2, 3]),
"not same type as x",
TypeError,
"expected bias to be of type Tracer or ndarray",
),
],
)
def test_inconsistent_input_types(
x,
weight,
bias,
expected_error,
expected_message,
):
"""
Test conv with inconsistent input types.
"""
with pytest.raises(expected_error) as excinfo:
connx.conv(
x,
weight,
bias=bias,
)
assert str(excinfo.value) == expected_message

View File

@@ -0,0 +1,323 @@
"""
Tests of execution of direct table lookup operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
def identity_table_lookup_generator(n):
"""
Get identity table lookup function.
"""
return lambda x: cnp.LookupTable(range(2**n))[x]
def random_table_lookup_1b(x):
"""
Lookup on a random table with 1-bit input.
"""
# fmt: off
table = cnp.LookupTable([10, 12])
# fmt: on
return table[x]
def random_table_lookup_2b(x):
"""
Lookup on a random table with 2-bit input.
"""
# fmt: off
table = cnp.LookupTable([3, 8, 22, 127])
# fmt: on
return table[x]
def random_table_lookup_3b(x):
"""
Lookup on a random table with 3-bit input.
"""
# fmt: off
table = cnp.LookupTable([30, 52, 125, 23, 17, 12, 90, 4])
# fmt: on
return table[x]
def random_table_lookup_4b(x):
"""
Lookup on a random table with 4-bit input.
"""
# fmt: off
table = cnp.LookupTable([30, 52, 125, 23, 17, 12, 90, 4, 21, 51, 22, 15, 53, 100, 75, 90])
# fmt: on
return table[x]
def random_table_lookup_5b(x):
"""
Lookup on a random table with 5-bit input.
"""
# fmt: off
table = cnp.LookupTable(
[
1, 5, 2, 3, 10, 2, 4, 8, 1, 12, 15, 12, 10, 1, 0, 2,
4, 3, 8, 7, 10, 11, 6, 13, 9, 0, 2, 1, 15, 11, 12, 5
]
)
# fmt: on
return table[x]
def random_table_lookup_6b(x):
"""
Lookup on a random table with 6-bit input.
"""
# fmt: off
table = cnp.LookupTable(
[
95, 74, 11, 83, 24, 116, 28, 75, 26, 85, 114, 121, 91, 123, 78, 69,
72, 115, 67, 5, 39, 11, 120, 88, 56, 43, 74, 16, 72, 85, 103, 92,
44, 115, 50, 56, 107, 77, 25, 71, 52, 45, 80, 35, 69, 8, 40, 87,
26, 85, 84, 53, 73, 95, 86, 22, 16, 45, 59, 112, 53, 113, 98, 116
]
)
# fmt: on
return table[x]
def random_table_lookup_7b(x):
"""
Lookup on a random table with 7-bit input.
"""
# fmt: off
table = cnp.LookupTable(
[
13, 58, 38, 58, 15, 15, 77, 86, 80, 94, 108, 27, 126, 60, 65, 95,
50, 79, 22, 97, 38, 60, 25, 48, 73, 112, 27, 45, 88, 20, 67, 17,
16, 6, 71, 60, 77, 43, 93, 40, 41, 31, 99, 122, 120, 40, 94, 13,
111, 44, 96, 62, 108, 91, 34, 90, 103, 58, 3, 103, 19, 69, 55, 108,
0, 111, 113, 0, 0, 73, 22, 52, 81, 2, 88, 76, 36, 121, 97, 121,
123, 79, 82, 120, 12, 65, 54, 101, 90, 52, 84, 106, 23, 15, 110, 79,
85, 101, 30, 61, 104, 35, 81, 30, 98, 44, 111, 32, 68, 18, 45, 123,
84, 80, 68, 27, 31, 38, 126, 61, 51, 7, 49, 37, 63, 114, 22, 18,
]
)
# fmt: on
return table[x]
def negative_identity_table_lookup_generator(n):
"""
Get negative identity table lookup function.
"""
return lambda x: cnp.LookupTable([-i for i in range(2**n)])[x]
@pytest.mark.parametrize(
"bits,function",
[
pytest.param(1, identity_table_lookup_generator(1)),
pytest.param(2, identity_table_lookup_generator(2)),
pytest.param(3, identity_table_lookup_generator(3)),
pytest.param(4, identity_table_lookup_generator(4)),
pytest.param(5, identity_table_lookup_generator(5)),
pytest.param(6, identity_table_lookup_generator(6)),
pytest.param(7, identity_table_lookup_generator(7)),
pytest.param(1, random_table_lookup_1b),
pytest.param(2, random_table_lookup_2b),
pytest.param(3, random_table_lookup_3b),
pytest.param(4, random_table_lookup_4b),
pytest.param(5, random_table_lookup_5b),
pytest.param(6, random_table_lookup_6b),
pytest.param(7, random_table_lookup_7b),
pytest.param(1, negative_identity_table_lookup_generator(1)),
pytest.param(2, negative_identity_table_lookup_generator(2)),
pytest.param(3, negative_identity_table_lookup_generator(3)),
pytest.param(4, negative_identity_table_lookup_generator(4)),
pytest.param(5, negative_identity_table_lookup_generator(5)),
pytest.param(6, negative_identity_table_lookup_generator(6)),
],
)
def test_direct_table_lookup(bits, function, helpers):
"""
Test direct table lookup.
"""
configuration = helpers.configuration()
# scalar
# ------
compiler = cnp.Compiler(function, {"x": "encrypted"})
inputset = range(2**bits)
circuit = compiler.compile(inputset, configuration)
sample = int(np.random.randint(0, 2**bits))
helpers.check_execution(circuit, function, sample)
# tensor
# ------
compiler = cnp.Compiler(function, {"x": "encrypted"})
inputset = [np.random.randint(0, 2**bits, size=(3, 2)) for _ in range(100)]
circuit = compiler.compile(inputset, configuration)
sample = np.random.randint(0, 2**bits, size=(3, 2))
helpers.check_execution(circuit, function, sample)
# negative scalar
# ---------------
compiler = cnp.Compiler(function, {"x": "encrypted"})
inputset = range(-(2 ** (bits - 1)), 2 ** (bits - 1))
circuit = compiler.compile(inputset, configuration)
sample = int(np.random.randint(-(2 ** (bits - 1)), 2 ** (bits - 1)))
helpers.check_execution(circuit, function, sample)
# negative tensor
# ---------------
compiler = cnp.Compiler(function, {"x": "encrypted"})
inputset = [
np.random.randint(-(2 ** (bits - 1)), 2 ** (bits - 1), size=(3, 2)) for _ in range(100)
]
circuit = compiler.compile(inputset, configuration)
sample = np.random.randint(-(2 ** (bits - 1)), 2 ** (bits - 1), size=(3, 2))
helpers.check_execution(circuit, function, sample)
def test_direct_multi_table_lookup(helpers):
"""
Test direct multi table lookup.
"""
configuration = helpers.configuration()
square = cnp.LookupTable([i * i for i in range(4)])
cube = cnp.LookupTable([i * i * i for i in range(4)])
table = cnp.LookupTable(
[
[square, cube],
[cube, square],
[square, cube],
]
)
def function(x):
return table[x]
compiler = cnp.Compiler(function, {"x": "encrypted"})
inputset = [np.random.randint(0, 2**2, size=(3, 2)) for _ in range(100)]
circuit = compiler.compile(inputset, configuration)
sample = np.random.randint(0, 2**2, size=(3, 2))
helpers.check_execution(circuit, function, sample)
def test_bad_direct_table_lookup(helpers):
"""
Test direct table lookup with bad parameters.
"""
configuration = helpers.configuration()
# empty table
# -----------
with pytest.raises(ValueError) as excinfo:
cnp.LookupTable([])
assert str(excinfo.value) == "LookupTable cannot be constructed with []"
# invalid table
# -------------
with pytest.raises(ValueError) as excinfo:
cnp.LookupTable([[0, 1], [2, 3]])
assert str(excinfo.value) == "LookupTable cannot be constructed with [[0, 1], [2, 3]]"
# invalid multi table
# -------------------
with pytest.raises(ValueError) as excinfo:
cnp.LookupTable(["abc", 3.2])
assert str(excinfo.value) == "LookupTable cannot be constructed with ['abc', 3.2]"
# simulation with float value
# ---------------------------
with pytest.raises(ValueError) as excinfo:
random_table_lookup_3b(1.1)
assert str(excinfo.value) == "LookupTable cannot be looked up with 1.1"
# simulation with invalid shape
# -----------------------------
square = cnp.LookupTable([i * i for i in range(4)])
cube = cnp.LookupTable([i * i * i for i in range(4)])
table = cnp.LookupTable(
[
[square, cube],
[cube, square],
[square, cube],
]
)
with pytest.raises(ValueError) as excinfo:
_ = table[np.array([1, 2])]
assert str(excinfo.value) == "LookupTable of shape (3, 2) cannot be looked up with [1 2]"
# compilation with float value
# ----------------------------
compiler = cnp.Compiler(random_table_lookup_3b, {"x": "encrypted"})
inputset = [1.5]
with pytest.raises(ValueError) as excinfo:
compiler.compile(inputset, configuration)
assert str(excinfo.value) == "LookupTable cannot be looked up with EncryptedScalar<float64>"
# compilation with invalid shape
# ------------------------------
compiler = cnp.Compiler(lambda x: table[x], {"x": "encrypted"})
inputset = [10, 5, 6, 2]
with pytest.raises(ValueError) as excinfo:
compiler.compile(inputset, configuration)
assert str(excinfo.value) == (
"LookupTable of shape (3, 2) cannot be looked up with EncryptedScalar<uint4>"
)

View File

@@ -0,0 +1,47 @@
"""
Tests of execution of dot operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"size",
[1, 4, 6, 10],
)
def test_dot(size, helpers):
"""
Test dot.
"""
configuration = helpers.configuration()
bound = int(np.floor(np.sqrt(127 / size)))
cst = np.random.randint(0, bound, size=(size,))
@cnp.compiler({"x": "encrypted"})
def left_function(x):
return np.dot(x, cst)
@cnp.compiler({"x": "encrypted"})
def right_function(x):
return np.dot(cst, x)
@cnp.compiler({"x": "encrypted"})
def method(x):
return x.dot(cst)
inputset = [np.random.randint(0, bound, size=(size,)) for i in range(100)]
left_function_circuit = left_function.compile(inputset, configuration)
right_function_circuit = right_function.compile(inputset, configuration)
method_circuit = method.compile(inputset, configuration)
sample = np.random.randint(0, bound, size=(size,))
helpers.check_execution(left_function_circuit, left_function, sample)
helpers.check_execution(right_function_circuit, right_function, sample)
helpers.check_execution(method_circuit, method, sample)

View File

@@ -0,0 +1,30 @@
"""
Tests of execution of iteration of tracer.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize("shape", [(3,), (3, 2), (3, 2, 4)])
def test_iter(shape, helpers):
"""
Test iteration of tracers.
"""
def function(x):
result = cnp.zeros(x.shape[1:])
for value in x:
result += value
return result
configuration = helpers.configuration()
compiler = cnp.Compiler(function, {"x": "encrypted"})
inputset = [np.random.randint(0, 2**2, size=shape) for _ in range(100)]
circuit = compiler.compile(inputset, configuration)
sample = np.random.randint(0, 2**2, size=shape)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,158 @@
"""
Tests of execution of matmul operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"lhs_shape,rhs_shape,bounds",
[
pytest.param(
(3, 2),
(2, 3),
(0, 3),
),
pytest.param(
(1, 2),
(2, 1),
(0, 3),
),
pytest.param(
(3, 3),
(3, 3),
(0, 3),
),
pytest.param(
(2, 1),
(1, 2),
(0, 7),
),
pytest.param(
(2,),
(2,),
(0, 7),
),
pytest.param(
(5, 5),
(5,),
(0, 3),
),
pytest.param(
(5,),
(5, 5),
(0, 3),
),
pytest.param(
(5,),
(5, 3),
(0, 3),
),
pytest.param(
(5, 3),
(3,),
(0, 3),
),
pytest.param(
(5,),
(4, 5, 3),
(0, 5),
),
pytest.param(
(4, 5, 3),
(3,),
(0, 5),
),
pytest.param(
(5,),
(2, 4, 5, 3),
(0, 5),
),
pytest.param(
(2, 4, 5, 3),
(3,),
(0, 5),
),
pytest.param(
(5, 4, 3),
(3, 2),
(0, 5),
),
pytest.param(
(4, 3),
(5, 3, 2),
(0, 5),
),
pytest.param(
(2, 5, 4, 3),
(3, 2),
(0, 5),
),
pytest.param(
(5, 4, 3),
(1, 3, 2),
(0, 5),
),
pytest.param(
(1, 4, 3),
(5, 3, 2),
(0, 5),
),
pytest.param(
(5, 4, 3),
(2, 1, 3, 2),
(0, 5),
),
pytest.param(
(2, 1, 4, 3),
(5, 3, 2),
(0, 5),
),
],
)
def test_matmul(lhs_shape, rhs_shape, bounds, helpers):
"""
Test matmul.
"""
configuration = helpers.configuration()
minimum, maximum = bounds
lhs_cst = list(np.random.randint(minimum, maximum, size=lhs_shape))
rhs_cst = list(np.random.randint(minimum, maximum, size=rhs_shape))
@cnp.compiler({"x": "encrypted"})
def lhs_operator(x):
return x @ rhs_cst
@cnp.compiler({"x": "encrypted"})
def rhs_operator(x):
return lhs_cst @ x
@cnp.compiler({"x": "encrypted"})
def lhs_function(x):
return np.matmul(x, rhs_cst)
@cnp.compiler({"x": "encrypted"})
def rhs_function(x):
return np.matmul(lhs_cst, x)
lhs_inputset = [np.random.randint(minimum, maximum, size=lhs_shape) for i in range(100)]
rhs_inputset = [np.random.randint(minimum, maximum, size=rhs_shape) for i in range(100)]
lhs_operator_circuit = lhs_operator.compile(lhs_inputset, configuration)
rhs_operator_circuit = rhs_operator.compile(rhs_inputset, configuration)
lhs_function_circuit = lhs_function.compile(lhs_inputset, configuration)
rhs_function_circuit = rhs_function.compile(rhs_inputset, configuration)
lhs_sample = np.random.randint(minimum, maximum, size=lhs_shape)
rhs_sample = np.random.randint(minimum, maximum, size=rhs_shape)
helpers.check_execution(lhs_operator_circuit, lhs_operator, lhs_sample)
helpers.check_execution(rhs_operator_circuit, rhs_operator, rhs_sample)
helpers.check_execution(lhs_function_circuit, lhs_function, lhs_sample)
helpers.check_execution(rhs_function_circuit, rhs_function, rhs_sample)

View File

@@ -0,0 +1,393 @@
"""
Tests of execution of maxpool operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
import concrete.onnx as connx
@pytest.mark.parametrize(
"operation,sample_input,expected_output",
[
pytest.param(
{"kernel_shape": (3,)},
[1, 2, 2, 3, 2, 2, 2, 4, 1, 5, 2, 6],
[2, 3, 3, 3, 2, 4, 4, 5, 5, 6],
),
pytest.param(
{"kernel_shape": (3,), "strides": (2,)},
[1, 2, 2, 3, 2, 2, 2, 4, 1, 5, 2, 6, 7],
[2, 3, 2, 4, 5, 7],
),
pytest.param(
{
"kernel_shape": (2, 2),
},
[
[3, 1, 2],
[1, 1, 1],
[2, 3, 4],
[4, 1, 2],
],
[
[3, 2],
[3, 4],
[4, 4],
],
),
pytest.param(
{
"kernel_shape": (2, 2),
"strides": (2, 1),
},
[
[3, 1, 2],
[1, 1, 1],
[2, 3, 4],
[4, 1, 2],
],
[
[3, 2],
[4, 4],
],
),
],
)
def test_maxpool(
operation,
sample_input,
expected_output,
helpers,
):
"""
Test maxpool.
"""
sample_input = np.expand_dims(np.array(sample_input), axis=(0, 1))
expected_output = np.expand_dims(np.array(expected_output), axis=(0, 1))
assert np.array_equal(connx.maxpool(sample_input, **operation), expected_output)
@cnp.compiler({"x": "encrypted"})
def function(x):
return connx.maxpool(x, **operation)
graph = function.trace([sample_input], helpers.configuration())
assert np.array_equal(graph(sample_input), expected_output)
@pytest.mark.parametrize(
"input_shape,operation,expected_error,expected_message",
[
pytest.param(
(10, 10),
{
"kernel_shape": (),
},
ValueError,
"Expected input to have at least 3 dimensions (N, C, D1, ...) but it only has 2",
),
pytest.param(
(1, 1, 5, 4, 3, 2),
{
"kernel_shape": (),
},
NotImplementedError,
"4D maximum pooling is not supported yet",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": "",
},
TypeError,
"Expected kernel_shape to be a tuple or a list but it's str",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": ["0"],
},
TypeError,
"Expected kernel_shape to consist of integers but it has an element of type str",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (3,),
},
ValueError,
"Expected kernel_shape to have 2 elements but it has 1",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"strides": "",
},
TypeError,
"Expected strides to be a tuple or a list but it's str",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"strides": ["0"],
},
TypeError,
"Expected strides to consist of integers but it has an element of type str",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"strides": (3,),
},
ValueError,
"Expected strides to have 2 elements but it has 1",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"auto_pad": True,
},
TypeError,
"Expected auto_pad to be of type str but it's bool",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"auto_pad": "YES_PLEASE",
},
ValueError,
"Expected auto_pad to be one of NOTSET, SAME_LOWER, SAME_UPPER, VALID "
"but it's YES_PLEASE",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"auto_pad": "VALID",
},
NotImplementedError,
"Desired auto_pad of VALID is not supported yet",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"pads": "",
},
TypeError,
"Expected pads to be a tuple or a list but it's str",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"pads": ["0"],
},
TypeError,
"Expected pads to consist of integers but it has an element of type str",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"pads": (3,),
},
ValueError,
"Expected pads to have 4 elements but it has 1",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"pads": (1, 1, 2, 2),
},
NotImplementedError,
"Desired pads of (1, 1, 2, 2) is not supported yet because of uneven padding",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"dilations": "",
},
TypeError,
"Expected dilations to be a tuple or a list but it's str",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"dilations": ["0"],
},
TypeError,
"Expected dilations to consist of integers but it has an element of type str",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"dilations": (3,),
},
ValueError,
"Expected dilations to have 2 elements but it has 1",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"ceil_mode": None,
},
TypeError,
"Expected ceil_mode to be of type int but it's NoneType",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"ceil_mode": 10,
},
ValueError,
"Expected ceil_mode to be one of 0, 1 but it's 10",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"ceil_mode": 1,
},
NotImplementedError,
"Desired ceil_mode of 1 is not supported yet",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"storage_order": None,
},
TypeError,
"Expected storage_order to be of type int but it's NoneType",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"storage_order": 10,
},
ValueError,
"Expected storage_order to be one of 0, 1 but it's 10",
),
pytest.param(
(1, 1, 5, 4),
{
"kernel_shape": (2, 3),
"storage_order": 1,
},
NotImplementedError,
"Desired storage_order of 1 is not supported yet",
),
],
)
def test_bad_maxpool(
input_shape,
operation,
expected_error,
expected_message,
helpers,
):
"""
Test maxpool with bad parameters.
"""
with pytest.raises(expected_error) as excinfo:
connx.maxpool(np.random.randint(0, 10, size=input_shape), **operation)
helpers.check_str(expected_message, str(excinfo.value))
def test_bad_maxpool_special(helpers):
"""
Test maxpool with bad parameters for special cases.
"""
# compile
# -------
@cnp.compiler({"x": "encrypted"})
def not_compilable(x):
return connx.maxpool(x, kernel_shape=(4, 3))
inputset = [np.random.randint(0, 10, size=(1, 1, 10, 10)) for i in range(100)]
with pytest.raises(NotImplementedError) as excinfo:
not_compilable.compile(inputset, helpers.configuration())
helpers.check_str("MaxPool operation cannot be compiled yet", str(excinfo.value))
# clear input
# -----------
@cnp.compiler({"x": "clear"})
def clear_input(x):
return connx.maxpool(x, kernel_shape=(4, 3, 2))
inputset = [np.zeros((1, 1, 10, 10, 10), dtype=np.int64)]
with pytest.raises(RuntimeError) as excinfo:
clear_input.compile(inputset, helpers.configuration())
helpers.check_str(
# pylint: disable=line-too-long
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # ClearTensor<uint1, shape=(1, 1, 10, 10, 10)> ∈ [0, 0]
%1 = maxpool(%0, kernel_shape=(4, 3, 2), strides=(1, 1, 1), pads=(0, 0, 0, 0, 0, 0), dilations=(1, 1, 1), ceil_mode=False) # ClearTensor<uint1, shape=(1, 1, 7, 8, 9)> ∈ [0, 0]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only encrypted maxpool is supported
return %1
""".strip(), # noqa: E501
# pylint: enable=line-too-long
str(excinfo.value),
)
# badly typed ndarray input
# -------------------------
with pytest.raises(TypeError) as excinfo:
connx.maxpool(np.array([{}, None]), ())
helpers.check_str(
# pylint: disable=line-too-long
"""
Expected input elements to be of type np.integer, np.floating, or np.bool_ but it's dtype[object_]
""".strip(), # noqa: E501
# pylint: enable=line-too-long
str(excinfo.value),
)
# badly typed input
# -----------------
with pytest.raises(TypeError) as excinfo:
connx.maxpool("", ())
helpers.check_str(
# pylint: disable=line-too-long
"""
Expected input to be of type np.ndarray or Tracer but it's str
""".strip(), # noqa: E501
# pylint: enable=line-too-long
str(excinfo.value),
)

View File

@@ -0,0 +1,76 @@
"""
Tests of execution of mul operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x: x * 3,
id="x * 3",
),
pytest.param(
lambda x: 3 * x,
id="3 * x",
),
pytest.param(
lambda x: np.dot(x, 3),
id="np.dot(x, 3)",
),
pytest.param(
lambda x: np.dot(3, x),
id="np.dot(3, x)",
),
pytest.param(
lambda x: x * np.array([1, 2, 3]),
id="x * [1, 2, 3]",
),
pytest.param(
lambda x: np.array([1, 2, 3]) * x,
id="[1, 2, 3] * x",
),
pytest.param(
lambda x: x * np.array([[1, 2, 3], [3, 1, 2]]),
id="x * [[1, 2, 3], [3, 1, 2]]",
),
pytest.param(
lambda x: np.array([[1, 2, 3], [3, 1, 2]]) * x,
id="[[1, 2, 3], [3, 1, 2]] * x",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 40], "status": "encrypted"},
},
{
"x": {"range": [0, 40], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 40], "status": "encrypted", "shape": (2, 3)},
},
],
)
def test_constant_mul(function, parameters, helpers):
"""
Test mul where one of the operators is a constant.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,52 @@
"""
Tests of execution of neg operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 64], "status": "encrypted"},
},
{
"x": {"range": [0, 64], "status": "encrypted", "shape": (3, 2)},
},
{
"x": {"range": [-63, 0], "status": "encrypted"},
},
{
"x": {"range": [-63, 0], "status": "encrypted", "shape": (3, 2)},
},
],
)
def test_neg(parameters, helpers):
"""
Test neg.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
@cnp.compiler(parameter_encryption_statuses)
def operator(x):
return -x
@cnp.compiler(parameter_encryption_statuses)
def function(x):
return np.negative(x)
inputset = helpers.generate_inputset(parameters)
operator_circuit = operator.compile(inputset, configuration)
function_circuit = function.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(operator_circuit, operator, sample)
helpers.check_execution(function_circuit, function, sample)

View File

@@ -0,0 +1,49 @@
"""
Tests of execution of ones operation.
"""
import random
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x: cnp.one() + x,
id="cnp.one() + x",
),
pytest.param(
lambda x: cnp.ones(()) + x,
id="cnp.ones(()) + x",
),
pytest.param(
lambda x: cnp.ones(10) + x,
id="cnp.ones(10) + x",
),
pytest.param(
lambda x: cnp.ones((10,)) + x,
id="cnp.ones((10,)) + x",
),
pytest.param(
lambda x: cnp.ones((3, 2)) + x,
id="cnp.ones((3, 2)) + x",
),
],
)
def test_ones(function, helpers):
"""
Test ones.
"""
configuration = helpers.configuration()
compiler = cnp.Compiler(function, {"x": "encrypted"})
inputset = range(10)
circuit = compiler.compile(inputset, configuration)
sample = random.randint(0, 11)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,902 @@
"""
Tests of execution of operations converted to table lookups.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
def fusable_with_bigger_search(x, y):
"""
Fusable function that requires a single iteration for fusing.
"""
x = x + 1
x_1 = x.astype(np.int64)
x_1 = x_1 + 1.5
x_2 = x.astype(np.int64)
x_2 = x_2 + 3.4
add = x_1 + x_2
add_int = add.astype(np.int64)
return add_int + y
def fusable_with_bigger_search_needs_second_iteration(x, y):
"""
Fusable function that requires more than one iteration for fusing.
"""
x = x + 1
x = x + 0.5
x = np.cos(x)
x_1 = x.astype(np.int64)
x_1 = x_1 + 1.5
x_p = x + 1
x_p2 = x_p + 1
x_2 = (x_p + x_p2).astype(np.int64)
x_2 = x_2 + 3.4
add = x_1 + x_2
add_int = add.astype(np.int64)
return add_int + y
def fusable_with_one_of_the_start_nodes_is_lca_generator():
"""
Generator of a fusable function that has one of the start nodes as lca.
"""
# pylint: disable=invalid-name,too-many-locals,too-many-statements
def subgraph_18(x):
t0 = 0
t1 = 3
t2 = 2
t3 = 2.4688520431518555
t4 = 2.4688520431518555
t5 = x
t6 = np.multiply(t4, t5)
t7 = np.true_divide(t6, t3)
t8 = np.add(t7, t2)
t9 = np.rint(t8)
t10 = np.clip(t9, t0, t1)
t11 = t10.astype(np.int64)
return t11
def subgraph_24(x):
t0 = 0
t1 = [0.15588106, -0.01305565]
t2 = 1.3664466152828822
t3 = [[4, -4]]
t4 = 0
t5 = x
t6 = t5.astype(np.float32)
t7 = np.add(t6, t4)
t8 = np.add(t7, t3)
t9 = np.multiply(t2, t8)
t10 = np.add(t1, t9)
t11 = np.greater(t10, t0)
return t11
cst0 = np.random.randint(-2, 2, size=(10, 2))
cst1 = np.random.randint(0, 2, size=(10, 1))
def function(x):
t0 = 0
t1 = 3
t2 = 1
t3 = 1.2921873902965313
t4 = 1.0507009873554805
t5 = 1
t6 = 1.7580993408473766
t7 = [0.15588106, -0.01305565]
t8 = 1
t9 = 1.3664466152828822
t10 = [[4, -4]]
t11 = 0
t12 = cst0
t13 = 0
t14 = cst1
t15 = x
t16 = -2
t17 = np.add(t15, t16)
t18 = subgraph_18(t17)
t19 = np.matmul(t18, t12)
t20 = np.matmul(t18, t14)
t21 = np.multiply(t13, t20)
t22 = np.add(t19, t21)
t23 = t22.astype(np.float32)
t24 = subgraph_24(t22)
t25 = np.add(t23, t11)
t26 = np.subtract(t5, t24)
t27 = np.add(t25, t10)
t28 = np.multiply(t9, t27)
t29 = np.add(t7, t28)
t30 = np.multiply(t4, t29)
t31 = np.exp(t29)
t32 = np.multiply(t24, t30)
t33 = np.subtract(t31, t8)
t34 = np.multiply(t6, t33)
t35 = np.multiply(t26, t34)
t36 = np.add(t32, t35)
t37 = np.true_divide(t36, t3)
t38 = np.add(t37, t2)
t39 = np.rint(t38)
t40 = np.clip(t39, t0, t1)
t41 = t40.astype(np.int64)
return t41
return function
# pylint: enable=invalid-name,too-many-locals,too-many-statements
def fusable_with_hard_to_find_lca(x):
"""
Fusable function that requires harder lca search.
"""
a = x * 3
b = x // 3
c = a + b
return ((np.sin(a) ** 2) + (np.cos(c) ** 2)).round().astype(np.int64)
def fusable_with_hard_to_find_lca_used_twice(x):
"""
Fusable function that uses `fusable_with_hard_to_find_lca` twice.
"""
a = x @ np.array([[3, 1], [4, 2]])
b = x @ np.array([[1, 2], [3, 4]])
a = fusable_with_hard_to_find_lca(a)
b = fusable_with_hard_to_find_lca(b)
return a + b
def fusable_additional_1(x):
"""
Another fusable function for additional safety.
"""
a = x.astype(np.float64) * 3.0
b = x + 1
c = a.astype(np.int64)
return (a + b + c).astype(np.int64)
def fusable_additional_2(x):
"""
Another fusable function for additional safety.
"""
a = x.astype(np.float64) / 3.0
b = x + 1
c = a * a
return (a + b + c).astype(np.int64)
def deterministic_unary_function(x):
"""
An example deterministic unary function.
"""
def per_element(element):
result = 0
for i in range(element):
result += i
return result
return np.vectorize(per_element)(x)
@pytest.mark.parametrize(
"function,parameters",
[
pytest.param(
lambda x: x // 3,
{
"x": {"status": "encrypted", "range": [0, 127]},
},
id="x // 3",
),
pytest.param(
lambda x: 127 // x,
{
"x": {"status": "encrypted", "range": [1, 127]},
},
id="127 // x",
),
pytest.param(
lambda x: (x / 3).astype(np.int64),
{
"x": {"status": "encrypted", "range": [0, 127]},
},
id="(x / 3).astype(np.int64)",
),
pytest.param(
lambda x: (127 / x).astype(np.int64),
{
"x": {"status": "encrypted", "range": [1, 127]},
},
id="(127 / x).astype(np.int64)",
),
pytest.param(
lambda x: x**2,
{
"x": {"status": "encrypted", "range": [0, 11]},
},
id="x ** 2",
),
pytest.param(
lambda x: 2**x,
{
"x": {"status": "encrypted", "range": [0, 6]},
},
id="2 ** x",
),
pytest.param(
lambda x: x % 10,
{
"x": {"status": "encrypted", "range": [0, 127]},
},
id="x % 10",
),
pytest.param(
lambda x: 121 % x,
{
"x": {"status": "encrypted", "range": [1, 127]},
},
id="121 % x",
),
pytest.param(
lambda x: +x,
{
"x": {"status": "encrypted", "range": [0, 127]},
},
id="+x",
),
pytest.param(
lambda x: abs(42 - x),
{
"x": {"status": "encrypted", "range": [0, 84]},
},
id="abs(64 - x)",
),
pytest.param(
lambda x: ~x,
{
"x": {"status": "encrypted", "range": [0, 16]},
},
id="~x",
),
pytest.param(
lambda x: x & 10,
{
"x": {"status": "encrypted", "range": [0, 16]},
},
id="x & 10",
),
pytest.param(
lambda x: 5 & x,
{
"x": {"status": "encrypted", "range": [0, 16]},
},
id="5 & x",
),
pytest.param(
lambda x: x | 6,
{
"x": {"status": "encrypted", "range": [0, 16]},
},
id="x | 6",
),
pytest.param(
lambda x: 11 | x,
{
"x": {"status": "encrypted", "range": [0, 16]},
},
id="11 | x",
),
pytest.param(
lambda x: x ^ 9,
{
"x": {"status": "encrypted", "range": [0, 16]},
},
id="x ^ 9",
),
pytest.param(
lambda x: 13 ^ x,
{
"x": {"status": "encrypted", "range": [0, 16]},
},
id="13 ^ x",
),
pytest.param(
lambda x: x << 2,
{
"x": {"status": "encrypted", "range": [0, 16]},
},
id="x << 2",
),
pytest.param(
lambda x: 2 << x,
{
"x": {"status": "encrypted", "range": [0, 5]},
},
id="2 << x",
),
pytest.param(
lambda x: x >> 2,
{
"x": {"status": "encrypted", "range": [0, 120]},
},
id="x >> 2",
),
pytest.param(
lambda x: 120 >> x,
{
"x": {"status": "encrypted", "range": [0, 16]},
},
id="120 >> x",
),
pytest.param(
lambda x: x > 50,
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="x > 50",
),
pytest.param(
lambda x: 50 > x, # pylint: disable=misplaced-comparison-constant
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="50 > x",
),
pytest.param(
lambda x: x < 50,
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="x < 50",
),
pytest.param(
lambda x: 50 < x, # pylint: disable=misplaced-comparison-constant
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="50 < x",
),
pytest.param(
lambda x: x >= 50,
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="x >= 50",
),
pytest.param(
lambda x: 50 >= x, # pylint: disable=misplaced-comparison-constant
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="50 >= x",
),
pytest.param(
lambda x: x <= 50,
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="x <= 50",
),
pytest.param(
lambda x: 50 <= x, # pylint: disable=misplaced-comparison-constant
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="50 <= x",
),
pytest.param(
lambda x: x == 50,
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="x == 50",
),
pytest.param(
lambda x: 50 == x, # pylint: disable=misplaced-comparison-constant
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="50 == x",
),
pytest.param(
lambda x: x != 50,
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="x != 50",
),
pytest.param(
lambda x: 50 != x, # pylint: disable=misplaced-comparison-constant
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="50 != x",
),
pytest.param(
lambda x: x.clip(5, 10),
{
"x": {"status": "encrypted", "range": [0, 15]},
},
id="x.clip(5, 10)",
),
pytest.param(
lambda x: (60 * np.sin(x)).astype(np.int64) + 60,
{
"x": {"status": "encrypted", "range": [0, 127]},
},
id="(60 * np.sin(x)).astype(np.int64) + 60",
),
pytest.param(
lambda x: ((np.sin(x) ** 2) + (np.cos(x) ** 2)).astype(np.int64),
{
"x": {"status": "encrypted", "range": [0, 127]},
},
id="((np.sin(x) ** 2) + (np.cos(x) ** 2)).astype(np.int64)",
),
pytest.param(
lambda x: np.maximum(x, [[10, 20], [30, 40], [50, 60]]),
{
"x": {"status": "encrypted", "range": [0, 127], "shape": (3, 2)},
},
id="np.maximum(x, [[10, 20], [30, 40], [50, 60]])",
),
pytest.param(
fusable_with_bigger_search,
{
"x": {"status": "encrypted", "range": [5, 10]},
"y": {"status": "encrypted", "range": [5, 10]},
},
id="fusable_with_bigger_search",
),
pytest.param(
fusable_with_bigger_search_needs_second_iteration,
{
"x": {"status": "encrypted", "range": [5, 10]},
"y": {"status": "encrypted", "range": [5, 10]},
},
id="fusable_with_bigger_search_needs_second_iteration",
),
pytest.param(
fusable_with_one_of_the_start_nodes_is_lca_generator(),
{
"x": {"status": "encrypted", "range": [0, 4], "shape": (1, 10)},
},
id="fusable_with_one_of_the_start_nodes_is_lca",
),
pytest.param(
fusable_with_hard_to_find_lca,
{
"x": {"status": "encrypted", "range": [0, 10]},
},
id="fusable_with_hard_to_find_lca",
),
pytest.param(
fusable_with_hard_to_find_lca_used_twice,
{
"x": {"status": "encrypted", "range": [0, 4], "shape": (2, 2)},
},
id="fusable_with_hard_to_find_lca_used_twice",
),
pytest.param(
fusable_additional_1,
{
"x": {"status": "encrypted", "range": [0, 10]},
},
id="fusable_additional_1",
),
pytest.param(
fusable_additional_2,
{
"x": {"status": "encrypted", "range": [0, 10]},
},
id="fusable_additional_2",
),
pytest.param(
lambda x: x + x.shape[0] + x.ndim + x.size,
{
"x": {"status": "encrypted", "range": [0, 15], "shape": (3, 2)},
},
id="x + x.shape[0] + x.ndim + x.size",
),
pytest.param(
lambda x: (50 * np.sin(x.transpose())).astype(np.int64),
{
"x": {"status": "encrypted", "range": [0, 15], "shape": (3, 2)},
},
id="(50 * np.sin(x.transpose())).astype(np.int64)",
),
pytest.param(
lambda x: np.where(x < 5, x * 3, x),
{
"x": {"status": "encrypted", "range": [0, 10]},
},
id="np.where(x < 5, x * 3, x)",
),
pytest.param(
lambda x: x + np.ones_like(x),
{
"x": {"status": "encrypted", "range": [0, 10]},
},
id="x + np.ones_like(x)",
),
pytest.param(
lambda x: x + np.zeros_like(x),
{
"x": {"status": "encrypted", "range": [0, 10]},
},
id="x + np.zeros_like(x)",
),
pytest.param(
lambda x: cnp.univariate(deterministic_unary_function)(x),
{
"x": {"status": "encrypted", "range": [0, 10]},
},
id="cnp.univariate(deterministic_unary_function)(x)",
),
pytest.param(
lambda x: round(np.sqrt(x)),
{
"x": {"status": "encrypted", "range": [0, 100], "shape": ()},
},
id="round(np.sqrt(x))",
),
pytest.param(
lambda x: np.sqrt(x).round().astype(np.int64),
{
"x": {"status": "encrypted", "range": [0, 100]},
},
id="np.sqrt(x).round().astype(np.int64)",
),
pytest.param(
lambda x: (2.5 * round(np.sqrt(x), ndigits=4)).astype(np.int64),
{
"x": {"status": "encrypted", "range": [0, 100], "shape": ()},
},
id="(2.5 * round(np.sqrt(x), decimals=4)).astype(np.int64)",
),
pytest.param(
lambda x, y: cnp.LookupTable(list(range(32)))[x + y],
{
"x": {"status": "encrypted", "range": [-10, 10]},
"y": {"status": "encrypted", "range": [-10, 10]},
},
id="cnp.LookupTable(list(range(32)))[x + y]",
),
pytest.param(
lambda x: np.expand_dims(x, axis=0),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (3, 2)},
},
id="np.expand_dims(x, axis=0)",
),
pytest.param(
lambda x: np.expand_dims(x, axis=1),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (3, 2)},
},
id="np.expand_dims(x, axis=1)",
),
pytest.param(
lambda x: np.expand_dims(x, axis=2),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (3, 2)},
},
id="np.expand_dims(x, axis=2)",
),
pytest.param(
lambda x: np.expand_dims(x, axis=(0, 1)),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (3, 2)},
},
id="np.expand_dims(x, axis=(0, 1))",
),
pytest.param(
lambda x: np.expand_dims(x, axis=(0, 2)),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (3, 2)},
},
id="np.expand_dims(x, axis=(0, 2))",
),
pytest.param(
lambda x: np.expand_dims(x, axis=(1, 2)),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (3, 2)},
},
id="np.expand_dims(x, axis=(1, 2))",
),
pytest.param(
lambda x: np.expand_dims(x, axis=(0, 1, 2)),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (3, 2)},
},
id="np.expand_dims(x, axis=(0, 1, 2))",
),
pytest.param(
lambda x: x**3,
{
"x": {"status": "encrypted", "range": [-30, 30]},
},
id="x ** 3",
),
pytest.param(
lambda x: np.squeeze(x),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (1, 2, 1, 3, 1, 4)},
},
id="np.squeeze(x)",
),
pytest.param(
lambda x: np.squeeze(x, axis=2),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (1, 2, 1, 3, 1, 4)},
},
id="np.squeeze(x, axis=2)",
),
pytest.param(
lambda x: np.squeeze(x, axis=(0, 4)),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (1, 2, 1, 3, 1, 4)},
},
id="np.squeeze(x, axis=(0, 4))",
),
pytest.param(
lambda x: np.squeeze(x),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (1, 1, 1)},
},
id="np.squeeze(x) where x.shape == (1, 1, 1)",
),
pytest.param(
lambda x: np.squeeze(x, axis=1),
{
"x": {"status": "encrypted", "range": [-10, 10], "shape": (1, 1, 1)},
},
id="np.squeeze(x, axis=1) where x.shape == (1, 1, 1)",
),
],
)
def test_others(function, parameters, helpers):
"""
Test others.
"""
# scalar
# ------
if "shape" not in parameters["x"] or parameters["x"]["shape"] == ():
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)
# tensor
# ------
if "shape" not in parameters["x"]:
parameters["x"]["shape"] = (3, 2)
if parameters["x"]["shape"] == ():
return
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)
def test_others_bad_fusing(helpers):
"""
Test others with bad fusing.
"""
configuration = helpers.configuration()
# two variable inputs
# -------------------
@cnp.compiler({"x": "encrypted", "y": "clear"})
def function1(x, y):
return (10 * (np.sin(x) ** 2) + 10 * (np.cos(y) ** 2)).astype(np.int64)
with pytest.raises(RuntimeError) as excinfo:
inputset = [(i, i) for i in range(100)]
function1.compile(inputset, configuration)
helpers.check_str(
# pylint: disable=line-too-long
"""
A subgraph within the function you are trying to compile cannot be fused because it has multiple input nodes
%0 = x # EncryptedScalar<uint1>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is one of the input nodes
%1 = y # ClearScalar<uint1>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is one of the input nodes
%2 = sin(%0) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%3 = 2 # ClearScalar<uint2>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%4 = power(%2, %3) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%5 = 10 # ClearScalar<uint4>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%6 = multiply(%5, %4) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%7 = cos(%1) # ClearScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%8 = 2 # ClearScalar<uint2>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%9 = power(%7, %8) # ClearScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%10 = 10 # ClearScalar<uint4>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%11 = multiply(%10, %9) # ClearScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%12 = add(%6, %11) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%13 = astype(%12, dtype=int_) # EncryptedScalar<uint1>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
return %13
""", # noqa: E501
# pylint: enable=line-too-long
str(excinfo.value),
)
# intermediates with different shape
# ----------------------------------
@cnp.compiler({"x": "encrypted"})
def function2(x):
return np.abs(np.sin(x)).reshape((2, 3)).astype(np.int64)
with pytest.raises(RuntimeError) as excinfo:
inputset = [np.random.randint(0, 2**7, size=(3, 2)) for _ in range(100)]
function2.compile(inputset, configuration)
helpers.check_str(
# pylint: disable=line-too-long
"""
A subgraph within the function you are trying to compile cannot be fused because of a node, which is has a different shape than the input node
%0 = x # EncryptedTensor<uint7, shape=(3, 2)>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ with this input node
%1 = sin(%0) # EncryptedTensor<float64, shape=(3, 2)>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%2 = absolute(%1) # EncryptedTensor<float64, shape=(3, 2)>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%3 = reshape(%2, newshape=(2, 3)) # EncryptedTensor<float64, shape=(2, 3)>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%4 = astype(%3, dtype=int_) # EncryptedTensor<uint1, shape=(2, 3)>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this node has a different shape than the input node
return %4
""", # noqa: E501
# pylint: enable=line-too-long
str(excinfo.value),
)
# non-fusable operation
# ---------------------
@cnp.compiler({"x": "encrypted"})
def function3(x):
return np.abs(np.sin(x)).transpose().astype(np.int64)
with pytest.raises(RuntimeError) as excinfo:
inputset = [[[0, 1], [2, 3]]]
function3.compile(inputset, configuration)
helpers.check_str(
# pylint: disable=line-too-long
"""
A subgraph within the function you are trying to compile cannot be fused because of a node, which is marked explicitly as non-fusable
%0 = x # EncryptedTensor<uint2, shape=(2, 2)>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ with this input node
%1 = sin(%0) # EncryptedTensor<float64, shape=(2, 2)>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%2 = absolute(%1) # EncryptedTensor<float64, shape=(2, 2)>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
%3 = transpose(%2) # EncryptedTensor<float64, shape=(2, 2)>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this node is not fusable
%4 = astype(%3, dtype=int_) # EncryptedTensor<uint1, shape=(2, 2)>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
return %4
""", # noqa: E501
# pylint: enable=line-too-long
str(excinfo.value),
)
# integer two variable inputs
# ---------------------------
@cnp.compiler({"x": "encrypted", "y": "clear"})
def function4(x, y):
return np.maximum(x, y)
with pytest.raises(RuntimeError) as excinfo:
inputset = [(i, i) for i in range(100)]
function4.compile(inputset, configuration)
helpers.check_str(
# pylint: disable=line-too-long
"""
A subgraph within the function you are trying to compile cannot be fused because it has multiple input nodes
%0 = x # EncryptedScalar<uint1>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is one of the input nodes
%1 = y # ClearScalar<uint1>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is one of the input nodes
%2 = maximum(%0, %1) # EncryptedScalar<uint1>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ within this subgraph
return %2
""", # noqa: E501
# pylint: enable=line-too-long
str(excinfo.value),
)
def test_others_bad_univariate(helpers):
"""
Test univariate with bad function.
"""
configuration = helpers.configuration()
def bad_univariate(x):
return np.array([x, x, x])
@cnp.compiler({"x": "encrypted"})
def f(x):
return cnp.univariate(bad_univariate)(x)
with pytest.raises(ValueError) as excinfo:
inputset = range(10)
f.compile(inputset, configuration)
helpers.check_str(
"Function bad_univariate cannot be used with cnp.univariate",
str(excinfo.value),
)

View File

@@ -0,0 +1,170 @@
"""
Tests of execution of reshape operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"shape,newshape",
[
pytest.param(
(12,),
(12, 1),
),
pytest.param(
(12,),
(1, 12),
),
pytest.param(
(12,),
(3, 4),
),
pytest.param(
(12,),
(3, 2, 2),
),
pytest.param(
(3, 4),
12,
),
pytest.param(
(3, 4),
(12,),
),
pytest.param(
(3, 4),
(4, 3),
),
pytest.param(
(3, 4),
(2, 2, 3),
),
pytest.param(
(3, 4),
(2, 3, 2),
),
pytest.param(
(3, 4),
(3, 2, 2),
),
pytest.param(
(3, 4),
(3, 1, 4),
),
pytest.param(
(3, 4),
(12, 1),
),
pytest.param(
(3, 4),
(-1,),
),
pytest.param(
(3, 4),
-1,
),
pytest.param(
(2, 2, 3),
(3, 4),
),
pytest.param(
(2, 2, 3),
(4, 3),
),
pytest.param(
(2, 2, 3),
(3, 2, 2),
),
pytest.param(
(2, 3, 4, 5, 6),
(6, 4, 30),
),
pytest.param(
(6, 4, 30),
(2, 3, 4, 5, 6),
),
pytest.param(
(2, 3, 4, 5, 6),
(2, 60, 6),
),
pytest.param(
(2, 60, 6),
(2, 3, 4, 5, 6),
),
pytest.param(
(2, 3, 2, 3, 4),
(6, 6, -1),
),
pytest.param(
(2, 3, 2, 3, 4),
(6, -1, 12),
),
pytest.param(
(2, 3, 2, 3, 4),
(-1, 18, 4),
),
],
)
def test_reshape(shape, newshape, helpers):
"""
Test reshape.
"""
configuration = helpers.configuration()
@cnp.compiler({"x": "encrypted"})
def function(x):
return np.reshape(x, newshape)
@cnp.compiler({"x": "encrypted"})
def method(x):
return x.reshape(newshape)
inputset = [np.random.randint(0, 2**5, size=shape) for i in range(100)]
function_circuit = function.compile(inputset, configuration)
method_circuit = method.compile(inputset, configuration)
sample = np.random.randint(0, 2**5, size=shape)
helpers.check_execution(function_circuit, function, sample)
helpers.check_execution(method_circuit, method, sample)
@pytest.mark.parametrize(
"shape",
[
pytest.param(
(12,),
),
pytest.param(
(3, 4),
),
pytest.param(
(2, 2, 3),
),
pytest.param(
(2, 3, 4, 5, 6),
),
],
)
def test_flatten(shape, helpers):
"""
Test flatten.
"""
configuration = helpers.configuration()
@cnp.compiler({"x": "encrypted"})
def function(x):
return x.flatten()
inputset = [np.random.randint(0, 2**5, size=shape) for i in range(100)]
circuit = function.compile(inputset, configuration)
sample = np.random.randint(0, 2**5, size=shape)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,282 @@
"""
Tests of execution of round bit pattern operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
from concrete.numpy.representation.utils import format_constant
@pytest.mark.parametrize(
"sample,lsbs_to_remove,expected_output",
[
(0b_0000_0011, 0, 0b_0000_0011),
(0b_0000_0100, 0, 0b_0000_0100),
(0b_0000_0000, 3, 0b_0000_0000),
(0b_0000_0001, 3, 0b_0000_0000),
(0b_0000_0010, 3, 0b_0000_0000),
(0b_0000_0011, 3, 0b_0000_0000),
(0b_0000_0100, 3, 0b_0000_1000),
(0b_0000_0101, 3, 0b_0000_1000),
(0b_0000_0110, 3, 0b_0000_1000),
(0b_0000_0111, 3, 0b_0000_1000),
(0b_0000_1000, 3, 0b_0000_1000),
(0b_0000_1001, 3, 0b_0000_1000),
(0b_0000_1010, 3, 0b_0000_1000),
(0b_0000_1011, 3, 0b_0000_1000),
(0b_0000_1100, 3, 0b_0001_0000),
(0b_0000_1101, 3, 0b_0001_0000),
(0b_0000_1110, 3, 0b_0001_0000),
(0b_0000_1111, 3, 0b_0001_0000),
],
)
def test_plain_round_bit_pattern(sample, lsbs_to_remove, expected_output):
"""
Test round bit pattern in evaluation context.
"""
assert cnp.round_bit_pattern(sample, lsbs_to_remove=lsbs_to_remove) == expected_output
@pytest.mark.parametrize(
"sample,lsbs_to_remove,expected_error,expected_message",
[
(
np.array([3.2, 4.1]),
3,
TypeError,
"Expected input elements to be integers but they are dtype[float64]",
),
(
"foo",
3,
TypeError,
"Expected input to be an int or a numpy array but it's str",
),
],
)
def test_bad_plain_round_bit_pattern(
sample,
lsbs_to_remove,
expected_error,
expected_message,
):
"""
Test round bit pattern in evaluation context with bad parameters.
"""
with pytest.raises(expected_error) as excinfo:
cnp.round_bit_pattern(sample, lsbs_to_remove=lsbs_to_remove)
assert str(excinfo.value) == expected_message
@pytest.mark.parametrize(
"input_bits,lsbs_to_remove",
[
(3, 1),
(3, 2),
(4, 1),
(4, 2),
(4, 3),
(5, 1),
(5, 2),
(5, 3),
(5, 4),
],
)
def test_round_bit_pattern(input_bits, lsbs_to_remove, helpers):
"""
Test round bit pattern in evaluation context.
"""
@cnp.compiler({"x": "encrypted"})
def function(x):
x_rounded = cnp.round_bit_pattern(x, lsbs_to_remove=lsbs_to_remove)
return np.abs(50 * np.sin(x_rounded)).astype(np.int64)
circuit = function.compile([(2**input_bits) - 1], helpers.configuration())
helpers.check_execution(circuit, function, np.random.randint(0, 2**input_bits), simulate=True)
def test_auto_rounding(helpers):
"""
Test round bit pattern with auto rounding.
"""
# with auto adjust rounders configuration
# ---------------------------------------
# y has the max value of 1999, so it's 11 bits
# our target msb is 5 bits, which means we need to remove 6 of the least significant bits
rounder1 = cnp.AutoRounder(target_msbs=5)
@cnp.compiler({"x": "encrypted"})
def function1(x):
y = x + 1000
z = cnp.round_bit_pattern(y, lsbs_to_remove=rounder1)
return np.sqrt(z).astype(np.int64)
inputset1 = range(1000)
function1.trace(inputset1, helpers.configuration(), auto_adjust_rounders=True)
assert rounder1.lsbs_to_remove == 6
# manual
# ------
# y has the max value of 1999, so it's 11 bits
# our target msb is 3 bits, which means we need to remove 8 of the least significant bits
rounder2 = cnp.AutoRounder(target_msbs=3)
@cnp.compiler({"x": "encrypted"})
def function2(x):
y = x + 1000
z = cnp.round_bit_pattern(y, lsbs_to_remove=rounder2)
return np.sqrt(z).astype(np.int64)
inputset2 = range(1000)
cnp.AutoRounder.adjust(function2, inputset2)
assert rounder2.lsbs_to_remove == 8
# complicated case
# ----------------
# have 2 ** 8 entries during evaluation, it won't matter after compilation
entries3 = list(range(2**8))
# we have 8-bit inputs for this table, and we only want to use first 5-bits
for i in range(0, 2**8, 2**3):
# so we set every 8th entry to a 4-bit value
entries3[i] = np.random.randint(0, (2**4) - (2**2))
# when this tlu is applied to an 8-bit value with 5-bit msb rounding, result will be 4-bits
table3 = cnp.LookupTable(entries3)
# and this is the rounder for table1, which should have lsbs_to_remove of 3
rounder3 = cnp.AutoRounder(target_msbs=5)
# have 2 ** 8 entries during evaluation, it won't matter after compilation
entries4 = list(range(2**8))
# we have 4-bit inputs for this table, and we only want to use first 2-bits
for i in range(0, 2**4, 2**2):
# so we set every 4th entry to an 8-bit value
entries4[i] = np.random.randint(2**7, 2**8)
# when this tlu is applied to a 4-bit value with 2-bit msb rounding, result will be 8-bits
table4 = cnp.LookupTable(entries4)
# and this is the rounder for table2, which should have lsbs_to_remove of 2
rounder4 = cnp.AutoRounder(target_msbs=2)
@cnp.compiler({"x": "encrypted"})
def function3(x):
a = cnp.round_bit_pattern(x, lsbs_to_remove=rounder3)
b = table3[a]
c = cnp.round_bit_pattern(b, lsbs_to_remove=rounder4)
d = table4[c]
return d
inputset3 = range((2**8) - (2**3))
circuit3 = function3.compile(
inputset3,
helpers.configuration(),
auto_adjust_rounders=True,
)
assert rounder3.lsbs_to_remove == 3
assert rounder4.lsbs_to_remove == 2
table3_formatted_string = format_constant(table3.table, 25)
table4_formatted_string = format_constant(table4.table, 25)
helpers.check_str(
f"""
%0 = x # EncryptedScalar<uint8>
%1 = round_bit_pattern(%0, lsbs_to_remove=3) # EncryptedScalar<uint8>
%2 = tlu(%1, table={table3_formatted_string}) # EncryptedScalar<uint4>
%3 = round_bit_pattern(%2, lsbs_to_remove=2) # EncryptedScalar<uint4>
%4 = tlu(%3, table={table4_formatted_string}) # EncryptedScalar<uint8>
return %4
""",
str(circuit3.graph.format(show_bounds=False)),
)
def test_auto_rounding_without_adjustment():
"""
Test round bit pattern with auto rounding but without adjustment.
"""
rounder = cnp.AutoRounder(target_msbs=5)
def function(x):
y = x + 1000
z = cnp.round_bit_pattern(y, lsbs_to_remove=rounder)
return np.sqrt(z).astype(np.int64)
with pytest.raises(RuntimeError) as excinfo:
function(100)
assert str(excinfo.value) == (
"AutoRounders cannot be used before adjustment, "
"please call AutoRounder.adjust with the function that will be compiled "
"and provide the exact inputset that will be used for compilation"
)
def test_auto_rounding_with_empty_inputset():
"""
Test round bit pattern with auto rounding but with empty inputset.
"""
rounder = cnp.AutoRounder(target_msbs=5)
def function(x):
y = x + 1000
z = cnp.round_bit_pattern(y, lsbs_to_remove=rounder)
return np.sqrt(z).astype(np.int64)
with pytest.raises(ValueError) as excinfo:
cnp.AutoRounder.adjust(function, [])
assert str(excinfo.value) == "AutoRounders cannot be adjusted with an empty inputset"
def test_auto_rounding_recursive_adjustment():
"""
Test round bit pattern with auto rounding but with recursive adjustment.
"""
rounder = cnp.AutoRounder(target_msbs=5)
def function(x):
cnp.AutoRounder.adjust(function, range(10))
y = x + 1000
z = cnp.round_bit_pattern(y, lsbs_to_remove=rounder)
return np.sqrt(z).astype(np.int64)
with pytest.raises(RuntimeError) as excinfo:
cnp.AutoRounder.adjust(function, range(10))
assert str(excinfo.value) == "AutoRounders cannot be adjusted recursively"
def test_auto_rounding_construct_in_function():
"""
Test round bit pattern with auto rounding but rounder is constructed within the function.
"""
def function(x):
y = x + 1000
z = cnp.round_bit_pattern(y, lsbs_to_remove=cnp.AutoRounder(target_msbs=5))
return np.sqrt(z).astype(np.int64)
with pytest.raises(RuntimeError) as excinfo:
cnp.AutoRounder.adjust(function, range(10))
assert str(excinfo.value) == (
"AutoRounders cannot be constructed during adjustment, "
"please construct AutoRounders outside the function and reference it"
)

View File

@@ -0,0 +1,178 @@
"""
Tests of execution of shift operations.
"""
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x, y: x << y,
id="x << y",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 1], "status": "encrypted"},
"y": {"range": [0, 7], "status": "encrypted"},
},
{
"x": {"range": [0, 3], "status": "encrypted"},
"y": {"range": [0, 3], "status": "encrypted", "shape": (2,)},
},
{
"x": {"range": [0, 3], "status": "encrypted", "shape": (2,)},
"y": {"range": [0, 3], "status": "encrypted"},
},
{
"x": {"range": [0, 3], "status": "encrypted", "shape": (2,)},
"y": {"range": [0, 3], "status": "encrypted", "shape": (2,)},
},
],
)
def test_left_shift(function, parameters, helpers):
"""
Test left shift between encrypted integers.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x, y: x >> y,
id="x >> y",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 1 << 7], "status": "encrypted"},
"y": {"range": [0, 7], "status": "encrypted"},
},
{
"x": {"range": [0, 1 << 4], "status": "encrypted"},
"y": {"range": [0, 3], "status": "encrypted", "shape": (2,)},
},
{
"x": {"range": [0, 1 << 4], "status": "encrypted", "shape": (2,)},
"y": {"range": [0, 3], "status": "encrypted"},
},
{
"x": {"range": [0, 1 << 4], "status": "encrypted", "shape": (2,)},
"y": {"range": [0, 3], "status": "encrypted", "shape": (2,)},
},
],
)
def test_right_shift(function, parameters, helpers):
"""
Test right shift between encrypted integers.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x, y: x << y,
id="x << y",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 1], "status": "encrypted"},
"y": {"range": [0, 7], "status": "encrypted"},
},
],
)
def test_left_shift_coverage(function, parameters, helpers):
"""
Test left shift between encrypted integers all cases.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
for i in range(2):
for j in range(8):
helpers.check_execution(circuit, function, [i, j])
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x, y: x >> y,
id="x >> y",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 1 << 7], "status": "encrypted"},
"y": {"range": [0, 7], "status": "encrypted"},
},
],
)
def test_right_shift_coverage(function, parameters, helpers):
"""
Test right shift between encrypted integers all cases.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
helpers.check_execution(circuit, function, [0b11, 0])
helpers.check_execution(circuit, function, [0b11, 1])
helpers.check_execution(circuit, function, [0b110, 2])
helpers.check_execution(circuit, function, [0b1100, 3])
helpers.check_execution(circuit, function, [0b11000, 4])
helpers.check_execution(circuit, function, [0b110000, 5])
helpers.check_execution(circuit, function, [0b110000, 6])
helpers.check_execution(circuit, function, [0b1100000, 7])

View File

@@ -0,0 +1,530 @@
"""
Tests of execution of static assignment operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
def assignment_case_0():
"""
Assignment test case.
"""
shape = (3,)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[:] = value
return x
return shape, assign
def assignment_case_1():
"""
Assignment test case.
"""
shape = (3,)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[0] = value
return x
return shape, assign
def assignment_case_2():
"""
Assignment test case.
"""
shape = (3,)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[1] = value
return x
return shape, assign
def assignment_case_3():
"""
Assignment test case.
"""
shape = (3,)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[2] = value
return x
return shape, assign
def assignment_case_4():
"""
Assignment test case.
"""
shape = (5,)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[0:3] = value
return x
return shape, assign
def assignment_case_5():
"""
Assignment test case.
"""
shape = (5,)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[1:4] = value
return x
return shape, assign
def assignment_case_6():
"""
Assignment test case.
"""
shape = (5,)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[1:4:2] = value
return x
return shape, assign
def assignment_case_7():
"""
Assignment test case.
"""
shape = (10,)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[::2] = value
return x
return shape, assign
def assignment_case_8():
"""
Assignment test case.
"""
shape = (5,)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[2:0:-1] = value
return x
return shape, assign
def assignment_case_9():
"""
Assignment test case.
"""
shape = (5,)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[4:0:-2] = value
return x
return shape, assign
def assignment_case_10():
"""
Assignment test case.
"""
shape = (5,)
value = np.random.randint(0, 2**7, size=(3,))
def assign(x):
x[1:4] = value
return x
return shape, assign
def assignment_case_11():
"""
Assignment test case.
"""
shape = (5,)
value = np.random.randint(0, 2**7, size=(3,))
def assign(x):
x[4:1:-1] = value
return x
return shape, assign
def assignment_case_12():
"""
Assignment test case.
"""
shape = (10,)
value = np.random.randint(0, 2**7, size=(3,))
def assign(x):
x[1:7:2] = value
return x
return shape, assign
def assignment_case_13():
"""
Assignment test case.
"""
shape = (10,)
value = np.random.randint(0, 2**7, size=(3,))
def assign(x):
x[7:1:-2] = value
return x
return shape, assign
def assignment_case_14():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[0, 0] = value
return x
return shape, assign
def assignment_case_15():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[3, 1] = value
return x
return shape, assign
def assignment_case_16():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[0] = value
return x
return shape, assign
def assignment_case_17():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=(4,))
def assign(x):
x[0] = value
return x
return shape, assign
def assignment_case_18():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=(5,))
def assign(x):
x[:, 0] = value
return x
return shape, assign
def assignment_case_19():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=(5,))
def assign(x):
x[:, 1] = value
return x
return shape, assign
def assignment_case_20():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[0:3, :] = value
return x
return shape, assign
def assignment_case_21():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=(3, 4))
def assign(x):
x[0:3, :] = value
return x
return shape, assign
def assignment_case_22():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=(4,))
def assign(x):
x[0:3, :] = value
return x
return shape, assign
def assignment_case_23():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=(3,))
def assign(x):
x[0:3, 1:4] = value
return x
return shape, assign
def assignment_case_24():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=(3, 3))
def assign(x):
x[0:3, 1:4] = value
return x
return shape, assign
def assignment_case_25():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=(3, 3))
def assign(x):
x[4:1:-1, 3:0:-1] = value
return x
return shape, assign
def assignment_case_26():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=(3,))
def assign(x):
x[3:0:-1, 0] = value
return x
return shape, assign
def assignment_case_27():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=(2,))
def assign(x):
x[0, 1:3] = value
return x
return shape, assign
def assignment_case_28():
"""
Assignment test case.
"""
shape = (5, 4)
value = np.random.randint(0, 2**7, size=())
def assign(x):
x[2:4, 1:3] = value
return x
return shape, assign
@pytest.mark.parametrize(
"shape,function",
[
pytest.param(*assignment_case_0()),
pytest.param(*assignment_case_1()),
pytest.param(*assignment_case_2()),
pytest.param(*assignment_case_3()),
pytest.param(*assignment_case_4()),
pytest.param(*assignment_case_5()),
pytest.param(*assignment_case_6()),
pytest.param(*assignment_case_7()),
pytest.param(*assignment_case_8()),
pytest.param(*assignment_case_9()),
pytest.param(*assignment_case_10()),
pytest.param(*assignment_case_11()),
pytest.param(*assignment_case_12()),
pytest.param(*assignment_case_13()),
pytest.param(*assignment_case_14()),
pytest.param(*assignment_case_15()),
pytest.param(*assignment_case_16()),
pytest.param(*assignment_case_17()),
pytest.param(*assignment_case_18()),
pytest.param(*assignment_case_19()),
pytest.param(*assignment_case_20()),
pytest.param(*assignment_case_21()),
pytest.param(*assignment_case_22()),
pytest.param(*assignment_case_23()),
pytest.param(*assignment_case_24()),
pytest.param(*assignment_case_25()),
pytest.param(*assignment_case_26()),
pytest.param(*assignment_case_27()),
pytest.param(*assignment_case_28()),
],
)
def test_static_assignment(shape, function, helpers):
"""
Test static assignment.
"""
configuration = helpers.configuration()
compiler = cnp.Compiler(function, {"x": "encrypted"})
inputset = [np.random.randint(0, 2**7, size=shape) for _ in range(100)]
circuit = compiler.compile(inputset, configuration)
sample = np.random.randint(0, 2**7, size=shape)
helpers.check_execution(circuit, function, sample)
def test_bad_static_assignment(helpers):
"""
Test static assingment with bad parameters.
"""
configuration = helpers.configuration()
# with float
# ----------
def f(x):
x[1.5] = 0
return x
compiler = cnp.Compiler(f, {"x": "encrypted"})
inputset = [np.random.randint(0, 2**3, size=(3,)) for _ in range(100)]
with pytest.raises(ValueError) as excinfo:
compiler.compile(inputset, configuration)
assert str(excinfo.value) == "Assigning to '1.5' is not supported"
# with bad slice
# --------------
def g(x):
x[slice(1.5, 2.5, None)] = 0
return x
compiler = cnp.Compiler(g, {"x": "encrypted"})
inputset = [np.random.randint(0, 2**3, size=(3,)) for _ in range(100)]
with pytest.raises(ValueError) as excinfo:
compiler.compile(inputset, configuration)
assert str(excinfo.value) == "Assigning to '1.5:2.5' is not supported"

View File

@@ -0,0 +1,198 @@
"""
Tests of execution of static indexing operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"shape,function",
[
pytest.param(
(3,),
lambda x: x[0],
id="x[0] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[1],
id="x[1] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[2],
id="x[2] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[-1],
id="x[-1] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[-2],
id="x[-2] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[-3],
id="x[-3] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[0:1],
id="x[0:1] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[1:2],
id="x[1:2] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[2:3],
id="x[2:3] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[0:2],
id="x[0:2] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[1:3],
id="x[1:3] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[0:3],
id="x[0:3] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[2:0:-1],
id="x[2:0:-1] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[::-1],
id="x[::-1] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[:-1],
id="x[:-1] where x.shape == (3,)",
),
pytest.param(
(3,),
lambda x: x[-2:],
id="x[-2:] where x.shape == (3,)",
),
pytest.param(
(3, 4),
lambda x: x[0, 0],
id="x[0, 0] where x.shape == (3, 4)",
),
pytest.param(
(3, 4),
lambda x: x[0, -1],
id="x[0, -1] where x.shape == (3, 4)",
),
pytest.param(
(3, 4),
lambda x: x[-1, 0],
id="x[-1, 0] where x.shape == (3, 4)",
),
pytest.param(
(3, 4),
lambda x: x[-1, -1],
id="x[-1, -1] where x.shape == (3, 4)",
),
pytest.param(
(3, 4),
lambda x: x[2, 1],
id="x[2, 1] where x.shape == (3, 4)",
),
pytest.param(
(3, 4),
lambda x: x[0],
id="x[0] where x.shape == (3, 4)",
),
pytest.param(
(3, 4),
lambda x: x[:, 0],
id="x[:, 0] where x.shape == (3, 4)",
),
pytest.param(
(3, 4),
lambda x: x[-1],
id="x[-1] where x.shape == (3, 4)",
),
pytest.param(
(3, 4),
lambda x: x[:, -1],
id="x[:, -1] where x.shape == (3, 4)",
),
pytest.param(
(3, 4),
lambda x: x[1:3, 1:3],
id="x[1:3, 1:3] where x.shape == (3, 4)",
),
pytest.param(
(3, 4),
lambda x: x[::-1],
id="x[::-1] where x.shape == (3, 4)",
),
pytest.param(
(10,),
lambda x: x[slice(np.int64(8), np.int64(2), np.int64(-2))],
id="x[8:2:-2] where x.shape == (10,)",
),
],
)
def test_static_indexing(shape, function, helpers):
"""
Test static indexing.
"""
configuration = helpers.configuration()
compiler = cnp.Compiler(function, {"x": "encrypted"})
inputset = [np.random.randint(0, 2**5, size=shape) for _ in range(100)]
circuit = compiler.compile(inputset, configuration)
sample = np.random.randint(0, 2**5, size=shape)
helpers.check_execution(circuit, function, sample)
def test_bad_static_indexing(helpers):
"""
Test static indexing with bad parameters.
"""
configuration = helpers.configuration()
# with float
# ----------
compiler = cnp.Compiler(lambda x: x[1.5], {"x": "encrypted"})
inputset = [np.random.randint(0, 2**3, size=(3,)) for _ in range(100)]
with pytest.raises(ValueError) as excinfo:
compiler.compile(inputset, configuration)
assert str(excinfo.value) == "Indexing with '1.5' is not supported"
# with bad slice
# --------------
compiler = cnp.Compiler(lambda x: x[slice(1.5, 2.5, None)], {"x": "encrypted"})
inputset = [np.random.randint(0, 2**3, size=(3,)) for _ in range(100)]
with pytest.raises(ValueError) as excinfo:
compiler.compile(inputset, configuration)
assert str(excinfo.value) == "Indexing with '1.5:2.5' is not supported"

View File

@@ -0,0 +1,159 @@
"""
Tests of execution of sub operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x: x - 42,
id="x - 42",
),
pytest.param(
lambda x: 42 - x,
id="42 - x",
),
pytest.param(
lambda x: x - np.array([1, 2, 3]),
id="x - [1, 2, 3]",
),
pytest.param(
lambda x: np.array([1, 2, 3]) - x,
id="[1, 2, 3] - x",
),
pytest.param(
lambda x: x - np.array([[1, 2, 3], [4, 5, 6]]),
id="x - [[1, 2, 3], [4, 5, 6]]",
),
pytest.param(
lambda x: np.array([[1, 2, 3], [4, 5, 6]]) - x,
id="[[1, 2, 3], [4, 5, 6]] - x",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 60], "status": "encrypted"},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (2, 3)},
},
],
)
def test_constant_sub(function, parameters, helpers):
"""
Test sub where one of the operators is a constant.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x, y: x - y,
id="x - y",
),
],
)
@pytest.mark.parametrize(
"parameters",
[
{
"x": {"range": [0, 60], "status": "clear"},
"y": {"range": [0, 60], "status": "encrypted"},
},
{
"x": {"range": [0, 60], "status": "encrypted"},
"y": {"range": [0, 60], "status": "clear"},
},
{
"x": {"range": [0, 60], "status": "encrypted"},
"y": {"range": [0, 60], "status": "encrypted"},
},
{
"x": {"range": [0, 60], "status": "clear", "shape": (3,)},
"y": {"range": [0, 60], "status": "encrypted"},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
"y": {"range": [0, 60], "status": "clear"},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
"y": {"range": [0, 60], "status": "encrypted"},
},
{
"x": {"range": [0, 60], "status": "clear"},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted"},
"y": {"range": [0, 60], "status": "clear", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted"},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "clear", "shape": (3,)},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
"y": {"range": [0, 60], "status": "clear", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "clear", "shape": (2, 1)},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (2, 1)},
"y": {"range": [0, 60], "status": "clear", "shape": (3,)},
},
{
"x": {"range": [0, 60], "status": "encrypted", "shape": (2, 1)},
"y": {"range": [0, 60], "status": "encrypted", "shape": (3,)},
},
],
)
def test_sub(function, parameters, helpers):
"""
Test sub where both of the operators are dynamic.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,120 @@
"""
Tests of execution of sum operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function,parameters",
[
pytest.param(
lambda x: np.sum(x),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=None), # type: ignore
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=0),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=1),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=-1),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=-2),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=(0, 1)),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=(-2, -1)),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, keepdims=True),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=0, keepdims=True),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=1, keepdims=True),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=-1, keepdims=True),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=-2, keepdims=True),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=(0, 1), keepdims=True),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.sum(x, axis=(-2, -1), keepdims=True),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
],
)
def test_sum(function, parameters, helpers):
"""
Test sum.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,78 @@
"""
Tests of execution of transpose operation.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function,parameters",
[
pytest.param(
lambda x: np.transpose(x),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: x.transpose(),
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: x.T,
{
"x": {"shape": (3, 2), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: x.transpose((1, 0, 2)),
{
"x": {"shape": (2, 3, 4), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: x.transpose((1, 2, 0)),
{
"x": {"shape": (2, 3, 4), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: x.transpose((0, 2, 1)),
{
"x": {"shape": (2, 3, 4), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: x.transpose((2, 0, 1)),
{
"x": {"shape": (2, 3, 4), "range": [0, 10], "status": "encrypted"},
},
),
pytest.param(
lambda x: np.transpose(x, (3, 0, 2, 1)),
{
"x": {"shape": (2, 3, 4, 5), "range": [0, 10], "status": "encrypted"},
},
),
],
)
def test_transpose(function, parameters, helpers):
"""
Test transpose.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
inputset = helpers.generate_inputset(parameters)
circuit = compiler.compile(inputset, configuration)
sample = helpers.generate_sample(parameters)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,49 @@
"""
Tests of execution of zeros operation.
"""
import random
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function",
[
pytest.param(
lambda x: cnp.zero() + x,
id="cnp.zero() + x",
),
pytest.param(
lambda x: cnp.zeros(()) + x,
id="cnp.zeros(()) + x",
),
pytest.param(
lambda x: cnp.zeros(10) + x,
id="cnp.zeros(10) + x",
),
pytest.param(
lambda x: cnp.zeros((10,)) + x,
id="cnp.zeros((10,)) + x",
),
pytest.param(
lambda x: cnp.zeros((3, 2)) + x,
id="cnp.zeros((3, 2)) + x",
),
],
)
def test_zeros(function, helpers):
"""
Test zeros.
"""
configuration = helpers.configuration()
compiler = cnp.Compiler(function, {"x": "encrypted"})
inputset = range(10)
circuit = compiler.compile(inputset, configuration)
sample = random.randint(0, 11)
helpers.check_execution(circuit, function, sample)

View File

@@ -0,0 +1,3 @@
"""
Tests of extensions.
"""

View File

@@ -0,0 +1,37 @@
"""
Tests of 'array' extension.
"""
import pytest
import concrete.numpy as cnp
@pytest.mark.parametrize(
"function,parameters,expected_error",
[
pytest.param(
lambda x, y: cnp.array([x, y]),
{
"x": {"range": [0, 10], "status": "encrypted", "shape": ()},
"y": {"range": [0, 10], "status": "encrypted", "shape": (2, 3)},
},
"Encrypted arrays can only be created from scalars",
),
],
)
def test_bad_array(function, parameters, expected_error, helpers):
"""
Test array with bad parameters.
"""
parameter_encryption_statuses = helpers.generate_encryption_statuses(parameters)
configuration = helpers.configuration()
compiler = cnp.Compiler(function, parameter_encryption_statuses)
with pytest.raises(ValueError) as excinfo:
inputset = helpers.generate_inputset(parameters)
compiler.compile(inputset, configuration)
assert str(excinfo.value) == expected_error

View File

@@ -0,0 +1,28 @@
"""
Tests of 'LookupTable' extension.
"""
import pytest
from concrete.numpy import LookupTable
@pytest.mark.parametrize(
"table, expected_result",
[
pytest.param(
LookupTable([1, 2, 3]),
"[1, 2, 3]",
),
pytest.param(
LookupTable([LookupTable([1, 2, 3]), LookupTable([4, 5, 6])]),
"[[1, 2, 3], [4, 5, 6]]",
),
],
)
def test_lookup_table_repr(table, expected_result):
"""
Test `__repr__` method of `LookupTable` class.
"""
assert repr(table) == expected_result

View File

@@ -0,0 +1,64 @@
"""
Tests of 'tag' extension.
"""
import numpy as np
import concrete.numpy as cnp
def test_tag(helpers):
"""
Test tag extension.
"""
def g(z):
with cnp.tag("def"):
a = 120 - z
b = a // 4
return b
@cnp.compiler({"x": "encrypted"})
def f(x):
with cnp.tag("abc"):
x = x * 2
with cnp.tag("foo"):
y = x + 42
z = np.sqrt(y).astype(np.int64)
return g(z + 3) * 2
inputset = range(10)
circuit = f.trace(inputset, configuration=helpers.configuration())
helpers.check_str(
"""
%0 = x # EncryptedScalar<uint4>
%1 = 2 # ClearScalar<uint2> @ abc
%2 = multiply(%0, %1) # EncryptedScalar<uint5> @ abc
%3 = 42 # ClearScalar<uint6> @ abc.foo
%4 = add(%2, %3) # EncryptedScalar<uint6> @ abc.foo
%5 = subgraph(%4) # EncryptedScalar<uint3> @ abc
%6 = 3 # ClearScalar<uint2>
%7 = add(%5, %6) # EncryptedScalar<uint4>
%8 = 120 # ClearScalar<uint7> @ def
%9 = subtract(%8, %7) # EncryptedScalar<uint7> @ def
%10 = 4 # ClearScalar<uint3> @ def
%11 = floor_divide(%9, %10) # EncryptedScalar<uint5> @ def
%12 = 2 # ClearScalar<uint2>
%13 = multiply(%11, %12) # EncryptedScalar<uint6>
return %13
Subgraphs:
%5 = subgraph(%4):
%0 = input # EncryptedScalar<uint2> @ abc.foo
%1 = sqrt(%0) # EncryptedScalar<float64> @ abc
%2 = astype(%1, dtype=int_) # EncryptedScalar<uint1> @ abc
return %2
""".strip(),
circuit.format(show_bounds=False),
)

View File

@@ -0,0 +1,24 @@
"""
Tests of 'univariate' extension.
"""
import pytest
import concrete.numpy as cnp
def test_bad_univariate(helpers):
"""
Test 'univariate' extension with bad parameters.
"""
with pytest.raises(ValueError) as excinfo:
@cnp.circuit({"x": "encrypted"}, helpers.configuration())
def function(x: cnp.uint3):
return cnp.univariate(lambda x: x**2)(x)
assert str(excinfo.value) == (
"Univariate extension requires `outputs` argument for direct circuit definition "
"(e.g., cnp.univariate(function, outputs=cnp.uint4)(x))"
)

View File

@@ -0,0 +1,3 @@
"""
Tests of `concrete.numpy.internal` namespace.
"""

View File

@@ -0,0 +1,29 @@
"""
Tests of utilities related to the entire project.
"""
import pytest
from concrete.numpy.internal.utils import assert_that, unreachable
def test_assert_that():
"""
Test `assert_that` function.
"""
with pytest.raises(AssertionError) as excinfo:
assert_that(2 + 2 == 3, "no")
assert str(excinfo.value) == "no"
def test_unreachable():
"""
Test `unreachable` function.
"""
with pytest.raises(RuntimeError) as excinfo:
unreachable()
assert str(excinfo.value) == "Entered unreachable code"

View File

@@ -0,0 +1,3 @@
"""
Tests of `concrete.numpy.mlir` namespace.
"""

View File

@@ -0,0 +1,501 @@
"""
Tests of `GraphConverter` class.
"""
import numpy as np
import pytest
import concrete.numpy as cnp
import concrete.onnx as connx
# pylint: disable=line-too-long
def assign(x):
"""
Simple assignment to a vector.
"""
x[0] = 0
return x
@pytest.mark.parametrize(
"function,encryption_statuses,inputset,expected_error,expected_message",
[
pytest.param(
lambda x, y: (x - y, x + y),
{"x": "encrypted", "y": "clear"},
[(0, 0), (7, 7), (0, 7), (7, 0)],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedScalar<uint3> ∈ [0, 7]
%1 = y # ClearScalar<uint3> ∈ [0, 7]
%2 = subtract(%0, %1) # EncryptedScalar<int4> ∈ [-7, 7]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only a single output is supported
%3 = add(%0, %1) # EncryptedScalar<uint4> ∈ [0, 14]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only a single output is supported
return (%2, %3)
""", # noqa: E501
),
pytest.param(
lambda x: x,
{"x": "clear"},
range(-10, 10),
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # ClearScalar<int5> ∈ [-10, 9]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only encrypted signed integer inputs are supported
return %0
""", # noqa: E501
),
pytest.param(
lambda x: x * 1.5,
{"x": "encrypted"},
[2.5 * x for x in range(100)],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedScalar<float64> ∈ [0.0, 247.5]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer inputs are supported
%1 = 1.5 # ClearScalar<float64> ∈ [1.5, 1.5]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer constants are supported
%2 = multiply(%0, %1) # EncryptedScalar<float64> ∈ [0.0, 371.25]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
return %2
""", # noqa: E501
),
pytest.param(
lambda x: np.sin(x),
{"x": "encrypted"},
range(100),
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedScalar<uint7> ∈ [0, 99]
%1 = sin(%0) # EncryptedScalar<float64> ∈ [-0.99999, 0.999912]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
return %1
""", # noqa: E501
),
pytest.param(
lambda x, y: np.concatenate((x, y)),
{"x": "encrypted", "y": "clear"},
[
(
np.random.randint(0, 2**3, size=(3, 2)),
np.random.randint(0, 2**3, size=(3, 2)),
)
for _ in range(100)
],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedTensor<uint3, shape=(3, 2)> ∈ [0, 7]
%1 = y # ClearTensor<uint3, shape=(3, 2)> ∈ [0, 7]
%2 = concatenate((%0, %1)) # EncryptedTensor<uint3, shape=(6, 2)> ∈ [0, 7]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only all encrypted concatenate is supported
return %2
""", # noqa: E501
),
pytest.param(
lambda x, w: connx.conv(x, w),
{"x": "encrypted", "w": "encrypted"},
[
(
np.random.randint(0, 2, size=(1, 1, 4)),
np.random.randint(0, 2, size=(1, 1, 1)),
)
for _ in range(100)
],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedTensor<uint1, shape=(1, 1, 4)> ∈ [0, 1]
%1 = w # EncryptedTensor<uint1, shape=(1, 1, 1)> ∈ [0, 1]
%2 = conv1d(%0, %1, [0], pads=(0, 0), strides=(1,), dilations=(1,), group=1) # EncryptedTensor<uint1, shape=(1, 1, 4)> ∈ [0, 1]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only conv1d with encrypted input and clear weight is supported
return %2
""", # noqa: E501
),
pytest.param(
lambda x, w: connx.conv(x, w),
{"x": "encrypted", "w": "encrypted"},
[
(
np.random.randint(0, 2, size=(1, 1, 4, 4)),
np.random.randint(0, 2, size=(1, 1, 1, 1)),
)
for _ in range(100)
],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedTensor<uint1, shape=(1, 1, 4, 4)> ∈ [0, 1]
%1 = w # EncryptedTensor<uint1, shape=(1, 1, 1, 1)> ∈ [0, 1]
%2 = conv2d(%0, %1, [0], pads=(0, 0, 0, 0), strides=(1, 1), dilations=(1, 1), group=1) # EncryptedTensor<uint1, shape=(1, 1, 4, 4)> ∈ [0, 1]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only conv2d with encrypted input and clear weight is supported
return %2
""", # noqa: E501
),
pytest.param(
lambda x, w: connx.conv(x, w),
{"x": "encrypted", "w": "encrypted"},
[
(
np.random.randint(0, 2, size=(1, 1, 4, 4, 4)),
np.random.randint(0, 2, size=(1, 1, 1, 1, 1)),
)
for _ in range(100)
],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedTensor<uint1, shape=(1, 1, 4, 4, 4)> ∈ [0, 1]
%1 = w # EncryptedTensor<uint1, shape=(1, 1, 1, 1, 1)> ∈ [0, 1]
%2 = conv3d(%0, %1, [0], pads=(0, 0, 0, 0, 0, 0), strides=(1, 1, 1), dilations=(1, 1, 1), group=1) # EncryptedTensor<uint1, shape=(1, 1, 4, 4, 4)> ∈ [0, 1]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only conv3d with encrypted input and clear weight is supported
return %2
""", # noqa: E501
),
pytest.param(
lambda x, y: np.dot(x, y),
{"x": "encrypted", "y": "encrypted"},
[([0], [0]), ([3], [3]), ([3], [0]), ([0], [3]), ([1], [1])],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedTensor<uint2, shape=(1,)> ∈ [0, 3]
%1 = y # EncryptedTensor<uint2, shape=(1,)> ∈ [0, 3]
%2 = dot(%0, %1) # EncryptedScalar<uint4> ∈ [0, 9]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only dot product between encrypted and clear is supported
return %2
""", # noqa: E501
),
pytest.param(
lambda x: x[0],
{"x": "clear"},
[[0, 1, 2, 3], [7, 6, 5, 4]],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # ClearTensor<uint3, shape=(4,)> ∈ [0, 7]
%1 = %0[0] # ClearScalar<uint3> ∈ [0, 7]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only encrypted indexing supported
return %1
""", # noqa: E501
),
pytest.param(
lambda x, y: x @ y,
{"x": "encrypted", "y": "encrypted"},
[
(
np.random.randint(0, 2**1, size=(1, 1)),
np.random.randint(0, 2**1, size=(1, 1)),
)
for _ in range(100)
],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedTensor<uint1, shape=(1, 1)> ∈ [0, 1]
%1 = y # EncryptedTensor<uint1, shape=(1, 1)> ∈ [0, 1]
%2 = matmul(%0, %1) # EncryptedTensor<uint1, shape=(1, 1)> ∈ [0, 1]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only matrix multiplication between encrypted and clear is supported
return %2
""", # noqa: E501
),
pytest.param(
lambda x, y: x * y,
{"x": "encrypted", "y": "encrypted"},
[(0, 0), (7, 7), (0, 7), (7, 0)],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedScalar<uint3> ∈ [0, 7]
%1 = y # EncryptedScalar<uint3> ∈ [0, 7]
%2 = multiply(%0, %1) # EncryptedScalar<uint6> ∈ [0, 49]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only multiplication between encrypted and clear is supported
return %2
""", # noqa: E501
),
pytest.param(
lambda x: -x,
{"x": "clear"},
[0, 7],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # ClearScalar<uint3> ∈ [0, 7]
%1 = negative(%0) # ClearScalar<int4> ∈ [-7, 0]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only encrypted negation is supported
return %1
""", # noqa: E501
),
pytest.param(
lambda x: x.reshape((3, 2)),
{"x": "clear"},
[np.random.randint(0, 2**3, size=(2, 3)) for _ in range(100)],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # ClearTensor<uint3, shape=(2, 3)> ∈ [0, 7]
%1 = reshape(%0, newshape=(3, 2)) # ClearTensor<uint3, shape=(3, 2)> ∈ [0, 7]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only encrypted reshape is supported
return %1
""", # noqa: E501
),
pytest.param(
lambda x: np.sum(x),
{"x": "clear"},
[np.random.randint(0, 2, size=(1,)) for _ in range(100)],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # ClearTensor<uint1, shape=(1,)> ∈ [0, 1]
%1 = sum(%0) # ClearScalar<uint1> ∈ [0, 1]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only encrypted sum is supported
return %1
""", # noqa: E501
),
pytest.param(
lambda x: np.maximum(x, np.array([3])),
{"x": "clear"},
[[0], [1]],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # ClearTensor<uint1, shape=(1,)> ∈ [0, 1]
%1 = [3] # ClearTensor<uint2, shape=(1,)> ∈ [3, 3]
%2 = maximum(%0, %1) # ClearTensor<uint2, shape=(1,)> ∈ [3, 3]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one of the operands must be encrypted
return %2
""", # noqa: E501
),
pytest.param(
lambda x: np.transpose(x),
{"x": "clear"},
[np.random.randint(0, 2, size=(3, 2)) for _ in range(10)],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # ClearTensor<uint1, shape=(3, 2)> ∈ [0, 1]
%1 = transpose(%0) # ClearTensor<uint1, shape=(2, 3)> ∈ [0, 1]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only encrypted transpose is supported
return %1
""", # noqa: E501
),
pytest.param(
lambda x: np.broadcast_to(x, shape=(3, 2)),
{"x": "clear"},
[np.random.randint(0, 2, size=(2,)) for _ in range(10)],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # ClearTensor<uint1, shape=(2,)> ∈ [0, 1]
%1 = broadcast_to(%0, shape=(3, 2)) # ClearTensor<uint1, shape=(3, 2)> ∈ [0, 1]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only encrypted broadcasting is supported
return %1
""", # noqa: E501
),
pytest.param(
assign,
{"x": "clear"},
[np.random.randint(0, 2, size=(3,)) for _ in range(10)],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # ClearTensor<uint1, shape=(3,)> ∈ [0, 1]
%1 = 0 # ClearScalar<uint1> ∈ [0, 0]
%2 = (%0[0] = %1) # ClearTensor<uint1, shape=(3,)> ∈ [0, 1]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only assignment to encrypted tensors are supported
return %2
""", # noqa: E501
),
pytest.param(
lambda x: np.abs(10 * np.sin(x + 300)).astype(np.int64),
{"x": "encrypted"},
[200000],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR:
%0 = x # EncryptedScalar<uint18> ∈ [200000, 200000]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this input is 18-bits
%1 = 300 # ClearScalar<uint9> ∈ [300, 300]
%2 = add(%0, %1) # EncryptedScalar<uint18> ∈ [200300, 200300]
%3 = subgraph(%2) # EncryptedScalar<uint4> ∈ [9, 9]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ table lookups are only supported on circuits with up to 16-bits
return %3
Subgraphs:
%3 = subgraph(%2):
%0 = input # EncryptedScalar<uint2>
%1 = sin(%0) # EncryptedScalar<float64>
%2 = 10 # ClearScalar<uint4>
%3 = multiply(%2, %1) # EncryptedScalar<float64>
%4 = absolute(%3) # EncryptedScalar<float64>
%5 = astype(%4, dtype=int_) # EncryptedScalar<uint1>
return %5
""", # noqa: E501
),
pytest.param(
lambda x, y: x << y,
{"x": "encrypted", "y": "encrypted"},
[(-1, 1), (-2, 3)],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedScalar<int2> ∈ [-2, -1]
%1 = y # EncryptedScalar<uint2> ∈ [1, 3]
%2 = left_shift(%0, %1) # EncryptedScalar<int5> ∈ [-16, -2]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only unsigned bitwise operations are supported
return %2
""", # noqa: E501
),
pytest.param(
lambda x, y: x << y,
{"x": "encrypted", "y": "encrypted"},
[(1, 20), (2, 10)],
RuntimeError,
"""
Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedScalar<uint2> ∈ [1, 2]
%1 = y # EncryptedScalar<uint5> ∈ [10, 20]
%2 = left_shift(%0, %1) # EncryptedScalar<uint21> ∈ [2048, 1048576]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only up to 4-bit shifts are supported
return %2
""", # noqa: E501
),
],
)
def test_graph_converter_bad_convert(
function,
encryption_statuses,
inputset,
expected_error,
expected_message,
helpers,
):
"""
Test unsupported graph conversion.
"""
configuration = helpers.configuration()
compiler = cnp.Compiler(function, encryption_statuses)
with pytest.raises(expected_error) as excinfo:
compiler.compile(inputset, configuration)
helpers.check_str(expected_message, str(excinfo.value))
@pytest.mark.parametrize(
"function,inputset,expected_mlir",
[
pytest.param(
lambda x: 1 + cnp.LookupTable([4, 1, 2, 3])[x] + cnp.LookupTable([4, 1, 2, 3])[x + 1],
range(3),
"""
module {
func.func @main(%arg0: !FHE.eint<3>) -> !FHE.eint<3> {
%c1_i4 = arith.constant 1 : i4
%cst = arith.constant dense<[4, 1, 2, 3, 3, 3, 3, 3]> : tensor<8xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<3>
%1 = "FHE.add_eint_int"(%arg0, %c1_i4) : (!FHE.eint<3>, i4) -> !FHE.eint<3>
%2 = "FHE.add_eint_int"(%0, %c1_i4) : (!FHE.eint<3>, i4) -> !FHE.eint<3>
%3 = "FHE.apply_lookup_table"(%1, %cst) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<3>
%4 = "FHE.add_eint"(%2, %3) : (!FHE.eint<3>, !FHE.eint<3>) -> !FHE.eint<3>
return %4 : !FHE.eint<3>
}
}
""", # noqa: E501
# Notice that there is only a single 1 and a single table cst above
),
],
)
def test_constant_cache(function, inputset, expected_mlir, helpers):
"""
Test caching MLIR constants.
"""
configuration = helpers.configuration()
compiler = cnp.Compiler(function, {"x": "encrypted"})
circuit = compiler.compile(inputset, configuration)
helpers.check_str(expected_mlir, circuit.mlir)
# pylint: enable=line-too-long

View File

@@ -0,0 +1,3 @@
"""
Tests of `concrete.numpy.representation` namespace.
"""

View File

@@ -0,0 +1,371 @@
"""
Tests of `Graph` class.
"""
import os
import re
import numpy as np
import pytest
import concrete.numpy as cnp
import tests
tests_directory = os.path.dirname(tests.__file__)
def g(z):
"""
Example function with a tag.
"""
with cnp.tag("def"):
a = 120 - z
b = a // 4
return b
def f(x):
"""
Example function with nested tags.
"""
with cnp.tag("abc"):
x = x * 2
with cnp.tag("foo"):
y = x + 42
z = np.sqrt(y).astype(np.int64)
return g(z + 3) * 2
@pytest.mark.parametrize(
"function,inputset,tag_filter,operation_filter,expected_result",
[
pytest.param(
lambda x: x + 1,
range(5),
None,
None,
3,
),
pytest.param(
lambda x: x + 42,
range(10),
None,
None,
6,
),
pytest.param(
lambda x: x + 42,
range(50),
None,
None,
7,
),
pytest.param(
lambda x: x + 1.2,
[1.5, 4.2],
None,
None,
-1,
),
pytest.param(
f,
range(10),
None,
None,
7,
),
pytest.param(
f,
range(10),
"",
None,
6,
),
pytest.param(
f,
range(10),
"abc",
None,
5,
),
pytest.param(
f,
range(10),
["abc", "def"],
None,
7,
),
pytest.param(
f,
range(10),
re.compile(".*b.*"),
None,
6,
),
pytest.param(
f,
range(10),
None,
"input",
4,
),
pytest.param(
f,
range(10),
None,
"constant",
7,
),
pytest.param(
f,
range(10),
None,
"subgraph",
3,
),
pytest.param(
f,
range(10),
None,
"add",
6,
),
pytest.param(
f,
range(10),
None,
["subgraph", "add"],
6,
),
pytest.param(
f,
range(10),
None,
re.compile("sub.*"),
7,
),
pytest.param(
f,
range(10),
"abc.foo",
"add",
6,
),
pytest.param(
f,
range(10),
"abc",
"floor_divide",
-1,
),
],
)
def test_graph_maximum_integer_bit_width(
function,
inputset,
tag_filter,
operation_filter,
expected_result,
helpers,
):
"""
Test `maximum_integer_bit_width` method of `Graph` class.
"""
configuration = helpers.configuration()
compiler = cnp.Compiler(function, {"x": "encrypted"})
graph = compiler.trace(inputset, configuration)
assert graph.maximum_integer_bit_width(tag_filter, operation_filter) == expected_result
@pytest.mark.parametrize(
"function,inputset,tag_filter,operation_filter,expected_result",
[
pytest.param(
lambda x: x + 42,
range(-10, 10),
None,
None,
(-10, 51),
),
pytest.param(
lambda x: x + 1.2,
[1.5, 4.2],
None,
None,
None,
),
pytest.param(
f,
range(10),
None,
None,
(0, 120),
),
pytest.param(
f,
range(10),
"",
None,
(0, 54),
),
pytest.param(
f,
range(10),
"abc",
None,
(0, 18),
),
pytest.param(
f,
range(10),
["abc", "def"],
None,
(0, 120),
),
pytest.param(
f,
range(10),
re.compile(".*b.*"),
None,
(0, 60),
),
pytest.param(
f,
range(10),
None,
"input",
(0, 9),
),
pytest.param(
f,
range(10),
None,
"constant",
(2, 120),
),
pytest.param(
f,
range(10),
None,
"subgraph",
(6, 7),
),
pytest.param(
f,
range(10),
None,
"add",
(9, 60),
),
pytest.param(
f,
range(10),
None,
["subgraph", "add"],
(6, 60),
),
pytest.param(
f,
range(10),
None,
re.compile("sub.*"),
(6, 111),
),
pytest.param(
f,
range(10),
"abc.foo",
"add",
(42, 60),
),
pytest.param(
f,
range(10),
"abc",
"floor_divide",
None,
),
],
)
def test_graph_integer_range(
function,
inputset,
tag_filter,
operation_filter,
expected_result,
helpers,
):
"""
Test `integer_range` method of `Graph` class.
"""
configuration = helpers.configuration()
compiler = cnp.Compiler(function, {"x": "encrypted"})
graph = compiler.trace(inputset, configuration)
assert graph.integer_range(tag_filter, operation_filter) == expected_result
def test_graph_format_show_lines(helpers):
"""
Test `format` method of `Graph` class with show_lines=True.
"""
configuration = helpers.configuration()
compiler = cnp.Compiler(f, {"x": "encrypted"})
graph = compiler.trace(range(10), configuration)
# pylint: disable=line-too-long
expected = f"""
%0 = x # EncryptedScalar<uint4> ∈ [0, 9] {tests_directory}/representation/test_graph.py:324
%1 = 2 # ClearScalar<uint2> ∈ [2, 2] @ abc {tests_directory}/representation/test_graph.py:34
%2 = multiply(%0, %1) # EncryptedScalar<uint5> ∈ [0, 18] @ abc {tests_directory}/representation/test_graph.py:34
%3 = 42 # ClearScalar<uint6> ∈ [42, 42] @ abc.foo {tests_directory}/representation/test_graph.py:36
%4 = add(%2, %3) # EncryptedScalar<uint6> ∈ [42, 60] @ abc.foo {tests_directory}/representation/test_graph.py:36
%5 = subgraph(%4) # EncryptedScalar<uint3> ∈ [6, 7] @ abc {tests_directory}/representation/test_graph.py:37
%6 = 3 # ClearScalar<uint2> ∈ [3, 3] {tests_directory}/representation/test_graph.py:39
%7 = add(%5, %6) # EncryptedScalar<uint4> ∈ [9, 10] {tests_directory}/representation/test_graph.py:39
%8 = 120 # ClearScalar<uint7> ∈ [120, 120] @ def {tests_directory}/representation/test_graph.py:23
%9 = subtract(%8, %7) # EncryptedScalar<uint7> ∈ [110, 111] @ def {tests_directory}/representation/test_graph.py:23
%10 = 4 # ClearScalar<uint3> ∈ [4, 4] @ def {tests_directory}/representation/test_graph.py:24
%11 = floor_divide(%9, %10) # EncryptedScalar<uint5> ∈ [27, 27] @ def {tests_directory}/representation/test_graph.py:24
%12 = 2 # ClearScalar<uint2> ∈ [2, 2] {tests_directory}/representation/test_graph.py:39
%13 = multiply(%11, %12) # EncryptedScalar<uint6> ∈ [54, 54] {tests_directory}/representation/test_graph.py:39
return %13
Subgraphs:
%5 = subgraph(%4):
%0 = input # EncryptedScalar<uint2> @ abc.foo {tests_directory}/representation/test_graph.py:36
%1 = sqrt(%0) # EncryptedScalar<float64> @ abc {tests_directory}/representation/test_graph.py:37
%2 = astype(%1, dtype=int_) # EncryptedScalar<uint1> @ abc {tests_directory}/representation/test_graph.py:37
return %2
""" # noqa: E501
# pylint: enable=line-too-long
actual = graph.format(show_locations=True)
assert (
actual.strip() == expected.strip()
), f"""
Expected Output
===============
{expected}
Actual Output
=============
{actual}
"""

View File

@@ -0,0 +1,294 @@
"""
Tests of `Node` class.
"""
import numpy as np
import pytest
from concrete.numpy.dtypes import UnsignedInteger
from concrete.numpy.representation import Node
from concrete.numpy.values import ClearScalar, EncryptedScalar, EncryptedTensor, Value
@pytest.mark.parametrize(
"constant,expected_error,expected_message",
[
pytest.param(
"abc",
ValueError,
"Constant 'abc' is not supported",
),
],
)
def test_node_bad_constant(constant, expected_error, expected_message):
"""
Test `constant` function of `Node` class with bad parameters.
"""
with pytest.raises(expected_error) as excinfo:
Node.constant(constant)
assert str(excinfo.value) == expected_message
@pytest.mark.parametrize(
"node,args,expected_error,expected_message",
[
pytest.param(
Node.constant(1),
["abc"],
ValueError,
"Evaluation of constant '1' node using 'abc' failed "
"because of invalid number of arguments",
),
pytest.param(
Node.generic(
name="add",
inputs=[Value.of(4), Value.of(10, is_encrypted=True)],
output=Value.of(14),
operation=lambda x, y: x + y,
),
["abc"],
ValueError,
"Evaluation of generic 'add' node using 'abc' failed "
"because of invalid number of arguments",
),
pytest.param(
Node.generic(
name="add",
inputs=[Value.of(4), Value.of(10, is_encrypted=True)],
output=Value.of(14),
operation=lambda x, y: x + y,
),
["abc", "def"],
ValueError,
"Evaluation of generic 'add' node using 'abc', 'def' failed "
"because argument 'abc' is not valid",
),
pytest.param(
Node.generic(
name="add",
inputs=[Value.of([3, 4]), Value.of(10, is_encrypted=True)],
output=Value.of([13, 14]),
operation=lambda x, y: x + y,
),
[[1, 2, 3, 4], 10],
ValueError,
"Evaluation of generic 'add' node using [1, 2, 3, 4], 10 failed "
"because argument [1, 2, 3, 4] does not have the expected shape of (2,)",
),
pytest.param(
Node.generic(
name="unknown",
inputs=[],
output=Value.of(10),
operation=lambda: "abc",
),
[],
ValueError,
"Evaluation of generic 'unknown' node resulted in 'abc' of type str "
"which is not acceptable either because of the type or because of overflow",
),
pytest.param(
Node.generic(
name="unknown",
inputs=[],
output=Value.of(10),
operation=lambda: np.array(["abc", "def"]),
),
[],
ValueError,
"Evaluation of generic 'unknown' node resulted in array(['abc', 'def'], dtype='<U3') "
"of type np.ndarray and of underlying type 'dtype[str_]' "
"which is not acceptable because of the underlying type",
),
pytest.param(
Node.generic(
name="unknown",
inputs=[],
output=Value.of(10),
operation=lambda: [1, (), 3],
),
[],
ValueError,
"Evaluation of generic 'unknown' node resulted in [1, (), 3] of type list "
"which is not acceptable either because of the type or because of overflow",
),
pytest.param(
Node.generic(
name="unknown",
inputs=[],
output=Value.of(10),
operation=lambda: [1, 2, 3],
),
[],
ValueError,
"Evaluation of generic 'unknown' node resulted in array([1, 2, 3]) "
"which does not have the expected shape of ()",
),
],
)
def test_node_bad_call(node, args, expected_error, expected_message):
"""
Test `__call__` method of `Node` class.
"""
with pytest.raises(expected_error) as excinfo:
node(*args)
assert str(excinfo.value) == expected_message
@pytest.mark.parametrize(
"node,predecessors,expected_result",
[
pytest.param(
Node.constant(1),
[],
"1",
),
pytest.param(
Node.input("x", EncryptedScalar(UnsignedInteger(3))),
[],
"x",
),
pytest.param(
Node.generic(
name="tlu",
inputs=[
EncryptedTensor(UnsignedInteger(3), shape=(3, 2)),
],
output=EncryptedTensor(UnsignedInteger(3), shape=(3, 2)),
operation=lambda x, table: table[x],
kwargs={"table": np.array([4, 1, 3, 2])},
),
["%0"],
"tlu(%0, table=[4 1 3 2])",
),
pytest.param(
Node.generic(
name="index.static",
inputs=[EncryptedTensor(UnsignedInteger(3), shape=(3,))],
output=EncryptedTensor(UnsignedInteger(3), shape=(3,)),
operation=lambda x: x[slice(None, None, -1)],
kwargs={"index": (slice(None, None, -1),)},
),
["%0"],
"%0[::-1]",
),
pytest.param(
Node.generic(
name="concatenate",
inputs=[
EncryptedTensor(UnsignedInteger(3), shape=(3, 2)),
EncryptedTensor(UnsignedInteger(3), shape=(3, 2)),
EncryptedTensor(UnsignedInteger(3), shape=(3, 2)),
],
output=EncryptedTensor(UnsignedInteger(3), shape=(3, 6)),
operation=lambda *args, **kwargs: np.concatenate(tuple(args), **kwargs),
kwargs={"axis": 1},
),
["%0", "%1", "%2"],
"concatenate((%0, %1, %2), axis=1)",
),
pytest.param(
Node.generic(
name="array",
inputs=[
EncryptedScalar(UnsignedInteger(3)),
ClearScalar(UnsignedInteger(3)),
ClearScalar(UnsignedInteger(3)),
EncryptedScalar(UnsignedInteger(3)),
],
output=EncryptedTensor(UnsignedInteger(3), shape=(2, 2)),
operation=lambda *args: np.array(args).reshape((2, 2)),
),
["%0", "%1", "%2", "%3"],
"array([[%0, %1], [%2, %3]])",
),
pytest.param(
Node.generic(
name="assign.static",
inputs=[EncryptedTensor(UnsignedInteger(3), shape=(3, 4))],
output=EncryptedTensor(UnsignedInteger(3), shape=(3, 4)),
operation=lambda *args: args,
kwargs={"index": (1, 2)},
),
["%0", "%1"],
"(%0[1, 2] = %1)",
),
],
)
def test_node_format(node, predecessors, expected_result):
"""
Test `format` method of `Node` class.
"""
assert node.format(predecessors) == expected_result
@pytest.mark.parametrize(
"node,expected_result",
[
pytest.param(
Node.constant(1),
"1",
),
pytest.param(
Node.input("x", EncryptedScalar(UnsignedInteger(3))),
"x",
),
pytest.param(
Node.generic(
name="tlu",
inputs=[
EncryptedTensor(UnsignedInteger(3), shape=(3, 2)),
],
output=EncryptedTensor(UnsignedInteger(3), shape=(3, 2)),
operation=lambda x, table: table[x],
kwargs={"table": np.array([4, 1, 3, 2])},
),
"tlu",
),
pytest.param(
Node.generic(
name="concatenate",
inputs=[
EncryptedTensor(UnsignedInteger(3), shape=(3, 2)),
EncryptedTensor(UnsignedInteger(3), shape=(3, 2)),
EncryptedTensor(UnsignedInteger(3), shape=(3, 2)),
],
output=EncryptedTensor(UnsignedInteger(3), shape=(3, 6)),
operation=lambda *args, **kwargs: np.concatenate(tuple(args), **kwargs),
kwargs={"axis": -1},
),
"concatenate",
),
pytest.param(
Node.generic(
name="index.static",
inputs=[EncryptedTensor(UnsignedInteger(3), shape=(3, 4))],
output=EncryptedTensor(UnsignedInteger(3), shape=()),
operation=lambda *args: args,
kwargs={"index": (1, 2)},
),
"□[1, 2]",
),
pytest.param(
Node.generic(
name="assign.static",
inputs=[EncryptedTensor(UnsignedInteger(3), shape=(3, 4))],
output=EncryptedTensor(UnsignedInteger(3), shape=(3, 4)),
operation=lambda *args: args,
kwargs={"index": (1, 2)},
),
"□[1, 2] = □",
),
],
)
def test_node_label(node, expected_result):
"""
Test `label` method of `Node` class.
"""
assert node.label() == expected_result

View File

@@ -0,0 +1,56 @@
"""
Tests of utilities related to representation of computation.
"""
import numpy as np
import pytest
from concrete.numpy.representation.utils import format_constant
@pytest.mark.parametrize(
"constant,maximum_length,keep_newlines,expected_result",
[
pytest.param(
1,
45,
True,
"1",
),
pytest.param(
np.uint32,
45,
True,
"uintc",
),
pytest.param(
np.array([[1, 2], [3, 4]]),
45,
True,
"[[1 2]\n [3 4]]",
),
pytest.param(
np.array([[1, 2], [3, 4]]),
45,
False,
"[[1 2] [3 4]]",
),
pytest.param(
np.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 0], [1, 2], [3, 4], [5, 6]]),
45,
True,
"[[1 2]\n [3 4]\n [5 6]\n...\n[1 2]\n [3 4]\n [5 6]]",
),
pytest.param(
np.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 0], [1, 2], [3, 4], [5, 6]]),
45,
False,
"[[1 2] [3 4] [5 6] [ ... ] [1 2] [3 4] [5 6]]",
),
],
)
def test_format_constant(constant, maximum_length, keep_newlines, expected_result):
"""
Test `format_constant` function.
"""
assert format_constant(constant, maximum_length, keep_newlines) == expected_result

View File

@@ -0,0 +1,3 @@
"""
Tests of `concrete.numpy.tracing` namespace.
"""

View File

@@ -0,0 +1,157 @@
"""
Tests of `Tracer` class.
"""
import numpy as np
import pytest
from concrete.numpy.dtypes import UnsignedInteger
from concrete.numpy.tracing import Tracer
from concrete.numpy.tracing.typing import uint4
from concrete.numpy.values import EncryptedTensor
@pytest.mark.parametrize(
"function,parameters,expected_error,expected_message",
[
pytest.param(
lambda x: np.ravel(x),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(3, 2))},
RuntimeError,
"Function 'np.ravel' is not supported",
),
pytest.param(
lambda x: np.sum(x, initial=42),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(3, 2))},
RuntimeError,
"Function 'np.sum' is not supported with kwarg 'initial'",
),
pytest.param(
lambda x: np.absolute(x, where=False),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(1, 2, 3))},
RuntimeError,
"Function 'np.absolute' is not supported with kwarg 'where'",
),
pytest.param(
lambda x: np.multiply.outer(x, [1, 2, 3]),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(4,))},
RuntimeError,
"Only __call__ hook is supported for numpy ufuncs",
),
pytest.param(
lambda x: x.astype(uint4),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(4,))},
ValueError,
"`astype` method must be called with a "
"numpy type for compilation (e.g., value.astype(np.int64))",
),
pytest.param(
lambda x: x + 1 if x else x + x,
{"x": EncryptedTensor(UnsignedInteger(7), shape=())},
RuntimeError,
"Branching within circuits is not possible",
),
],
)
def test_tracer_bad_trace(function, parameters, expected_error, expected_message):
"""
Test `trace` function of `Tracer` class with bad parameters.
"""
with pytest.raises(expected_error) as excinfo:
Tracer.trace(function, parameters)
assert str(excinfo.value) == expected_message
@pytest.mark.parametrize(
"function,parameters,expected_message",
[
pytest.param(
lambda x: x.astype(np.int8),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(3, 2))},
(
"Warning: When using `value.astype(newtype)` "
"with an integer newtype, "
"only use `np.int64` as the newtype "
"to avoid unexpected overflows "
"during inputset evaluation"
),
),
pytest.param(
lambda x: x.astype(np.int16),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(3, 2))},
(
"Warning: When using `value.astype(newtype)` "
"with an integer newtype, "
"only use `np.int64` as the newtype "
"to avoid unexpected overflows "
"during inputset evaluation"
),
),
pytest.param(
lambda x: x.astype(np.int32),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(3, 2))},
(
"Warning: When using `value.astype(newtype)` "
"with an integer newtype, "
"only use `np.int64` as the newtype "
"to avoid unexpected overflows "
"during inputset evaluation"
),
),
pytest.param(
lambda x: x.astype(np.uint8),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(3, 2))},
(
"Warning: When using `value.astype(newtype)` "
"with an integer newtype, "
"only use `np.int64` as the newtype "
"to avoid unexpected overflows "
"during inputset evaluation"
),
),
pytest.param(
lambda x: x.astype(np.uint16),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(3, 2))},
(
"Warning: When using `value.astype(newtype)` "
"with an integer newtype, "
"only use `np.int64` as the newtype "
"to avoid unexpected overflows "
"during inputset evaluation"
),
),
pytest.param(
lambda x: x.astype(np.uint32),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(3, 2))},
(
"Warning: When using `value.astype(newtype)` "
"with an integer newtype, "
"only use `np.int64` as the newtype "
"to avoid unexpected overflows "
"during inputset evaluation"
),
),
pytest.param(
lambda x: x.astype(np.uint64),
{"x": EncryptedTensor(UnsignedInteger(7), shape=(3, 2))},
(
"Warning: When using `value.astype(newtype)` "
"with an integer newtype, "
"only use `np.int64` as the newtype "
"to avoid unexpected overflows "
"during inputset evaluation"
),
),
],
)
def test_tracer_warning_trace(function, parameters, expected_message, capsys):
"""
Test `trace` function of `Tracer` class with parameters that result in a warning.
"""
Tracer.trace(function, parameters)
captured = capsys.readouterr()
assert captured.out.strip() == expected_message

View File

@@ -0,0 +1,54 @@
"""
Test type annotations.
"""
import pytest
import concrete.numpy as cnp
def test_bad_tensor():
"""
Test `tensor` type with bad parameters
"""
# invalid dtype
# -------------
with pytest.raises(ValueError) as excinfo:
def case1(x: cnp.tensor[int]):
return x
case1(None)
assert str(excinfo.value) == (
"First argument to tensor annotations should be a "
"concrete-numpy data type (e.g., cnp.uint4) not int"
)
# no shape
# --------
with pytest.raises(ValueError) as excinfo:
def case2(x: cnp.tensor[cnp.uint3]):
return x
case2(None)
assert str(excinfo.value) == (
"Tensor annotations should have a shape (e.g., cnp.tensor[cnp.uint4, 3, 2])"
)
# bad shape
# ---------
with pytest.raises(ValueError) as excinfo:
def case3(x: cnp.tensor[cnp.uint3, 1.5]):
return x
case3(None)
assert str(excinfo.value) == "Tensor annotation shape elements must be 'int'"

View File

@@ -0,0 +1,3 @@
"""
Tests of `concrete.numpy.values` namespace.
"""

View File

@@ -0,0 +1,321 @@
"""
Tests of `Value` class.
"""
import numpy as np
import pytest
from concrete.numpy.dtypes import Float, SignedInteger, UnsignedInteger
from concrete.numpy.values import ClearScalar, ClearTensor, EncryptedScalar, EncryptedTensor, Value
@pytest.mark.parametrize(
"value,is_encrypted,expected_result",
[
pytest.param(
True,
True,
Value(dtype=UnsignedInteger(1), shape=(), is_encrypted=True),
),
pytest.param(
True,
False,
Value(dtype=UnsignedInteger(1), shape=(), is_encrypted=False),
),
pytest.param(
False,
True,
Value(dtype=UnsignedInteger(1), shape=(), is_encrypted=True),
),
pytest.param(
False,
False,
Value(dtype=UnsignedInteger(1), shape=(), is_encrypted=False),
),
pytest.param(
0,
False,
Value(dtype=UnsignedInteger(1), shape=(), is_encrypted=False),
),
pytest.param(
np.int32(0),
True,
Value(dtype=UnsignedInteger(1), shape=(), is_encrypted=True),
),
pytest.param(
0.0,
False,
Value(dtype=Float(64), shape=(), is_encrypted=False),
),
pytest.param(
np.float64(0.0),
True,
Value(dtype=Float(64), shape=(), is_encrypted=True),
),
pytest.param(
np.float32(0.0),
False,
Value(dtype=Float(32), shape=(), is_encrypted=False),
),
pytest.param(
np.float16(0.0),
True,
Value(dtype=Float(16), shape=(), is_encrypted=True),
),
pytest.param(
[True, False, True],
False,
Value(dtype=UnsignedInteger(1), shape=(3,), is_encrypted=False),
),
pytest.param(
[True, False, True],
True,
Value(dtype=UnsignedInteger(1), shape=(3,), is_encrypted=True),
),
pytest.param(
[0, 3, 1, 2],
False,
Value(dtype=UnsignedInteger(2), shape=(4,), is_encrypted=False),
),
pytest.param(
np.array([0, 3, 1, 2], dtype=np.int32),
True,
Value(dtype=UnsignedInteger(2), shape=(4,), is_encrypted=True),
),
pytest.param(
np.array([0.2, 3.4, 1.5, 2.0], dtype=np.float64),
False,
Value(dtype=Float(64), shape=(4,), is_encrypted=False),
),
pytest.param(
np.array([0.2, 3.4, 1.5, 2.0], dtype=np.float32),
True,
Value(dtype=Float(32), shape=(4,), is_encrypted=True),
),
pytest.param(
np.array([0.2, 3.4, 1.5, 2.0], dtype=np.float16),
False,
Value(dtype=Float(16), shape=(4,), is_encrypted=False),
),
],
)
def test_value_of(value, is_encrypted, expected_result):
"""
Test `of` function of `Value` class.
"""
assert Value.of(value, is_encrypted) == expected_result
@pytest.mark.parametrize(
"value,is_encrypted,expected_error,expected_message",
[
pytest.param(
"abc",
False,
ValueError,
"Value cannot represent 'abc'",
),
pytest.param(
[1, (), 3],
False,
ValueError,
"Value cannot represent [1, (), 3]",
),
],
)
def test_value_bad_of(value, is_encrypted, expected_error, expected_message):
"""
Test `of` function of `Value` class with bad parameters.
"""
with pytest.raises(expected_error) as excinfo:
Value.of(value, is_encrypted)
assert str(excinfo.value) == expected_message
@pytest.mark.parametrize(
"lhs,rhs,expected_result",
[
pytest.param(
ClearScalar(SignedInteger(5)),
Value(dtype=SignedInteger(5), shape=(), is_encrypted=False),
True,
),
pytest.param(
ClearTensor(UnsignedInteger(5), shape=(3, 2)),
Value(dtype=UnsignedInteger(5), shape=(3, 2), is_encrypted=False),
True,
),
pytest.param(
EncryptedScalar(SignedInteger(5)),
Value(dtype=SignedInteger(5), shape=(), is_encrypted=True),
True,
),
pytest.param(
EncryptedTensor(UnsignedInteger(5), shape=(3, 2)),
Value(dtype=UnsignedInteger(5), shape=(3, 2), is_encrypted=True),
True,
),
pytest.param(
EncryptedTensor(UnsignedInteger(5), shape=(3, 2)),
ClearScalar(SignedInteger(3)),
False,
),
pytest.param(
ClearScalar(SignedInteger(3)),
"ClearScalar(SignedInteger(3))",
False,
),
pytest.param(
"ClearScalar(SignedInteger(3))",
ClearScalar(SignedInteger(3)),
False,
),
],
)
def test_value_eq(lhs, rhs, expected_result):
"""
Test `__eq__` method of `Value` class.
"""
assert (lhs == rhs) == expected_result
assert (rhs == lhs) == expected_result
@pytest.mark.parametrize(
"data_type,expected_result",
[
pytest.param(
ClearScalar(SignedInteger(5)),
"ClearScalar<int5>",
),
pytest.param(
EncryptedScalar(SignedInteger(5)),
"EncryptedScalar<int5>",
),
pytest.param(
ClearTensor(SignedInteger(5), shape=(3, 2)),
"ClearTensor<int5, shape=(3, 2)>",
),
pytest.param(
EncryptedTensor(SignedInteger(5), shape=(3, 2)),
"EncryptedTensor<int5, shape=(3, 2)>",
),
],
)
def test_value_str(data_type, expected_result):
"""
Test `__str__` method of `Value` class.
"""
assert str(data_type) == expected_result
@pytest.mark.parametrize(
"value,expected_result",
[
pytest.param(
ClearScalar(SignedInteger(5)),
True,
),
pytest.param(
EncryptedScalar(SignedInteger(5)),
False,
),
],
)
def test_value_is_clear(value, expected_result):
"""
Test `is_clear` property of `Value` class.
"""
assert value.is_clear == expected_result
@pytest.mark.parametrize(
"value,expected_result",
[
pytest.param(
EncryptedScalar(SignedInteger(5)),
True,
),
pytest.param(
EncryptedTensor(SignedInteger(5), shape=(3, 2)),
False,
),
],
)
def test_value_is_scalar(value, expected_result):
"""
Test `is_scalar` property of `Value` class.
"""
assert value.is_scalar == expected_result
@pytest.mark.parametrize(
"value,expected_result",
[
pytest.param(
EncryptedScalar(SignedInteger(5)),
0,
),
pytest.param(
EncryptedTensor(SignedInteger(5), shape=(3,)),
1,
),
pytest.param(
EncryptedTensor(SignedInteger(5), shape=(3, 2)),
2,
),
pytest.param(
EncryptedTensor(SignedInteger(5), shape=(5, 3, 2)),
3,
),
],
)
def test_value_ndim(value, expected_result):
"""
Test `ndim` property of `Value` class.
"""
assert value.ndim == expected_result
@pytest.mark.parametrize(
"value,expected_result",
[
pytest.param(
EncryptedScalar(SignedInteger(5)),
1,
),
pytest.param(
EncryptedTensor(SignedInteger(5), shape=(3,)),
3,
),
pytest.param(
EncryptedTensor(SignedInteger(5), shape=(3, 2)),
6,
),
pytest.param(
EncryptedTensor(SignedInteger(5), shape=(5, 3, 2)),
30,
),
pytest.param(
EncryptedTensor(SignedInteger(5), shape=(1,)),
1,
),
pytest.param(
EncryptedTensor(SignedInteger(5), shape=(1, 1)),
1,
),
],
)
def test_value_size(value, expected_result):
"""
Test `size` property of `Value` class.
"""
assert value.size == expected_result