Today, we are announcing the release of a new version of **Concrete Numpy**.

Concrete Numpy is an open-source set of tools which aims to simplify the use of fully homomorphic encryption (FHE) for data scientists. It can be used to implement machine learning models using a subset of numpy that compiles to FHE. Data scientists are able to train models with popular machine learning libraries and then convert the prediction functions of these models, written in numpy, to FHE.

This new version of Concrete Numpy v0.5 comes with many features:

- Support for new numpy operators. Note that some higher level Machine Learning features have been removed (they are now part of a new package built on top of concrete-numpy called concrete-ml).
- Increased bit precision to 8 bits and the addition of a transpose operator.
- Enabled loop parallelism.

Still built on the efficiency, usability, and simplicity of the Concrete library, this new version of concrete-numpy brings explicit encrypt, decrypt, and run support:

## Support numpy.sum

```
import concrete.numpy as hnp
import numpy as np
def f(x):
return np.sum(x)
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
inputset = [np.random.randint(0, 2 ** 3, size=(3, 2)) for _ in range(10)]
circuit = compiler.compile_on_inputset(inputset)
input = np.array(
[
[3, 2],
[4, 1],
[3, 7],
],
)
output = circuit.run(input.astype(np.uint8))
expected_output = 20
assert output == expected_output
```

## Support numpy.concatenate

```
import concrete.numpy as hnp
import numpy as np
def f(x):
return np.concatenate((x, x + 1))
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
inputset = [np.random.randint(0, 2 ** 3, size=(2, 2)) for _ in range(10)]
circuit = compiler.compile_on_inputset(inputset)
input = np.array(
[
[3, 3],
[7, 0],
],
)
output = circuit.run(input.astype(np.uint8))
expected_output = np.array(
[
[3, 3],
[7, 0],
[4, 4],
[8, 1],
],
)
assert np.array_equal(output, expected_output)
```

## Provide 2D convolution operation

```
import concrete.numpy as hnp
import numpy as np
def f(x):
return hnp.conv2d(x, np.array([[2, 1], [3, 2]]).reshape(1, 1, 2, 2))
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
inputset = [np.random.randint(0, 2 ** 3, size=(1, 1, 4, 4)) for _ in range(10)]
circuit = compiler.compile_on_inputset(inputset)
input = np.array(
[
[3, 2, 1, 0],
[3, 2, 1, 0],
[3, 2, 1, 0],
[3, 2, 1, 0],
]
).reshape(1, 1, 4, 4)
output = circuit.run(input.astype(np.uint8))
```

## Support the full behavior of numpy.matmul by extending the support to 1D tensor and ND tensor with N !=2

```
import concrete.numpy as hnp
import numpy as np
def f(x):
return np.matmul(x, np.array([[2, 1], [3, 2]]))
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
inputset = [np.random.randint(0, 2 ** 3, size=(2, 3, 2)) for _ in range(10)]
circuit = compiler.compile_on_inputset(inputset)
input = np.array(
[
[
[3, 2],
[4, 1],
[3, 7],
],
[
[2, 3],
[1, 4],
[7, 3],
],
]
)
output = circuit.run(input.astype(np.uint8))
expected_output = np.array(
[
[
[12, 7],
[11, 6],
[27, 17],
],
[
[13, 8],
[14, 9],
[23, 13],
],
],
)
assert np.array_equal(output, expected_output)
```

## Support numpy.transpose

```
import concrete.numpy as hnp
import numpy as np
def f(x):
return np.transpose(x)
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
inputset = [np.random.randint(0, 2 ** 3, size=(3, 2)) for _ in range(10)]
circuit = compiler.compile_on_inputset(inputset)
input = np.array(
[
[3, 2],
[4, 1],
[3, 7],
],
)
output = circuit.run(input.astype(np.uint8))
expected_output = np.array(
[
[3, 4, 3],
[2, 1, 7],
],
)
assert np.array_equal(output, expected_output)
```

## Increase maximum precision from 7 bits to 8 bits.

```
import concrete.numpy as hnp
import numpy as np
def f(x):
return x + 100
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
inputset = range(100)
circuit = compiler.compile_on_inputset(inputset)
input = 50
output = circuit.run(input)
expected_output = 150
assert output == expected_output
```

## Support loop parallelism

Before this version, the concrete-compiler parallelism infrastructure was not exposed to concrete-numpy, but with this new release, the loop parallelism is exposed (by default) to concrete-numpy.

## Other changes:

- `**run**` has now been replaced by `**encrypt_run_decrypt**` in compiled circuits (`**FHECircuit**`).

- Separate API for doing key generation, encryption, decryption, and execution.

```
import concrete.numpy as hnp
import numpy as np
def f(x):
return np.sum(x)
compiler = hnp.NPFHECompiler(f, {"x": "encrypted"})
inputset = [np.random.randint(0, 2 ** 3, size=(3, 2)) for _ in range(10)]
circuit = compiler.compile_on_inputset(inputset)
input = np.array(
[
[3, 2],
[4, 1],
[3, 7],
], dtype=np.uint8
)
circuit.keygen()
public_arguments = circuit.encrypt(input)
encrypted_result = circuit.run(public_arguments)
output = circuit.decrypt(encrypted_result)
```

## Additional Links

- Release notes

- Github repo

- Documentation

- List of contributors