Direct circuits
Direct circuits are still experimental. It is very easy to make mistakes (e.g., due to no overflow checks or type coercion) while using direct circuits, so utilize them with care.
For some applications, the data types of inputs, intermediate values, and outputs are known (e.g., for manipulating bytes, you would want to use uint8). Using inputsets to determine bounds in these cases is not necessary, and can even be error-prone. Therefore, another interface for defining such circuits is introduced:
There are a few differences between direct circuits and traditional circuits:
Remember that the resulting dtype for each operation will be determined by its inputs. This can lead to some unexpected results if you're not careful (e.g., if you do
-x
wherex: fhe.uint8
, you won't receive a negative value as the result will befhe.uint8
as well)There is no inputset evaluation when using fhe types in
.astype(...)
calls (e.g.,np.sqrt(x).astype(fhe.uint4)
), so the bit width of the output cannot be determined.Specify the resulting data type in univariate extension (e.g.,
fhe.univariate(function, outputs=fhe.uint4)(x)
), for the same reason as above.Be careful with overflows. With inputset evaluation, you'll get bigger bit widths but no overflows. With direct definition, you must ensure that there aren't any overflows manually.
Let's review a more complicated example to see how direct circuits behave:
This prints:
Here is the breakdown of the assigned data types:
As you can see, %8
is subtraction of two unsigned values, and the result is unsigned as well. In the case that c > d
, we have an overflow, and this results in undefined behavior.
Last updated