Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
docker pull zamafhe/concrete-python:v2.0.0
docker run --rm -it zamafhe/concrete-python:latest /bin/bash

import numpy as np
def encrypted_max(x: uint4):
return np.maximum(5, x)def encrypted_max(x: uint4):
lut = [5, 5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
return lut[x]module {
func.func @main(%arg0: !FHE.eint<4>) -> !FHE.eint<4> {
%cst = arith.constant dense<[5, 5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
return %0 : !FHE.eint<4>
}
}from concrete import fhe
def add(x, y):
return x + y
compiler = fhe.Compiler(add, {"x": "encrypted", "y": "clear"})
inputset = [(2, 3), (0, 0), (1, 6), (7, 7), (7, 1)]
circuit = compiler.compile(inputset)
x = 4
y = 4
clear_evaluation = add(x, y)
homomorphic_evaluation = circuit.encrypt_run_decrypt(x, y)
print(x, "+", y, "=", clear_evaluation, "=", homomorphic_evaluation)str(circuit)print(circuit)from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
return (x + 1) ** 2
inputset = [np.random.randint(0, 10, size=(10,)) for _ in range(10)]
circuit = f.compile(inputset, p_error=0.1, fhe_simulation=True)
sample = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
actual = f(sample)
simulation = circuit.simulate(sample)
print(actual.tolist())
print(simulation.tolist())[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[1, 4, 9, 16, 16, 36, 49, 64, 81, 100]from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return x + 42
inputset = range(10)
circuit = f.compile(inputset)
assert circuit.encrypt_run_decrypt(10) == f(10)from concrete import fhedef add(x, y):
return x + ycompiler = fhe.Compiler(add, {"x": "encrypted", "y": "clear"})inputset = [(2, 3), (0, 0), (1, 6), (7, 7), (7, 1)]circuit = compiler.compile(inputset)homomorphic_evaluation = circuit.encrypt_run_decrypt(4, 4)fuse:def g(z):
with fhe.tag("def"):
a = 120 - z
b = a // 4
return b
def f(x):
with fhe.tag("abc"):
x = x * 2
with fhe.tag("foo"):
y = x + 42
z = np.sqrt(y).astype(np.int64)
return g(z + 3) * 2from concrete import fhe
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def add(x, y):
return x + y
inputset = [(2, 3), (0, 0), (1, 6), (7, 7), (7, 1), (3, 2), (6, 1), (1, 7), (4, 5), (5, 4)]
circuit = add.compile(inputset)
sample_y = 4
_, encrypted_y = circuit.encrypt(None, sample_y)
for sample_x in range(3, 6):
encrypted_x, _ = circuit.encrypt(sample_x, None)
encrypted_result = circuit.run(encrypted_x, encrypted_y)
result = circuit.decrypt(encrypted_result)
assert result == sample_x + sample_y %0 = x # EncryptedScalar<uint4> ∈ [0, 9]
%1 = 2 # ClearScalar<uint2> ∈ [2, 2] @ abc
%2 = multiply(%0, %1) # EncryptedScalar<uint5> ∈ [0, 18] @ abc
%3 = 42 # ClearScalar<uint6> ∈ [42, 42] @ abc.foo
%4 = add(%2, %3) # EncryptedScalar<uint6> ∈ [42, 60] @ abc.foo
%5 = subgraph(%4) # EncryptedScalar<uint3> ∈ [6, 7] @ abc
%6 = 3 # ClearScalar<uint2> ∈ [3, 3]
%7 = add(%5, %6) # EncryptedScalar<uint4> ∈ [9, 10]
%8 = 120 # ClearScalar<uint7> ∈ [120, 120] @ def
%9 = subtract(%8, %7) # EncryptedScalar<uint7> ∈ [110, 111] @ def
%10 = 4 # ClearScalar<uint3> ∈ [4, 4] @ def
%11 = floor_divide(%9, %10) # EncryptedScalar<uint5> ∈ [27, 27] @ def
%12 = 2 # ClearScalar<uint2> ∈ [2, 2]
%13 = multiply(%11, %12) # EncryptedScalar<uint6> ∈ [54, 54]
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 %2lut = [0, 1, 4, 9, 16, 25, 36, 49, 64]concrete-cuda that provides a GPU acceleration of TFHE primitives.from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
a = x + 1.5
b = np.sin(x)
c = np.around(a + b)
d = c.astype(np.int64)
return d
inputset = range(8)
circuit = f.compile(inputset)
for x in range(8):
assert circuit.encrypt_run_decrypt(x) == f(x)fhe.Keys%0 = x # EncryptedScalar<uint3> ∈ [0, 7]
%1 = y # EncryptedScalar<uint4> ∈ [0, 15]
%2 = add(%0, %1) # EncryptedScalar<uint5> ∈ [2, 22]
return %2 ^ these are ^^^^^^^
the assigned based on
bit-widths these boundsD: data
N: noise
3-bit number
------------
D2 D1 D0 0 0 0 ... 0 0 0 N N N N
4-bit number
------------
D3 D2 D1 D0 0 0 0 ... 0 0 0 N N N Nfrom concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def f(x, y):
a = x + 1.5
b = np.sin(y)
c = np.around(a + b)
d = c.astype(np.int64)
return d
inputset = [(1, 2), (3, 0), (2, 2), (1, 3)]
circuit = f.compile(inputset)
for x in range(8):
assert circuit.encrypt_run_decrypt(x) == f(x)RuntimeError: Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedScalar<uint2>
%1 = 1.5 # ClearScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer constants are supported
%2 = y # EncryptedScalar<uint2>
%3 = add(%0, %1) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
%4 = sin(%2) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
%5 = add(%3, %4) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
%6 = around(%5) # EncryptedScalar<float64>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
%7 = astype(%6, dtype=int_) # EncryptedScalar<uint3>
return %7from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return x ** 2
inputset = range(10)
circuit = f.compile(inputset)circuit.keys.generate()circuit.keys.generate(seed=420)serialized_keys: bytes = circuit.keys.serialize()keys: fhe.Keys = fhe.Keys.deserialize(serialized_keys)circuit.keys = keyscircuit.keys.save("/path/to/keys")circuit.keys.load("/path/to/keys")circuit.keys.load_if_exists_generate_and_save_otherwise("/path/to/keys")5-bit number
------------
D4 D3 D2 D1 D0 0 0 0 ... 0 0 0 N N N N%0 = x # EncryptedScalar<uint5>
%1 = y # EncryptedScalar<uint5>
%2 = add(%0, %1) # EncryptedScalar<uint5>
return %2%0 = x # EncryptedScalar<uint2> ∈ [0, 3]
%1 = y # EncryptedScalar<uint5> ∈ [0, 31]
%2 = 2 # ClearScalar<uint2> ∈ [2, 2]
%3 = power(%0, %2) # EncryptedScalar<uint4> ∈ [0, 9]
%4 = add(%3, %1) # EncryptedScalar<uint6> ∈ [1, 39]
return %4%0 = x # EncryptedScalar<uint2> ∈ [0, 3]
%1 = y # EncryptedScalar<uint6> ∈ [0, 31]
%2 = 2 # ClearScalar<uint2> ∈ [2, 2]
%3 = power(%0, %2) # EncryptedScalar<uint6> ∈ [0, 9]
%4 = add(%3, %1) # EncryptedScalar<uint6> ∈ [1, 39]
return %4Tracing.trace_ciphertext (::mlir::concretelang::Tracing::TraceCiphertextOp)-DCONCRETELANG_CUDA_SUPPORT=${CUDA_SUPPORT}
-DCUDAToolkit_ROOT=$(CUDA_PATH)from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def function(x):
return x + 42
inputset = range(10)
circuit = function.compile(inputset)circuit.server.save("server.zip")from concrete import fhe
server = fhe.Server.load("server.zip")serialized_client_specs: str = server.client_specs.serialize()client_specs = fhe.ClientSpecs.deserialize(serialized_client_specs)
client = fhe.Client(client_specs)client.keys.generate()serialized_evaluation_keys: bytes = client.evaluation_keys.serialize()arg: fhe.Value = client.encrypt(7)
serialized_arg: bytes = arg.serialize()deserialized_evaluation_keys = fhe.EvaluationKeys.deserialize(serialized_evaluation_keys)
deserialized_arg = fhe.Value.deserialize(serialized_arg)result: fhe.Value = server.run(deserialized_arg, evaluation_keys=deserialized_evaluation_keys)
serialized_result: bytes = result.serialize()deserialized_result = fhe.Value.deserialize(serialized_result)decrypted_result = client.decrypt(deserialized_result)
assert decrypted_result == 49make generate-curvesmake compare-curvesmake generate-codesage: X = load("128.sobj")sage: X["128"][0]
(2366, 64.0, 4.0, 128.51)sage: curves = load("verified_curves.sobj")sage: curves[2][:3]
(-0.026599462343105267, 2.981543184145991, 128)bindgen (as we did to build the Rust bindings) to generate the Rust FFI from the CAPI and use it as is, we will here show how to use the Rust API that is built on top of that, as it's easier to use.Evaluation: 10% |█████.............................................| 10% (scaling.r)
^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^
Title Progressbar TagEvaluation: 30% |███████████████...................................| 30% (scaling.g)Evaluation: 50% |█████████████████████████.........................| 50% (scaling.b)func.func @main(%arg0: tensor<4x4x!FHE.eint<6>>, %arg1: tensor<4x2xi7>) -> tensor<4x2x!FHE.eint<6>> {
%0 = "FHELinalg.matmul_eint_int"(%arg0, %arg1): (tensor<4x4x!FHE.eint<6>>, tensor<4x2xi7>) -> (tensor<4x2x!FHE.eint<6>>)
%tlu = arith.constant dense<[40, 13, 20, 62, 47, 41, 46, 30, 59, 58, 17, 4, 34, 44, 49, 5, 10, 63, 18, 21, 33, 45, 7, 14, 24, 53, 56, 3, 22, 29, 1, 39, 48, 32, 38, 28, 15, 12, 52, 35, 42, 11, 6, 43, 0, 16, 27, 9, 31, 51, 36, 37, 55, 57, 54, 2, 8, 25, 50, 23, 61, 60, 26, 19]> : tensor<64xi64>
%result = "FHELinalg.apply_lookup_table"(%0, %tlu): (tensor<4x2x!FHE.eint<6>>, tensor<64xi64>) -> (tensor<4x2x!FHE.eint<6>>)
return %result: tensor<4x2x!FHE.eint<6>>
}$ concretecompiler --action=compile -o rust-demo example.mlir$ ls rust-demo/
client_parameters.concrete.params.json compilation_feedback.json fhecircuit-client.h sharedlib.so staticlib.ause concrete_compiler::compiler::{KeySet, LambdaArgument, LibrarySupport};let lib_support = LibrarySupport::new(
"/path/to/your/rust-demo/",
None,
)
.unwrap();
let compilation_result = lib_support.load_compilation_result().unwrap();let server_lambda = lib_support.load_server_lambda(&compilation_result).unwrap();
let client_params = lib_support.load_client_parameters(&compilation_result).unwrap();let key_set = KeySet::new(&client_params, None, None, None).unwrap();
let args = [
LambdaArgument::from_tensor_u8(&[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4], &[4, 4])
.unwrap(),
LambdaArgument::from_tensor_u8(&[1, 2, 1, 2, 1, 2, 1, 2], &[4, 2]).unwrap(),
];
let encrypted_args = key_set.encrypt_args(&args).unwrap();let eval_keys = key_set.evaluation_keys().unwrap();
let encrypted_result = lib_support
.server_lambda_call(&server_lambda, &encrypted_args, &eval_keys)
.unwrap()let result_arg = key_set.decrypt_result(&encrypted_result).unwrap();
println!("result tensor dims: {:?}", result_arg.dims().unwrap());
println!("result tensor data: {:?}", result_arg.data().unwrap());import time
import matplotlib.pyplot as plt
import numpy as np
import randimage
from concrete import fhe
configuration = fhe.Configuration(
enable_unsafe_features=True,
use_insecure_key_cache=True,
insecure_key_cache_location=".keys",
# To enable displaying progressbar
show_progress=True,
# To enable showing tags in the progressbar (does not work in notebooks)
progress_tag=True,
# To give a title to the progressbar
progress_title="Evaluation:",
)
@fhe.compiler({"image": "encrypted"})
def to_grayscale(image):
with fhe.tag("scaling.r"):
r = image[:, :, 0]
r = (r * 0.30).astype(np.int64)
with fhe.tag("scaling.g"):
g = image[:, :, 1]
g = (g * 0.59).astype(np.int64)
with fhe.tag("scaling.b"):
b = image[:, :, 2]
b = (b * 0.11).astype(np.int64)
with fhe.tag("combining.rgb"):
gray = r + g + b
with fhe.tag("creating.result"):
gray = np.expand_dims(gray, axis=2)
result = np.concatenate((gray, gray, gray), axis=2)
return result
image_size = (16, 16)
image_data = (randimage.get_random_image(image_size) * 255).round().astype(np.int64)
print()
print(f"Compilation started @ {time.strftime('%H:%M:%S', time.localtime())}")
start = time.time()
inputset = [np.random.randint(0, 256, size=image_data.shape) for _ in range(100)]
circuit = to_grayscale.compile(inputset, configuration)
end = time.time()
print(f"(took {end - start:.3f} seconds)")
print()
print(f"Key generation started @ {time.strftime('%H:%M:%S', time.localtime())}")
start = time.time()
circuit.keygen()
end = time.time()
print(f"(took {end - start:.3f} seconds)")
print()
print(f"Evaluation started @ {time.strftime('%H:%M:%S', time.localtime())}")
start = time.time()
grayscale_image_data = circuit.encrypt_run_decrypt(image_data)
end = time.time()
print(f"(took {end - start:.3f} seconds)")
fig, axs = plt.subplots(1, 2)
axs = axs.flatten()
axs[0].set_title("Original")
axs[0].imshow(image_data)
axs[0].axis("off")
axs[1].set_title("Grayscale")
axs[1].imshow(grayscale_image_data)
axs[1].axis("off")
plt.show()



IndexError: index 10 is out of bounds for axis 0 with size 6from concrete import fhe
table = fhe.LookupTable([2, -1, 3, 0])
@fhe.compiler({"x": "encrypted"})
def f(x):
return table[x]
inputset = range(4)
circuit = f.compile(inputset)
assert circuit.encrypt_run_decrypt(0) == table[0] == 2
assert circuit.encrypt_run_decrypt(1) == table[1] == -1
assert circuit.encrypt_run_decrypt(2) == table[2] == 3
assert circuit.encrypt_run_decrypt(3) == table[3] == 0from concrete import fhe
import numpy as np
table = fhe.LookupTable([2, -1, 3, 0])
@fhe.compiler({"x": "encrypted"})
def f(x):
return table[x]
inputset = [np.random.randint(0, 4, size=(2, 3)) for _ in range(10)]
circuit = f.compile(inputset)
sample = [
[0, 1, 3],
[2, 3, 1],
]
expected_output = [
[2, -1, 0],
[3, 0, -1],
]
actual_output = circuit.encrypt_run_decrypt(np.array(sample))
for i in range(2):
for j in range(3):
assert actual_output[i][j] == expected_output[i][j] == table[sample[i][j]]from concrete import fhe
table = fhe.LookupTable([2, -1, 3, 0])
@fhe.compiler({"x": "encrypted"})
def f(x):
return table[-x]
inputset = range(1, 5)
circuit = f.compile(inputset)
assert circuit.encrypt_run_decrypt(1) == table[-1] == 0
assert circuit.encrypt_run_decrypt(2) == table[-2] == 3
assert circuit.encrypt_run_decrypt(3) == table[-3] == -1
assert circuit.encrypt_run_decrypt(4) == table[-4] == 2from concrete import fhe
import numpy as np
squared = fhe.LookupTable([i ** 2 for i in range(4)])
cubed = fhe.LookupTable([i ** 3 for i in range(4)])
table = fhe.LookupTable([
[squared, cubed],
[squared, cubed],
[squared, cubed],
])
@fhe.compiler({"x": "encrypted"})
def f(x):
return table[x]
inputset = [np.random.randint(0, 4, size=(3, 2)) for _ in range(10)]
circuit = f.compile(inputset)
sample = [
[0, 1],
[2, 3],
[3, 0],
]
expected_output = [
[0, 1],
[4, 27],
[9, 0]
]
actual_output = circuit.encrypt_run_decrypt(np.array(sample))
for i in range(3):
for j in range(2):
if j == 0:
assert actual_output[i][j] == expected_output[i][j] == squared[sample[i][j]]
else:
assert actual_output[i][j] == expected_output[i][j] == cubed[sample[i][j]]from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
return (42 * np.sin(x)).astype(np.int64) // 10
inputset = range(8)
circuit = f.compile(inputset)
for x in range(8):
assert circuit.encrypt_run_decrypt(x) == f(x)from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return (x**2) + (2*x) + 4
inputset = range(2**2)
circuit = f.compile(inputset, show_statistics=True)Statistics
--------------------------------------------------------------------------------
size_of_secret_keys: 22648
size_of_bootstrap_keys: 51274176
size_of_keyswitch_keys: 64092720
size_of_inputs: 16392
size_of_outputs: 16392
p_error: 9.627450598589458e-06
global_p_error: 9.627450598589458e-06
complexity: 99198195.0
programmable_bootstrap_count: 1
programmable_bootstrap_count_per_parameter: {
BootstrapKeyParam(polynomial_size=2048, glwe_dimension=1, input_lwe_dimension=781, level=1, base_log=23, variance=9.940977002694397e-32): 1
}
key_switch_count: 1
key_switch_count_per_parameter: {
KeyswitchKeyParam(level=5, base_log=3, variance=1.939836732335308e-11): 1
}
packing_key_switch_count: 0
clear_addition_count: 1
clear_addition_count_per_parameter: {
LweSecretKeyParam(dimension=2048): 1
}
encrypted_addition_count: 1
encrypted_addition_count_per_parameter: {
LweSecretKeyParam(dimension=2048): 1
}
clear_multiplication_count: 1
clear_multiplication_count_per_parameter: {
LweSecretKeyParam(dimension=2048): 1
}
encrypted_negation_count: 0
--------------------------------------------------------------------------------Statistics
--------------------------------------------------------------------------------
clear_multiplication_count_per_tag: {
/model/model: 53342
/model/model.0/Gemm: 14720
/model/model.0/Gemm.matmul: 14720
/model/model.2/Gemm: 11730
/model/model.2/Gemm.matmul: 11730
/model/model.4/Gemm: 9078
/model/model.4/Gemm.matmul: 9078
/model/model.6/Gemm: 6764
/model/model.6/Gemm.matmul: 6764
/model/model.8/Gemm: 4788
/model/model.8/Gemm.matmul: 4788
/model/model.10/Gemm: 3150
/model/model.10/Gemm.matmul: 3150
/model/model.12/Gemm: 1850
/model/model.12/Gemm.matmul: 1850
/model/model.14/Gemm: 888
/model/model.14/Gemm.matmul: 888
/model/model.16/Gemm: 264
/model/model.16/Gemm.matmul: 264
/model/model.18/Gemm: 110
/model/model.18/Gemm.matmul: 110
}
encrypted_addition_count_per_tag: {
/model/model: 53342
/model/model.0/Gemm: 14720
/model/model.0/Gemm.matmul: 14720
/model/model.2/Gemm: 11730
/model/model.2/Gemm.matmul: 11730
/model/model.4/Gemm: 9078
/model/model.4/Gemm.matmul: 9078
/model/model.6/Gemm: 6764
/model/model.6/Gemm.matmul: 6764
/model/model.8/Gemm: 4788
/model/model.8/Gemm.matmul: 4788
/model/model.10/Gemm: 3150
/model/model.10/Gemm.matmul: 3150
/model/model.12/Gemm: 1850
/model/model.12/Gemm.matmul: 1850
/model/model.14/Gemm: 888
/model/model.14/Gemm.matmul: 888
/model/model.16/Gemm: 264
/model/model.16/Gemm.matmul: 264
/model/model.18/Gemm: 110
/model/model.18/Gemm.matmul: 110
}
--------------------------------------------------------------------------------from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
return x + 42
inputset = range(10)
circuit = f.compile(inputset, p_error=0.01, dataflow_parallelize=True)from concrete import fhe
import numpy as np
configuration = fhe.Configuration(p_error=0.01)
@fhe.compiler({"x": "encrypted"})
def f(x):
return x + 42
inputset = range(10)
circuit = f.compile(inputset, configuration=configuration, loop_parallelize=True)*: [4, 4]

from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return x ** 2
inputset = range(2 ** 4)
circuit = f.compile(inputset)from concrete import fhe
table = fhe.LookupTable([x ** 2 for x in range(2 ** 4)])
@fhe.compiler({"x": "encrypted"})
def f(x):
return table[x]
inputset = range(2 ** 4)
circuit = f.compile(inputset)SDFG.get (::mlir::concretelang::SDFG::Get)def f(x):
return (2 * x) + 3def f(x, y):
return x + 2 * y
x = Tracer(computation=Input("x"))
y = Tracer(computation=Input("y"))
resulting_tracer = f(x, y)[2, 3, 1]"SDFG.get" (%stream) : (!SDFG.stream<1024xi64>) -> (tensor<1024xi64>)"SDFG.init" : () -> !SDFG.dfg%in0 = "SDFG.make_stream" { type = #SDFG.stream_kind<host_to_device> }(%dfg) : (!SDFG.dfg) -> !SDFG.stream<tensor<1024xi64>>
%in1 = "SDFG.make_stream" { type = #SDFG.stream_kind<host_to_device> }(%dfg) : (!SDFG.dfg) -> !SDFG.stream<tensor<1024xi64>>
%out = "SDFG.make_stream" { type = #SDFG.stream_kind<device_to_host> }(%dfg) : (!SDFG.dfg) -> !SDFG.stream<tensor<1024xi64>>
"SDFG.make_process" { type = #SDFG.process_kind<add_eint> }(%dfg, %in0, %in1, %out) :
(!SDFG.dfg, !SDFG.stream<tensor<1024xi64>>, !SDFG.stream<tensor<1024xi64>>, !SDFG.stream<tensor<1024xi64>>) -> ()"SDFG.make_stream" { name = "stream", type = #SDFG.stream_kind<host_to_device> }(%dfg)
: (!SDFG.dfg) -> !SDFG.stream<tensor<1024xi64>>"SDFG.put" (%stream, %data) : (!SDFG.stream<1024xi64>, tensor<1024xi64>) -> ()"SDFG.shutdown" (%dfg) : !SDFG.dfg"SDFG.start"(%dfg) : !SDFG.dfg#SDFG.process_kind<
::mlir::concretelang::SDFG::ProcessKind # value
>#SDFG.stream_kind<
::mlir::concretelang::SDFG::StreamKind # value
>import numpy as np
from concrete import fhe
def complex_univariate_function(x):
def per_element(element):
result = 0
for i in range(element):
result += i
return result
return np.vectorize(per_element)(x)
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.univariate(complex_univariate_function)(x)
inputset = [np.random.randint(0, 5, size=(3, 2)) for _ in range(10)]
circuit = f.compile(inputset)
sample = np.array([
[0, 4],
[2, 1],
[3, 0],
])
assert np.array_equal(circuit.encrypt_run_decrypt(sample), complex_univariate_function(sample))import numpy as np
from concrete import fhe
weight = np.array([[2, 1], [3, 2]]).reshape(1, 1, 2, 2)
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.conv(x, weight, strides=(2, 2), dilations=(1, 1), group=1)
inputset = [np.random.randint(0, 4, size=(1, 1, 4, 4)) for _ in range(10)]
circuit = f.compile(inputset)
sample = np.array(
[
[3, 2, 1, 0],
[3, 2, 1, 0],
[3, 2, 1, 0],
[3, 2, 1, 0],
]
).reshape(1, 1, 4, 4)
assert np.array_equal(circuit.encrypt_run_decrypt(sample), f(sample))import numpy as np
from concrete import fhe
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.maxpool(x, kernel_shape=(2, 2), strides=(2, 2), dilations=(1, 1))
inputset = [np.random.randint(0, 4, size=(1, 1, 4, 4)) for _ in range(10)]
circuit = f.compile(inputset)
sample = np.array(
[
[3, 2, 1, 0],
[3, 2, 1, 0],
[3, 2, 1, 0],
[3, 2, 1, 0],
]
).reshape(1, 1, 4, 4)
assert np.array_equal(circuit.encrypt_run_decrypt(sample), f(sample))import numpy as np
from concrete import fhe
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def f(x, y):
return fhe.array([x, y])
inputset = [(3, 2), (7, 0), (0, 7), (4, 2)]
circuit = f.compile(inputset)
sample = (3, 4)
assert np.array_equal(circuit.encrypt_run_decrypt(*sample), f(*sample))from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
z = fhe.zero()
return x + z
inputset = range(10)
circuit = f.compile(inputset)
for x in range(10):
assert circuit.encrypt_run_decrypt(x) == xfrom concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
z = fhe.zeros((2, 3))
return x + z
inputset = range(10)
circuit = f.compile(inputset)
for x in range(10):
assert np.array_equal(circuit.encrypt_run_decrypt(x), np.array([[x, x, x], [x, x, x]]))from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
z = fhe.one()
return x + z
inputset = range(10)
circuit = f.compile(inputset)
for x in range(10):
assert circuit.encrypt_run_decrypt(x) == x + 1from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x):
z = fhe.ones((2, 3))
return x + z
inputset = range(10)
circuit = f.compile(inputset)
for x in range(10):
assert np.array_equal(circuit.encrypt_run_decrypt(x), np.array([[x, x, x], [x, x, x]]) + 1)from concrete import fhe
import numpy as np
@fhe.compiler({"x": "encrypted"})
def f(x, y, z):
a = x | y
b = y & z
c = a ^ b
return c
inputset = [
(np.random.randint(0, 2**8), np.random.randint(0, 2**8), np.random.randint(0, 2**8))
for _ in range(3)
]
circuit = f.compile(inputset)
print(circuit)%0 = x # EncryptedScalar<uint8> ∈ [173, 240]
%1 = y # EncryptedScalar<uint8> ∈ [52, 219]
%2 = z # EncryptedScalar<uint8> ∈ [36, 252]
%3 = bitwise_or(%0, %1) # EncryptedScalar<uint8> ∈ [243, 255]
%4 = bitwise_and(%1, %2) # EncryptedScalar<uint7> ∈ [0, 112]
^^^^^ this can lead to bugs
%5 = bitwise_xor(%3, %4) # EncryptedScalar<uint8> ∈ [131, 255]
return %5@fhe.compiler({"x": "encrypted", "y": "encrypted", "z": "encrypted"})
def f(x, y, z):
# hint that inputs should be considered at least 8-bits
x = fhe.hint(x, bit_width=8)
y = fhe.hint(y, bit_width=8)
z = fhe.hint(z, bit_width=8)
# hint that intermediates should be considered at least 8-bits
a = fhe.hint(x | y, bit_width=8)
b = fhe.hint(y & z, bit_width=8)
c = fhe.hint(a ^ b, bit_width=8)
return c%0 = x # EncryptedScalar<uint8> ∈ [...]
%1 = y # EncryptedScalar<uint8> ∈ [...]
%2 = z # EncryptedScalar<uint8> ∈ [...]
%3 = bitwise_or(%0, %1) # EncryptedScalar<uint8> ∈ [...]
%4 = bitwise_and(%1, %2) # EncryptedScalar<uint8> ∈ [...]
%5 = bitwise_xor(%3, %4) # EncryptedScalar<uint8> ∈ [...]
return %5import numpy as np
from concrete import fhe
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def min_two(x, y):
diff = y - x
min_x_y = y - np.maximum(y - x, 0)
return min_x_y
inputset = [tuple(np.random.randint(0, 16, size=2)) for _ in range(50)]
circuit = min_two.compile(inputset)
x, y = np.random.randint(0, 16, size=2)
assert circuit.encrypt_run_decrypt(x, y) == min(x, y)import numpy as np
from concrete import fhe
@fhe.compiler({"x": "encrypted", "y": "encrypted"})
def max_two(x, y):
diff = y - x
max_x_y = y - np.minimum(y - x, 0)
return max_x_y
inputset = [tuple(np.random.randint(0, 16, size=2)) for _ in range(50)]
circuit = max_two.compile(inputset)
x, y = np.random.randint(0, 16, size=2)
assert circuit.encrypt_run_decrypt(x, y) == max(x, y)import numpy as np
from concrete import fhe
@fhe.compiler({"args": "encrypted"})
def fhe_min(args):
remaining = list(args)
while len(remaining) > 1:
a = remaining.pop()
b = remaining.pop()
min_a_b = b - np.maximum(b - a, 0)
remaining.insert(0, min_a_b)
return remaining[0]
inputset = [np.random.randint(0, 16, size=5) for _ in range(50)]
circuit = fhe_min.compile(inputset)
x1, x2, x3, x4, x5 = np.random.randint(0, 16, size=5)
assert circuit.encrypt_run_decrypt([x1, x2, x3, x4, x5]) == min(x1, x2, x3, x4, x5)import numpy as np
from concrete import fhe
@fhe.compiler({"array": "encrypted", "index": "encrypted"})
def indexed_value(array, index):
all_indices = np.arange(array.size)
index_selection = index == all_indices
selection_and_zeros = array * index_selection
selection = np.sum(selection_and_zeros)
return selection
inputset = [(np.random.randint(0, 16, size=5), np.random.randint(0, 5)) for _ in range(50)]
circuit = indexed_value.compile(inputset)
array = np.random.randint(0, 16, size=5)
index = np.random.randint(0, 5)
assert circuit.encrypt_run_decrypt(array, index) == array[index]import numpy as np
from concrete import fhe
@fhe.compiler({"numbers": "encrypted", "threshold": "encrypted"})
def filtering(numbers, threshold):
is_greater = numbers > threshold
shifted_numbers = numbers * 2 # open space for a single bit at the end
combined_numbers_and_is_greater = shifted_numbers + is_greater # put is_greater to that bit
def extract(combination):
is_greater = (combination % 2) == 1 # extract is_greater back from packing
if_true = combination // 2 # if is greater is true, we unpack the number and use it
if_false = 0 # otherwise we set the element to zero
return np.where(is_greater, if_true, if_false) # and apply the operation
return fhe.univariate(extract)(combined_numbers_and_is_greater)
inputset = [(np.random.randint(0, 16, size=5), np.random.randint(0, 16)) for _ in range(50)]
circuit = filtering.compile(inputset)
numbers = np.random.randint(0, 16, size=5)
threshold = np.random.randint(0, 16)
assert np.array_equal(circuit.encrypt_run_decrypt(numbers, threshold), list(map(lambda x: x if x > threshold else 0, numbers)))
import numpy as np
from concrete import fhe
def smallest_prime_divisor(n):
if n % 2 == 0:
return 2
for i in range(3, int(np.sqrt(n)) + 1):
if n % i == 0:
return i
return n
def mean_of_vector(x):
assert x.size != 0
if x.size == 1:
return x[0]
group_size = smallest_prime_divisor(x.size)
if x.size == group_size:
return np.round(np.sum(x) / x.size).astype(np.int64)
groups = []
for i in range(x.size // group_size):
start = i * group_size
end = start + group_size
groups.append(x[start:end])
mean_of_groups = []
for group in groups:
mean_of_groups.append(np.round(np.sum(group) / group_size).astype(np.int64))
return mean_of_vector(fhe.array(mean_of_groups))
@fhe.compiler(({"x": "encrypted"}))
def mean_of_matrix(x):
return mean_of_vector(x.flatten())
@fhe.compiler(({"x": "encrypted"}))
def mean_of_rows_of_matrix(x):
means = []
for i in range(x.shape[0]):
means.append(mean_of_vector(x[i]))
return fhe.array(means)
@fhe.compiler(({"x": "encrypted"}))
def mean_of_columns_of_matrix(x):
means = []
for i in range(x.shape[1]):
means.append(mean_of_vector(x[:, i]))
return fhe.array(means)
inputset = [np.random.randint(0, 16, size=(5,5)) for _ in range(50)]
matrix = np.random.randint(0, 16, size=(5, 5))
circuit = mean_of_matrix.compile(inputset)
assert circuit.encrypt_run_decrypt(matrix) == round(matrix.mean())
circuit = mean_of_rows_of_matrix.compile(inputset)
assert np.array_equal(circuit.encrypt_run_decrypt(matrix), [round(x) for x in matrix.mean(1)])
circuit = mean_of_columns_of_matrix.compile(inputset)
assert np.array_equal(circuit.encrypt_run_decrypt(matrix), [round(x) for x in matrix.mean(0)])RT.await_future (::mlir::concretelang::RT::AwaitFutureOp)RT.build_return_ptr_placeholder (::mlir::concretelang::RT::BuildReturnPtrPlaceholderOp)RT.clone_future (::mlir::concretelang::RT::CloneFutureOp)RT.create_async_task (::mlir::concretelang::RT::CreateAsyncTaskOp)RT.dataflow_task (::mlir::concretelang::RT::DataflowTaskOp)RT.dataflow_yield (::mlir::concretelang::RT::DataflowYieldOp)RT.deallocate_future_data (::mlir::concretelang::RT::DeallocateFutureDataOp)RT.deallocate_future (::mlir::concretelang::RT::DeallocateFutureOp)RT.deref_return_ptr_placeholder (::mlir::concretelang::RT::DerefReturnPtrPlaceholderOp)RT.deref_work_function_argument_ptr_placeholder (::mlir::concretelang::RT::DerefWorkFunctionArgumentPtrPlaceholderOp)RT.make_ready_future (::mlir::concretelang::RT::MakeReadyFutureOp)RT.register_task_work_function (::mlir::concretelang::RT::RegisterTaskWorkFunctionOp)RT.work_function_return (::mlir::concretelang::RT::WorkFunctionReturnOp)import matplotlib.pyplot as plt
import numpy as np
from concrete import fhe
original_bit_width = 5
lsbs_to_remove = 2
assert 0 < lsbs_to_remove < original_bit_width
original_values = list(range(2**original_bit_width))
rounded_values = [
fhe.round_bit_pattern(value, lsbs_to_remove)
for value in original_values
]
previous_rounded = rounded_values[0]
for original, rounded in zip(original_values, rounded_values):
if rounded != previous_rounded:
previous_rounded = rounded
print()
original_binary = np.binary_repr(original, width=(original_bit_width + 1))
rounded_binary = np.binary_repr(rounded, width=(original_bit_width + 1))
print(
f"{original:2} = 0b_{original_binary[:-lsbs_to_remove]}[{original_binary[-lsbs_to_remove:]}] "
f"=> "
f"0b_{rounded_binary[:-lsbs_to_remove]}[{rounded_binary[-lsbs_to_remove:]}] = {rounded}"
)
fig = plt.figure()
ax = fig.add_subplot()
plt.plot(original_values, original_values, label="original", color="black")
plt.plot(original_values, rounded_values, label="rounded", color="green")
plt.legend()
ax.set_aspect("equal", adjustable="box")
plt.show() 0 = 0b_0000[00] => 0b_0000[00] = 0
1 = 0b_0000[01] => 0b_0000[00] = 0
2 = 0b_0000[10] => 0b_0001[00] = 4
3 = 0b_0000[11] => 0b_0001[00] = 4
4 = 0b_0001[00] => 0b_0001[00] = 4
5 = 0b_0001[01] => 0b_0001[00] = 4
6 = 0b_0001[10] => 0b_0010[00] = 8
7 = 0b_0001[11] => 0b_0010[00] = 8
8 = 0b_0010[00] => 0b_0010[00] = 8
9 = 0b_0010[01] => 0b_0010[00] = 8
10 = 0b_0010[10] => 0b_0011[00] = 12
11 = 0b_0010[11] => 0b_0011[00] = 12
12 = 0b_0011[00] => 0b_0011[00] = 12
13 = 0b_0011[01] => 0b_0011[00] = 12
14 = 0b_0011[10] => 0b_0100[00] = 16
15 = 0b_0011[11] => 0b_0100[00] = 16
16 = 0b_0100[00] => 0b_0100[00] = 16
17 = 0b_0100[01] => 0b_0100[00] = 16
18 = 0b_0100[10] => 0b_0101[00] = 20
19 = 0b_0100[11] => 0b_0101[00] = 20
20 = 0b_0101[00] => 0b_0101[00] = 20
21 = 0b_0101[01] => 0b_0101[00] = 20
22 = 0b_0101[10] => 0b_0110[00] = 24
23 = 0b_0101[11] => 0b_0110[00] = 24
24 = 0b_0110[00] => 0b_0110[00] = 24
25 = 0b_0110[01] => 0b_0110[00] = 24
26 = 0b_0110[10] => 0b_0111[00] = 28
27 = 0b_0110[11] => 0b_0111[00] = 28
28 = 0b_0111[00] => 0b_0111[00] = 28
29 = 0b_0111[01] => 0b_0111[00] = 28
30 = 0b_0111[10] => 0b_1000[00] = 32
31 = 0b_0111[11] => 0b_1000[00] = 32import itertools
import time
import matplotlib.pyplot as plt
import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
enable_unsafe_features=True,
use_insecure_key_cache=True,
insecure_key_cache_location=".keys",
single_precision=False,
parameter_selection_strategy=fhe.ParameterSelectionStrategy.MULTI,
)
input_bit_width = 6
input_range = np.array(range(2**input_bit_width))
timings = {}
results = {}
for lsbs_to_remove in range(input_bit_width):
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.round_bit_pattern(x, lsbs_to_remove) ** 2
circuit = f.compile(inputset=[input_range], configuration=configuration)
circuit.keygen()
encrypted_sample = circuit.encrypt(input_range)
start = time.time()
encrypted_result = circuit.run(encrypted_sample)
end = time.time()
result = circuit.decrypt(encrypted_result)
took = end - start
timings[lsbs_to_remove] = took
results[lsbs_to_remove] = result
number_of_figures = len(results)
columns = 1
for i in range(2, number_of_figures):
if number_of_figures % i == 0:
columns = i
rows = number_of_figures // columns
fig, axs = plt.subplots(rows, columns)
axs = axs.flatten()
baseline = timings[0]
for lsbs_to_remove in range(input_bit_width):
timing = timings[lsbs_to_remove]
speedup = baseline / timing
print(f"lsbs_to_remove={lsbs_to_remove} => {speedup:.2f}x speedup")
axs[lsbs_to_remove].set_title(f"lsbs_to_remove={lsbs_to_remove}")
axs[lsbs_to_remove].plot(input_range, results[lsbs_to_remove])
plt.show()lsbs_to_remove=0 => 1.00x speedup
lsbs_to_remove=1 => 1.20x speedup
lsbs_to_remove=2 => 2.17x speedup
lsbs_to_remove=3 => 3.75x speedup
lsbs_to_remove=4 => 2.64x speedup
lsbs_to_remove=5 => 2.61x speedupimport itertools
import time
import matplotlib.pyplot as plt
import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
enable_unsafe_features=True,
use_insecure_key_cache=True,
insecure_key_cache_location=".keys",
single_precision=False,
parameter_selection_strategy=fhe.ParameterSelectionStrategy.MULTI,
)
input_bit_width = 6
input_range = np.array(range(2**input_bit_width))
timings = {}
results = {}
for target_msbs in reversed(range(1, input_bit_width + 1)):
rounder = fhe.AutoRounder(target_msbs)
@fhe.compiler({"x": "encrypted"})
def f(x):
return fhe.round_bit_pattern(x, rounder) ** 2
fhe.AutoRounder.adjust(f, inputset=[input_range])
circuit = f.compile(inputset=[input_range], configuration=configuration)
circuit.keygen()
encrypted_sample = circuit.encrypt(input_range)
start = time.time()
encrypted_result = circuit.run(encrypted_sample)
end = time.time()
result = circuit.decrypt(encrypted_result)
took = end - start
timings[target_msbs] = took
results[target_msbs] = result
number_of_figures = len(results)
columns = 1
for i in range(2, number_of_figures):
if number_of_figures % i == 0:
columns = i
rows = number_of_figures // columns
fig, axs = plt.subplots(rows, columns)
axs = axs.flatten()
baseline = timings[input_bit_width]
for i, target_msbs in enumerate(reversed(range(1, input_bit_width + 1))):
timing = timings[target_msbs]
speedup = baseline / timing
print(f"target_msbs={target_msbs} => {speedup:.2f}x speedup")
axs[i].set_title(f"target_msbs={target_msbs}")
axs[i].plot(input_range, results[target_msbs])
plt.show()target_msbs=6 => 1.00x speedup
target_msbs=5 => 1.22x speedup
target_msbs=4 => 1.95x speedup
target_msbs=3 => 3.11x speedup
target_msbs=2 => 2.23x speedup
target_msbs=1 => 2.34x speedup


func @test(%0 : i64): (i64, i64) {
// Execute right now as %0 is ready.
%1, %2 = "RT.dataflow_task"(%0) ({
%a = addi %0, %0 : i64
%b = muli %0, %0 : i64
"RT.dataflow_yield"(%a, %b) : (i64, i64) -> i64
}) : (i64, i64) -> (i64, i64)
// Concurrently execute both tasks below when the task above is completed.
%3 = "RT.dataflow_task"(%1) ({
%c = constant 1 : %i64
%a = addi %1, %c : i64
"RT.dataflow_yield"(%a) : (i64, i64) -> i64
}) : (i64, i64) -> (i64, i64)
%4 = "RT.dataflow_task"(%2) ({
%c = constant 2 : %i64
%a = addi %2, %c : i64
"RT.dataflow_yield"(%a) : (i64, i64) -> i64
}) : (i64, i64) -> (i64, i64)
return %3, %4 : (i64, i64)
}%0 = constant 1 : i64
%1 = constant 2 : i64
"RT.dataflow_yield" %0, %1 : i64, i64!RT.future<i64>def f(x):
return np.sin(x)Linux-5.12.13-arch1-2-x86_64-with-glibc2.29 #1 SMP PREEMPT Fri, 25 Jun 2021 22:56:51 +0000
Python 3.8.10astroid==2.15.0
attrs==22.2.0
auditwheel==5.3.0
...
wheel==0.40.0
wrapt==1.15.0
zipp==3.15.0def f(x):
return np.sin(x)x :: encrypted%0 = x # EncryptedScalar<uint3>
%1 = sin(%0) # EncryptedScalar<float64>
return %1%0 = x # EncryptedScalar<uint3>
%1 = sin(%0) # EncryptedScalar<float64>
return %1Traceback (most recent call last):
File "/path/to/your/script.py", line 9, in <module>
circuit = f.compile(inputset)
File "/usr/local/lib/python3.10/site-packages/concrete/fhe/compilation/decorators.py", line 159, in compile
return self.compiler.compile(inputset, configuration, artifacts, **kwargs)
File "/usr/local/lib/python3.10/site-packages/concrete/fhe/compilation/compiler.py", line 437, in compile
mlir = GraphConverter.convert(self.graph)
File "/usr/local/lib/python3.10/site-packages/concrete/fhe/mlir/graph_converter.py", line 677, in convert
GraphConverter._check_graph_convertibility(graph)
File "/usr/local/lib/python3.10/site-packages/concrete/fhe/mlir/graph_converter.py", line 240, in _check_graph_convertibility
raise RuntimeError(message)
RuntimeError: Function you are trying to compile cannot be converted to MLIR
%0 = x # EncryptedScalar<uint3> ∈ [3, 5]
%1 = sin(%0) # EncryptedScalar<float64> ∈ [-0.958924, 0.14112]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only integer operations are supported
/path/to/your/script.py:6
return %1from concrete import fhe
import numpy as np
artifacts = fhe.DebugArtifacts("/tmp/custom/export/path")
@fhe.compiler({"x": "encrypted"})
def f(x):
return 127 - (50 * (np.sin(x) + 1)).astype(np.int64)
inputset = range(2 ** 3)
circuit = f.compile(inputset, artifacts=artifacts)
artifacts.export()%0 = x # EncryptedScalar<uint1>
%1 = sin(%0) # EncryptedScalar<float64>
%2 = 1 # ClearScalar<uint1>
%3 = add(%1, %2) # EncryptedScalar<float64>
%4 = 50 # ClearScalar<uint6>
%5 = multiply(%4, %3) # EncryptedScalar<float64>
%6 = astype(%5, dtype=int_) # EncryptedScalar<uint1>
%7 = 127 # ClearScalar<uint7>
%8 = subtract(%7, %6) # EncryptedScalar<uint1>
return %8%0 = x # EncryptedScalar<uint1>
%1 = subgraph(%0) # EncryptedScalar<uint1>
%2 = 127 # ClearScalar<uint7>
%3 = subtract(%2, %1) # EncryptedScalar<uint1>
return %3
Subgraphs:
%1 = subgraph(%0):
%0 = input # EncryptedScalar<uint1>
%1 = sin(%0) # EncryptedScalar<float64>
%2 = 1 # ClearScalar<uint1>
%3 = add(%1, %2) # EncryptedScalar<float64>
%4 = 50 # ClearScalar<uint6>
%5 = multiply(%4, %3) # EncryptedScalar<float64>
%6 = astype(%5, dtype=int_) # EncryptedScalar<uint1>
return %6%0 = x # EncryptedScalar<uint3> ∈ [0, 7]
%1 = subgraph(%0) # EncryptedScalar<uint7> ∈ [2, 95]
%2 = 127 # ClearScalar<uint7> ∈ [127, 127]
%3 = subtract(%2, %1) # EncryptedScalar<uint7> ∈ [32, 125]
return %3
Subgraphs:
%1 = subgraph(%0):
%0 = input # EncryptedScalar<uint1>
%1 = sin(%0) # EncryptedScalar<float64>
%2 = 1 # ClearScalar<uint1>
%3 = add(%1, %2) # EncryptedScalar<float64>
%4 = 50 # ClearScalar<uint6>
%5 = multiply(%4, %3) # EncryptedScalar<float64>
%6 = astype(%5, dtype=int_) # EncryptedScalar<uint1>
return %6module {
func.func @main(%arg0: !FHE.eint<7>) -> !FHE.eint<7> {
%c127_i8 = arith.constant 127 : i8
%cst = arith.constant dense<"..."> : tensor<128xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<7>, tensor<128xi64>) -> !FHE.eint<7>
%1 = "FHE.sub_int_eint"(%c127_i8, %0) : (i8, !FHE.eint<7>) -> !FHE.eint<7>
return %1 : !FHE.eint<7>
}
}{
"bootstrapKeys": [
{
"baseLog": 22,
"glweDimension": 1,
"inputLweDimension": 908,
"inputSecretKeyID": 1,
"level": 1,
"outputSecretKeyID": 0,
"polynomialSize": 8192,
"variance": 4.70197740328915e-38
}
],
"functionName": "main",
"inputs": [
{
"encryption": {
"encoding": {
"isSigned": false,
"precision": 7
},
"secretKeyID": 0,
"variance": 4.70197740328915e-38
},
"shape": {
"dimensions": [],
"sign": false,
"size": 0,
"width": 7
}
}
],
"keyswitchKeys": [
{
"baseLog": 3,
"inputSecretKeyID": 0,
"level": 6,
"outputSecretKeyID": 1,
"variance": 1.7944329123150665e-13
}
],
"outputs": [
{
"encryption": {
"encoding": {
"isSigned": false,
"precision": 7
},
"secretKeyID": 0,
"variance": 4.70197740328915e-38
},
"shape": {
"dimensions": [],
"sign": false,
"size": 0,
"width": 7
}
}
],
"packingKeyswitchKeys": [],
"secretKeys": [
{
"dimension": 8192
},
{
"dimension": 908
}
]
}from concrete import fhe
@fhe.circuit({"x": "encrypted"})
def circuit(x: fhe.uint8):
return x + 42
assert circuit.encrypt_run_decrypt(10) == 52from concrete import fhe
import numpy as np
def square(value):
return value ** 2
@fhe.circuit({"x": "encrypted", "y": "encrypted"})
def circuit(x: fhe.uint8, y: fhe.int2):
a = x + 10
b = y + 10
c = np.sqrt(a).round().astype(fhe.uint4)
d = fhe.univariate(square, outputs=fhe.uint8)(b)
return d - c
print(circuit)%0 = x # EncryptedScalar<uint8>
%1 = y # EncryptedScalar<int2>
%2 = 10 # ClearScalar<uint4>
%3 = add(%0, %2) # EncryptedScalar<uint8>
%4 = 10 # ClearScalar<uint4>
%5 = add(%1, %4) # EncryptedScalar<int4>
%6 = subgraph(%3) # EncryptedScalar<uint4>
%7 = square(%5) # EncryptedScalar<uint8>
%8 = subtract(%7, %6) # EncryptedScalar<uint8>
return %8
Subgraphs:
%6 = subgraph(%3):
%0 = input # EncryptedScalar<uint8>
%1 = sqrt(%0) # EncryptedScalar<float64>
%2 = around(%1, decimals=0) # EncryptedScalar<float64>
%3 = astype(%2) # EncryptedScalar<uint4>
return %3%0 is uint8 because it's specified in the definition
%1 is int2 because it's specified in the definition
%2 is uint4 because it's the constant 10
%3 is uint8 because it's the addition between uint8 and uint4
%4 is uint4 because it's the constant 10
%5 is int4 because it's the addition between int2 and uint4
%6 is uint4 because it's specified in astype
%7 is uint8 because it's specified in univariate
%8 is uint8 because it's subtraction between uint8 and uint4func.func @main(%arg0: tensor<4x3x!FHE.eint<2>>, %arg1: tensor<3x2xi3>) -> tensor<4x2x!FHE.eint<2>> {
%0 = "FHELinalg.matmul_eint_int"(%arg0, %arg1) : (tensor<4x3x!FHE.eint<2>>, tensor<3x2xi3>) -> tensor<4x2x!FHE.eint<2>>
return %0 : tensor<4x2x!FHE.eint<2>>
}#map = affine_map<(d0, d1, d2) -> (d0, d2)>
#map1 = affine_map<(d0, d1, d2) -> (d2, d1)>
#map2 = affine_map<(d0, d1, d2) -> (d0, d1)>
func.func @main(%arg0: tensor<4x3x!FHE.eint<2>>, %arg1: tensor<3x2xi3>) -> tensor<4x2x!FHE.eint<2>> {
%0 = "FHE.zero_tensor"() : () -> tensor<4x2x!FHE.eint<2>>
%1 = linalg.generic {indexing_maps = [#map, #map1, #map2], iterator_types = ["parallel", "parallel", "reduction"]} ins(%arg0, %arg1 : tensor<4x3x!FHE.eint<2>>, tensor<3x2xi3>) outs(%0 : tensor<4x2x!FHE.eint<2>>) {
^bb0(%in: !FHE.eint<2>, %in_0: i3, %out: !FHE.eint<2>):
%2 = "FHE.mul_eint_int"(%in, %in_0) : (!FHE.eint<2>, i3) -> !FHE.eint<2>
%3 = "FHE.add_eint"(%out, %2) : (!FHE.eint<2>, !FHE.eint<2>) -> !FHE.eint<2>
linalg.yield %3 : !FHE.eint<2>
} -> tensor<4x2x!FHE.eint<2>>
return %1 : tensor<4x2x!FHE.eint<2>>
}func.func @main(%arg0: tensor<4x3x!FHE.eint<2>>, %arg1: tensor<3x2xi3>) -> tensor<4x2x!FHE.eint<2>> {
%c0 = arith.constant 0 : index
%c4 = arith.constant 4 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c3 = arith.constant 3 : index
%0 = "FHE.zero_tensor"() : () -> tensor<4x2x!FHE.eint<2>>
%1 = scf.for %arg2 = %c0 to %c4 step %c1 iter_args(%arg3 = %0) -> (tensor<4x2x!FHE.eint<2>>) {
%2 = scf.for %arg4 = %c0 to %c2 step %c1 iter_args(%arg5 = %arg3) -> (tensor<4x2x!FHE.eint<2>>) {
%3 = scf.for %arg6 = %c0 to %c3 step %c1 iter_args(%arg7 = %arg5) -> (tensor<4x2x!FHE.eint<2>>) {
%extracted = tensor.extract %arg0[%arg2, %arg6] : tensor<4x3x!FHE.eint<2>>
%extracted_0 = tensor.extract %arg1[%arg6, %arg4] : tensor<3x2xi3>
%extracted_1 = tensor.extract %arg7[%arg2, %arg4] : tensor<4x2x!FHE.eint<2>>
%4 = "FHE.mul_eint_int"(%extracted, %extracted_0) : (!FHE.eint<2>, i3) -> !FHE.eint<2>
%5 = "FHE.add_eint"(%extracted_1, %4) : (!FHE.eint<2>, !FHE.eint<2>) -> !FHE.eint<2>
%inserted = tensor.insert %5 into %arg7[%arg2, %arg4] : tensor<4x2x!FHE.eint<2>>
scf.yield %inserted : tensor<4x2x!FHE.eint<2>>
}
scf.yield %3 : tensor<4x2x!FHE.eint<2>>
}
scf.yield %2 : tensor<4x2x!FHE.eint<2>>
}
return %1 : tensor<4x2x!FHE.eint<2>>
}func.func @main(%arg0: tensor<4x3x!TFHE.glwe<sk?>>, %arg1: tensor<3x2xi3>) -> tensor<4x2x!TFHE.glwe<sk?>> {
%c0 = arith.constant 0 : index
%c4 = arith.constant 4 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c3 = arith.constant 3 : index
%0 = "TFHE.zero_tensor"() : () -> tensor<4x2x!TFHE.glwe<sk?>>
%1 = scf.for %arg2 = %c0 to %c4 step %c1 iter_args(%arg3 = %0) -> (tensor<4x2x!TFHE.glwe<sk?>>) {
%2 = scf.for %arg4 = %c0 to %c2 step %c1 iter_args(%arg5 = %arg3) -> (tensor<4x2x!TFHE.glwe<sk?>>) {
%3 = scf.for %arg6 = %c0 to %c3 step %c1 iter_args(%arg7 = %arg5) -> (tensor<4x2x!TFHE.glwe<sk?>>) {
%extracted = tensor.extract %arg0[%arg2, %arg6] : tensor<4x3x!TFHE.glwe<sk?>>
%extracted_0 = tensor.extract %arg1[%arg6, %arg4] : tensor<3x2xi3>
%extracted_1 = tensor.extract %arg7[%arg2, %arg4] : tensor<4x2x!TFHE.glwe<sk?>>
%4 = arith.extsi %extracted_0 : i3 to i64
%5 = "TFHE.mul_glwe_int"(%extracted, %4) : (!TFHE.glwe<sk?>, i64) -> !TFHE.glwe<sk?>
%6 = "TFHE.add_glwe"(%extracted_1, %5) : (!TFHE.glwe<sk?>, !TFHE.glwe<sk?>) -> !TFHE.glwe<sk?>
%inserted = tensor.insert %6 into %arg7[%arg2, %arg4] : tensor<4x2x!TFHE.glwe<sk?>>
scf.yield %inserted : tensor<4x2x!TFHE.glwe<sk?>>
}
scf.yield %3 : tensor<4x2x!TFHE.glwe<sk?>>
}
scf.yield %2 : tensor<4x2x!TFHE.glwe<sk?>>
}
return %1 : tensor<4x2x!TFHE.glwe<sk?>>
}func.func @main(%arg0: tensor<4x3x!TFHE.glwe<sk<0,1,512>>>, %arg1: tensor<3x2xi3>) -> tensor<4x2x!TFHE.glwe<sk<0,1,512>>> {
%c0 = arith.constant 0 : index
%c4 = arith.constant 4 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c3 = arith.constant 3 : index
%0 = "TFHE.zero_tensor"() : () -> tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
%1 = scf.for %arg2 = %c0 to %c4 step %c1 iter_args(%arg3 = %0) -> (tensor<4x2x!TFHE.glwe<sk<0,1,512>>>) {
%2 = scf.for %arg4 = %c0 to %c2 step %c1 iter_args(%arg5 = %arg3) -> (tensor<4x2x!TFHE.glwe<sk<0,1,512>>>) {
%3 = scf.for %arg6 = %c0 to %c3 step %c1 iter_args(%arg7 = %arg5) -> (tensor<4x2x!TFHE.glwe<sk<0,1,512>>>) {
%extracted = tensor.extract %arg0[%arg2, %arg6] : tensor<4x3x!TFHE.glwe<sk<0,1,512>>>
%extracted_0 = tensor.extract %arg1[%arg6, %arg4] : tensor<3x2xi3>
%extracted_1 = tensor.extract %arg7[%arg2, %arg4] : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
%4 = arith.extsi %extracted_0 : i3 to i64
%5 = "TFHE.mul_glwe_int"(%extracted, %4) : (!TFHE.glwe<sk<0,1,512>>, i64) -> !TFHE.glwe<sk<0,1,512>>
%6 = "TFHE.add_glwe"(%extracted_1, %5) : (!TFHE.glwe<sk<0,1,512>>, !TFHE.glwe<sk<0,1,512>>) -> !TFHE.glwe<sk<0,1,512>>
%inserted = tensor.insert %6 into %arg7[%arg2, %arg4] : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
scf.yield %inserted : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
}
scf.yield %3 : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
}
scf.yield %2 : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
}
return %1 : tensor<4x2x!TFHE.glwe<sk<0,1,512>>>
}func.func @main(%arg0: tensor<4x3x513xi64>, %arg1: tensor<3x2xi3>) -> tensor<4x2x513xi64> {
%c0 = arith.constant 0 : index
%c4 = arith.constant 4 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c3 = arith.constant 3 : index
%generated = tensor.generate {
^bb0(%arg2: index, %arg3: index, %arg4: index):
%c0_i64 = arith.constant 0 : i64
tensor.yield %c0_i64 : i64
} : tensor<4x2x513xi64>
%0 = scf.for %arg2 = %c0 to %c4 step %c1 iter_args(%arg3 = %generated) -> (tensor<4x2x513xi64>) {
%1 = scf.for %arg4 = %c0 to %c2 step %c1 iter_args(%arg5 = %arg3) -> (tensor<4x2x513xi64>) {
%2 = scf.for %arg6 = %c0 to %c3 step %c1 iter_args(%arg7 = %arg5) -> (tensor<4x2x513xi64>) {
%extracted_slice = tensor.extract_slice %arg0[%arg2, %arg6, 0] [1, 1, 513] [1, 1, 1] : tensor<4x3x513xi64> to tensor<513xi64>
%extracted = tensor.extract %arg1[%arg6, %arg4] : tensor<3x2xi3>
%extracted_slice_0 = tensor.extract_slice %arg7[%arg2, %arg4, 0] [1, 1, 513] [1, 1, 1] : tensor<4x2x513xi64> to tensor<513xi64>
%3 = arith.extsi %extracted : i3 to i64
%4 = "Concrete.mul_cleartext_lwe_tensor"(%extracted_slice, %3) : (tensor<513xi64>, i64) -> tensor<513xi64>
%5 = "Concrete.add_lwe_tensor"(%extracted_slice_0, %4) : (tensor<513xi64>, tensor<513xi64>) -> tensor<513xi64>
%inserted_slice = tensor.insert_slice %5 into %arg7[%arg2, %arg4, 0] [1, 1, 513] [1, 1, 1] : tensor<513xi64> into tensor<4x2x513xi64>
scf.yield %inserted_slice : tensor<4x2x513xi64>
}
scf.yield %2 : tensor<4x2x513xi64>
}
scf.yield %1 : tensor<4x2x513xi64>
}
return %0 : tensor<4x2x513xi64>
}func.func @main(%arg0: memref<4x3x513xi64, strided<[?, ?, ?], offset: ?>>, %arg1: memref<3x2xi3, strided<[?, ?], offset: ?>>, %arg2: !Concrete.context) -> memref<4x2x513xi64> {
%c0_i64 = arith.constant 0 : i64
call @_dfr_start(%c0_i64, %arg2) : (i64, !Concrete.context) -> ()
%c0 = arith.constant 0 : index
%c4 = arith.constant 4 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c513 = arith.constant 513 : index
%c0_i64_0 = arith.constant 0 : i64
%c3 = arith.constant 3 : index
%alloc = memref.alloc() {alignment = 64 : i64} : memref<4x2x513xi64>
scf.for %arg3 = %c0 to %c4 step %c1 {
scf.for %arg4 = %c0 to %c2 step %c1 {
scf.for %arg5 = %c0 to %c513 step %c1 {
memref.store %c0_i64_0, %alloc[%arg3, %arg4, %arg5] : memref<4x2x513xi64>
}
}
}
scf.for %arg3 = %c0 to %c4 step %c1 {
scf.for %arg4 = %c0 to %c2 step %c1 {
%subview = memref.subview %alloc[%arg3, %arg4, 0] [1, 1, 513] [1, 1, 1] : memref<4x2x513xi64> to memref<513xi64, strided<[1], offset: ?>>
scf.for %arg5 = %c0 to %c3 step %c1 {
%subview_1 = memref.subview %arg0[%arg3, %arg5, 0] [1, 1, 513] [1, 1, 1] : memref<4x3x513xi64, strided<[?, ?, ?], offset: ?>> to memref<513xi64, strided<[?], offset: ?>>
%0 = memref.load %arg1[%arg5, %arg4] : memref<3x2xi3, strided<[?, ?], offset: ?>>
%1 = arith.extsi %0 : i3 to i64
%alloc_2 = memref.alloc() {alignment = 64 : i64} : memref<513xi64>
%cast = memref.cast %alloc_2 : memref<513xi64> to memref<?xi64, #map>
%cast_3 = memref.cast %subview_1 : memref<513xi64, strided<[?], offset: ?>> to memref<?xi64, #map>
func.call @memref_mul_cleartext_lwe_ciphertext_u64(%cast, %cast_3, %1) : (memref<?xi64, #map>, memref<?xi64, #map>, i64) -> ()
%alloc_4 = memref.alloc() {alignment = 64 : i64} : memref<513xi64>
%cast_5 = memref.cast %alloc_4 : memref<513xi64> to memref<?xi64, #map>
%cast_6 = memref.cast %subview : memref<513xi64, strided<[1], offset: ?>> to memref<?xi64, #map>
%cast_7 = memref.cast %alloc_2 : memref<513xi64> to memref<?xi64, #map>
func.call @memref_add_lwe_ciphertexts_u64(%cast_5, %cast_6, %cast_7) : (memref<?xi64, #map>, memref<?xi64, #map>, memref<?xi64, #map>) -> ()
memref.dealloc %alloc_2 : memref<513xi64>
memref.copy %alloc_4, %subview : memref<513xi64> to memref<513xi64, strided<[1], offset: ?>>
memref.dealloc %alloc_4 : memref<513xi64>
}
}
}
call @_dfr_stop(%c0_i64) : (i64) -> ()
return %alloc : memref<4x2x513xi64>
}
# (example below is for bit-width of 8 and chunk size of 4)
# extract chunks of lhs using table lookups
lhs_chunks = [lhs.bits[0:4], lhs.bits[4:8]]
# extract chunks of rhs using table lookups
rhs_chunks = [rhs.bits[0:4], rhs.bits[4:8]]
# pack chunks of lhs and rhs using clear multiplications and additions
packed_chunks = []
for lhs_chunk, rhs_chunk in zip(lhs_chunks, rhs_chunks):
shifted_lhs_chunk = lhs_chunk * 2**4 # (i.e., lhs_chunk << 4)
packed_chunks.append(shifted_lhs_chunk + rhs_chunk)
# apply comparison table lookup to packed chunks
bitwise_table = fhe.LookupTable([...])
result_chunks = bitwise_table[packed_chunks]
# sum resulting chunks obtain the result
result = np.sum(result_chunks)import numpy as np
from concrete import fhe
def f(x, y):
return x & y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, show_mlir=True)module {
// no promotions
func.func @main(%arg0: !FHE.eint<4>, %arg1: !FHE.eint<4>) -> !FHE.eint<4> {
// extracting the first chunk of x, adjusted for shifting
%cst = arith.constant dense<[0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the first chunk of y
%cst_0 = arith.constant dense<[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]> : tensor<16xi64>
%1 = "FHE.apply_lookup_table"(%arg1, %cst_0) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// packing first chunks
%2 = "FHE.add_eint"(%0, %1) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// applying the bitwise operation to first chunks, adjusted for addition in the end
%cst_1 = arith.constant dense<[0, 0, 0, 0, 0, 4, 0, 4, 0, 0, 8, 8, 0, 4, 8, 12]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the second chunk of x, adjusted for shifting
%cst_2 = arith.constant dense<[0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12]> : tensor<16xi64>
%4 = "FHE.apply_lookup_table"(%arg0, %cst_2) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the second chunk of y
%cst_3 = arith.constant dense<[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]> : tensor<16xi64>
%5 = "FHE.apply_lookup_table"(%arg1, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// packing second chunks
%6 = "FHE.add_eint"(%4, %5) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// applying the bitwise operation to second chunks
%cst_4 = arith.constant dense<[0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 0, 1, 2, 3]> : tensor<16xi64>
%7 = "FHE.apply_lookup_table"(%6, %cst_4) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// adding resulting chunks to obtain the result
%8 = "FHE.add_eint"(%7, %3) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
return %8 : !FHE.eint<4>
}
}x.bit_width + y.bit_width <= MAXIMUM_TLU_BIT_WIDTHbitwise_lut = fhe.LookupTable([...])
result = bitwise_lut[pack(x_promoted_to_uint9, y_promoted_to_uint9)]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
bitwise_strategy_preference=fhe.BitwiseStrategy.ONE_TLU_PROMOTED,
)
def f(x, y):
return x & y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............ ............
func.func @main(%arg0: !FHE.eint<8>, %arg1: !FHE.eint<8>) -> !FHE.eint<4> {
// packing
%c16_i9 = arith.constant 16 : i9
%0 = "FHE.mul_eint_int"(%arg0, %c16_i9) : (!FHE.eint<8>, i9) -> !FHE.eint<8>
%1 = "FHE.add_eint"(%0, %arg1) : (!FHE.eint<8>, !FHE.eint<8>) -> !FHE.eint<8>
// computing the result
%cst = arith.constant dense<"..."> : tensor<256xi64>
%2 = "FHE.apply_lookup_table"(%1, %cst) : (!FHE.eint<8>, tensor<256xi64>) -> !FHE.eint<4>
return %2 : !FHE.eint<4>
}
}uint3_to_uint9_lut = fhe.LookupTable([...])
x_cast_to_uint9 = uint3_to_uint9_lut[x]
uint6_to_uint9_lut = fhe.LookupTable([...])
y_cast_to_uint9 = uint6_to_uint9_lut[y]
bitwise_lut = fhe.LookupTable([...])
result = bitwise_lut[pack(x_cast_to_uint9, y_cast_to_uint9)]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.BitwiseStrategy.THREE_TLU_CASTED,
)
def f(x, y):
return x & y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// no promotions
func.func @main(%arg0: !FHE.eint<4>, %arg1: !FHE.eint<4>) -> !FHE.eint<4> {
// casting
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<8>
%1 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<8>
// packing
%c16_i9 = arith.constant 16 : i9
%2 = "FHE.mul_eint_int"(%0, %c16_i9) : (!FHE.eint<8>, i9) -> !FHE.eint<8>
%3 = "FHE.add_eint"(%2, %1) : (!FHE.eint<8>, !FHE.eint<8>) -> !FHE.eint<8>
// computing the result
%cst_0 = arith.constant dense<"..."> : tensor<256xi64>
%4 = "FHE.apply_lookup_table"(%3, %cst_0) : (!FHE.eint<8>, tensor<256xi64>) -> !FHE.eint<4>
return %4 : !FHE.eint<4>
}
}uint3_to_uint9_lut = fhe.LookupTable([...])
x_cast_to_uint9 = uint3_to_uint9_lut[x]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_cast_to_uint9 - y_promoted_to_uint9]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
bitwise_strategy_preference=fhe.BitwiseStrategy.TWO_TLU_BIGGER_PROMOTED_SMALLER_CASTED,
)
def f(x, y):
return x & y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**6))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<3>, %arg1: !FHE.eint<8>) -> !FHE.eint<3> {
// casting smaller operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7]> : tensor<8xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<8>
// packing
%c32_i9 = arith.constant 32 : i9
%1 = "FHE.mul_eint_int"(%0, %c32_i9) : (!FHE.eint<8>, i9) -> !FHE.eint<8>
%2 = "FHE.add_eint"(%1, %arg1) : (!FHE.eint<8>, !FHE.eint<8>) -> !FHE.eint<8>
// computing the result
%cst_0 = arith.constant dense<"..."> : tensor<256xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.eint<8>, tensor<256xi64>) -> !FHE.eint<3>
return %3 : !FHE.eint<3>
}
}uint6_to_uint9_lut = fhe.LookupTable([...])
y_cast_to_uint9 = uint6_to_uint9_lut[y]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_promoted_to_uint9 - y_cast_to_uint9]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
bitwise_strategy_preference=fhe.BitwiseStrategy.TWO_TLU_BIGGER_CASTED_SMALLER_PROMOTED,
)
def f(x, y):
return x | y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**6))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<9>, %arg1: !FHE.eint<6>) -> !FHE.eint<6> {
// casting bigger operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]> : tensor<64xi64>
%0 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<9>
// packing
%c64_i10 = arith.constant 64 : i10
%1 = "FHE.mul_eint_int"(%arg0, %c64_i10) : (!FHE.eint<9>, i10) -> !FHE.eint<9>
%2 = "FHE.add_eint"(%1, %0) : (!FHE.eint<9>, !FHE.eint<9>) -> !FHE.eint<9>
// computing the result
%cst_0 = arith.constant dense<"..."> : tensor<512xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.eint<9>, tensor<512xi64>) -> !FHE.eint<6>
return %3 : !FHE.eint<6>
}
}import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
shifts_with_promotion=True,
)
def f(x, y):
return x << y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**2))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<6>, %arg1: !FHE.eint<2>) -> !FHE.eint<6> {
// shifting for the second bit of y
%cst = arith.constant dense<[0, 0, 1, 1]> : tensor<4xi64>
%0 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<2>, tensor<4xi64>) -> !FHE.eint<4>
%cst_0 = arith.constant dense<[0, 0, 0, 2, 2, 2, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]> : tensor<64xi64>
%1 = "FHE.apply_lookup_table"(%arg0, %cst_0) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%2 = "FHE.add_eint"(%1, %0) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%cst_1 = arith.constant dense<[0, 0, 0, 8, 0, 16, 0, 24, 0, 32, 0, 40, 0, 48, 0, 56]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%cst_2 = arith.constant dense<[0, 6, 12, 2, 8, 14, 4, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]> : tensor<64xi64>
%4 = "FHE.apply_lookup_table"(%arg0, %cst_2) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%5 = "FHE.add_eint"(%4, %0) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%cst_3 = arith.constant dense<[0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7]> : tensor<16xi64>
%6 = "FHE.apply_lookup_table"(%5, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%7 = "FHE.add_eint"(%3, %6) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
%8 = "FHE.add_eint"(%7, %arg0) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
// shifting for the first bit of y
%cst_4 = arith.constant dense<[0, 1, 0, 1]> : tensor<4xi64>
%9 = "FHE.apply_lookup_table"(%arg1, %cst_4) : (!FHE.eint<2>, tensor<4xi64>) -> !FHE.eint<4>
%cst_5 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 14, 14, 14, 14, 14, 14, 14, 14]> : tensor<64xi64>
%10 = "FHE.apply_lookup_table"(%8, %cst_5) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%11 = "FHE.add_eint"(%10, %9) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%12 = "FHE.apply_lookup_table"(%11, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%cst_6 = arith.constant dense<[0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14]> : tensor<64xi64>
%13 = "FHE.apply_lookup_table"(%8, %cst_6) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%14 = "FHE.add_eint"(%13, %9) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%15 = "FHE.apply_lookup_table"(%14, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%16 = "FHE.add_eint"(%12, %15) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
%17 = "FHE.add_eint"(%16, %8) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
return %17 : !FHE.eint<6>
}
}import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
shifts_with_promotion=False,
)
def f(x, y):
return x << y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**2))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// no promotions
func.func @main(%arg0: !FHE.eint<3>, %arg1: !FHE.eint<2>) -> !FHE.eint<6> {
// shifting for the second bit of y
%cst = arith.constant dense<[0, 0, 1, 1]> : tensor<4xi64>
%0 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<2>, tensor<4xi64>) -> !FHE.eint<4>
%cst_0 = arith.constant dense<[0, 0, 0, 2, 2, 2, 4, 4]> : tensor<8xi64>
%1 = "FHE.apply_lookup_table"(%arg0, %cst_0) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<4>
%2 = "FHE.add_eint"(%1, %0) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%cst_1 = arith.constant dense<[0, 0, 0, 8, 0, 16, 0, 24, 0, 32, 0, 40, 0, 48, 0, 56]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%cst_2 = arith.constant dense<[0, 6, 12, 2, 8, 14, 4, 10]> : tensor<8xi64>
%4 = "FHE.apply_lookup_table"(%arg0, %cst_2) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<4>
%5 = "FHE.add_eint"(%4, %0) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%cst_3 = arith.constant dense<[0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7]> : tensor<16xi64>
%6 = "FHE.apply_lookup_table"(%5, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%7 = "FHE.add_eint"(%3, %6) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
// cast x to 6-bits to compute the result using addition/subtraction
%cst_4 = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7]> : tensor<8xi64>
%8 = "FHE.apply_lookup_table"(%arg0, %cst_4) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.eint<6>
// this was done using promotion instead of casting in runtime when the flag was turned on
%9 = "FHE.add_eint"(%7, %8) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
// shifting for the first bit of y
%cst_5 = arith.constant dense<[0, 1, 0, 1]> : tensor<4xi64>
%10 = "FHE.apply_lookup_table"(%arg1, %cst_5) : (!FHE.eint<2>, tensor<4xi64>) -> !FHE.eint<4>
%cst_6 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 14, 14, 14, 14, 14, 14, 14, 14]> : tensor<64xi64>
%11 = "FHE.apply_lookup_table"(%9, %cst_6) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%12 = "FHE.add_eint"(%11, %10) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%13 = "FHE.apply_lookup_table"(%12, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%cst_7 = arith.constant dense<[0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14]> : tensor<64xi64>
%14 = "FHE.apply_lookup_table"(%9, %cst_7) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.eint<4>
%15 = "FHE.add_eint"(%14, %10) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
%16 = "FHE.apply_lookup_table"(%15, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<6>
%17 = "FHE.add_eint"(%13, %16) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
%18 = "FHE.add_eint"(%17, %9) : (!FHE.eint<6>, !FHE.eint<6>) -> !FHE.eint<6>
return %18 : !FHE.eint<6>
}
}(x - y).bit_width <= MAXIMUM_TLU_BIT_WIDTHx.bit_width != y.bit_widthsmaller = x if x.bit_width < y.bit_width else y
bigger = x if x.bit_width > y.bit_width else y
clipped = lambda value: np.clip(value, smaller.min() - 1, smaller.max() + 1)
any(
(
bit_width <= MAXIMUM_TLU_BIT_WIDTH and
bit_width <= bigger.dtype.bit_width and
bit_width > smaller.dtype.bit_width
)
for bit_width in [
(smaller - clipped(bigger)).bit_width,
(clipped(bigger) - smaller).bit_width,
]
)# (example below is for bit-width of 8 and chunk size of 4)
# extract chunks of lhs using table lookups
lhs_chunks = [lhs.bits[0:4], lhs.bits[4:8]]
# extract chunks of rhs using table lookups
rhs_chunks = [rhs.bits[0:4], rhs.bits[4:8]]
# pack chunks of lhs and rhs using clear multiplications and additions
packed_chunks = []
for lhs_chunk, rhs_chunk in zip(lhs_chunks, rhs_chunks):
shifted_lhs_chunk = lhs_chunk * 2**4 # (i.e., lhs_chunk << 4)
packed_chunks.append(shifted_lhs_chunk + rhs_chunk)
# apply comparison table lookup to packed chunks
comparison_table = fhe.LookupTable([...])
chunk_comparisons = comparison_table[packed_chunks]
# reduce chunk comparisons to comparison of numbers
result = chunk_comparisons[0]
for chunk_comparison in chunk_comparisons[1:]:
chunk_reduction_table = fhe.LookupTable([...])
shifted_chunk_comparison= chunk_comparison * 2**2 # (i.e., lhs_chunk << 2)
result = chunk_reduction_table[result + shifted_chunk_comparison]import numpy as np
from concrete import fhe
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, show_mlir=True)module {
func.func @main(%arg0: !FHE.eint<4>, %arg1: !FHE.eint<4>) -> !FHE.eint<1> {
// extracting the first chunk of x, adjusted for shifting
%cst = arith.constant dense<[0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the first chunk of y
%cst_0 = arith.constant dense<[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]> : tensor<16xi64>
%1 = "FHE.apply_lookup_table"(%arg1, %cst_0) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// packing first chunks
%2 = "FHE.add_eint"(%0, %1) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// comparing first chunks
%cst_1 = arith.constant dense<[0, 1, 1, 1, 2, 0, 1, 1, 2, 2, 0, 1, 2, 2, 2, 0]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the second chunk of x, adjusted for shifting
%cst_2 = arith.constant dense<[0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12]> : tensor<16xi64>
%4 = "FHE.apply_lookup_table"(%arg0, %cst_2) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// extracting the second chunk of y
%cst_3 = arith.constant dense<[0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]> : tensor<16xi64>
%5 = "FHE.apply_lookup_table"(%arg1, %cst_3) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// packing second chunks
%6 = "FHE.add_eint"(%4, %5) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// comparing second chunks
%cst_4 = arith.constant dense<[0, 4, 4, 4, 8, 0, 4, 4, 8, 8, 0, 4, 8, 8, 8, 0]> : tensor<16xi64>
%7 = "FHE.apply_lookup_table"(%6, %cst_4) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<4>
// packing comparisons
%8 = "FHE.add_eint"(%7, %3) : (!FHE.eint<4>, !FHE.eint<4>) -> !FHE.eint<4>
// reducing comparisons to result
%cst_5 = arith.constant dense<[0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0]> : tensor<16xi64>
%9 = "FHE.apply_lookup_table"(%8, %cst_5) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.eint<1>
return %9 : !FHE.eint<1>
}
}comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_promoted_to_uint7 - y_promoted_to_uint7]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.ONE_TLU_PROMOTED,
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............ ............
func.func @main(%arg0: !FHE.eint<5>, %arg1: !FHE.eint<5>) -> !FHE.eint<1> {
// subtraction
%0 = "FHE.to_signed"(%arg0) : (!FHE.eint<5>) -> !FHE.esint<5>
%1 = "FHE.to_signed"(%arg1) : (!FHE.eint<5>) -> !FHE.esint<5>
%2 = "FHE.sub_eint"(%0, %1) : (!FHE.esint<5>, !FHE.esint<5>) -> !FHE.esint<5>
// computing the result
%cst = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<32xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst) : (!FHE.esint<5>, tensor<32xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}uint3_to_uint7_lut = fhe.LookupTable([...])
x_cast_to_uint7 = uint3_to_uint7_lut[x]
uint6_to_uint7_lut = fhe.LookupTable([...])
y_cast_to_uint7 = uint6_to_uint7_lut[y]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_cast_to_uint7 - y_cast_to_uint7]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.THREE_TLU_CASTED,
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**4), np.random.randint(0, 2**4))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// no promotions
func.func @main(%arg0: !FHE.eint<3>, %arg1: !FHE.eint<6>) -> !FHE.eint<1> {
// casting
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]> : tensor<16xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.esint<5>
%1 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<4>, tensor<16xi64>) -> !FHE.esint<5>
// subtraction
%2 = "FHE.sub_eint"(%0, %1) : (!FHE.esint<5>, !FHE.esint<5>) -> !FHE.esint<5>
// computing the result
%cst_0 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<32xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.esint<5>, tensor<32xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}uint3_to_uint7_lut = fhe.LookupTable([...])
x_cast_to_uint7 = uint3_to_uint7_lut[x]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_cast_to_uint7 - y_promoted_to_uint7]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.TWO_TLU_BIGGER_PROMOTED_SMALLER_CASTED,
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**5))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<3>, %arg1: !FHE.eint<6>) -> !FHE.eint<1> {
// casting the smaller operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7]> : tensor<8xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.esint<6>
// subtraction
%1 = "FHE.to_signed"(%arg1) : (!FHE.eint<6>) -> !FHE.esint<6>
%2 = "FHE.sub_eint"(%0, %1) : (!FHE.esint<6>, !FHE.esint<6>) -> !FHE.esint<6>
// computing the result
%cst_0 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<64xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.esint<6>, tensor<64xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}uint6_to_uint7_lut = fhe.LookupTable([...])
y_cast_to_uint7 = uint6_to_uint7_lut[y]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_promoted_to_uint7 - y_cast_to_uint7]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.TWO_TLU_BIGGER_PROMOTED_SMALLER_CASTED,
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**5))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<6>, %arg1: !FHE.eint<5>) -> !FHE.eint<1> {
// casting the bigger operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]> : tensor<32xi64>
%0 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<5>, tensor<32xi64>) -> !FHE.esint<6>
// subtraction
%1 = "FHE.to_signed"(%arg0) : (!FHE.eint<6>) -> !FHE.esint<6>
%2 = "FHE.sub_eint"(%1, %0) : (!FHE.esint<6>, !FHE.esint<6>) -> !FHE.esint<6>
// computing the result
%cst_0 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<64xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.esint<6>, tensor<64xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}uint3_to_uint4_lut = fhe.LookupTable([...])
x_cast_to_uint4 = uint3_to_uint4_lut[x]
clipper = fhe.LookupTable([...])
y_clipped = clipper[y]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_cast_to_uint4 - y_clipped]
# or
another_comparison_lut = fhe.LookupTable([...])
result = another_comparison_lut[y_clipped - x_cast_to_uint4]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.THREE_TLU_BIGGER_CLIPPED_SMALLER_CASTED
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**6))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// no promotions
func.func @main(%arg0: !FHE.eint<3>, %arg1: !FHE.eint<6>) -> !FHE.eint<1> {
// casting the smaller operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7]> : tensor<8xi64>
%0 = "FHE.apply_lookup_table"(%arg0, %cst) : (!FHE.eint<3>, tensor<8xi64>) -> !FHE.esint<4>
// clipping the bigger operand
%cst_0 = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]> : tensor<64xi64>
%1 = "FHE.apply_lookup_table"(%arg1, %cst_0) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.esint<4>
// subtraction
%2 = "FHE.sub_eint"(%0, %1) : (!FHE.esint<4>, !FHE.esint<4>) -> !FHE.esint<4>
// computing the result
%cst_1 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_1) : (!FHE.esint<4>, tensor<16xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}clipper = fhe.LookupTable([...])
y_clipped = clipper[y]
comparison_lut = fhe.LookupTable([...])
result = comparison_lut[x_promoted_to_uint4 - y_clipped]
# or
another_comparison_lut = fhe.LookupTable([...])
result = another_comparison_lut[y_clipped - x_promoted_to_uint4]import numpy as np
from concrete import fhe
configuration = fhe.Configuration(
comparison_strategy_preference=fhe.ComparisonStrategy.TWO_TLU_BIGGER_CLIPPED_SMALLER_PROMOTED
)
def f(x, y):
return x < y
inputset = [
(np.random.randint(0, 2**3), np.random.randint(0, 2**6))
for _ in range(100)
]
compiler = fhe.Compiler(f, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile(inputset, configuration, show_mlir=True)module {
// promotions ............
func.func @main(%arg0: !FHE.eint<4>, %arg1: !FHE.eint<6>) -> !FHE.eint<1> {
// clipping the bigger operand
%cst = arith.constant dense<[0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8]> : tensor<64xi64>
%0 = "FHE.apply_lookup_table"(%arg1, %cst) : (!FHE.eint<6>, tensor<64xi64>) -> !FHE.esint<4>
// subtraction
%1 = "FHE.to_signed"(%arg0) : (!FHE.eint<4>) -> !FHE.esint<4>
%2 = "FHE.sub_eint"(%1, %0) : (!FHE.esint<4>, !FHE.esint<4>) -> !FHE.esint<4>
// computing the result
%cst_0 = arith.constant dense<[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]> : tensor<16xi64>
%3 = "FHE.apply_lookup_table"(%2, %cst_0) : (!FHE.esint<4>, tensor<16xi64>) -> !FHE.eint<1>
return %3 : !FHE.eint<1>
}
}FHE.add_eint_int (::mlir::concretelang::FHE::AddEintIntOp)FHE.add_eint (::mlir::concretelang::FHE::AddEintOp)FHE.apply_lookup_table (::mlir::concretelang::FHE::ApplyLookupTableEintOp)FHE.and (::mlir::concretelang::FHE::BoolAndOp)FHE.nand (::mlir::concretelang::FHE::BoolNandOp)FHE.not (::mlir::concretelang::FHE::BoolNotOp)FHE.or (::mlir::concretelang::FHE::BoolOrOp)FHE.xor (::mlir::concretelang::FHE::BoolXorOp)FHE.from_bool (::mlir::concretelang::FHE::FromBoolOp)FHE.gen_gate (::mlir::concretelang::FHE::GenGateOp)FHE.max_eint (::mlir::concretelang::FHE::MaxEintOp)FHE.mul_eint_int (::mlir::concretelang::FHE::MulEintIntOp)FHE.mul_eint (::mlir::concretelang::FHE::MulEintOp)FHE.mux (::mlir::concretelang::FHE::MuxOp)FHE.neg_eint (::mlir::concretelang::FHE::NegEintOp)FHE.round (::mlir::concretelang::FHE::RoundEintOp)FHE.sub_eint_int (::mlir::concretelang::FHE::SubEintIntOp)FHE.sub_eint (::mlir::concretelang::FHE::SubEintOp)FHE.sub_int_eint (::mlir::concretelang::FHE::SubIntEintOp)FHE.to_bool (::mlir::concretelang::FHE::ToBoolOp)FHE.to_signed (::mlir::concretelang::FHE::ToSignedOp)FHE.to_unsigned (::mlir::concretelang::FHE::ToUnsignedOp)FHE.zero (::mlir::concretelang::FHE::ZeroEintOp)FHE.zero_tensor (::mlir::concretelang::FHE::ZeroTensorOp)// ok
"FHE.add_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.eint<2>
"FHE.add_eint_int"(%a, %i) : (!FHE.esint<2>, i3) -> !FHE.esint<2>
// error
"FHE.add_eint_int"(%a, %i) : (!FHE.eint<2>, i4) -> !FHE.eint<2>
"FHE.add_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.eint<3>
"FHE.add_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.esint<2>// ok
"FHE.add_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<2>)
"FHE.add_eint"(%a, %b): (!FHE.esint<2>, !FHE.esint<2>) -> (!FHE.esint<2>)
// error
"FHE.add_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<3>) -> (!FHE.eint<2>)
"FHE.add_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<3>)
"FHE.add_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.esint<2>)
"FHE.add_eint"(%a, %b): (!FHE.esint<2>, !FHE.eint<2>) -> (!FHE.eint<2>)// ok
"FHE.apply_lookup_table"(%a, %lut): (!FHE.eint<2>, tensor<4xi64>) -> (!FHE.eint<2>)
"FHE.apply_lookup_table"(%a, %lut): (!FHE.eint<2>, tensor<4xi64>) -> (!FHE.eint<3>)
"FHE.apply_lookup_table"(%a, %lut): (!FHE.eint<3>, tensor<4xi64>) -> (!FHE.eint<2>)
// error
"FHE.apply_lookup_table"(%a, %lut): (!FHE.eint<2>, tensor<8xi64>) -> (!FHE.eint<2>)"FHE.and"(%a, %b): (!FHE.ebool, !FHE.ebool) -> (!FHE.ebool)"FHE.nand"(%a, %b): (!FHE.ebool, !FHE.ebool) -> (!FHE.ebool)"FHE.not"(%a): (!FHE.ebool) -> (!FHE.ebool)"FHE.or"(%a, %b): (!FHE.ebool, !FHE.ebool) -> (!FHE.ebool)"FHE.xor"(%a, %b): (!FHE.ebool, !FHE.ebool) -> (!FHE.ebool)"FHE.from_bool"(%x) : (!FHE.ebool) -> !FHE.eint<1>
"FHE.from_bool"(%x) : (!FHE.ebool) -> !FHE.eint<2>
"FHE.from_bool"(%x) : (!FHE.ebool) -> !FHE.eint<4>// ok
"FHE.gen_gate"(%a, %b, %ttable): (!FHE.ebool, !FHE.ebool, tensor<4xi64>) -> (!FHE.ebool)
// error
"FHE.gen_gate"(%a, %b, %ttable): (!FHE.ebool, !FHE.ebool, tensor<7xi64>) -> (!FHE.ebool)// ok
"FHE.max_eint"(%x, %y) : (!FHE.eint<2>, !FHE.eint<2>) -> !FHE.eint<2>
"FHE.max_eint"(%x, %y) : (!FHE.esint<3>, !FHE.esint<3>) -> !FHE.esint<3>
// error
"FHE.max_eint"(%x, %y) : (!FHE.eint<2>, !FHE.eint<3>) -> !FHE.eint<2>
"FHE.max_eint"(%x, %y) : (!FHE.eint<2>, !FHE.eint<2>) -> !FHE.esint<2>
"FHE.max_eint"(%x, %y) : (!FHE.esint<2>, !FHE.eint<2>) -> !FHE.eint<2>// ok
"FHE.mul_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.eint<2>
"FHE.mul_eint_int"(%a, %i) : (!FHE.esint<2>, i3) -> !FHE.esint<2>
// error
"FHE.mul_eint_int"(%a, %i) : (!FHE.eint<2>, i4) -> !FHE.eint<2>
"FHE.mul_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.eint<3>
"FHE.mul_eint_int"(%a, %i) : (!FHE.eint<2>, i3) -> !FHE.esint<2>// ok
"FHE.mul_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<2>)
"FHE.mul_eint"(%a, %b): (!FHE.eint<3>, !FHE.eint<3>) -> (!FHE.eint<3>)
"FHE.mul_eint"(%a, %b): (!FHE.esint<3>, !FHE.esint<3>) -> (!FHE.esint<3>)
// error
"FHE.mul_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<3>) -> (!FHE.eint<2>)
"FHE.mul_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<3>)
"FHE.mul_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.esint<2>)
"FHE.mul_eint"(%a, %b): (!FHE.esint<2>, !FHE.eint<2>) -> (!FHE.eint<2>)"FHE.mux"(%cond, %c1, %c2): (!FHE.ebool, !FHE.ebool, !FHE.ebool) -> (!FHE.ebool)// ok
"FHE.neg_eint"(%a): (!FHE.eint<2>) -> (!FHE.eint<2>)
"FHE.neg_eint"(%a): (!FHE.esint<2>) -> (!FHE.esint<2>)
// error
"FHE.neg_eint"(%a): (!FHE.eint<2>) -> (!FHE.eint<3>)
"FHE.neg_eint"(%a): (!FHE.eint<2>) -> (!FHE.esint<2>) // ok
"FHE.round"(%a): (!FHE.eint<6>) -> (!FHE.eint<5>)
"FHE.round"(%a): (!FHE.eint<5>) -> (!FHE.eint<3>)
"FHE.round"(%a): (!FHE.eint<3>) -> (!FHE.eint<2>)
"FHE.round"(%a): (!FHE.esint<3>) -> (!FHE.esint<2>)
// error
"FHE.round"(%a): (!FHE.eint<6>) -> (!FHE.eint<6>)
"FHE.round"(%a): (!FHE.eint<4>) -> (!FHE.eint<5>)
"FHE.round"(%a): (!FHE.eint<4>) -> (!FHE.esint<5>)
// ok
"FHE.sub_eint_int"(%i, %a) : (!FHE.eint<2>, i3) -> !FHE.eint<2>
"FHE.sub_eint_int"(%i, %a) : (!FHE.esint<2>, i3) -> !FHE.esint<2>
// error
"FHE.sub_eint_int"(%i, %a) : (!FHE.eint<2>, i4) -> !FHE.eint<2>
"FHE.sub_eint_int"(%i, %a) : (!FHE.eint<2>, i3) -> !FHE.eint<3>
"FHE.sub_eint_int"(%i, %a) : (!FHE.eint<2>, i3) -> !FHE.esint<2>// ok
"FHE.sub_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<2>)
"FHE.sub_eint"(%a, %b): (!FHE.esint<2>, !FHE.esint<2>) -> (!FHE.esint<2>)
// error
"FHE.sub_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<3>) -> (!FHE.eint<2>)
"FHE.sub_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.eint<3>)
"FHE.sub_eint"(%a, %b): (!FHE.eint<2>, !FHE.esint<2>) -> (!FHE.esint<2>)
"FHE.sub_eint"(%a, %b): (!FHE.eint<2>, !FHE.eint<2>) -> (!FHE.esint<2>)// ok
"FHE.sub_int_eint"(%i, %a) : (i3, !FHE.eint<2>) -> !FHE.eint<2>
"FHE.sub_int_eint"(%i, %a) : (i3, !FHE.esint<2>) -> !FHE.esint<2>
// error
"FHE.sub_int_eint"(%i, %a) : (i4, !FHE.eint<2>) -> !FHE.eint<2>
"FHE.sub_int_eint"(%i, %a) : (i3, !FHE.eint<2>) -> !FHE.eint<3>
"FHE.sub_int_eint"(%i, %a) : (i3, !FHE.eint<2>) -> !FHE.esint<2>// ok
"FHE.to_bool"(%x) : (!FHE.eint<1>) -> !FHE.ebool
"FHE.to_bool"(%x) : (!FHE.eint<2>) -> !FHE.ebool
// error
"FHE.to_bool"(%x) : (!FHE.eint<3>) -> !FHE.ebool// ok
"FHE.to_signed"(%x) : (!FHE.eint<2>) -> !FHE.esint<2>
// error
"FHE.to_signed"(%x) : (!FHE.eint<2>) -> !FHE.esint<3>// ok
"FHE.to_unsigned"(%x) : (!FHE.esint<2>) -> !FHE.eint<2>
// error
"FHE.to_unsigned"(%x) : (!FHE.esint<2>) -> !FHE.eint<3>"FHE.zero"() : () -> !FHE.eint<2>
"FHE.zero"() : () -> !FHE.esint<2>%tensor = "FHE.zero_tensor"() : () -> tensor<5x!FHE.eint<4>>
%tensor = "FHE.zero_tensor"() : () -> tensor<5x!FHE.esint<4>>!FHE.esint<7>
!FHE.esint<6>!FHE.eint<7>
!FHE.eint<6>TFHE.batched_add_glwe_cst_int (::mlir::concretelang::TFHE::ABatchedAddGLWECstIntOp)TFHE.batched_add_glwe_int_cst (::mlir::concretelang::TFHE::ABatchedAddGLWEIntCstOp)TFHE.batched_add_glwe_int (::mlir::concretelang::TFHE::ABatchedAddGLWEIntOp)TFHE.batched_add_glwe (::mlir::concretelang::TFHE::ABatchedAddGLWEOp)TFHE.add_glwe_int (::mlir::concretelang::TFHE::AddGLWEIntOp)TFHE.add_glwe (::mlir::concretelang::TFHE::AddGLWEOp)TFHE.batched_bootstrap_glwe (::mlir::concretelang::TFHE::BatchedBootstrapGLWEOp)TFHE.batched_keyswitch_glwe (::mlir::concretelang::TFHE::BatchedKeySwitchGLWEOp)TFHE.batched_mapped_bootstrap_glwe (::mlir::concretelang::TFHE::BatchedMappedBootstrapGLWEOp)TFHE.batched_mul_glwe_cst_int (::mlir::concretelang::TFHE::BatchedMulGLWECstIntOp)TFHE.batched_mul_glwe_int_cst (::mlir::concretelang::TFHE::BatchedMulGLWEIntCstOp)TFHE.batched_mul_glwe_int (::mlir::concretelang::TFHE::BatchedMulGLWEIntOp)TFHE.batched_neg_glwe (::mlir::concretelang::TFHE::BatchedNegGLWEOp)TFHE.bootstrap_glwe (::mlir::concretelang::TFHE::BootstrapGLWEOp)TFHE.encode_expand_lut_for_bootstrap (::mlir::concretelang::TFHE::EncodeExpandLutForBootstrapOp)TFHE.encode_lut_for_crt_woppbs (::mlir::concretelang::TFHE::EncodeLutForCrtWopPBSOp)TFHE.encode_plaintext_with_crt (::mlir::concretelang::TFHE::EncodePlaintextWithCrtOp)TFHE.keyswitch_glwe (::mlir::concretelang::TFHE::KeySwitchGLWEOp)TFHE.mul_glwe_int (::mlir::concretelang::TFHE::MulGLWEIntOp)TFHE.neg_glwe (::mlir::concretelang::TFHE::NegGLWEOp)TFHE.sub_int_glwe (::mlir::concretelang::TFHE::SubGLWEIntOp)TFHE.wop_pbs_glwe (::mlir::concretelang::TFHE::WopPBSGLWEOp)TFHE.zero (::mlir::concretelang::TFHE::ZeroGLWEOp)TFHE.zero_tensor (::mlir::concretelang::TFHE::ZeroTensorGLWEOp)#TFHE.bsk<
mlir::concretelang::TFHE::GLWESecretKey, # inputKey
mlir::concretelang::TFHE::GLWESecretKey, # outputKey
int, # polySize
int, # glweDim
int, # levels
int, # baseLog
int # index
>#TFHE.ksk<
mlir::concretelang::TFHE::GLWESecretKey, # inputKey
mlir::concretelang::TFHE::GLWESecretKey, # outputKey
int, # levels
int, # baseLog
int # index
>#TFHE.pksk<
mlir::concretelang::TFHE::GLWESecretKey, # inputKey
mlir::concretelang::TFHE::GLWESecretKey, # outputKey
int, # outputPolySize
int, # inputLweDim
int, # glweDim
int, # levels
int, # baseLog
int # index
>Concrete.add_lwe_buffer (::mlir::concretelang::Concrete::AddLweBufferOp)Concrete.add_lwe_tensor (::mlir::concretelang::Concrete::AddLweTensorOp)Concrete.add_plaintext_lwe_buffer (::mlir::concretelang::Concrete::AddPlaintextLweBufferOp)Concrete.add_plaintext_lwe_tensor (::mlir::concretelang::Concrete::AddPlaintextLweTensorOp)Concrete.batched_add_lwe_buffer (::mlir::concretelang::Concrete::BatchedAddLweBufferOp)Concrete.batched_add_lwe_tensor (::mlir::concretelang::Concrete::BatchedAddLweTensorOp)Concrete.batched_add_plaintext_cst_lwe_buffer (::mlir::concretelang::Concrete::BatchedAddPlaintextCstLweBufferOp)Concrete.batched_add_plaintext_cst_lwe_tensor (::mlir::concretelang::Concrete::BatchedAddPlaintextCstLweTensorOp)Concrete.batched_add_plaintext_lwe_buffer (::mlir::concretelang::Concrete::BatchedAddPlaintextLweBufferOp)Concrete.batched_add_plaintext_lwe_tensor (::mlir::concretelang::Concrete::BatchedAddPlaintextLweTensorOp)Concrete.batched_bootstrap_lwe_buffer (::mlir::concretelang::Concrete::BatchedBootstrapLweBufferOp)Concrete.batched_bootstrap_lwe_tensor (::mlir::concretelang::Concrete::BatchedBootstrapLweTensorOp)Concrete.batched_keyswitch_lwe_buffer (::mlir::concretelang::Concrete::BatchedKeySwitchLweBufferOp)Concrete.batched_keyswitch_lwe_tensor (::mlir::concretelang::Concrete::BatchedKeySwitchLweTensorOp)Concrete.batched_mapped_bootstrap_lwe_buffer (::mlir::concretelang::Concrete::BatchedMappedBootstrapLweBufferOp)Concrete.batched_mapped_bootstrap_lwe_tensor (::mlir::concretelang::Concrete::BatchedMappedBootstrapLweTensorOp)Concrete.batched_mul_cleartext_cst_lwe_buffer (::mlir::concretelang::Concrete::BatchedMulCleartextCstLweBufferOp)Concrete.batched_mul_cleartext_cst_lwe_tensor (::mlir::concretelang::Concrete::BatchedMulCleartextCstLweTensorOp)Concrete.batched_mul_cleartext_lwe_buffer (::mlir::concretelang::Concrete::BatchedMulCleartextLweBufferOp)Concrete.batched_mul_cleartext_lwe_tensor (::mlir::concretelang::Concrete::BatchedMulCleartextLweTensorOp)Concrete.batched_negate_lwe_buffer (::mlir::concretelang::Concrete::BatchedNegateLweBufferOp)Concrete.batched_negate_lwe_tensor (::mlir::concretelang::Concrete::BatchedNegateLweTensorOp)Concrete.bootstrap_lwe_buffer (::mlir::concretelang::Concrete::BootstrapLweBufferOp)Concrete.bootstrap_lwe_tensor (::mlir::concretelang::Concrete::BootstrapLweTensorOp)Concrete.encode_expand_lut_for_bootstrap_buffer (::mlir::concretelang::Concrete::EncodeExpandLutForBootstrapBufferOp)Concrete.encode_expand_lut_for_bootstrap_tensor (::mlir::concretelang::Concrete::EncodeExpandLutForBootstrapTensorOp)Concrete.encode_lut_for_crt_woppbs_buffer (::mlir::concretelang::Concrete::EncodeLutForCrtWopPBSBufferOp)Concrete.encode_lut_for_crt_woppbs_tensor (::mlir::concretelang::Concrete::EncodeLutForCrtWopPBSTensorOp)Concrete.encode_plaintext_with_crt_buffer (::mlir::concretelang::Concrete::EncodePlaintextWithCrtBufferOp)Concrete.encode_plaintext_with_crt_tensor (::mlir::concretelang::Concrete::EncodePlaintextWithCrtTensorOp)Concrete.keyswitch_lwe_buffer (::mlir::concretelang::Concrete::KeySwitchLweBufferOp)Concrete.keyswitch_lwe_tensor (::mlir::concretelang::Concrete::KeySwitchLweTensorOp)Concrete.mul_cleartext_lwe_buffer (::mlir::concretelang::Concrete::MulCleartextLweBufferOp)Concrete.mul_cleartext_lwe_tensor (::mlir::concretelang::Concrete::MulCleartextLweTensorOp)Concrete.negate_lwe_buffer (::mlir::concretelang::Concrete::NegateLweBufferOp)Concrete.negate_lwe_tensor (::mlir::concretelang::Concrete::NegateLweTensorOp)Concrete.wop_pbs_crt_lwe_buffer (::mlir::concretelang::Concrete::WopPBSCRTLweBufferOp)Concrete.wop_pbs_crt_lwe_tensor (::mlir::concretelang::Concrete::WopPBSCRTLweTensorOp)FHELinalg.add_eint_int (::mlir::concretelang::FHELinalg::AddEintIntOp)// Returns the term-by-term addition of `%a0` with `%a1`
"FHELinalg.add_eint_int"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4xi5>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term addition of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.add_eint_int"(%a0, %a1) : (tensor<4x1x4x!FHE.eint<4>>, tensor<1x4x4xi5>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the addition of a 3x3 matrix of encrypted integers and a 3x1 matrix (a column) of integers.
//
// [1,2,3] [1] [2,3,4]
// [4,5,6] + [2] = [6,7,8]
// [7,8,9] [3] [10,11,12]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.add_eint_int"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x1xi5>) -> tensor<3x3x!FHE.eint<4>>
// Returns the addition of a 3x3 matrix of encrypted integers and a 1x3 matrix (a line) of integers.
//
// [1,2,3] [2,4,6]
// [4,5,6] + [1,2,3] = [5,7,9]
// [7,8,9] [8,10,12]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.add_eint_int"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<1x3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 is missing of operand #2.
"FHELinalg.add_eint_int(%a0, %a1)" : (tensor<3x4x!FHE.eint<4>>, tensor<3xi5>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the term-by-term addition of `%a0` with `%a1`
"FHELinalg.add_eint"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4x!FHE.eint<4>>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term addition of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.add_eint"(%a0, %a1) : (tensor<4x1x4x!FHE.eint<4>>, tensor<1x4x4x!FHE.eint<4>>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the addition of a 3x3 matrix of encrypted integers and a 3x1 matrix (a column) of encrypted integers.
//
// [1,2,3] [1] [2,3,4]
// [4,5,6] + [2] = [6,7,8]
// [7,8,9] [3] [10,11,12]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.add_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x1x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Returns the addition of a 3x3 matrix of encrypted integers and a 1x3 matrix (a line) of encrypted integers.
//
// [1,2,3] [2,4,6]
// [4,5,6] + [1,2,3] = [5,7,9]
// [7,8,9] [8,10,12]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.add_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<1x3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 of operand #2 is missing.
"FHELinalg.add_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>// The result of this operation, is a tensor that contains the result of a lookup table.
// i.e. %res[i, ..., k] = %lut[%t[i, ..., k]]
%res = FHELinalg.apply_lookup_table(%t, %lut): tensor<DNx...xD1x!FHE.eint<$p>>, tensor<D2^$pxi64> -> tensor<DNx...xD1x!FHE.eint<$p>>
// Returns the lookup of 3x3 matrix of encrypted indices of with 2 on a table of size 4=2² of clear integers.
//
// [0,1,2] [1,3,5]
// [3,0,1] lut [1,3,5,7] = [7,1,3]
// [2,3,0] [5,7,1]
"FHELinalg.apply_lookup_table"(%t, %lut) : (tensor<3x3x!FHE.eint<2>>, tensor<4xi64>) -> tensor<3x3x!FHE.eint<3>>// The result of this operation, is a tensor that contains the result of the lookup on different tables.
// i.e. %res[i, ..., k] = %luts[ %map[i, ..., k] ][ %t[i, ..., k] ]
%res = FHELinalg.apply_mapped_lookup_table(%t, %luts, %map): tensor<DNx...xD1x!FHE.eint<$p>>, tensor<DM x ^$p>, tensor<DNx...xD1xindex> -> tensor<DNx...xD1x!FHE.eint<$p>>
// Returns the lookup of 3x2 matrix of encrypted indices of width 2 on a vector of 2 tables of size 4=2^2 of clear integers.
//
// [0,1] [0, 1] = [1,2]
// [3,0] lut [[1,3,5,7], [0,2,4,6]] with [0, 1] = [7,0]
// [2,3] [0, 1] = [5,6]
"FHELinalg.apply_mapped_lookup_table"(%t, %luts, %map) : (tensor<3x2x!FHE.eint<2>>, tensor<2x4xi64>, tensor<3x2xindex>) -> tensor<3x2x!FHE.eint<3>>// The result of this operation, is a tensor that contains the result of the lookup on different tables.
// i.e. %res[i, ..., k] = [ %luts[i][%t[i]], ..., %luts[k][%t[k]] ]
%res = FHELinalg.apply_multi_lookup_table(%t, %lut): tensor<DNx...xD1x!FHE.eint<$p>>, tensor<DMx...xD1xD2^$pxi64> -> tensor<DNx...xD1x!FHE.eint<$p>>
// Returns the lookup of 3x2 matrix of encrypted indices of width 2 on a vector of 2 tables of size 4=2² of clear integers.
// The tables are broadcasted along the first dimension of the tensor.
//
// [0,1] = [1,2]
// [3,0] lut [[1,3,5,7], [0,2,4,6]] = [7,0]
// [2,3] = [5,6]
"FHELinalg.apply_multi_lookup_table"(%t, %luts) : (tensor<3x2x!FHE.eint<2>>, tensor<2x4xi64>) -> tensor<3x2x!FHE.eint<3>>
// Returns the lookup of a vector of 3 encrypted indices of width 2 on a vector of 3 tables of size 4=2² of clear integers.
//
// [3,0,1] lut [[1,3,5,7], [0,2,4,6], [1,2,3,4]] = [7,0,2]
"FHELinalg.apply_multi_lookup_table"(%t, %luts) : (tensor<3x!FHE.eint<2>>, tensor<3x4xi64>) -> tensor<3x!FHE.eint<3>>"FHELinalg.concat"(%a, %b) { axis = 0 } : (tensor<3x3x!FHE.eint<4>>, tensor<3x3x!FHE.eint<4>>) -> tensor<6x3x!FHE.eint<4>>
//
// ( [1,2,3] [1,2,3] ) [1,2,3]
// concat ( [4,5,6], [4,5,6] ) = [4,5,6]
// ( [7,8,9] [7,8,9] ) [7,8,9]
// [1,2,3]
// [4,5,6]
// [7,8,9]
//"FHELinalg.concat"(%a, %b) { axis = 1 } : (tensor<3x3x!FHE.eint<4>>, tensor<3x3x!FHE.eint<4>>) -> tensor<3x6x!FHE.eint<4>>
//
// ( [1,2,3] [1,2,3] ) [1,2,3,1,2,3]
// concat ( [4,5,6], [4,5,6] ) = [4,5,6,4,5,6]
// ( [7,8,9] [7,8,9] ) [7,8,9,7,8,9]
//// Returns the dot product of `%a0` with `%a1`
"FHELinalg.dot_eint_int"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4xi5>) -> !FHE.eint<4>
// Returns the dot product of `%a0` with `%a1`
"FHELinalg.dot_eint_eint"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4x!FHE.eint<4>>) -> !FHE.eint<4>
"FHELinalg.from_element"(%a) : (Type) -> tensor<1xType>- If both arguments are 2-D,
they are multiplied like conventional matrices.
e.g.,
arg0: tensor<MxN> = [...]
arg1: tensor<NxP> = [...]
result: tensor<MxP> = [...]
- If the first argument is a vector (1-D),
it is treated as a matrix with a single row and standard matrix multiplication is performed.
After standard matrix multiplication,
the first dimension is removed from the result.
e.g.,
arg0: tensor<3> = [x, y, z]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
is treated as
arg0: tensor<1x3> = [
[x, y, z]
]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
and matrix multiplication is performed with the following form (1x3 @ 3xM -> 1xM)
result: tensor<1xM> = [[_, _, ..., _, _]]
finally, the first dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, ..., _, _]
- If the second argument is 1-D,
it is treated as a matrix with a single column and standard matrix multiplication is performed.
After standard matrix multiplication,
the last dimension is removed from the result.
e.g.,
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3> = [x, y, z]
is treated as
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3x1> = [
[x],
[y],
[z],
]
and matrix multiplication is performed with the following form (Mx3 @ 3x1 -> Mx1)
result: tensor<Mx1> = [
[_],
[_],
...,
[_],
[_],
]
finally, the last dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, _]
- If either argument is N-D where N > 2,
the operation is treated as a collection of matrices residing in the last two indices and broadcasted accordingly.
arg0: tensor<Kx1MxN> = [...]
arg1: tensor<LxNxP> = [...]
result: tensor<KxLxMxP> = [...]"FHELinalg.matmul_eint_eint(%a, %b) : (tensor<MxNx!FHE.eint<p>>, tensor<NxPx!FHE.eint<p>'>) -> tensor<MxPx!FHE.eint<p>>"
"FHELinalg.matmul_eint_eint(%a, %b) : (tensor<KxLxMxNx!FHE.eint<p>>, tensor<KxLxNxPx!FHE.eint<p>'>) -> tensor<KxLxMxPx!FHE.eint<p>>"
"FHELinalg.matmul_eint_eint(%a, %b) : (tensor<MxNx!FHE.eint<p>>, tensor<Nx!FHE.eint<p>'>) -> tensor<Mx!FHE.eint<p>>"
"FHELinalg.matmul_eint_eint(%a, %b) : (tensor<Nx!FHE.eint<p>>, tensor<NxPx!FHE.eint<p>'>) -> tensor<Px!FHE.eint<p>>"// Returns the matrix multiplication of a 3x2 matrix of encrypted integers and a 2x3 matrix of integers.
// [ 1, 2, 3]
// [ 2, 3, 4]
// *
// [1,2] [ 5, 8,11]
// [3,4] = [11,18,25]
// [5,6] [17,28,39]
//
"FHELinalg.matmul_eint_eint"(%a, %b) : (tensor<3x2x!FHE.eint<6>>, tensor<2x3x!FHE.eint<6>>) -> tensor<3x3x!FHE.eint<12>>- If both arguments are 2-D,
they are multiplied like conventional matrices.
e.g.,
arg0: tensor<MxN> = [...]
arg1: tensor<NxP> = [...]
result: tensor<MxP> = [...]
- If the first argument is a vector (1-D),
it is treated as a matrix with a single row and standard matrix multiplication is performed.
After standard matrix multiplication,
the first dimension is removed from the result.
e.g.,
arg0: tensor<3> = [x, y, z]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
is treated as
arg0: tensor<1x3> = [
[x, y, z]
]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
and matrix multiplication is performed with the following form (1x3 @ 3xM -> 1xM)
result: tensor<1xM> = [[_, _, ..., _, _]]
finally, the first dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, ..., _, _]
- If the second argument is 1-D,
it is treated as a matrix with a single column and standard matrix multiplication is performed.
After standard matrix multiplication,
the last dimension is removed from the result.
e.g.,
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3> = [x, y, z]
is treated as
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3x1> = [
[x],
[y],
[z],
]
and matrix multiplication is performed with the following form (Mx3 @ 3x1 -> Mx1)
result: tensor<Mx1> = [
[_],
[_],
...,
[_],
[_],
]
finally, the last dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, _]
- If either argument is N-D where N > 2,
the operation is treated as a collection of matrices residing in the last two indices and broadcasted accordingly.
arg0: tensor<Kx1MxN> = [...]
arg1: tensor<LxNxP> = [...]
result: tensor<KxLxMxP> = [...]"FHELinalg.matmul_eint_int(%a, %b) : (tensor<MxNx!FHE.eint<p>>, tensor<NxPxip'>) -> tensor<MxPx!FHE.eint<p>>"
"FHELinalg.matmul_eint_int(%a, %b) : (tensor<KxLxMxNx!FHE.eint<p>>, tensor<KxLxNxPxip'>) -> tensor<KxLxMxPx!FHE.eint<p>>"
"FHELinalg.matmul_eint_int(%a, %b) : (tensor<MxNx!FHE.eint<p>>, tensor<Nxip'>) -> tensor<Mx!FHE.eint<p>>"
"FHELinalg.matmul_eint_int(%a, %b) : (tensor<Nx!FHE.eint<p>>, tensor<NxPxip'>) -> tensor<Px!FHE.eint<p>>"// Returns the matrix multiplication of a 3x2 matrix of encrypted integers and a 2x3 matrix of integers.
// [ 1, 2, 3]
// [ 2, 3, 4]
// *
// [1,2] [ 5, 8,11]
// [3,4] = [11,18,25]
// [5,6] [17,28,39]
//
"FHELinalg.matmul_eint_int"(%a, %b) : (tensor<3x2x!FHE.eint<6>>, tensor<2x3xi7>) -> tensor<3x3x!FHE.eint<6>>- If both arguments are 2-D,
they are multiplied like conventional matrices.
e.g.,
arg0: tensor<MxN> = [...]
arg1: tensor<NxP> = [...]
result: tensor<MxP> = [...]
- If the first argument is a vector (1-D),
it is treated as a matrix with a single row and standard matrix multiplication is performed.
After standard matrix multiplication,
the first dimension is removed from the result.
e.g.,
arg0: tensor<3> = [x, y, z]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
is treated as
arg0: tensor<1x3> = [
[x, y, z]
]
arg1: tensor<3xM> = [
[_, _, ..., _, _],
[_, _, ..., _, _],
[_, _, ..., _, _],
]
and matrix multiplication is performed with the following form (1x3 @ 3xM -> 1xM)
result: tensor<1xM> = [[_, _, ..., _, _]]
finally, the first dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, ..., _, _]
- If the second argument is 1-D,
it is treated as a matrix with a single column and standard matrix multiplication is performed.
After standard matrix multiplication,
the last dimension is removed from the result.
e.g.,
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3> = [x, y, z]
is treated as
arg0: tensor<Mx3> = [
[_, _, _],
[_, _, _],
...,
[_, _, _],
[_, _, _],
]
arg1: tensor<3x1> = [
[x],
[y],
[z],
]
and matrix multiplication is performed with the following form (Mx3 @ 3x1 -> Mx1)
result: tensor<Mx1> = [
[_],
[_],
...,
[_],
[_],
]
finally, the last dimension is removed by definition so the result has the following form
result: tensor<M> = [_, _, _]
- If either argument is N-D where N > 2,
the operation is treated as a collection of matrices residing in the last two indices and broadcasted accordingly.
arg0: tensor<Kx1MxN> = [...]
arg1: tensor<LxNxP> = [...]
result: tensor<KxLxMxP> = [...]"FHELinalg.matmul_int_eint(%a, %b) : (tensor<MxNxip'>, tensor<NxPxFHE.eint<p>>) -> tensor<MxPx!FHE.eint<p>>"
"FHELinalg.matmul_int_eint(%a, %b) : (tensor<KxLxMxNxip'>, tensor<KxLxNxPxFHE.eint<p>>) -> tensor<KxLxMxPx!FHE.eint<p>>"
"FHELinalg.matmul_int_eint(%a, %b) : (tensor<MxNxip'>, tensor<NxFHE.eint<p>>) -> tensor<Mx!FHE.eint<p>>"
"FHELinalg.matmul_int_eint(%a, %b) : (tensor<Nxip'>, tensor<NxPxFHE.eint<p>>) -> tensor<Px!FHE.eint<p>>"// Returns the matrix multiplication of a 3x2 matrix of clear integers and a 2x3 matrix of encrypted integers.
// [ 1, 2, 3]
// [ 2, 3, 4]
// *
// [1,2] [ 5, 8,11]
// [3,4] = [11,18,25]
// [5,6] [17,28,39]
//
"FHELinalg.matmul_int_eint"(%a, %b) : (tensor<3x2xi7>, tensor<2x3x!FHE.eint<6>>) -> tensor<3x3x!FHE.eint<6>>// Returns the term-by-term multiplication of `%a0` with `%a1`
"FHELinalg.mul_eint_int"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4xi5>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term multiplication of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.mul_eint_int"(%a0, %a1) : (tensor<4x1x4x!FHE.eint<4>>, tensor<1x4x4xi5>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the multiplication of a 3x3 matrix of encrypted integers and a 3x1 matrix (a column) of integers.
//
// [1,2,3] [1] [1,2,3]
// [4,5,6] * [2] = [8,10,18]
// [7,8,9] [3] [21,24,27]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.mul_eint_int"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x1xi5>) -> tensor<3x3x!FHE.eint<4>>
// Returns the multiplication of a 3x3 matrix of encrypted integers and a 1x3 matrix (a line) of integers.
//
// [1,2,3] [2,4,6]
// [4,5,6] * [1,2,3] = [5,7,9]
// [7,8,9] [8,10,12]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.mul_eint_int"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<1x3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 is missing of operand #2.
"FHELinalg.mul_eint_int"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Returns the term-by-term multiplication of `%a0` with `%a1`
"FHELinalg.mul_eint"(%a0, %a1) : (tensor<4x!FHE.eint<8>>, tensor<4x!FHE.eint<8>>) -> tensor<4x!FHE.eint<8>>
// Returns the term-by-term multiplication of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.mul_eint"(%a0, %a1) : (tensor<4x1x4x!FHE.eint<8>>, tensor<1x4x4x!FHE.eint<8>>) -> tensor<4x4x4x!FHE.eint<8>>
// Returns the multiplication of a 3x3 matrix of encrypted integers and a 3x1 matrix (a column) of encrypted integers.
//
// [1,2,3] [1] [1,2,3]
// [4,5,6] * [2] = [8,10,12]
// [7,8,9] [3] [21,24,27]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.mul_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<8>>, tensor<3x1x!FHE.eint<8>>) -> tensor<3x3x!FHE.eint<8>>
// Returns the multiplication of a 3x3 matrix of encrypted integers and a 1x3 matrix (a line) of encrypted integers.
//
// [1,2,3] [1,4,9]
// [4,5,6] * [1,2,3] = [4,10,18]
// [7,8,9] [7,16,27]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.mul_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<8>>, tensor<1x3x!FHE.eint<8>>) -> tensor<3x3x!FHE.eint<8>>
// Same behavior as the previous one, but as the dimension #2 of operand #2 is missing.
"FHELinalg.mul_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<8>>, tensor<3x!FHE.eint<8>>) -> tensor<3x3x!FHE.eint<8>>// Returns the term-by-term negation of `%a0`
"FHELinalg.neg_eint"(%a0) : (tensor<3x3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
//
// ( [1,2,3] ) [31,30,29]
// negate ( [4,5,6] ) = [28,27,26]
// ( [7,8,9] ) [25,24,23]
//
// The negation is computed as `2**(p+1) - a` where p=4 here. Assuming a ciphertext whose message is implemented over `p` bits, this
operation rounds it to fit to `q` bits where `p>q`.
Example:
```mlir
// ok
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<6>>) -> (tensor<3x!FHE.eint<5>>)
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<5>>) -> (tensor<3x!FHE.eint<3>>)
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<3>>) -> (tensor<3x!FHE.eint<2>>)
"FHELinalg.round"(%a): (tensor<3x!FHE.esint<3>>) -> (tensor<3x!FHE.esint<2>>)
// error
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<6>>) -> (tensor<3x!FHE.eint<6>>)
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<4>>) -> (tensor<3x!FHE.eint<5>>)
"FHELinalg.round"(%a): (tensor<3x!FHE.eint<4>>) -> (tensor<3x!FHE.esint<2>>)
Traits: AlwaysSpeculatableImplTrait, TensorUnaryEint
Interfaces: ConditionallySpeculatable, NoMemoryEffect (MemoryEffectOpInterface), UnaryEint
Effects: MemoryEffects::Effect{}
#### Operands:
| Operand | Description |
| :-----: | ----------- |
| `input` |
#### Results:
| Result | Description |
| :----: | ----------- |
| `output` |
### `FHELinalg.sub_eint_int` (::mlir::concretelang::FHELinalg::SubEintIntOp)
Returns a tensor that contains the subtraction of a tensor of clear integers from a tensor of encrypted integers.
Performs a subtraction following the broadcasting rules between a tensor of clear integers from a tensor of encrypted integers.
The width of the clear integers must be less than or equal to the width of encrypted integers.
Examples:
```mlir
// Returns the term-by-term subtraction of `%a0` with `%a1`
"FHELinalg.sub_eint_int"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4xi5>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term subtraction of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.sub_eint_int"(%a0, %a1) : (tensor<1x4x4x!FHE.eint<4>>, tensor<4x1x4xi5>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 3x1 matrix (a column) of encrypted integers.
//
// [1,2,3] [1] [0,2,3]
// [4,5,6] - [2] = [2,3,4]
// [7,8,9] [3] [4,5,6]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_eint_int"(%a0, %a1) : (tensor<3x1x!FHE.eint<4>>, tensor<3x3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 1x3 matrix (a line) of encrypted integers.
//
// [1,2,3] [0,0,0]
// [4,5,6] - [1,2,3] = [3,3,3]
// [7,8,9] [6,6,6]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_eint_int"(%a0, %a1) : (tensor<1x3x!FHE.eint<4>>, tensor<3x3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 is missing of operand #2.
"FHELinalg.sub_eint_int"(%a0, %a1) : (tensor<3x!FHE.eint<4>>, tensor<3x3xi5>) -> tensor<3x3x!FHE.eint<4>>
// Returns the term-by-term subtraction of `%a0` with `%a1`
"FHELinalg.sub_eint"(%a0, %a1) : (tensor<4x!FHE.eint<4>>, tensor<4x!FHE.eint<4>>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term subtraction of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.sub_eint"(%a0, %a1) : (tensor<4x1x4x!FHE.eint<4>>, tensor<1x4x4x!FHE.eint<4>>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 3x1 matrix (a column) of encrypted integers.
//
// [1,2,3] [1] [0,2,3]
// [4,5,6] - [2] = [2,3,4]
// [7,8,9] [3] [4,5,6]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x1x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 1x3 matrix (a line) of encrypted integers.
//
// [1,2,3] [0,0,0]
// [4,5,6] - [1,2,3] = [3,3,3]
// [7,8,9] [6,6,6]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<1x3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 of operand #2 is missing.
"FHELinalg.sub_eint"(%a0, %a1) : (tensor<3x3x!FHE.eint<4>>, tensor<3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>// Returns the term-by-term subtraction of `%a0` with `%a1`
"FHELinalg.sub_int_eint"(%a0, %a1) : (tensor<4xi5>, tensor<4x!FHE.eint<4>>) -> tensor<4x!FHE.eint<4>>
// Returns the term-by-term subtraction of `%a0` with `%a1`, where dimensions equal to one are stretched.
"FHELinalg.sub_int_eint"(%a0, %a1) : (tensor<4x1x4xi5>, tensor<1x4x4x!FHE.eint<4>>) -> tensor<4x4x4x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 3x1 matrix (a column) of encrypted integers.
//
// [1,2,3] [1] [0,2,3]
// [4,5,6] - [2] = [2,3,4]
// [7,8,9] [3] [4,5,6]
//
// The dimension #1 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_int_eint"(%a0, %a1) : (tensor<3x3xi5>, tensor<3x1x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Returns the subtraction of a 3x3 matrix of integers and a 1x3 matrix (a line) of encrypted integers.
//
// [1,2,3] [0,0,0]
// [4,5,6] - [1,2,3] = [3,3,3]
// [7,8,9] [6,6,6]
//
// The dimension #2 of operand #2 is stretched as it is equal to 1.
"FHELinalg.sub_int_eint"(%a0, %a1) : (tensor<3x3xi5>, tensor<1x3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Same behavior as the previous one, but as the dimension #2 is missing of operand #2.
"FHELinalg.sub_int_eint"(%a0, %a1) : (tensor<3x3xi5>, tensor<3x!FHE.eint<4>>) -> tensor<3x3x!FHE.eint<4>>
// Returns the sum of all elements of `%a0`
"FHELinalg.sum"(%a0) : (tensor<3x3x!FHE.eint<4>>) -> !FHE.eint<4>
//
// ( [1,2,3] )
// sum ( [4,5,6] ) = 45
// ( [7,8,9] )
//// Returns the sum of all elements of `%a0` along columns
"FHELinalg.sum"(%a0) { axes = [0] } : (tensor<3x2x!FHE.eint<4>>) -> tensor<2x!FHE.eint<4>>
//
// ( [1,2] )
// sum ( [3,4] ) = [9, 12]
// ( [5,6] )
//// Returns the sum of all elements of `%a0` along columns while preserving dimensions
"FHELinalg.sum"(%a0) { axes = [0], keep_dims = true } : (tensor<3x2x!FHE.eint<4>>) -> tensor<1x2x!FHE.eint<4>>
//
// ( [1,2] )
// sum ( [3,4] ) = [[9, 12]]
// ( [5,6] )
//// Returns the sum of all elements of `%a0` along rows
"FHELinalg.sum"(%a0) { axes = [1] } : (tensor<3x2x!FHE.eint<4>>) -> tensor<3x!FHE.eint<4>>
//
// ( [1,2] )
// sum ( [3,4] ) = [3, 7, 11]
// ( [5,6] )
//// Returns the sum of all elements of `%a0` along rows while preserving dimensions
"FHELinalg.sum"(%a0) { axes = [1], keep_dims = true } : (tensor<3x2x!FHE.eint<4>>) -> tensor<3x1x!FHE.eint<4>>
//
// ( [1,2] ) [3]
// sum ( [3,4] ) = [7]
// ( [5,6] ) [11]
//// ok
"FHELinalg.to_signed"(%x) : (tensor<3x2x!FHE.eint<2>>) -> tensor<3x2x!FHE.esint<2>>
// error
"FHELinalg.to_signed"(%x) : (tensor<3x2x!FHE.eint<2>>) -> tensor<3x2x!FHE.esint<3>>// ok
"FHELinalg.to_unsigned"(%x) : (tensor<3x2x!FHE.esint<2>>) -> tensor<3x2x!FHE.eint<2>>
// error
"FHELinalg.to_unsigned"(%x) : (tensor<3x2x!FHE.esint<2>>) -> tensor<3x2x!FHE.eint<3>>"FHELinalg.transpose"(%a) : (tensor<n0xn1x...xnNxType>) -> tensor<nNx...xn1xn0xType>// Transpose the input tensor
// [1,2] [1, 3, 5]
// [3,4] => [2, 4, 6]
// [5,6]
//
"FHELinalg.transpose"(%a) : (tensor<3x2xi7>) -> tensor<2x3xi7>"FHELinalg.transpose"(%a) { axes = [1, 3, 0, 2] } : (tensor<2x3x4x5xi7>) -> tensor<3x5x2x4xi7>