diff --git a/generate-streamlit-app.py b/generate-streamlit-app.py
new file mode 100644
index 0000000..a176b9e
--- /dev/null
+++ b/generate-streamlit-app.py
@@ -0,0 +1,161 @@
+import questionary
+from pathlib import Path
+import os
+import yaml
+import subprocess
+import shutil
+import sys
+
+STREAMLIT_APP_TEMPLATE = '''# This file was automatically generated by the generate-streamlit-app script.
+# To run this file: from the root directory run `streamlit run streamlit_demo_apps/app_{program_name}.py`
+
+import sys
+import os
+
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+import streamlit_app
+
+program_name = "{program_name}"
+program_test_name = "{program_test_name}"
+
+def main():
+ current_dir = os.path.dirname(os.path.abspath(__file__))
+ path_nada_bin = os.path.join(current_dir, "compiled_nada_programs", f"{{program_name}}.nada.bin")
+ path_nada_json = os.path.join(current_dir, "compiled_nada_programs", f"{{program_name}}.nada.json")
+
+ if not os.path.exists(path_nada_bin):
+ raise FileNotFoundError(f"Add `{{program_name}}.nada.bin` to the compiled_nada_programs folder.")
+ if not os.path.exists(path_nada_json):
+ raise FileNotFoundError(f"Run nada build --mir-json and add `{{program_name}}.nada.json` to the compiled_nada_programs folder.")
+
+ streamlit_app.main(program_test_name, path_nada_bin, path_nada_json)
+
+if __name__ == "__main__":
+ main()
+'''
+
+def get_programs(directory):
+ return sorted([f.stem for f in Path(directory).glob('*.py') if f.is_file()])
+
+def get_test_files(directory, program_name):
+ matching_files = []
+ for file in Path(directory).glob('*.yaml'):
+ try:
+ with open(file, 'r') as f:
+ test_data = yaml.safe_load(f)
+ if test_data and 'program' in test_data and test_data['program'] == program_name:
+ matching_files.append(file)
+ except yaml.YAMLError:
+ print(f"Error reading {file}. Skipping.")
+ except Exception as e:
+ print(f"Unexpected error reading {file}: {e}. Skipping.")
+ return matching_files
+
+def select_program_and_test():
+ programs = get_programs('src')
+ if not programs:
+ print("No Python programs found in 'src' directory.")
+ return None, None
+
+ selected_program = questionary.select(
+ "Select an existing program to create a streamlit app demo:",
+ choices=programs
+ ).ask()
+
+ test_files = get_test_files('tests', selected_program)
+ if not test_files:
+ print(f"No test files found for '{selected_program}' in 'tests' directory.")
+ return selected_program, None
+
+ selected_test = questionary.select(
+ "Select a test file for starting input values:",
+ choices=[f.name for f in test_files]
+ ).ask()
+
+ return selected_program, selected_test
+
+def build_nada_program(program_name):
+ try:
+ subprocess.run(['nada', 'build', program_name, '--mir-json'], check=True)
+ print(f"Successfully built {program_name}")
+ return True
+ except subprocess.CalledProcessError as e:
+ print(f"Error building {program_name}: {e}")
+ return False
+ except FileNotFoundError:
+ print("Error: 'nada' command not found. Make sure it's installed and in your PATH.")
+ return False
+
+def copy_nada_files(program_name):
+ source_dir = Path('target')
+ dest_dir = Path('streamlit_demo_apps/compiled_nada_programs')
+
+ for ext in ['.nada.json', '.nada.bin']:
+ source_file = source_dir / f"{program_name}{ext}"
+ dest_file = dest_dir / f"{program_name}{ext}"
+
+ if source_file.exists():
+ shutil.copy2(source_file, dest_file)
+ print(f"Copied {source_file} to {dest_file}")
+ else:
+ print(f"Warning: {source_file} not found")
+
+def create_streamlit_app(program_name, test_name):
+ try:
+ app_content = STREAMLIT_APP_TEMPLATE.format(
+ program_name=program_name,
+ program_test_name=test_name
+ )
+
+ app_file_path = Path('streamlit_demo_apps') / f"app_{program_name}.py"
+ print(f"Attempting to create file at: {app_file_path.absolute()}")
+
+ # Ensure the directory exists
+ app_file_path.parent.mkdir(parents=True, exist_ok=True)
+
+ with open(app_file_path, 'w') as f:
+ f.write(app_content)
+ print(f"Created Streamlit app file: {app_file_path}")
+
+ if app_file_path.exists():
+ print(f"Streamlit app file successfully created at {app_file_path}")
+ return app_file_path
+ else:
+ print(f"Error: File creation verified failed for {app_file_path}")
+ return None
+ except Exception as e:
+ print(f"Error creating Streamlit app file: {e}")
+ return None
+
+def run_streamlit_app(app_path):
+ try:
+ print(f"Attempting to run Streamlit app: {app_path}")
+ subprocess.run([sys.executable, '-m', 'streamlit', 'run', str(app_path)], check=True)
+ except subprocess.CalledProcessError as e:
+ print(f"Error running Streamlit app: {e}")
+ except Exception as e:
+ print(f"Unexpected error running Streamlit app: {e}")
+
+def main():
+ program, test = select_program_and_test()
+
+ if program:
+ print(f"Selected program: {program}")
+ if test:
+ print(f"Selected test file: {test}")
+
+ with open(os.path.join('tests', test), 'r') as file:
+ test_data = yaml.safe_load(file)
+ print("\nTest file contents:")
+ print(yaml.dump(test_data, default_flow_style=False))
+
+ if build_nada_program(program):
+ copy_nada_files(program)
+ app_path = create_streamlit_app(program, os.path.splitext(test)[0] if test else '')
+ if app_path:
+ run_streamlit_app(app_path)
+ else:
+ print("No program selected.")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 58801ce..adcfef2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,6 +5,7 @@ nada-test
# streamlit demo dependencies
pyyaml
streamlit
+questionary
# nillion_client_script dependencies for streamlit demo
py-nillion-client
diff --git a/streamlit_app.py b/streamlit_app.py
index d6cf7b9..d13ddb1 100644
--- a/streamlit_app.py
+++ b/streamlit_app.py
@@ -104,6 +104,39 @@ def parse_nada_json(json_data):
return input_info, output_info
+import streamlit as st
+
+def create_party_inputs(input_info, input_values):
+ party_names = sorted(set(info['party'] for info in input_info.values()))
+ updated_input_values = input_values.copy()
+
+ if len(party_names) > 1:
+ # Create two columns if there's more than one party
+ columns = st.columns(2)
+ else:
+ # Create a single column if there's only one party
+ columns = [st.columns(1)[0]]
+
+ # Distribute parties between the columns
+ for i, party_name in enumerate(party_names):
+ with columns[i % len(columns)]:
+ st.subheader(f"{party_name}'s Inputs")
+ for input_name, value in input_values.items():
+ if input_info[input_name]['party'] == party_name:
+ input_type = input_info[input_name]['type']
+ if input_type == 'SecretBoolean':
+ updated_input_values[input_name] = st.checkbox(
+ label=f"{input_type}: {input_name}",
+ value=bool(value)
+ )
+ else:
+ updated_input_values[input_name] = st.number_input(
+ label=f"{input_type}: {input_name}",
+ value=value
+ )
+
+ return updated_input_values
+
def main(nada_test_file_name=None, path_nada_bin=None, path_nada_json=None):
# pass test name in via the command line
if nada_test_file_name is None:
@@ -153,28 +186,11 @@ def main(nada_test_file_name=None, path_nada_bin=None, path_nada_json=None):
# Display the program code
st.subheader(f"{program_name}.py")
- st.code(program_code, language='python')
+ with st.expander(f"Nada Program: {program_name}"):
+ st.code(program_code, language='python')
# Display inputs grouped by party, alphabetized
- updated_input_values = {}
- # Get unique party names and sort them alphabetically
- party_names = sorted(set(info['party'] for info in input_info.values()))
-
- for party_name in party_names:
- st.subheader(f"{party_name}'s Inputs")
- for input_name, value in input_values.items():
- if input_info[input_name]['party'] == party_name:
- input_type = input_info[input_name]['type']
- if input_type == 'SecretBoolean':
- updated_input_values[input_name] = st.checkbox(
- label=f"{input_type}: {input_name}",
- value=bool(value)
- )
- else:
- updated_input_values[input_name] = st.number_input(
- label=f"{input_type}: {input_name}",
- value=value
- )
+ updated_input_values = create_party_inputs(input_info, input_values)
output_parties = list(set(output['party'] for output in output_info.values()))
@@ -182,6 +198,7 @@ def main(nada_test_file_name=None, path_nada_bin=None, path_nada_json=None):
# Button to store inputs with a loading screen
if st.button('Run blind computation'):
+ st.divider()
# Conditional spinner text
if should_store_inputs:
spinner_text = "Storing the Nada program, storing inputs, and running blind computation on the Nillion Network Testnet..."
@@ -203,8 +220,6 @@ def main(nada_test_file_name=None, path_nada_bin=None, path_nada_json=None):
# Call the async store_inputs_and_run_blind_computation function and wait for it to complete
result_message = asyncio.run(store_inputs_and_run_blind_computation(input_data, program_name, output_parties, nilchain_private_key, path_nada_bin, cluster_id_from_streamlit_config, grpc_endpoint_from_streamlit_config, chain_id_from_streamlit_config, bootnodes, should_store_inputs))
- st.divider()
-
st.subheader("Nada Program Result")
st.text('Output(s)')
diff --git a/streamlit_demo_apps/README.md b/streamlit_demo_apps/README.md
index cdd0da5..c490471 100644
--- a/streamlit_demo_apps/README.md
+++ b/streamlit_demo_apps/README.md
@@ -1,49 +1,78 @@
# Deploying Streamlit Apps
-Deployed Streamlit apps live here in the streamlit_demo_apps folder.
+Follow the steps to deploy a live Streamlit app for your Nada program. The app will connect to the Nillion Testnet to store your Nada program, store secret inputs (or use computation time secrets), and run blind computation.
## How to add a new Streamlit App
-### 0. Create a streamlit secrets file and add your nilchain private key within `.streamlit/secrets.toml`
+### 0. Fork this repo
+
+### 1. Create a streamlit secrets file
+
+Run this command to create a `.streamlit/secrets.toml` copied from the example.
```
cp .streamlit/secrets.toml.example .streamlit/secrets.toml
```
-### 1. Create an app file in the streamlit_demo_apps folder
-
-Check out the addition app file example:
+Add your Nilchain private key to the .streamlit/secrets.toml file. The private key must be linked to a funded Nillion Testnet address that was created using a Google account (not a mnemonic). This allows you to retrieve the private key from Keplr. If you don’t have a Testnet wallet yet, you can learn how to create one here: https://docs.nillion.com/testnet-guides
-`app_addition.py`
+### 2. Run the script to generate a new streamlit app for your program
-### 2. Copy the compiled Nada program files from the target/ folder into the streamlit_demo_apps/compiled_nada_programs folder
+From the root folder of this repo, run the generate-streamlit-app script:
-Check out the compiled Nada program files for addition:
-
-nada binary `addition.nada.bin`
-nada json `addition.nada.json`
+```
+python3 generate-streamlit-app.py
+```
-### 3. Update your app file with the corresponding program name and program test name
+### 3. Follow the prompts to
-Check out the addition app file example:
+- Select an existing program (from the src/ directory)
+- Select an existing yaml test file for your program (from the tests/ directory)
-`app_addition.py`
+This will generate a Streamlit app file: streamlit*demo_apps/app*[your_program_name].py. The script will run the Streamlit app locally with this command
```
-program_name = 'addition'
-program_test_name = 'addition_test'
+streamlit run streamlit_demo_apps/app_[your_program_name].py`
```
### 4. Test your Streamlit app locally
-Make sure the apps will work when deployed by testing this command from the root folder.
+View the app in your browser to make sure everything works as expected.
+
+### 5. Commit your code to GitHub
+
+Add and commit your new streamlit app code to your forked Github repo. (Code must be connected to a remote, open source GitHub repository to deploy a Streamlit app.)
```
-streamlit run streamlit_demo_apps/[app_file_name].py
+git add .
+git commit -m "my new streamlit nillion app"
+git push origin main
```
-For example to make sure the addition app will work when deployed, run
+Once you've committed the open source code, you can click the "deploy" button within your local streamlit app. Sign in with Github and select the "Deploy Now" on Streamlit Community Cloud option to deploy the app for free.
+
+
+
+### 6. Deploy your app from Streamlit.io
+
+When you click "Deploy Now" from your local app, you'll be taken to streamlit.io and asked to log in with Github to create a new Streamlit app. Set the main file path to your new app `streamlit_demo_apps/app_[your_program_name].py`
+
+
+
+#### Add your Nilchain Private Key using Advanced Settings > Secrets
+
+Go to "Advanced settings" and in Secrets, copy in the contents of your .streamlit/secrets.toml file. At a minimum, make sure to add your secret private key:
```
-streamlit run streamlit_demo_apps/app_addition.py
+nilchain_private_key = "YOUR_FUNDED_PRIVATE_KEY"
```
+
+
+
+Save and click "Deploy" to deploy your testnet-connected Streamlit app.
+
+### 7. Access Your Live Streamlit App
+
+Once deployed, you’ll get a live link to your Nillion Testnet Streamlit app!
+
+Example live Streamlit App: https://stephs-nada-multiplication-app.streamlit.app/
diff --git a/streamlit_demo_apps/app_addition.py b/streamlit_demo_apps/app_addition.py
index 5022832..25ac92a 100644
--- a/streamlit_demo_apps/app_addition.py
+++ b/streamlit_demo_apps/app_addition.py
@@ -1,22 +1,26 @@
+# This file was automatically generated by the generate-streamlit-app script.
+# To run this file: from the root directory run `streamlit run streamlit_demo_apps/app_addition.py`
+
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-import streamlit_app
+import streamlit_app
-program_name = 'addition'
-program_test_name = 'addition_test'
+program_name = "addition"
+program_test_name = "addition_test"
def main():
current_dir = os.path.dirname(os.path.abspath(__file__))
path_nada_bin = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.bin")
path_nada_json = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.json")
+
if not os.path.exists(path_nada_bin):
raise FileNotFoundError(f"Add `{program_name}.nada.bin` to the compiled_nada_programs folder.")
if not os.path.exists(path_nada_json):
raise FileNotFoundError(f"Run nada build --mir-json and add `{program_name}.nada.json` to the compiled_nada_programs folder.")
+
streamlit_app.main(program_test_name, path_nada_bin, path_nada_json)
if __name__ == "__main__":
main()
-
\ No newline at end of file
diff --git a/streamlit_demo_apps/app_multiplication.py b/streamlit_demo_apps/app_equality.py
similarity index 75%
rename from streamlit_demo_apps/app_multiplication.py
rename to streamlit_demo_apps/app_equality.py
index 00e8684..4d15c69 100644
--- a/streamlit_demo_apps/app_multiplication.py
+++ b/streamlit_demo_apps/app_equality.py
@@ -1,22 +1,26 @@
+# This file was automatically generated by the generate-streamlit-app script.
+# To run this file: from the root directory run `streamlit run streamlit_demo_apps/app_equality.py`
+
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-import streamlit_app
+import streamlit_app
-program_name = 'multiplication'
-program_test_name = 'multiplication_test'
+program_name = "equality"
+program_test_name = "equality_test"
def main():
current_dir = os.path.dirname(os.path.abspath(__file__))
path_nada_bin = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.bin")
path_nada_json = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.json")
+
if not os.path.exists(path_nada_bin):
raise FileNotFoundError(f"Add `{program_name}.nada.bin` to the compiled_nada_programs folder.")
if not os.path.exists(path_nada_json):
raise FileNotFoundError(f"Run nada build --mir-json and add `{program_name}.nada.json` to the compiled_nada_programs folder.")
+
streamlit_app.main(program_test_name, path_nada_bin, path_nada_json)
if __name__ == "__main__":
main()
-
\ No newline at end of file
diff --git a/streamlit_demo_apps/app_list_scan_linear.py b/streamlit_demo_apps/app_list_scan_linear.py
index 09bf05c..0b69356 100644
--- a/streamlit_demo_apps/app_list_scan_linear.py
+++ b/streamlit_demo_apps/app_list_scan_linear.py
@@ -1,22 +1,26 @@
+# This file was automatically generated by the generate-streamlit-app script.
+# To run this file: from the root directory run `streamlit run streamlit_demo_apps/app_list_scan_linear.py`
+
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-import streamlit_app
+import streamlit_app
-program_name = 'list_scan_linear'
-program_test_name = 'list_scan_linear_test'
+program_name = "list_scan_linear"
+program_test_name = "list_scan_linear_test"
def main():
current_dir = os.path.dirname(os.path.abspath(__file__))
path_nada_bin = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.bin")
path_nada_json = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.json")
+
if not os.path.exists(path_nada_bin):
raise FileNotFoundError(f"Add `{program_name}.nada.bin` to the compiled_nada_programs folder.")
if not os.path.exists(path_nada_json):
raise FileNotFoundError(f"Run nada build --mir-json and add `{program_name}.nada.json` to the compiled_nada_programs folder.")
+
streamlit_app.main(program_test_name, path_nada_bin, path_nada_json)
if __name__ == "__main__":
main()
-
\ No newline at end of file
diff --git a/streamlit_demo_apps/app_voting.py b/streamlit_demo_apps/app_voting.py
index bf81e9f..3cb4f00 100644
--- a/streamlit_demo_apps/app_voting.py
+++ b/streamlit_demo_apps/app_voting.py
@@ -1,22 +1,26 @@
+# This file was automatically generated by the generate-streamlit-app script.
+# To run this file: from the root directory run `streamlit run streamlit_demo_apps/app_voting.py`
+
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
-import streamlit_app
+import streamlit_app
-program_name = 'voting'
-program_test_name = 'voting_test'
+program_name = "voting"
+program_test_name = "voting_test"
def main():
current_dir = os.path.dirname(os.path.abspath(__file__))
path_nada_bin = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.bin")
path_nada_json = os.path.join(current_dir, "compiled_nada_programs", f"{program_name}.nada.json")
+
if not os.path.exists(path_nada_bin):
raise FileNotFoundError(f"Add `{program_name}.nada.bin` to the compiled_nada_programs folder.")
if not os.path.exists(path_nada_json):
raise FileNotFoundError(f"Run nada build --mir-json and add `{program_name}.nada.json` to the compiled_nada_programs folder.")
+
streamlit_app.main(program_test_name, path_nada_bin, path_nada_json)
if __name__ == "__main__":
main()
-
\ No newline at end of file
diff --git a/streamlit_demo_apps/compiled_nada_programs/addition.nada.bin b/streamlit_demo_apps/compiled_nada_programs/addition.nada.bin
index 49f41b7..14df2b2 100644
Binary files a/streamlit_demo_apps/compiled_nada_programs/addition.nada.bin and b/streamlit_demo_apps/compiled_nada_programs/addition.nada.bin differ
diff --git a/streamlit_demo_apps/compiled_nada_programs/addition.nada.json b/streamlit_demo_apps/compiled_nada_programs/addition.nada.json
index 17a9f83..d506519 100644
--- a/streamlit_demo_apps/compiled_nada_programs/addition.nada.json
+++ b/streamlit_demo_apps/compiled_nada_programs/addition.nada.json
@@ -1,73 +1 @@
-{
- "functions": [],
- "parties": [
- { "name": "Alice", "source_ref_index": 4 },
- { "name": "Bob", "source_ref_index": 5 },
- { "name": "Charlie", "source_ref_index": 6 }
- ],
- "inputs": [
- {
- "type": "SecretInteger",
- "party": "Alice",
- "name": "num_1",
- "doc": "",
- "source_ref_index": 2
- },
- {
- "type": "SecretInteger",
- "party": "Bob",
- "name": "num_2",
- "doc": "",
- "source_ref_index": 1
- }
- ],
- "literals": [],
- "outputs": [
- {
- "name": "sum",
- "operation_id": 4316114688,
- "party": "Charlie",
- "type": "SecretInteger",
- "source_ref_index": 3
- }
- ],
- "operations": {
- "4311539808": {
- "InputReference": {
- "id": 4311539808,
- "refers_to": "num_2",
- "type": "SecretInteger",
- "source_ref_index": 1
- }
- },
- "4316114688": {
- "Addition": {
- "id": 4316114688,
- "left": 4316113968,
- "right": 4311539808,
- "type": "SecretInteger",
- "source_ref_index": 0
- }
- },
- "4316113968": {
- "InputReference": {
- "id": 4316113968,
- "refers_to": "num_1",
- "type": "SecretInteger",
- "source_ref_index": 2
- }
- }
- },
- "source_files": {
- "addition.py": "from nada_dsl import *\n\ndef nada_main():\n party_alice = Party(name=\"Alice\")\n party_bob = Party(name=\"Bob\")\n party_charlie = Party(name=\"Charlie\")\n num_1 = SecretInteger(Input(name=\"num_1\", party=party_alice))\n num_2 = SecretInteger(Input(name=\"num_2\", party=party_bob))\n sum = num_1 + num_2\n return [Output(sum, \"sum\", party_charlie)]"
- },
- "source_refs": [
- { "file": "addition.py", "lineno": 9, "offset": 285, "length": 23 },
- { "file": "addition.py", "lineno": 8, "offset": 221, "length": 63 },
- { "file": "addition.py", "lineno": 7, "offset": 155, "length": 65 },
- { "file": "addition.py", "lineno": 10, "offset": 0, "length": 0 },
- { "file": "addition.py", "lineno": 4, "offset": 41, "length": 37 },
- { "file": "addition.py", "lineno": 5, "offset": 79, "length": 33 },
- { "file": "addition.py", "lineno": 6, "offset": 113, "length": 41 }
- ]
-}
+{"functions":[],"parties":[{"name":"Alice","source_ref_index":4},{"name":"Bob","source_ref_index":5},{"name":"Charlie","source_ref_index":6}],"inputs":[{"type":"SecretInteger","party":"Alice","name":"num_1","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Bob","name":"num_2","doc":"","source_ref_index":1}],"literals":[],"outputs":[{"name":"sum","operation_id":4338511424,"party":"Charlie","type":"SecretInteger","source_ref_index":3}],"operations":{"4338511376":{"InputReference":{"id":4338511376,"refers_to":"num_2","type":"SecretInteger","source_ref_index":1}},"4338509120":{"InputReference":{"id":4338509120,"refers_to":"num_1","type":"SecretInteger","source_ref_index":2}},"4338511424":{"Addition":{"id":4338511424,"left":4338509120,"right":4338511376,"type":"SecretInteger","source_ref_index":0}}},"source_files":{"addition.py":"from nada_dsl import *\n\ndef nada_main():\n party_alice = Party(name=\"Alice\")\n party_bob = Party(name=\"Bob\")\n party_charlie = Party(name=\"Charlie\")\n num_1 = SecretInteger(Input(name=\"num_1\", party=party_alice))\n num_2 = SecretInteger(Input(name=\"num_2\", party=party_bob))\n sum = num_1 + num_2\n return [Output(sum, \"sum\", party_charlie)]"},"source_refs":[{"file":"addition.py","lineno":9,"offset":285,"length":23},{"file":"addition.py","lineno":8,"offset":221,"length":63},{"file":"addition.py","lineno":7,"offset":155,"length":65},{"file":"addition.py","lineno":10,"offset":0,"length":0},{"file":"addition.py","lineno":4,"offset":41,"length":37},{"file":"addition.py","lineno":5,"offset":79,"length":33},{"file":"addition.py","lineno":6,"offset":113,"length":41}]}
\ No newline at end of file
diff --git a/streamlit_demo_apps/compiled_nada_programs/equality.nada.bin b/streamlit_demo_apps/compiled_nada_programs/equality.nada.bin
new file mode 100644
index 0000000..df527b1
Binary files /dev/null and b/streamlit_demo_apps/compiled_nada_programs/equality.nada.bin differ
diff --git a/streamlit_demo_apps/compiled_nada_programs/equality.nada.json b/streamlit_demo_apps/compiled_nada_programs/equality.nada.json
new file mode 100644
index 0000000..c931762
--- /dev/null
+++ b/streamlit_demo_apps/compiled_nada_programs/equality.nada.json
@@ -0,0 +1 @@
+{"functions":[],"parties":[{"name":"Alice","source_ref_index":4},{"name":"Bob","source_ref_index":5},{"name":"Charlie","source_ref_index":6}],"inputs":[{"type":"SecretInteger","party":"Alice","name":"secret_target","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Bob","name":"secret_guess","doc":"","source_ref_index":1}],"literals":[],"outputs":[{"name":"is_same_num","operation_id":4372066096,"party":"Charlie","type":"SecretBoolean","source_ref_index":3}],"operations":{"4372066096":{"Equals":{"id":4372066096,"left":4372063792,"right":4369728672,"type":"SecretBoolean","source_ref_index":0}},"4369728672":{"InputReference":{"id":4369728672,"refers_to":"secret_guess","type":"SecretInteger","source_ref_index":1}},"4372063792":{"InputReference":{"id":4372063792,"refers_to":"secret_target","type":"SecretInteger","source_ref_index":2}}},"source_files":{"equality.py":"from nada_dsl import *\n\ndef nada_main():\n party_alice = Party(name=\"Alice\")\n party_bob = Party(name=\"Bob\")\n party_charlie = Party(name=\"Charlie\")\n secret_target = SecretInteger(Input(name=\"secret_target\", party=party_alice))\n secret_guess = SecretInteger(Input(name=\"secret_guess\", party=party_bob))\n is_same_num = secret_target == secret_guess\n return [Output(is_same_num, \"is_same_num\", party=party_charlie)]"},"source_refs":[{"file":"equality.py","lineno":9,"offset":315,"length":47},{"file":"equality.py","lineno":8,"offset":237,"length":77},{"file":"equality.py","lineno":7,"offset":155,"length":81},{"file":"equality.py","lineno":10,"offset":0,"length":0},{"file":"equality.py","lineno":4,"offset":41,"length":37},{"file":"equality.py","lineno":5,"offset":79,"length":33},{"file":"equality.py","lineno":6,"offset":113,"length":41}]}
\ No newline at end of file
diff --git a/streamlit_demo_apps/compiled_nada_programs/list_scan_linear.nada.bin b/streamlit_demo_apps/compiled_nada_programs/list_scan_linear.nada.bin
index a0e11d4..cca390d 100644
Binary files a/streamlit_demo_apps/compiled_nada_programs/list_scan_linear.nada.bin and b/streamlit_demo_apps/compiled_nada_programs/list_scan_linear.nada.bin differ
diff --git a/streamlit_demo_apps/compiled_nada_programs/list_scan_linear.nada.json b/streamlit_demo_apps/compiled_nada_programs/list_scan_linear.nada.json
index c720b4f..a2ba835 100644
--- a/streamlit_demo_apps/compiled_nada_programs/list_scan_linear.nada.json
+++ b/streamlit_demo_apps/compiled_nada_programs/list_scan_linear.nada.json
@@ -1 +1 @@
-{"functions":[],"parties":[{"name":"Party0","source_ref_index":8},{"name":"Party1","source_ref_index":8},{"name":"Party2","source_ref_index":8},{"name":"Party3","source_ref_index":8},{"name":"Party4","source_ref_index":8},{"name":"Party5","source_ref_index":8},{"name":"Party6","source_ref_index":8},{"name":"Party7","source_ref_index":8},{"name":"Party8","source_ref_index":8},{"name":"Party9","source_ref_index":8}],"inputs":[{"type":"SecretInteger","party":"Party0","name":"num_0","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party1","name":"num_1","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party2","name":"num_2","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party3","name":"num_3","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party4","name":"num_4","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party5","name":"num_5","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party6","name":"num_6","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party7","name":"num_7","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party8","name":"num_8","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party9","name":"num_9","doc":"","source_ref_index":2}],"literals":[{"name":"0420609fa1d35394f41049df03ef341f","value":"0","type":"Integer"},{"name":"10d33944d37d5b1b833be6fd73d3033c","value":"1","type":"Integer"},{"name":"3a980e5ac46f3aa9cc3f1d27d3b9f1f9","value":"100","type":"Integer"},{"name":"84e55ea59b6b9ec4c529ed7e4655425e","value":"99","type":"Integer"}],"outputs":[{"name":"is_present_1","operation_id":4391657520,"party":"Party0","type":"SecretBoolean","source_ref_index":5},{"name":"is_present_2","operation_id":4391704368,"party":"Party0","type":"SecretBoolean","source_ref_index":7}],"operations":{"4391656560":{"LiteralReference":{"id":4391656560,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391516880":{"IfElse":{"id":4391516880,"this":4391516160,"arg_0":4391516448,"arg_1":4391516688,"type":"SecretInteger","source_ref_index":1}},"4391699760":{"IfElse":{"id":4391699760,"this":4391698944,"arg_0":4391699232,"arg_1":4391699520,"type":"SecretInteger","source_ref_index":1}},"4391664336":{"IfElse":{"id":4391664336,"this":4391663520,"arg_0":4391663808,"arg_1":4391664096,"type":"SecretInteger","source_ref_index":1}},"4391513232":{"InputReference":{"id":4391513232,"refers_to":"num_9","type":"SecretInteger","source_ref_index":2}},"4391658048":{"LiteralReference":{"id":4391658048,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":4}},"4391654304":{"Addition":{"id":4391654304,"left":4391653056,"right":4391654016,"type":"SecretInteger","source_ref_index":1}},"4391514960":{"Equals":{"id":4391514960,"left":4391513424,"right":4391511216,"type":"SecretBoolean","source_ref_index":1}},"4391660448":{"IfElse":{"id":4391660448,"this":4391659632,"arg_0":4391659920,"arg_1":4391660208,"type":"SecretInteger","source_ref_index":1}},"4391704368":{"LessThan":{"id":4391704368,"left":4391704128,"right":4391703888,"type":"SecretBoolean","source_ref_index":0}},"4391517360":{"Equals":{"id":4391517360,"left":4391513424,"right":4391511792,"type":"SecretBoolean","source_ref_index":1}},"4391665392":{"LiteralReference":{"id":4391665392,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391513760":{"LiteralReference":{"id":4391513760,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":4}},"4391701296":{"Addition":{"id":4391701296,"left":4391700000,"right":4391701056,"type":"SecretInteger","source_ref_index":1}},"4391662224":{"Equals":{"id":4391662224,"left":4391657760,"right":4391511792,"type":"SecretBoolean","source_ref_index":1}},"4391659920":{"LiteralReference":{"id":4391659920,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391703888":{"Addition":{"id":4391703888,"left":4391702592,"right":4391703648,"type":"SecretInteger","source_ref_index":1}},"4391664816":{"Equals":{"id":4391664816,"left":4391657760,"right":4391512272,"type":"SecretBoolean","source_ref_index":1}},"4391665104":{"LiteralReference":{"id":4391665104,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391657760":{"LiteralReference":{"id":4391657760,"refers_to":"84e55ea59b6b9ec4c529ed7e4655425e","type":"Integer","source_ref_index":6}},"4391698944":{"Equals":{"id":4391698944,"left":4391657760,"right":4391512512,"type":"SecretBoolean","source_ref_index":1}},"4391651856":{"Addition":{"id":4391651856,"left":4391650656,"right":4391651616,"type":"SecretInteger","source_ref_index":1}},"4391652816":{"IfElse":{"id":4391652816,"this":4391652096,"arg_0":4391652384,"arg_1":4391652624,"type":"SecretInteger","source_ref_index":1}},"4391514240":{"LiteralReference":{"id":4391514240,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391702352":{"IfElse":{"id":4391702352,"this":4391701536,"arg_0":4391701824,"arg_1":4391702112,"type":"SecretInteger","source_ref_index":1}},"4391651616":{"IfElse":{"id":4391651616,"this":4391650896,"arg_0":4391651184,"arg_1":4391651424,"type":"SecretInteger","source_ref_index":1}},"4391511216":{"InputReference":{"id":4391511216,"refers_to":"num_1","type":"SecretInteger","source_ref_index":2}},"4391701056":{"IfElse":{"id":4391701056,"this":4391700240,"arg_0":4391700528,"arg_1":4391700816,"type":"SecretInteger","source_ref_index":1}},"4391517888":{"LiteralReference":{"id":4391517888,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391703648":{"IfElse":{"id":4391703648,"this":4391702832,"arg_0":4391703120,"arg_1":4391703408,"type":"SecretInteger","source_ref_index":1}},"4391654928":{"LiteralReference":{"id":4391654928,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391511792":{"InputReference":{"id":4391511792,"refers_to":"num_3","type":"SecretInteger","source_ref_index":2}},"4391655984":{"Equals":{"id":4391655984,"left":4391513424,"right":4391513232,"type":"SecretBoolean","source_ref_index":1}},"4391515248":{"LiteralReference":{"id":4391515248,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391516448":{"LiteralReference":{"id":4391516448,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391658624":{"LiteralReference":{"id":4391658624,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391650416":{"IfElse":{"id":4391650416,"this":4391649696,"arg_0":4391649984,"arg_1":4391650224,"type":"SecretInteger","source_ref_index":1}},"4391515920":{"Addition":{"id":4391515920,"left":4391514768,"right":4391515680,"type":"SecretInteger","source_ref_index":1}},"4391663808":{"LiteralReference":{"id":4391663808,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391702112":{"LiteralReference":{"id":4391702112,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391663040":{"IfElse":{"id":4391663040,"this":4391662224,"arg_0":4391662512,"arg_1":4391662800,"type":"SecretInteger","source_ref_index":1}},"4391699232":{"LiteralReference":{"id":4391699232,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391658336":{"Equals":{"id":4391658336,"left":4391657760,"right":4350059936,"type":"SecretBoolean","source_ref_index":1}},"4391512272":{"InputReference":{"id":4391512272,"refers_to":"num_5","type":"SecretInteger","source_ref_index":2}},"4391650896":{"Equals":{"id":4391650896,"left":4391513424,"right":4391512272,"type":"SecretBoolean","source_ref_index":1}},"4391649984":{"LiteralReference":{"id":4391649984,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391517648":{"LiteralReference":{"id":4391517648,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391702592":{"Addition":{"id":4391702592,"left":4391701296,"right":4391702352,"type":"SecretInteger","source_ref_index":1}},"4391701824":{"LiteralReference":{"id":4391701824,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391698704":{"Addition":{"id":4391698704,"left":4391664576,"right":4391698512,"type":"SecretInteger","source_ref_index":1}},"4391700816":{"LiteralReference":{"id":4391700816,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391703408":{"LiteralReference":{"id":4391703408,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391664096":{"LiteralReference":{"id":4391664096,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391663280":{"Addition":{"id":4391663280,"left":4391661984,"right":4391663040,"type":"SecretInteger","source_ref_index":1}},"4391650224":{"LiteralReference":{"id":4391650224,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391700240":{"Equals":{"id":4391700240,"left":4391657760,"right":4391512752,"type":"SecretBoolean","source_ref_index":1}},"4391701536":{"Equals":{"id":4391701536,"left":4391657760,"right":4391512992,"type":"SecretBoolean","source_ref_index":1}},"4391514480":{"LiteralReference":{"id":4391514480,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391649696":{"Equals":{"id":4391649696,"left":4391513424,"right":4391512032,"type":"SecretBoolean","source_ref_index":1}},"4391653824":{"LiteralReference":{"id":4391653824,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391654592":{"Equals":{"id":4391654592,"left":4391513424,"right":4391512992,"type":"SecretBoolean","source_ref_index":1}},"4391659632":{"Equals":{"id":4391659632,"left":4391657760,"right":4391511216,"type":"SecretBoolean","source_ref_index":1}},"4350059936":{"InputReference":{"id":4350059936,"refers_to":"num_0","type":"SecretInteger","source_ref_index":2}},"4391515680":{"IfElse":{"id":4391515680,"this":4391514960,"arg_0":4391515248,"arg_1":4391515488,"type":"SecretInteger","source_ref_index":1}},"4391661984":{"Addition":{"id":4391661984,"left":4391660688,"right":4391661744,"type":"SecretInteger","source_ref_index":1}},"4391657280":{"LiteralReference":{"id":4391657280,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":0}},"4391659152":{"IfElse":{"id":4391659152,"this":4391658336,"arg_0":4391658624,"arg_1":4391658912,"type":"SecretInteger","source_ref_index":1}},"4391658912":{"LiteralReference":{"id":4391658912,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391652096":{"Equals":{"id":4391652096,"left":4391513424,"right":4391512512,"type":"SecretBoolean","source_ref_index":1}},"4391511552":{"InputReference":{"id":4391511552,"refers_to":"num_2","type":"SecretInteger","source_ref_index":2}},"4391700000":{"Addition":{"id":4391700000,"left":4391698704,"right":4391699760,"type":"SecretInteger","source_ref_index":1}},"4391661744":{"IfElse":{"id":4391661744,"this":4391660928,"arg_0":4391661216,"arg_1":4391661504,"type":"SecretInteger","source_ref_index":1}},"4391653296":{"Equals":{"id":4391653296,"left":4391513424,"right":4391512752,"type":"SecretBoolean","source_ref_index":1}},"4391703120":{"LiteralReference":{"id":4391703120,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391661504":{"LiteralReference":{"id":4391661504,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391515488":{"LiteralReference":{"id":4391515488,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391654016":{"IfElse":{"id":4391654016,"this":4391653296,"arg_0":4391653584,"arg_1":4391653824,"type":"SecretInteger","source_ref_index":1}},"4391650656":{"Addition":{"id":4391650656,"left":4391649456,"right":4391650416,"type":"SecretInteger","source_ref_index":1}},"4391661216":{"LiteralReference":{"id":4391661216,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391512512":{"InputReference":{"id":4391512512,"refers_to":"num_6","type":"SecretInteger","source_ref_index":2}},"4391657040":{"Addition":{"id":4391657040,"left":4391655744,"right":4391656800,"type":"SecretInteger","source_ref_index":1}},"4391655504":{"IfElse":{"id":4391655504,"this":4391654592,"arg_0":4391654928,"arg_1":4391655264,"type":"SecretInteger","source_ref_index":1}},"4391657520":{"LessThan":{"id":4391657520,"left":4391657280,"right":4391657040,"type":"SecretBoolean","source_ref_index":0}},"4391653584":{"LiteralReference":{"id":4391653584,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391514768":{"Addition":{"id":4391514768,"left":4391513760,"right":4391514672,"type":"SecretInteger","source_ref_index":1}},"4391514672":{"IfElse":{"id":4391514672,"this":4391513904,"arg_0":4391514240,"arg_1":4391514480,"type":"SecretInteger","source_ref_index":1}},"4391660928":{"Equals":{"id":4391660928,"left":4391657760,"right":4391511552,"type":"SecretBoolean","source_ref_index":1}},"4391516160":{"Equals":{"id":4391516160,"left":4391513424,"right":4391511552,"type":"SecretBoolean","source_ref_index":1}},"4391512992":{"InputReference":{"id":4391512992,"refers_to":"num_8","type":"SecretInteger","source_ref_index":2}},"4391653056":{"Addition":{"id":4391653056,"left":4391651856,"right":4391652816,"type":"SecretInteger","source_ref_index":1}},"4391518080":{"IfElse":{"id":4391518080,"this":4391517360,"arg_0":4391517648,"arg_1":4391517888,"type":"SecretInteger","source_ref_index":1}},"4391516688":{"LiteralReference":{"id":4391516688,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391664576":{"Addition":{"id":4391664576,"left":4391663280,"right":4391664336,"type":"SecretInteger","source_ref_index":1}},"4391698512":{"IfElse":{"id":4391698512,"this":4391664816,"arg_0":4391665104,"arg_1":4391665392,"type":"SecretInteger","source_ref_index":1}},"4391649456":{"Addition":{"id":4391649456,"left":4391517120,"right":4391518080,"type":"SecretInteger","source_ref_index":1}},"4391651184":{"LiteralReference":{"id":4391651184,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391699520":{"LiteralReference":{"id":4391699520,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391512752":{"InputReference":{"id":4391512752,"refers_to":"num_7","type":"SecretInteger","source_ref_index":2}},"4391700528":{"LiteralReference":{"id":4391700528,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391660208":{"LiteralReference":{"id":4391660208,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391656800":{"IfElse":{"id":4391656800,"this":4391655984,"arg_0":4391656272,"arg_1":4391656560,"type":"SecretInteger","source_ref_index":1}},"4391660688":{"Addition":{"id":4391660688,"left":4391659392,"right":4391660448,"type":"SecretInteger","source_ref_index":1}},"4391652384":{"LiteralReference":{"id":4391652384,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391659392":{"Addition":{"id":4391659392,"left":4391658048,"right":4391659152,"type":"SecretInteger","source_ref_index":1}},"4391704128":{"LiteralReference":{"id":4391704128,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":0}},"4391702832":{"Equals":{"id":4391702832,"left":4391657760,"right":4391513232,"type":"SecretBoolean","source_ref_index":1}},"4391656272":{"LiteralReference":{"id":4391656272,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391513424":{"LiteralReference":{"id":4391513424,"refers_to":"3a980e5ac46f3aa9cc3f1d27d3b9f1f9","type":"Integer","source_ref_index":3}},"4391663520":{"Equals":{"id":4391663520,"left":4391657760,"right":4391512032,"type":"SecretBoolean","source_ref_index":1}},"4391517120":{"Addition":{"id":4391517120,"left":4391515920,"right":4391516880,"type":"SecretInteger","source_ref_index":1}},"4391513904":{"Equals":{"id":4391513904,"left":4391513424,"right":4350059936,"type":"SecretBoolean","source_ref_index":1}},"4391662512":{"LiteralReference":{"id":4391662512,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4391512032":{"InputReference":{"id":4391512032,"refers_to":"num_4","type":"SecretInteger","source_ref_index":2}},"4391655264":{"LiteralReference":{"id":4391655264,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391652624":{"LiteralReference":{"id":4391652624,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391655744":{"Addition":{"id":4391655744,"left":4391654304,"right":4391655504,"type":"SecretInteger","source_ref_index":1}},"4391651424":{"LiteralReference":{"id":4391651424,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4391662800":{"LiteralReference":{"id":4391662800,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}}},"source_files":{"funcs.py":"\"\"\"\nThis module provides common functions to work with Nada Numpy. It includes: \n- the creation and manipulation of arrays and party objects.\n- non-linear functions over arrays.\n- random operations over arrays: random generation, shuffling.\n\"\"\"\n\n# pylint:disable=too-many-lines\n\nfrom typing import Any, Callable, List, Optional, Sequence, Tuple, Union\n\nimport numpy as np\nfrom nada_dsl import (Boolean, Integer, Output, Party, PublicInteger,\n PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, UnsignedInteger)\n\nfrom nada_numpy.array import NadaArray\nfrom nada_numpy.nada_typing import AnyNadaType, NadaCleartextNumber\nfrom nada_numpy.types import Rational, SecretRational, rational\nfrom nada_numpy.utils import copy_metadata\n\n__all__ = [\n \"parties\",\n \"from_list\",\n \"ones\",\n \"ones_like\",\n \"zeros\",\n \"zeros_like\",\n \"alphas\",\n \"alphas_like\",\n \"array\",\n \"random\",\n \"output\",\n \"vstack\",\n \"hstack\",\n \"ndim\",\n \"shape\",\n \"size\",\n \"pad\",\n \"frompyfunc\",\n \"vectorize\",\n \"eye\",\n \"arange\",\n \"linspace\",\n \"split\",\n \"compress\",\n \"copy\",\n \"cumprod\",\n \"cumsum\",\n \"diagonal\",\n \"mean\",\n \"prod\",\n \"put\",\n \"ravel\",\n \"repeat\",\n \"reshape\",\n \"resize\",\n \"squeeze\",\n \"sum\",\n \"swapaxes\",\n \"take\",\n \"trace\",\n \"transpose\",\n \"sign\",\n \"abs\",\n \"exp\",\n \"polynomial\",\n \"log\",\n \"reciprocal\",\n \"inv_sqrt\",\n \"sqrt\",\n \"cossin\",\n \"sin\",\n \"cos\",\n \"tan\",\n \"tanh\",\n \"sigmoid\",\n \"gelu\",\n \"silu\",\n \"shuffle\",\n]\n\n\ndef parties(num: int, party_names: Optional[List[str]] = None) -> List[Party]:\n \"\"\"\n Create a list of Party objects with specified names.\n\n Args:\n num (int): The number of parties to create.\n party_names (List[str], optional): Party names to use. Defaults to None.\n\n Raises:\n ValueError: Raised when incorrect number of party names is supplied.\n\n Returns:\n List[Party]: A list of Party objects.\n \"\"\"\n if party_names is None:\n party_names = [f\"Party{i}\" for i in range(num)]\n\n if len(party_names) != num:\n num_supplied_parties = len(party_names)\n raise ValueError(\n f\"Incorrect number of party names. Expected {num}, received {num_supplied_parties}\"\n )\n\n return [Party(name=party_name) for party_name in party_names]\n\n\ndef __from_numpy(arr: np.ndarray, nada_type: NadaCleartextNumber) -> List:\n \"\"\"\n Recursively convert a n-dimensional NumPy array to a nested list of NadaInteger objects.\n\n Args:\n arr (np.ndarray): A NumPy array of integers.\n nada_type (type): The type of NadaInteger objects to create.\n\n Returns:\n List: A nested list of NadaInteger objects.\n \"\"\"\n if len(arr.shape) == 1:\n if isinstance(nada_type, Rational):\n return [nada_type(elem) for elem in arr] # type: ignore\n return [nada_type(int(elem)) for elem in arr] # type: ignore\n return [__from_numpy(arr[i], nada_type) for i in range(arr.shape[0])]\n\n\ndef from_list(\n lst: Union[List, np.ndarray], nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray from a list of integers.\n\n Args:\n lst (Union[List, np.ndarray]): A list of integers representing the elements of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n lst_np = np.array(lst)\n return NadaArray(np.array(__from_numpy(lst_np, nada_type)))\n\n\ndef ones(dims: Sequence[int], nada_type: NadaCleartextNumber = Integer) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with ones.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with ones.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n return from_list(np.ones(dims), nada_type)\n\n\ndef ones_like(\n a: np.ndarray | NadaArray, nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with one with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): A reference array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with ones.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n if isinstance(a, NadaArray):\n a = a.inner\n return from_list(np.ones_like(a), nada_type)\n\n\ndef zeros(dims: Sequence[int], nada_type: NadaCleartextNumber = Integer) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with zeros.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with zeros.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n return from_list(np.zeros(dims), nada_type)\n\n\ndef zeros_like(\n a: np.ndarray | NadaArray, nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with zeros with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): A reference array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with zeros.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n if isinstance(a, NadaArray):\n a = a.inner\n return from_list(np.zeros_like(a), nada_type)\n\n\ndef alphas(dims: Sequence[int], alpha: Any) -> NadaArray:\n \"\"\"\n Create a NadaArray filled with a certain constant value.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n alpha (Any): Some constant value.\n\n Returns:\n NadaArray: NadaArray filled with constant value.\n \"\"\"\n ones_array = np.ones(dims)\n return NadaArray(np.frompyfunc(lambda _: alpha, 1, 1)(ones_array))\n\n\ndef alphas_like(a: np.ndarray | NadaArray, alpha: Any) -> NadaArray:\n \"\"\"\n Create a NadaArray filled with a certain constant value\n with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): Reference array.\n alpha (Any): Some constant value.\n\n Returns:\n NadaArray: NadaArray filled with constant value.\n \"\"\"\n if isinstance(a, NadaArray):\n a = a.inner\n ones_array = np.ones_like(a)\n return NadaArray(np.frompyfunc(lambda _: alpha, 1, 1)(ones_array))\n\n\ndef array(\n dims: Sequence[int],\n party: Party,\n prefix: str,\n nada_type: Union[\n SecretInteger,\n SecretUnsignedInteger,\n PublicInteger,\n PublicUnsignedInteger,\n SecretRational,\n Rational,\n ],\n) -> NadaArray:\n \"\"\"\n Create a NadaArray with the specified dimensions and elements of the given type.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n party (Party): The party object.\n prefix (str): A prefix for naming the array elements.\n nada_type (type): The type of elements to create.\n\n Returns:\n NadaArray: The created NadaArray.\n \"\"\"\n return NadaArray.array(dims, party, prefix, nada_type)\n\n\ndef random(\n dims: Sequence[int],\n nada_type: SecretInteger | SecretUnsignedInteger | SecretRational = SecretInteger,\n) -> NadaArray:\n \"\"\"\n Create a random NadaArray with the specified dimensions.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of elements to create. Defaults to SecretInteger.\n\n Returns:\n NadaArray: A NadaArray with random values of the specified type.\n \"\"\"\n return NadaArray.random(dims, nada_type)\n\n\ndef output(\n value: Union[NadaArray, AnyNadaType], party: Party, prefix: str\n) -> List[Output]:\n \"\"\"\n Generate a list of Output objects for some provided value.\n\n Args:\n value (Union[NadaArray, AnyNadaType]): The input NadaArray.\n party (Party): The party object.\n prefix (str): The prefix for naming the Output objects.\n\n Returns:\n List[Output]: A list of Output objects.\n \"\"\"\n if isinstance(value, NadaArray):\n # pylint:disable=protected-access\n return NadaArray._output_array(value, party, prefix)\n if isinstance(value, (Rational, SecretRational)):\n value = value.value\n return [Output(value, prefix, party)]\n\n\ndef vstack(arr_list: list) -> NadaArray:\n \"\"\"\n Stack arrays in sequence vertically (row wise).\n\n Args:\n arr_list (list): A list of NadaArray objects to stack.\n\n Returns:\n NadaArray: The stacked NadaArray.\n \"\"\"\n return NadaArray(np.vstack(arr_list))\n\n\ndef hstack(arr_list: list) -> NadaArray:\n \"\"\"\n Stack arrays in sequence horizontally (column wise).\n\n Args:\n arr_list (list): A list of NadaArray objects to stack.\n\n Returns:\n NadaArray: The stacked NadaArray.\n \"\"\"\n return NadaArray(np.hstack(arr_list))\n\n\ndef ndim(arr: NadaArray) -> int:\n \"\"\"\n Returns number of array dimensions.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array dimensions.\n \"\"\"\n return arr.ndim\n\n\ndef shape(arr: NadaArray) -> Tuple[int]:\n \"\"\"\n Returns Array shape.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array shape.\n \"\"\"\n return arr.shape\n\n\ndef size(arr: NadaArray) -> int:\n \"\"\"\n Returns array size.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array size.\n \"\"\"\n return arr.size\n\n\ndef to_nada(arr: np.ndarray, nada_type: NadaCleartextNumber) -> NadaArray:\n \"\"\"\n Converts a plain-text NumPy array to the equivalent NadaArray with\n a specified compatible NadaType.\n\n Args:\n arr (np.ndarray): Input Numpy array.\n nada_type (NadaCleartextNumber): Desired clear-text NadaType.\n\n Returns:\n NadaArray: Output NadaArray.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n else:\n arr = arr.astype(int)\n return NadaArray(np.frompyfunc(nada_type, 1, 1)(arr)) # type: ignore\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.pad)\ndef pad(\n arr: NadaArray,\n pad_width: Union[Sequence[int], int],\n mode: str = \"constant\",\n **kwargs,\n) -> NadaArray:\n if mode not in {\"constant\", \"edge\", \"reflect\", \"symmetric\", \"wrap\"}:\n raise NotImplementedError(\n f\"Not currently possible to pad NadaArray in mode `{mode}`\"\n )\n\n # Override python defaults by NadaType defaults\n overriden_kwargs = {}\n if mode == \"constant\" and \"constant_values\" not in kwargs:\n if arr.is_rational:\n default = rational(0)\n elif arr.is_integer:\n default = Integer(0)\n elif arr.is_unsigned_integer:\n default = UnsignedInteger(0)\n else:\n default = Boolean(False)\n\n overriden_kwargs[\"constant_values\"] = kwargs.get(\"constant_values\", default)\n\n padded_inner = np.pad( # type: ignore\n arr.inner,\n pad_width,\n mode,\n **overriden_kwargs,\n **kwargs,\n )\n\n return NadaArray(padded_inner)\n\n\n# pylint:disable=too-few-public-methods\nclass NadaCallable:\n \"\"\"Class that wraps a vectorized NumPy function\"\"\"\n\n def __init__(self, vfunc: Callable) -> None:\n \"\"\"\n Initialization.\n\n Args:\n vfunc (Callable): Vectorized function to wrap.\n \"\"\"\n self.vfunc = vfunc\n\n def __call__(self, *args, **kwargs) -> Any:\n \"\"\"\n Routes function call to wrapped vectorized function while\n ensuring any resulting NumPy arrays are converted to NadaArrays.\n\n Returns:\n Any: Function result.\n \"\"\"\n result = self.vfunc(*args, **kwargs)\n if isinstance(result, np.ndarray):\n return NadaArray(result)\n if isinstance(result, Sequence):\n return type(result)( # type: ignore\n NadaArray(value) if isinstance(value, np.ndarray) else value\n for value in result\n )\n return result\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.frompyfunc)\ndef frompyfunc(*args, **kwargs) -> NadaCallable:\n return NadaCallable(np.frompyfunc(*args, **kwargs))\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.vectorize)\ndef vectorize(*args, **kwargs) -> NadaCallable:\n return NadaCallable(np.vectorize(*args, **kwargs))\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.eye)\ndef eye(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.eye(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.arange)\ndef arange(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.arange(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.linspace)\ndef linspace(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.linspace(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.split)\ndef split(a: NadaArray, *args, **kwargs) -> List[NadaArray]:\n return [NadaArray(arr) for arr in np.split(a.inner, *args, **kwargs)]\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.compress)\ndef compress(a: NadaArray, *args, **kwargs):\n return a.compress(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.copy)\ndef copy(a: NadaArray, *args, **kwargs):\n return a.copy(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.cumprod)\ndef cumprod(a: NadaArray, *args, **kwargs):\n return a.cumprod(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.cumsum)\ndef cumsum(a: NadaArray, *args, **kwargs):\n return a.cumsum(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.diagonal)\ndef diagonal(a: NadaArray, *args, **kwargs):\n return a.diagonal(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.diagonal)\ndef mean(a: NadaArray, *args, **kwargs):\n return a.mean(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.prod)\ndef prod(a: NadaArray, *args, **kwargs):\n return a.prod(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.put)\ndef put(a: NadaArray, *args, **kwargs):\n return a.put(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.ravel)\ndef ravel(a: NadaArray, *args, **kwargs):\n return a.ravel(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.repeat)\ndef repeat(a: NadaArray, *args, **kwargs):\n return a.repeat(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.reshape)\ndef reshape(a: NadaArray, *args, **kwargs):\n return a.reshape(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.resize)\ndef resize(a: NadaArray, *args, **kwargs):\n return a.resize(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.squeeze)\ndef squeeze(a: NadaArray, *args, **kwargs):\n return a.squeeze(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring,redefined-builtin\n@copy_metadata(np.sum)\ndef sum(a: NadaArray, *args, **kwargs):\n return a.sum(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.swapaxes)\ndef swapaxes(a: NadaArray, *args, **kwargs):\n return a.swapaxes(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.take)\ndef take(a: NadaArray, *args, **kwargs):\n return a.take(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.trace)\ndef trace(a: NadaArray, *args, **kwargs):\n return a.trace(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.transpose)\ndef transpose(a: NadaArray, *args, **kwargs):\n return a.transpose(*args, **kwargs)\n\n\n# Non-linear functions\n\n\ndef sign(arr: NadaArray) -> \"NadaArray\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n return arr.sign()\n\n\ndef abs(arr: NadaArray) -> \"NadaArray\":\n \"\"\"Computes the absolute value\"\"\"\n return arr.abs()\n\n\ndef exp(arr: NadaArray, iterations: int = 8) -> \"NadaArray\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The exponential function is computed by choosing n = 2 ** d, where d is set to `iterations`.\n The calculation is performed by computing (1 + x / n) once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n NadaArray: The approximated value of the exponential function.\n \"\"\"\n return arr.exp(iterations=iterations)\n\n\ndef polynomial(arr: NadaArray, coefficients: list) -> \"NadaArray\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n NadaArray: The result of the polynomial function applied to the input x.\n \"\"\"\n return arr.polynomial(coefficients=coefficients)\n\n\ndef log(\n arr: NadaArray,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n) -> \"NadaArray\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation of exp.\n Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of Householder approximation).\n Defaults to 8.\n\n Returns:\n NadaArray: The approximate value of the natural logarithm.\n \"\"\"\n return arr.log(\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n\n\ndef reciprocal( # pylint: disable=too-many-arguments\n arr: NadaArray,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n # pylint:disable=duplicate-code\n return arr.reciprocal(\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n\n\ndef inv_sqrt(\n arr: NadaArray,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n return arr.inv_sqrt(initial=initial, iterations=iterations, method=method)\n\n\ndef sqrt(\n arr: NadaArray,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the inverse\n square root Newton-Raphson iterations. By default, this will be set to allow\n convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n return arr.sqrt(initial=initial, iterations=iterations, method=method)\n\n\n# Trigonometry\n\n\ndef cossin(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit through the\n formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[NadaArray, NadaArray]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n return arr.cossin(iterations=iterations)\n\n\ndef cos(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the cosine.\n \"\"\"\n return arr.cos(iterations=iterations)\n\n\ndef sin(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the sine.\n \"\"\"\n return arr.sin(iterations=iterations)\n\n\ndef tan(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the tan.\n \"\"\"\n return arr.tan(iterations=iterations)\n\n\n# Activation functions\n\n\ndef tanh(\n arr: NadaArray,\n chebyshev_terms: int = 32,\n method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n return arr.tanh(chebyshev_terms=chebyshev_terms, method=method)\n\n\ndef sigmoid(\n arr: NadaArray,\n chebyshev_terms: int = 32,\n method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses\n the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n return arr.sigmoid(chebyshev_terms=chebyshev_terms, method=method)\n\n\ndef gelu(\n arr: NadaArray,\n method: str = \"tanh\",\n tanh_method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n return arr.gelu(method=method, tanh_method=tanh_method)\n\n\ndef silu(\n arr: NadaArray,\n method_sigmoid: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses the\n identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n return arr.silu(method_sigmoid=method_sigmoid)\n\n\ndef shuffle(arr: NadaArray) -> NadaArray:\n \"\"\"\n Shuffles a 1D array using the Benes network.\n\n This function rearranges the elements of a 1-dimensional array in a deterministic but seemingly\n random order based on the Benes network, a network used in certain types of sorting and\n switching circuits. The Benes network requires the input array's length to be a power of two\n (e.g., 2, 4, 8, 16, ...).\n\n Note: The resulting shuffled arrays contain the same elements as the input arrays.\n\n Args:\n NadaArray: The input array to be shuffled. This must be a 1-dimensional NumPy array.\n The length of the array must be a power of two.\n\n Returns:\n NadaArray: The shuffled version of the input array. The output is a new array where\n the elements have been rearranged according to the Benes network.\n\n Raises:\n ValueError: If the length of the input array is not a power of two.\n\n Example:\n ```python\n import nada_numpy as na\n\n # Example arrays with different data types\n parties = na.parties(2)\n a = na.array([8], parties[0], \"A\", na.Rational)\n b = na.array([8], parties[0], \"B\", na.SecretRational)\n c = na.array([8], parties[0], \"C\", PublicInteger)\n d = na.array([8], parties[0], \"D\", SecretInteger)\n\n # Shuffling the arrays\n shuffled_a = shuffle(a)\n shuffled_b = shuffle(b)\n shuffled_c = shuffle(c)\n ```\n\n Frequency analysis:\n\n This script performs a frequency analysis of a shuffle function implemented using a Benes\n network. It includes a function for shuffle, a test function for evaluating randomness,\n and an example of running the test. Below is an overview of the code and its output.\n\n 1. **Shuffle Function**:\n\n The `shuffle` function shuffles a 1D array using a Benes network approach.\n The Benes network is defined by the function `_benes_network(n)`, which should provide the\n network stages required for the shuffle.\n\n ```python\n import numpy as np\n import random\n\n def rand_bool():\n # Simulates a random boolean value\n return random.choice([0, 1]) == 0\n\n def swap_gate(a, b):\n # Conditionally swaps two values based on a random boolean\n rbool = rand_bool()\n return (b, a) if rbool else (a, b)\n\n def shuffle(array):\n # Applies Benes network shuffle to a 1D array\n if array.ndim != 1:\n raise ValueError(\"Input array must be a 1D array.\")\n\n n = array.size\n bnet = benes_network(n)\n swap_array = np.ones(n)\n\n first_numbers = np.arange(0, n, 2)\n second_numbers = np.arange(1, n, 2)\n pairs = np.column_stack((first_numbers, second_numbers))\n\n for stage in bnet:\n for ((i0, i1), (a, b)) in zip(pairs, stage):\n swap_array[i0], swap_array[i1] = swap_gate(array[a], array[b])\n array = swap_array.copy()\n\n return array\n ```\n\n 2. **Randomness Test Function:**:\n The test_shuffle_randomness function evaluates the shuffle function by performing\n multiple shuffles and counting the occurrences of each element at each position.\n\n ```python\n def test_shuffle_randomness(vector_size, num_shuffles):\n # Initializes vector and count matrix\n vector = np.arange(vector_size)\n counts = np.zeros((vector_size, vector_size), dtype=int)\n\n # Performs shuffling and counts occurrences\n for _ in range(num_shuffles):\n shuffled_vector = shuffle(vector)\n for position, element in enumerate(shuffled_vector):\n counts[int(element), position] += 1\n\n # Computes average counts and deviation\n average_counts = num_shuffles / vector_size\n deviation = np.abs(counts - average_counts)\n\n return counts, average_counts, deviation\n ```\n\n\n Running the `test_shuffle_randomness` function with a vector size of 8 and 100,000 shuffles\n provides the following results:\n\n ```python\n vector_size = 8 # Size of the vector\n num_shuffles = 100000 # Number of shuffles to perform\n\n counts, average_counts, deviation = test_shuffle_randomness(vector_size,\n num_shuffles)\n\n print(\"Counts of numbers appearances at each position:\")\n for i in range(vector_size):\n print(f\"Number {i}: {counts[i]}\")\n print(\"Expected count of number per slot:\", average_counts)\n print(\"\\nDeviation from the expected average:\")\n for i in range(vector_size):\n print(f\"Number {i}: {deviation[i]}\")\n ```\n ```bash\n >>> Counts of numbers appearances at each position:\n >>> Number 0: [12477 12409 12611 12549 12361 12548 12591 12454]\n >>> Number 1: [12506 12669 12562 12414 12311 12408 12377 12753]\n >>> Number 2: [12595 12327 12461 12607 12492 12721 12419 12378]\n >>> Number 3: [12417 12498 12586 12433 12627 12231 12638 12570]\n >>> Number 4: [12370 12544 12404 12337 12497 12743 12588 12517]\n >>> Number 5: [12559 12420 12416 12791 12508 12489 12360 12457]\n >>> Number 6: [12669 12459 12396 12394 12757 12511 12423 12391]\n >>> Number 7: [12407 12674 12564 12475 12447 12349 12604 12480]\n >>> Expected count of number per slot: 12500.0\n >>>\n >>> Deviation from the expected average:\n >>> Number 0: [ 23. 91. 111. 49. 139. 48. 91. 46.]\n >>> Number 1: [ 6. 169. 62. 86. 189. 92. 123. 253.]\n >>> Number 2: [ 95. 173. 39. 107. 8. 221. 81. 122.]\n >>> Number 3: [ 83. 2. 86. 67. 127. 269. 138. 70.]\n >>> Number 4: [130. 44. 96. 163. 3. 243. 88. 17.]\n >>> Number 5: [ 59. 80. 84. 291. 8. 11. 140. 43.]\n >>> Number 6: [169. 41. 104. 106. 257. 11. 77. 109.]\n >>> Number 7: [ 93. 174. 64. 25. 53. 151. 104. 20.]\n ```\n \"\"\"\n return arr.shuffle()\n","list_scan_linear.py":"from nada_dsl import *\nimport nada_numpy as na\n\ndef is_number_present_in_list(array: List[SecretInteger], value: Integer) -> SecretBoolean:\n result = Integer(0)\n for element in array:\n # If the element is equal to the value, add 1 to the result.\n result += (value == element).if_else(Integer(1), Integer(0))\n return (result > Integer(0))\n\ndef nada_main():\n num_parties = 10\n parties = na.parties(num_parties)\n\n secrets_list = []\n for i in range(num_parties):\n secrets_list.append(\n SecretInteger(Input(name=\"num_\" + str(i), party=parties[i]))\n )\n\n # Check if 100 is a secret value in the list\n is_present_1 = is_number_present_in_list(secrets_list, Integer(100))\n\n # Check if 99 is a secret value in the list\n is_present_2 = is_number_present_in_list(secrets_list, Integer(99))\n\n return [\n Output(is_present_1, \"is_present_1\", party=parties[0]),\n Output(is_present_2, \"is_present_2\", party=parties[0]),\n ]\n","types.py":"\"\"\"Additional special data types\"\"\"\n\n# pylint:disable=too-many-lines\n\nimport functools\nimport warnings\nfrom typing import List, Optional, Tuple, Union\n\nimport nada_dsl as dsl\nimport numpy as np\nfrom nada_dsl import (Input, Integer, Party, PublicInteger,\n PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, UnsignedInteger)\n\n_NadaRational = Union[\"Rational\", \"SecretRational\"]\n\n_NadaType = Union[\n Integer,\n PublicInteger,\n PublicUnsignedInteger,\n SecretInteger,\n SecretUnsignedInteger,\n UnsignedInteger,\n]\n\n\nclass SecretBoolean(dsl.SecretBoolean):\n \"\"\"SecretBoolean rational wrapper\"\"\"\n\n def __init__(self, value: dsl.SecretBoolean) -> None:\n \"\"\"\n Initialization.\n\n Args:\n value (dsl.SecretBoolean): SecretBoolean value.\n \"\"\"\n super().__init__(value.inner)\n\n def if_else(\n self,\n true: Union[_NadaType, \"SecretRational\", \"Rational\"],\n false: Union[_NadaType, \"SecretRational\", \"Rational\"],\n ) -> Union[SecretInteger, SecretUnsignedInteger, \"SecretRational\"]:\n \"\"\"\n If-else logic. If the boolean is True, true is returned. If not, false is returned.\n\n Args:\n true (Union[_NadaType, SecretRational, Rational]): First argument.\n false (Union[_NadaType, SecretRational, Rational]): Second argument.\n\n Raises:\n ValueError: Raised when incompatibly-scaled values are passed.\n TypeError: Raised when invalid operation is called.\n\n Returns:\n Union[SecretInteger, SecretUnsignedInteger, \"SecretRational\"]: Return value.\n \"\"\"\n first_arg = true\n second_arg = false\n if isinstance(true, (SecretRational, Rational)) and isinstance(\n false, (SecretRational, Rational)\n ):\n # Both are SecretRational or Rational objects\n if true.log_scale != false.log_scale:\n raise ValueError(\"Cannot output values with different scales.\")\n first_arg = true.value\n second_arg = false.value\n elif isinstance(true, (Rational, SecretRational)) or isinstance(\n false, (Rational, SecretRational)\n ):\n # Both are SecretRational or Rational objects\n raise TypeError(f\"Invalid operation: {self}.IfElse({true}, {false})\")\n\n result = super().if_else(first_arg, second_arg)\n\n if isinstance(true, (SecretRational, Rational)):\n # If we have a SecretBoolean, the return type will be SecretInteger,\n # thus promoted to SecretRational\n return SecretRational(result, true.log_scale, is_scaled=True)\n return result\n\n\nclass PublicBoolean(dsl.PublicBoolean):\n \"\"\"PublicBoolean rational wrapper\"\"\"\n\n def __init__(self, value: dsl.PublicBoolean) -> None:\n \"\"\"\n Initialization.\n\n Args:\n value (dsl.PublicBoolean): PublicBoolean value.\n \"\"\"\n super().__init__(value.inner)\n\n def if_else(\n self,\n true: Union[_NadaType, \"SecretRational\", \"Rational\"],\n false: Union[_NadaType, \"SecretRational\", \"Rational\"],\n ) -> Union[\n PublicInteger,\n PublicUnsignedInteger,\n SecretInteger,\n SecretUnsignedInteger,\n \"Rational\",\n \"SecretRational\",\n ]:\n \"\"\"\n If-else logic. If the boolean is True, true is returned. If not, false is returned.\n\n Args:\n true (Union[_NadaType, SecretRational, Rational]): First argument.\n false (Union[_NadaType, SecretRational, Rational]): Second argument.\n\n Raises:\n ValueError: Raised when incompatibly-scaled values are passed.\n TypeError: Raised when invalid operation is called.\n\n Returns:\n Union[PublicInteger, PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, \"Rational\", \"SecretRational\"]: Return value.\n \"\"\"\n first_arg = true\n second_arg = false\n if isinstance(true, (SecretRational, Rational)) and isinstance(\n false, (SecretRational, Rational)\n ):\n # Both are SecretRational or Rational objects\n if true.log_scale != false.log_scale:\n raise ValueError(\"Cannot output values with different scales.\")\n first_arg = true.value\n second_arg = false.value\n elif isinstance(true, (Rational, SecretRational)) or isinstance(\n false, (Rational, SecretRational)\n ):\n # Both are SecretRational or Rational objects but of different type\n raise TypeError(f\"Invalid operation: {self}.IfElse({true}, {false})\")\n\n result = super().if_else(first_arg, second_arg)\n\n if isinstance(true, SecretRational) or isinstance(false, SecretRational):\n return SecretRational(result, true.log_scale, is_scaled=True)\n if isinstance(true, Rational) and isinstance(false, Rational):\n return Rational(result, true.log_scale, is_scaled=True)\n return result\n\n\nclass Rational: # pylint:disable=too-many-public-methods\n \"\"\"Wrapper class to store scaled Integer values representing a fixed-point number.\"\"\"\n\n def __init__(\n self,\n value: Union[Integer, PublicInteger],\n log_scale: Optional[int] = None,\n is_scaled: bool = True,\n ) -> None:\n \"\"\"\n Initializes wrapper around Integer object.\n\n Args:\n value (Union[Integer, PublicInteger]): The value to be representedas a Rational.\n log_scale (int, optional): Quantization scaling factor.\n Defaults to RationalConfig.log_scale.\n is_scaled (bool, optional): Flag that represents whether the value is already scaled.\n Defaults to True.\n\n Raises:\n TypeError: If value is of an incompatible type.\n \"\"\"\n if not isinstance(value, (Integer, PublicInteger)):\n raise TypeError(f\"Cannot instantiate Rational from type `{type(value)}`.\")\n\n if log_scale is None:\n log_scale = get_log_scale()\n self._log_scale = log_scale\n\n if is_scaled is False:\n value = value * Integer(\n 1 << log_scale\n ) # TODO: replace with shift when supported\n self._value = value\n\n @property\n def log_scale(self) -> int:\n \"\"\"\n Getter for the logarithmic scale value.\n\n Returns:\n int: Logarithmic scale value.\n \"\"\"\n return self._log_scale\n\n @property\n def value(self) -> Union[Integer, PublicInteger]:\n \"\"\"\n Getter for the underlying Integer value.\n\n Returns:\n Union[Integer, PublicInteger]: The Integer value.\n \"\"\"\n return self._value\n\n def add(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot add values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n other.value + self.value, self.log_scale, is_scaled=True\n )\n return Rational(self.value + other.value, self.log_scale, is_scaled=True)\n\n def __add__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def __iadd__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def sub(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to subtract.\n\n Returns:\n Union[Rational, SecretRational]: Result of the subtraction.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot substract values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value - other.value, self.log_scale, is_scaled=True\n )\n return Rational(self.value - other.value, self.log_scale, is_scaled=True)\n\n def __sub__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def __isub__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def mul_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers.\n\n WARNING: This function does not rescale by default. Use `mul` to multiply and rescale.\n\n Args:\n other (_NadaRational): Other rational number to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the multiplication.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot multiply values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value * other.value,\n self.log_scale + other.log_scale,\n is_scaled=True,\n )\n return Rational(\n self.value * other.value,\n self.log_scale + other.log_scale,\n is_scaled=True,\n )\n\n def mul(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers and rescale the result.\n\n Args:\n other (_NadaRational): Other rational number to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the multiplication.\n \"\"\"\n c = self.mul_no_rescale(other, ignore_scale=ignore_scale)\n d = c.rescale_down()\n return d\n\n def __mul__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def __imul__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def divide_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to divide by.\n\n Returns:\n Union[Rational, SecretRational]: Result of the division.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale + get_log_scale():\n raise ValueError(\n f\"Cannot divide values where scale is: {self.log_scale} / {other.log_scale}.\"\n f\"Required scale: {self.log_scale} / {other.log_scale + get_log_scale()}\"\n )\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value / other.value,\n self.log_scale - other.log_scale,\n is_scaled=True,\n )\n return Rational(\n self.value / other.value,\n self.log_scale - other.log_scale,\n is_scaled=True,\n )\n\n def divide(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers and rescale the result.\n\n Args:\n other (_NadaRational): Other rational number to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the division.\n \"\"\"\n a = self.rescale_up()\n c = a.divide_no_rescale(other, ignore_scale)\n return c\n\n def __truediv__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __itruediv__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __pow__(self, other: int) -> \"Rational\":\n \"\"\"\n Raise a rational number to an integer power using binary exponentiation.\n\n Args:\n other (int): The exponent.\n\n Returns:\n Rational: Result of the power operation.\n\n Raises:\n TypeError: If the exponent is not an integer.\n \"\"\"\n if not isinstance(other, int):\n raise TypeError(f\"Cannot raise Rational to a power of type `{type(other)}`\")\n\n result = Rational(Integer(1), self.log_scale, is_scaled=False)\n\n if other == 0:\n return result # Any number to the power of 0 is 1\n\n base = self\n\n exponent = abs(other)\n while exponent > 0:\n if exponent % 2 == 1:\n result = result * base # type: ignore\n base *= base # type: ignore\n exponent //= 2\n\n if other < 0:\n return rational(1) / Rational( # type: ignore\n result.value, result.log_scale, is_scaled=True\n )\n\n return result\n\n def __neg__(self) -> \"Rational\":\n \"\"\"\n Negate the Rational value.\n\n Returns:\n Rational: Negated Rational value.\n \"\"\"\n return Rational(self.value * Integer(-1), self.log_scale, is_scaled=True)\n\n def __lshift__(self, other: UnsignedInteger) -> \"Rational\":\n \"\"\"\n Left shift the Rational value.\n\n Args:\n other (UnsignedInteger): The value to left shift by.\n\n Returns:\n Rational: Left shifted Rational value.\n \"\"\"\n return Rational(self.value << other, self.log_scale)\n\n def __rshift__(self, other: UnsignedInteger) -> \"Rational\":\n \"\"\"\n Right shift the Rational value.\n\n Args:\n other (UnsignedInteger): The value to right shift by.\n\n Returns:\n Rational: Right shifted Rational value.\n \"\"\"\n return Rational(self.value >> other, self.log_scale)\n\n def __lt__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is less than another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value < other.value)\n return PublicBoolean(self.value < other.value)\n\n def __gt__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is greater than another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value > other.value)\n return PublicBoolean(self.value > other.value)\n\n def __le__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is less than or equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value <= other.value)\n return PublicBoolean(self.value <= other.value)\n\n def __ge__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is greater than or equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value >= other.value)\n return PublicBoolean(self.value >= other.value)\n\n def __eq__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]: # type: ignore\n \"\"\"\n Check if this Rational is equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value == other.value)\n return PublicBoolean(self.value == other.value)\n\n def __ne__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]: # type: ignore\n \"\"\"\n Check if this Rational is not equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value != other.value)\n return PublicBoolean(self.value != other.value)\n\n def rescale_up(self, log_scale: Optional[int] = None) -> \"Rational\":\n \"\"\"\n Rescale the value in the upward direction by a scaling factor.\n\n This is equivalent to multiplying the value by `2**(log_scale)`.\n\n Args:\n log_scale (int, optional): Scaling factor to rescale the value.\n Defaults to RationalConfig.log_scale.\n\n Returns:\n Rational: Rescaled Rational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return Rational(\n self._value\n * Integer(1 << log_scale), # TODO: replace with shift when supported\n self.log_scale + log_scale,\n is_scaled=True,\n )\n\n def rescale_down(self, log_scale: Optional[int] = None) -> \"Rational\":\n \"\"\"\n Rescale the value in the downward direction by a scaling factor.\n\n This is equivalent to dividing the value by `2**(log_scale)`.\n\n Args:\n log_scale (int, optional): Scaling factor to rescale the value.\n Defaults to RationalConfig.log_scale.\n\n Returns:\n Rational: Rescaled Rational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return Rational(\n self._value\n / Integer(1 << log_scale), # TODO: replace with shift when supported\n self.log_scale - log_scale,\n is_scaled=True,\n )\n\n # Non-linear functions\n\n def sign(self) -> \"Rational\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n result = sign(self)\n if not isinstance(result, Rational):\n raise TypeError(\"sign input should be of type Rational.\")\n return result\n\n def abs(self) -> \"Rational\":\n \"\"\"Computes the absolute value\"\"\"\n\n result = fxp_abs(self)\n if not isinstance(result, Rational):\n raise TypeError(\"abs input should be of type Rational.\")\n return result\n\n def exp(self, iterations: int = 8) -> \"Rational\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The function is computed by choosing n = 2 ** d, where d is set to `iterations`.\n The calculation is performed by computing (1 + x / n) once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n Rational: The approximated value of the exponential function.\n \"\"\"\n\n result = exp(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"exp input should be of type Rational.\")\n return result\n\n def polynomial(self, coefficients: list) -> \"Rational\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the\n highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n Rational: The result of the polynomial function applied to the input x.\n \"\"\"\n\n result = polynomial(self, coefficients=coefficients)\n if not isinstance(result, Rational):\n raise TypeError(\"polynomial input should be of type Rational.\")\n return result\n\n def log(\n self,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n ) -> \"Rational\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation\n of exp. Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of Householder\n approximation). Defaults to 8.\n\n Returns:\n Rational: The approximate value of the natural logarithm.\n \"\"\"\n\n result = log(\n self,\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n if not isinstance(result, Rational):\n raise TypeError(\"log input should be of type Rational.\")\n return result\n\n def reciprocal( # pylint: disable=too-many-arguments\n self,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n\n result = reciprocal(\n self,\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n if not isinstance(result, Rational):\n raise TypeError(\"reciprocal input should be of type Rational.\")\n return result\n\n def inv_sqrt(\n self,\n initial: Optional[\"Rational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[Rational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = inv_sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"inv_sqrt input should be of type Rational.\")\n return result\n\n def sqrt(\n self,\n initial: Optional[\"Rational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[Rational, None], optional): sets the initial value for the inverse\n square root Newton-Raphson iterations. By default, this will be set to allow\n convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"sqrt input should be of type Rational.\")\n return result\n\n # Trigonometry\n\n def cossin(self, iterations: int = 10) -> Tuple[\"Rational\", \"Rational\"]:\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit through\n the formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[Rational, Rational]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n result = cossin(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"cossin input should be of type Rational.\")\n return result\n\n def cos(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the cosine.\n \"\"\"\n\n result = cos(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"cos input should be of type Rational.\")\n return result\n\n def sin(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the sine.\n \"\"\"\n\n result = sin(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"sin input should be of type Rational.\")\n return result\n\n def tan(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the tan.\n \"\"\"\n\n result = tan(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"tan input should be of type Rational.\")\n return result\n\n # Activation functions\n\n def tanh(self, chebyshev_terms: int = 32, method: str = \"reciprocal\") -> \"Rational\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n Rational: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = tanh(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"tanh input should be of type Rational.\")\n return result\n\n def sigmoid(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"Rational\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses\n the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n Rational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = sigmoid(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"sigmoid input should be of type Rational.\")\n return result\n\n def gelu(self, method: str = \"tanh\", tanh_method: str = \"reciprocal\") -> \"Rational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n Rational: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = gelu(self, method=method, tanh_method=tanh_method)\n if not isinstance(result, Rational):\n raise TypeError(\"gelu input should be of type Rational.\")\n return result\n\n def silu(\n self,\n method_sigmoid: str = \"reciprocal\",\n ) -> \"Rational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n Rational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n\n if method_sigmoid is None:\n method_sigmoid = \"reciprocal\"\n\n result = silu(self, method_sigmoid=method_sigmoid)\n if not isinstance(result, Rational):\n raise TypeError(\"silu input should be of type Rational.\")\n return result\n\n\nclass SecretRational: # pylint:disable=too-many-public-methods\n \"\"\"Wrapper class to store scaled SecretInteger values representing a fixed-point number.\"\"\"\n\n def __init__(\n self,\n value: SecretInteger,\n log_scale: Optional[int] = None,\n is_scaled: bool = True,\n ) -> None:\n \"\"\"\n Initializes wrapper around SecretInteger object.\n The object should come scaled up by default otherwise precision may be lost.\n\n Args:\n value (SecretInteger): SecretInteger input value.\n log_scale (int, optional): Quantization scaling factor.\n Defaults to RationalConfig.log_scale.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Raises:\n TypeError: If value is of an incompatible type.\n \"\"\"\n if not isinstance(value, SecretInteger):\n raise TypeError(\n f\"Cannot instantiate SecretRational from type `{type(value)}`.\"\n )\n\n if log_scale is None:\n log_scale = get_log_scale()\n self._log_scale = log_scale\n\n if is_scaled is False:\n value = value << UnsignedInteger(log_scale)\n self._value = value\n\n @property\n def log_scale(self) -> int:\n \"\"\"\n Getter for the logarithmic scale value.\n\n Returns:\n int: Logarithmic scale value.\n \"\"\"\n return self._log_scale\n\n @property\n def value(self) -> SecretInteger:\n \"\"\"\n Getter for the underlying SecretInteger value.\n\n Returns:\n SecretInteger: The SecretInteger value.\n \"\"\"\n return self._value\n\n def add(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Add two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to add.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the addition.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot add values with different scales.\")\n\n return SecretRational(self.value + other.value, self.log_scale)\n\n def __add__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Add two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def __iadd__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Add two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def sub(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Subtract two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to subtract.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the subtraction.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot substract values with different scales.\")\n\n return SecretRational(self.value - other.value, self.log_scale)\n\n def __sub__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Subtract two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def __isub__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Subtract two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def mul_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Multiply two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the multiplication.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot multiply values with different scales.\")\n\n return SecretRational(\n self.value * other.value, self.log_scale + other.log_scale\n )\n\n def mul(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Multiply two SecretRational numbers and rescale the result.\n\n Args:\n other (_NadaRational): The other SecretRational to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n SecretRational: Result of the multiplication, rescaled.\n \"\"\"\n c = self.mul_no_rescale(other, ignore_scale=ignore_scale)\n if c is NotImplemented:\n # Note that, because this function would be executed under a NadaArray,\n # the NotImplemented value will be handled by the caller (in principle NadaArray)\n # The caller will then call the mul function of the NadaArray\n # The broadcasting will execute element-wise multiplication,\n # so rescale_down will be taken care by that function\n return c\n d = c.rescale_down()\n return d\n\n def __mul__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Multiply two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def __imul__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Multiply two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def divide_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Divide two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the division.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale + get_log_scale():\n raise ValueError(\n f\"Cannot divide values where scale is: {self.log_scale} / {other.log_scale}.\"\n f\"Required scale: {self.log_scale} / {other.log_scale + get_log_scale()}\"\n )\n\n return SecretRational(\n self.value / other.value, self.log_scale - other.log_scale\n )\n\n def divide(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Divide two SecretRational numbers and rescale the result.\n\n Args:\n other (_NadaRational): The other SecretRational to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n SecretRational: Result of the division, rescaled.\n \"\"\"\n # Note: If the other value is a NadaArray, the divide-no-rescale function will\n # return NotImplemented\n # This will cause that the divide function will return NotImplemented as well\n # The NotImplemented value will be handled by the caller (in principle NadaArray)\n # The caller will then call the divide function of the NadaArray\n # The rescale up, because there is no follow up, will not be taken into consideration.\n a = self.rescale_up()\n c = a.divide_no_rescale(other, ignore_scale=ignore_scale)\n return c\n\n def __truediv__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Divide two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __itruediv__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Divide two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __pow__(self, other: int) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Raise a SecretRational to an integer power using binary exponentiation.\n\n Args:\n other (int): The exponent.\n\n Raises:\n TypeError: If the exponent is not an integer.\n\n Returns:\n Union[Rational, SecretRational]: Result of the power operation.\n \"\"\"\n if not isinstance(other, int):\n raise TypeError(\n f\"Cannot raise SecretRational to a power of type `{type(other)}`\"\n )\n\n result = Rational(Integer(1), self.log_scale, is_scaled=False)\n\n if other == 0:\n return result # Any number to the power of 0 is 1\n\n base = self\n\n exponent = abs(other)\n while exponent > 0:\n if exponent % 2 == 1:\n result = result * base # type: ignore\n base *= base # type: ignore\n exponent //= 2\n\n if other < 0:\n return rational(1) / SecretRational( # type: ignore\n result.value, result.log_scale, is_scaled=True\n )\n\n return result\n\n def __neg__(self) -> \"SecretRational\":\n \"\"\"\n Negate the SecretRational value.\n\n Returns:\n SecretRational: Negated SecretRational value.\n \"\"\"\n return SecretRational(self.value * Integer(-1), self.log_scale)\n\n def __lshift__(self, other: UnsignedInteger) -> \"SecretRational\":\n \"\"\"\n Left shift the SecretRational value.\n\n Args:\n other (UnsignedInteger): The value to left shift by.\n\n Returns:\n SecretRational: Left shifted SecretRational value.\n \"\"\"\n return SecretRational(self.value << other, self.log_scale)\n\n def __rshift__(self, other: UnsignedInteger) -> \"SecretRational\":\n \"\"\"\n Right shift the SecretRational value.\n\n Args:\n other (UnsignedInteger): The value to right shift by.\n\n Returns:\n SecretRational: Right shifted SecretRational value.\n \"\"\"\n return SecretRational(self.value >> other, self.log_scale)\n\n def __lt__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is less than another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value < other.value)\n\n def __gt__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is greater than another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value > other.value)\n\n def __le__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is less than or equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value <= other.value)\n\n def __ge__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is greater than or equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value >= other.value)\n\n def __eq__(self, other: _NadaRational) -> SecretBoolean: # type: ignore\n \"\"\"\n Check if this SecretRational is equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value == other.value)\n\n def __ne__(self, other: _NadaRational) -> SecretBoolean: # type: ignore\n \"\"\"\n Check if this SecretRational is not equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value != other.value)\n\n def public_equals(self, other: _NadaRational) -> PublicBoolean:\n \"\"\"\n Check if this SecretRational is equal to another and reveal the result.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n PublicBoolean: Result of the comparison, revealed.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return self.value.public_equals(other.value)\n\n def reveal(self) -> Rational:\n \"\"\"\n Reveal the SecretRational value.\n\n Returns:\n Rational: Revealed SecretRational value.\n \"\"\"\n return Rational(self.value.reveal(), self.log_scale)\n\n def trunc_pr(self, arg_0: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Truncate the SecretRational value.\n\n Args:\n arg_0 (_NadaRational): The value to truncate by.\n\n Returns:\n SecretRational: Truncated SecretRational value.\n \"\"\"\n return SecretRational(self.value.trunc_pr(arg_0), self.log_scale)\n\n def rescale_up(self, log_scale: Optional[int] = None) -> \"SecretRational\":\n \"\"\"\n Rescale the SecretRational value upwards by a scaling factor.\n\n Args:\n log_scale (int, optional): The scaling factor. Defaults to RationalConfig.log_scale.\n\n Returns:\n SecretRational: Rescaled SecretRational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return SecretRational(\n self._value << UnsignedInteger(log_scale),\n self.log_scale + log_scale,\n is_scaled=True,\n )\n\n def rescale_down(self, log_scale: Optional[int] = None) -> \"SecretRational\":\n \"\"\"\n Rescale the SecretRational value downwards by a scaling factor.\n\n Args:\n log_scale (int, optional): The scaling factor. Defaults to RationalConfig.\n\n Returns:\n SecretRational: Rescaled SecretRational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return SecretRational(\n self._value >> UnsignedInteger(log_scale),\n self.log_scale - log_scale,\n is_scaled=True,\n )\n\n # Non-linear functions\n\n def sign(self) -> \"SecretRational\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n result = sign(self)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sign input should be of type SecretRational.\")\n return result\n\n def abs(self) -> \"SecretRational\":\n \"\"\"Computes the absolute value\"\"\"\n\n result = fxp_abs(self)\n if not isinstance(result, SecretRational):\n raise TypeError(\"abs input should be of type SecretRational.\")\n return result\n\n def exp(self, iterations: int = 8) -> \"SecretRational\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The exponential function is computed by choosing n = 2 ** d, where d is\n set to `iterations`. The calculation is performed by computing (1 + x / n)\n once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n SecretRational: The approximated value of the exponential function.\n \"\"\"\n\n result = exp(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"exp input should be of type SecretRational.\")\n return result\n\n def polynomial(self, coefficients: list) -> \"SecretRational\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the\n highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n SecretRational: The result of the polynomial function applied to the input x.\n \"\"\"\n\n result = polynomial(self, coefficients=coefficients)\n if not isinstance(result, SecretRational):\n raise TypeError(\"polynomial input should be of type SecretRational.\")\n return result\n\n def log(\n self,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n ) -> \"SecretRational\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation of\n exp. Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of\n Householder approximation). Defaults to 8.\n\n Returns:\n SecretRational: The approximate value of the natural logarithm.\n \"\"\"\n\n result = log(\n self,\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n if not isinstance(result, SecretRational):\n raise TypeError(\"log input should be of type SecretRational.\")\n return result\n\n def reciprocal( # pylint: disable=too-many-arguments\n self,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n\n result = reciprocal(\n self,\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n if not isinstance(result, SecretRational):\n raise TypeError(\"reciprocal input should be of type SecretRational.\")\n return result\n\n def inv_sqrt(\n self,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = inv_sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"inv_sqrt input should be of type SecretRational.\")\n return result\n\n def sqrt(\n self,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n inverse square root Newton-Raphson iterations. By default, this will be set\n to allow convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sqrt input should be of type SecretRational.\")\n return result\n\n # Trigonometry\n\n def cossin(self, iterations: int = 10) -> Tuple[\"SecretRational\", \"SecretRational\"]:\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit\n through the formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[SecretRational, SecretRational]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n result = cossin(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"cossin input should be of type SecretRational.\")\n return result\n\n def cos(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the cosine.\n \"\"\"\n\n result = cos(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"cos input should be of type SecretRational.\")\n return result\n\n def sin(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the sine.\n \"\"\"\n\n result = sin(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sin input should be of type SecretRational.\")\n return result\n\n def tan(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the tan.\n \"\"\"\n\n result = tan(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"tan input should be of type SecretRational.\")\n return result\n\n # Activation functions\n\n def tanh(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = tanh(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"tanh input should be of type SecretRational.\")\n return result\n\n def sigmoid(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = sigmoid(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sigmoid input should be of type SecretRational.\")\n return result\n\n def gelu(\n self, method: str = \"tanh\", tanh_method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = gelu(self, method=method, tanh_method=tanh_method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"gelu input should be of type SecretRational.\")\n return result\n\n def silu(\n self,\n method_sigmoid: str = \"reciprocal\",\n ) -> \"SecretRational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n\n result = silu(self, method_sigmoid=method_sigmoid)\n if not isinstance(result, SecretRational):\n raise TypeError(\"silu input should be of type SecretRational.\")\n return result\n\n\ndef secret_rational(\n name: str, party: Party, log_scale: Optional[int] = None, is_scaled: bool = True\n) -> SecretRational:\n \"\"\"\n Creates a SecretRational from a variable in the Nillion network.\n\n Args:\n name (str): Name of variable in Nillion network.\n party (Party): Name of party that provided variable.\n log_scale (int, optional): Quantization scaling factor. Defaults to None.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n SecretRational: Instantiated SecretRational object.\n \"\"\"\n value = SecretInteger(Input(name=name, party=party))\n return SecretRational(value, log_scale, is_scaled)\n\n\ndef public_rational(\n name: str, party: Party, log_scale: Optional[int] = None, is_scaled: bool = True\n) -> Rational:\n \"\"\"\n Creates a Rational from a variable in the Nillion network.\n\n Args:\n name (str): Name of variable in Nillion network.\n party (Party): Name of party that provided variable.\n log_scale (int, optional): Quantization scaling factor. Defaults to None.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n Rational: Instantiated Rational object.\n \"\"\"\n value = PublicInteger(Input(name=name, party=party))\n return Rational(value, log_scale, is_scaled)\n\n\ndef rational(\n value: Union[int, float, np.floating],\n log_scale: Optional[int] = None,\n is_scaled: bool = False,\n) -> Rational:\n \"\"\"\n Creates a Rational from a number variable.\n\n Args:\n value (Union[int, float, np.floating]): Provided input value.\n log_scale (int, optional): Quantization scaling factor. Defaults to default log_scale.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n Rational: Instantiated Rational object.\n \"\"\"\n if value == 0: # no use in rescaling 0\n return Rational(Integer(0), is_scaled=True)\n\n if log_scale is None:\n log_scale = get_log_scale()\n\n if isinstance(value, np.floating):\n value = value.item()\n if isinstance(value, int):\n return Rational(Integer(value), log_scale=log_scale, is_scaled=is_scaled)\n if isinstance(value, float):\n assert (\n is_scaled is False\n ), \"Got a value of type `float` with `is_scaled` set to True. This should never occur\"\n quantized = round(value * (1 << log_scale))\n return Rational(Integer(quantized), is_scaled=True)\n raise TypeError(f\"Cannot instantiate Rational from type `{type(value)}`.\")\n\n\nclass _MetaRationalConfig(type):\n \"\"\"Rational config metaclass that defines classproperties\"\"\"\n\n _log_scale: int\n _default_log_scale: int\n\n @property\n def default_log_scale(cls) -> int:\n \"\"\"\n Getter method.\n\n Returns:\n int: Default log scale.\n \"\"\"\n return cls._default_log_scale\n\n @property\n def log_scale(cls) -> int:\n \"\"\"\n Getter method.\n\n Returns:\n int: Log scale.\n \"\"\"\n return cls._log_scale\n\n @log_scale.setter\n def log_scale(cls, new_log_scale: int) -> None:\n \"\"\"\n Setter method.\n\n Args:\n new_log_scale (int): New log scale value to reset old value with.\n \"\"\"\n if new_log_scale <= 4:\n warnings.warn(\n f\"Provided log scale `{str(new_log_scale)}` is very low.\"\n \" Expected a value higher than 4.\"\n \" Using a low quantization scale can lead to poor quantization of rational values\"\n \" and thus poor performance & unexpected results.\"\n )\n if new_log_scale >= 64:\n warnings.warn(\n f\"Provided log scale `{str(new_log_scale)}` is very high.\"\n \" Expected a value lower than 64.\"\n \" Using a high quantization scale can lead to overflows & unexpected results.\"\n )\n\n cls._log_scale = new_log_scale\n\n\n# pylint:disable=too-few-public-methods\nclass _RationalConfig(metaclass=_MetaRationalConfig):\n \"\"\"Rational config data class\"\"\"\n\n _default_log_scale: int = 16\n _log_scale: int = _default_log_scale\n\n\ndef set_log_scale(new_log_scale: int) -> None:\n \"\"\"\n Sets the default Rational log scaling factor to a new value.\n Note that this value is the LOG scale and will be used as a base-2 exponent\n during quantization.\n\n Args:\n new_log_scale (int): New log scaling factor.\n \"\"\"\n if not isinstance(new_log_scale, int):\n raise TypeError(\n f\"Cannot set log scale to type `{type(new_log_scale)}`. Expected `int`.\"\n )\n _RationalConfig.log_scale = new_log_scale\n\n\ndef get_log_scale() -> int:\n \"\"\"\n Gets the Rational log scaling factor\n Note that this value is the LOG scale and is used as a base-2 exponent during quantization.\n\n Returns:\n int: Current log scale in use.\n \"\"\"\n return _RationalConfig.log_scale\n\n\ndef reset_log_scale() -> None:\n \"\"\"Resets the Rational log scaling factor to the original default value\"\"\"\n _RationalConfig.log_scale = _RationalConfig.default_log_scale\n\n\n# Fixed-point math operations\n\n# Copyright (c) Facebook, Inc. and its affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n#\n# Part of the code is from the CrypTen Facebook Project:\n# https://github.com/facebookresearch/CrypTen/blob/main/crypten/common/functions/logic.py\n# https://github.com/facebookresearch/CrypTen/blob/main/crypten/common/functions/approximations.py\n#\n# Modifications:\n# July, 2024\n# - Nada datatypes.\n# - Relative accuracy documentation.\n# - Some performance improvements.\n# - Fixed Tanh Chebyshev method by changing '_hardtanh' implementation.\n# - Tan.\n# - Motzkin's prolynomial preprocessing approach.\n# - GeLU and SiLU functions.\n\n\ndef sign(x: _NadaRational) -> _NadaRational:\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n ltz_cond = x < rational(0)\n ltz = ltz_cond.if_else(rational(1), rational(0))\n\n return rational(1) - ltz - ltz\n\n\ndef fxp_abs(x: _NadaRational) -> _NadaRational:\n \"\"\"Computes the absolute value of a rational\"\"\"\n return x * sign(x)\n\n\ndef exp(x: _NadaRational, iterations: int = 8) -> _NadaRational:\n \"\"\"\n Approximates the exponential function using a limit approximation.\n \"\"\"\n\n iters_na = UnsignedInteger(iterations)\n\n result = rational(1) + (x >> iters_na)\n for _ in range(iterations):\n result = result**2\n return result\n\n\ndef polynomial(x: _NadaRational, coefficients: List[Rational]) -> _NadaRational:\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the highest order term.\n **Note: The constant term is not included.**\n \"\"\"\n result = coefficients[0] * x\n\n for power, coeff in enumerate(coefficients[1:], start=2):\n result += coeff * (x**power)\n\n return result\n\n\ndef log(\n x: _NadaRational,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n) -> _NadaRational:\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n \"\"\"\n\n if input_in_01:\n return log(\n x * rational(100),\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n ) - rational(4.605170)\n\n # Initialization to a decent estimate (found by qualitative inspection):\n # ln(x) = x/120 - 20exp(-2x - 1.0) + 3.0\n term1 = x * rational(1 / 120.0)\n term2 = exp(-x - x - rational(1), iterations=exp_iterations) * rational(20)\n y = term1 - term2 + rational(3.0)\n\n # 8th order Householder iterations\n for _ in range(iterations):\n h = rational(1) - x * exp(-y, iterations=exp_iterations)\n y -= polynomial(h, [rational(1 / (i + 1)) for i in range(order)])\n return y\n\n\ndef reciprocal( # pylint: disable=too-many-arguments\n x: _NadaRational,\n all_pos: bool = False,\n initial: Optional[Rational] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n \"\"\"\n if input_in_01:\n rec = reciprocal(\n x * rational(64),\n method=method,\n all_pos=True,\n initial=initial,\n iterations=iterations,\n ) * rational(64)\n return rec\n\n if not all_pos:\n sgn = sign(x)\n pos = sgn * x\n return sgn * reciprocal(\n pos, method=method, all_pos=True, initial=initial, iterations=iterations\n )\n\n if method == \"NR\":\n if initial is None:\n # Initialization to a decent estimate (found by qualitative inspection):\n # 1/x = 3exp(1 - 2x) + 0.003\n result = rational(3) * exp(\n rational(1) - x - x, iterations=exp_iters\n ) + rational(0.003)\n else:\n result = initial\n for _ in range(iterations):\n result = result + result - result * result * x\n return result\n if method == \"log\":\n return exp(-log(x, iterations=log_iters), iterations=exp_iters)\n raise ValueError(f\"Invalid method {method} given for reciprocal function\")\n\n\ndef inv_sqrt(\n x: _NadaRational,\n initial: Optional[Union[_NadaRational, None]] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n \"\"\"\n\n if method == \"NR\":\n if initial is None:\n # Initialization to a decent estimate (found by qualitative inspection):\n # exp(- x/2 - 0.2) * 2.2 + 0.2 - x/1024\n y = exp(-(x >> UnsignedInteger(1)) - rational(0.2)) * rational(\n 2.2\n ) + rational(0.2)\n y -= x >> UnsignedInteger(10) # div by 1024\n else:\n y = initial\n\n # Newton Raphson iterations for inverse square root\n for _ in range(iterations):\n y = (y * (rational(3) - x * y * y)) >> UnsignedInteger(1)\n return y\n raise ValueError(f\"Invalid method {method} given for inv_sqrt function\")\n\n\ndef sqrt(\n x: _NadaRational,\n initial: Union[_NadaRational, None] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n \"\"\"\n\n if method == \"NR\":\n return inv_sqrt(x, initial=initial, iterations=iterations, method=method) * x\n\n raise ValueError(f\"Invalid method {method} given for sqrt function\")\n\n\n# Trigonometry\n\n\ndef _eix(x: _NadaRational, iterations: int = 10) -> Tuple[_NadaRational, _NadaRational]:\n r\"\"\"Computes e^(i * x) where i is the imaginary unit through the formula:\n\n .. math::\n Re\\{e^{i * x}\\}, Im\\{e^{i * x}\\} = \\cos(x), \\sin(x)\n\n Args:\n x (Union[Rational, SecretRational]): the input value.\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[Union[Rational, SecretRational], Union[Rational, SecretRational]]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n one = rational(1)\n im = x >> UnsignedInteger(iterations)\n\n # First iteration uses knowledge that `re` is public and = 1\n re = one - im * im\n im *= rational(2)\n\n # Compute (a + bi)^2 -> (a^2 - b^2) + (2ab)i `iterations` times\n for _ in range(iterations - 1):\n a2 = re * re\n b2 = im * im\n im = im * re\n im *= rational(2)\n re = a2 - b2\n\n return re, im\n\n\ndef cossin(\n x: _NadaRational, iterations: int = 10\n) -> Tuple[_NadaRational, _NadaRational]:\n r\"\"\"\n Computes cosine and sine through e^(i * x) where i is the imaginary unit.\n \"\"\"\n return _eix(x, iterations=iterations)\n\n\ndef cos(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the cosine of x using cos(x) = Re{exp(i * x)}.\n \"\"\"\n return cossin(x, iterations=iterations)[0]\n\n\ndef sin(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the sine of x using sin(x) = Im{exp(i * x)}.\n \"\"\"\n return cossin(x, iterations=iterations)[1]\n\n\ndef tan(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the tan of x using tan(x) = sin(x) / cos(x).\n \"\"\"\n c, s = cossin(x, iterations=iterations)\n return s * reciprocal(c)\n\n\n# Activation functions\n\n\n@functools.lru_cache(maxsize=10)\ndef chebyshev_series(func, width, terms):\n \"\"\"\n Computes Chebyshev coefficients.\n \"\"\"\n n_range = np.arange(start=0, stop=terms, dtype=float)\n x = width * np.cos((n_range + 0.5) * np.pi / terms)\n y = func(x)\n cos_term = np.cos(np.outer(n_range, n_range + 0.5) * np.pi / terms)\n coeffs = (2 / terms) * np.sum(y * cos_term, axis=1)\n return coeffs\n\n\ndef tanh(\n x: _NadaRational, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the hyperbolic tangent function.\n \"\"\"\n\n if method == \"reciprocal\":\n return sigmoid(x + x, method=method) * rational(2) - rational(1)\n if method == \"chebyshev\":\n coeffs = chebyshev_series(np.tanh, 1, chebyshev_terms)[1::2]\n # transform np.array of float to na.array of rationals\n coeffs = np.vectorize(rational)(coeffs)\n out = _chebyshev_polynomials(x, chebyshev_terms).transpose() @ coeffs\n # truncate outside [-maxval, maxval]\n return _hardtanh(x, out)\n if method == \"motzkin\":\n # Using approximation from \"BOLT: Privacy-Preserving, Accurate and Efficient\n # Inference for Transformers\"\n # section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n # ltz is used for absolute value of x and to compute sign (used to generate result).\n # We don't use 'abs' and 'sign' functions to avoid computing ltz twice.\n # sign = 1 - 2 * ltz, where ltz = (x < rational(0)).if_else(rational(1), rational(0))\n sgn = rational(1) - rational(2) * (x < rational(0)).if_else(\n rational(1), rational(0)\n )\n # absolute value\n abs_x = x * sgn\n\n # Motzkin’s polynomial preprocessing\n t0 = rational(-4.259314087994767)\n t1 = rational(18.86353816972803)\n t2 = rational(-36.42402897526823)\n t3 = rational(-0.013232131886235352)\n t4 = rational(-3.3289339650097993)\n t5 = rational(-0.0024920889620412097)\n tanh_p0 = (abs_x + t0) * abs_x + t1\n tanh_p1 = (tanh_p0 + abs_x + t2) * tanh_p0 * t3 * abs_x + t4 * abs_x + t5\n\n return (abs_x > rational(2.855)).if_else(sgn, sgn * tanh_p1)\n raise ValueError(f\"Unrecognized method {method} for tanh\")\n\n\n### Auxiliary functions for tanh\n\n\ndef _chebyshev_polynomials(x: _NadaRational, terms: int) -> np.ndarray:\n \"\"\"Evaluates odd degree Chebyshev polynomials at x.\n\n Chebyshev Polynomials of the first kind are defined as:\n\n .. math::\n P_0(x) = 1, P_1(x) = x, P_n(x) = 2 P_{n - 1}(x) - P_{n-2}(x)\n\n Args:\n x (Union[\"Rational\", \"SecretRational\"]): input at which polynomials are evaluated\n terms (int): highest degree of Chebyshev polynomials.\n Must be even and at least 6.\n Returns:\n NadaArray of polynomials evaluated at x of shape `(terms, *x)`.\n\n Raises:\n ValueError: Raised if 'terrms' is odd and < 6.\n \"\"\"\n if terms % 2 != 0 or terms < 6:\n raise ValueError(\"Chebyshev terms must be even and >= 6\")\n\n # Initiate base polynomials\n # P_0\n # polynomials = np.array([x])\n # y = rational(4) * x * x - rational(2)\n # z = y - rational(1)\n # # P_1\n # polynomials = np.append(polynomials, z * x)\n\n # # Generate remaining Chebyshev polynomials using the recurrence relation\n # for k in range(2, terms // 2):\n # next_polynomial = y * polynomials[k - 1] - polynomials[k - 2]\n # polynomials = np.append(polynomials, next_polynomial)\n\n # return polynomials\n\n polynomials = [x]\n y = rational(4) * x * x - rational(2)\n z = y - rational(1)\n # P_1\n polynomials.append(z * x)\n\n # Generate remaining Chebyshev polynomials using the recurrence relation\n for k in range(2, terms // 2):\n next_polynomial = y * polynomials[k - 1] - polynomials[k - 2]\n polynomials.append(next_polynomial)\n\n return np.array(polynomials)\n\n\ndef _hardtanh(\n x: _NadaRational,\n output: _NadaRational,\n abs_const: _NadaRational = rational(1),\n abs_range: _NadaRational = rational(1),\n) -> _NadaRational:\n r\"\"\"Applies the HardTanh function element-wise.\n\n HardTanh is defined as:\n\n .. math::\n \\text{HardTanh}(x) = \\begin{cases}\n 1 & \\text{ if } x > 1 \\\\\n -1 & \\text{ if } x < -1 \\\\\n Tanh(x) & \\text{ otherwise } \\\\\n \\end{cases}\n\n The range of the linear region :math:`[-1, 1]` can be adjusted using\n :attr:`abs_range`.\n\n Args:\n x (Union[Rational, SecretRational]): the input value of the Tanh.\n output (Union[Rational, SecretRational]): the output value of the approximation of Tanh.\n abs_const (Union[Rational, SecretRational]): constant value to which |Tanh(x)| converges.\n Defaults to 1.\n abs_range (Union[Rational, SecretRational]): absolute value of the range. Defaults to 1.\n\n Returns:\n Union[Rational, SecretRational]: HardTanh output.\n \"\"\"\n # absolute value\n sgn = sign(x)\n abs_x = x * sgn\n # chekc if inside [-abs_range, abs_range] interval\n ineight_cond = abs_x < abs_range\n result = ineight_cond.if_else(output, abs_const * sgn)\n\n return result\n\n\n### End of auxiliary functions for tanh\n\n\ndef sigmoid(\n x: _NadaRational, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the sigmoid function.\n \"\"\"\n if method == \"chebyshev\":\n tanh_approx = tanh(\n x >> UnsignedInteger(1), method=method, chebyshev_terms=chebyshev_terms\n )\n return (tanh_approx >> UnsignedInteger(1)) + rational(0.5)\n if method == \"motzkin\":\n tanh_approx = tanh(\n x >> UnsignedInteger(1), method=method, chebyshev_terms=chebyshev_terms\n )\n return (tanh_approx >> UnsignedInteger(1)) + rational(0.5)\n if method == \"reciprocal\":\n # ltz is used for absolute value of x and to generate 'result'.\n # We don't use 'abs' function to avoid computing ltz twice.\n ltz_cond = x < rational(0)\n ltz = ltz_cond.if_else(rational(1), rational(0))\n # compute absolute value of x\n sgn = rational(1) - rational(2) * ltz\n pos_x = x * sgn\n\n denominator = exp(-pos_x) + rational(1)\n pos_output = reciprocal(\n denominator, all_pos=True, initial=rational(0.75), iterations=3, exp_iters=9\n )\n\n # result is equivalent to (1 - ltz).if_else(pos_output, 1 - pos_output)\n result = pos_output + ltz - rational(2) * pos_output * ltz\n return result\n raise ValueError(f\"Unrecognized method {method} for sigmoid\")\n\n\ndef gelu(\n x: _NadaRational, method: str = \"tanh\", tanh_method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the gelu function.\n \"\"\"\n\n if method == \"tanh\":\n # Using common approximation:\n # x/2 * (1 + tanh(0.797884560 * ( x + 0.04471 * x ** 3 ) ) )\n # where 0.797884560 ~= sqrt(2/pi).\n val = rational(0.797884560) * (x + rational(0.044715) * x**3)\n return (x * (rational(1) + tanh(val, method=tanh_method))) >> UnsignedInteger(1)\n if method == \"motzkin\":\n # Using approximation from \"BOLT: Privacy-Preserving, Accurate and Efficient\n # Inference for Transformers\"\n # section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n # ltz is used for absolute value of x and to compute relu.\n # We don't use 'abs' and '_relu' functions to avoid computing ltz twice.\n ltz = (x < rational(0)).if_else(rational(1), rational(0))\n # absolute value\n sgn = rational(1) - rational(2) * ltz\n abs_x = x * sgn\n # relu\n relu = x * (rational(1) - ltz)\n\n # Motzkin’s polynomial preprocessing\n g0 = rational(0.14439048359960427)\n g1 = rational(-0.7077117131613893)\n g2 = rational(4.5702822654246535)\n g3 = rational(-8.15444702051307)\n g4 = rational(16.382265425072532)\n gelu_p0 = (g0 * abs_x + g1) * abs_x + g2\n gelu_p1 = (gelu_p0 + g0 * abs_x + g3) * gelu_p0 + g4 + (x >> UnsignedInteger(1))\n\n return (abs_x > rational(2.7)).if_else(relu, gelu_p1)\n raise ValueError(f\"Unrecognized method {method} for gelu\")\n\n\ndef silu(\n x: _NadaRational,\n method_sigmoid: str = \"reciprocal\",\n) -> _NadaRational:\n \"\"\"\n Computes the gelu function\n \"\"\"\n return x * sigmoid(x, method=method_sigmoid)\n"},"source_refs":[{"file":"list_scan_linear.py","lineno":9,"offset":328,"length":32},{"file":"list_scan_linear.py","lineno":8,"offset":259,"length":68},{"file":"list_scan_linear.py","lineno":18,"offset":523,"length":72},{"file":"list_scan_linear.py","lineno":22,"offset":656,"length":72},{"file":"list_scan_linear.py","lineno":5,"offset":140,"length":23},{"file":"list_scan_linear.py","lineno":28,"offset":864,"length":63},{"file":"list_scan_linear.py","lineno":25,"offset":778,"length":71},{"file":"list_scan_linear.py","lineno":29,"offset":928,"length":63},{"file":"funcs.py","lineno":107,"offset":2341,"length":65}]}
\ No newline at end of file
+{"functions":[],"parties":[{"name":"Party0","source_ref_index":8},{"name":"Party1","source_ref_index":8},{"name":"Party2","source_ref_index":8},{"name":"Party3","source_ref_index":8},{"name":"Party4","source_ref_index":8},{"name":"Party5","source_ref_index":8},{"name":"Party6","source_ref_index":8},{"name":"Party7","source_ref_index":8},{"name":"Party8","source_ref_index":8},{"name":"Party9","source_ref_index":8}],"inputs":[{"type":"SecretInteger","party":"Party0","name":"num_0","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party1","name":"num_1","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party2","name":"num_2","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party3","name":"num_3","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party4","name":"num_4","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party5","name":"num_5","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party6","name":"num_6","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party7","name":"num_7","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party8","name":"num_8","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Party9","name":"num_9","doc":"","source_ref_index":2}],"literals":[{"name":"0420609fa1d35394f41049df03ef341f","value":"0","type":"Integer"},{"name":"10d33944d37d5b1b833be6fd73d3033c","value":"1","type":"Integer"},{"name":"3a980e5ac46f3aa9cc3f1d27d3b9f1f9","value":"100","type":"Integer"},{"name":"84e55ea59b6b9ec4c529ed7e4655425e","value":"99","type":"Integer"}],"outputs":[{"name":"is_present_1","operation_id":4333839632,"party":"Party0","type":"SecretBoolean","source_ref_index":5},{"name":"is_present_2","operation_id":4333870144,"party":"Party0","type":"SecretBoolean","source_ref_index":7}],"operations":{"4333844960":{"LiteralReference":{"id":4333844960,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333836704":{"Equals":{"id":4333836704,"left":4333810176,"right":4333809744,"type":"SecretBoolean","source_ref_index":1}},"4333831568":{"Addition":{"id":4333831568,"left":4333830368,"right":4333831328,"type":"SecretInteger","source_ref_index":1}},"4333863424":{"Equals":{"id":4333863424,"left":4333839920,"right":4333809024,"type":"SecretBoolean","source_ref_index":1}},"4333841552":{"Addition":{"id":4333841552,"left":4333840208,"right":4333841312,"type":"SecretInteger","source_ref_index":1}},"4333833728":{"IfElse":{"id":4333833728,"this":4333833008,"arg_0":4333833296,"arg_1":4333833536,"type":"SecretInteger","source_ref_index":1}},"4333865296":{"LiteralReference":{"id":4333865296,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333811760":{"Equals":{"id":4333811760,"left":4333810176,"right":4317576320,"type":"SecretBoolean","source_ref_index":1}},"4333869184":{"LiteralReference":{"id":4333869184,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333868128":{"IfElse":{"id":4333868128,"this":4333867312,"arg_0":4333867600,"arg_1":4333867888,"type":"SecretInteger","source_ref_index":1}},"4333810512":{"LiteralReference":{"id":4333810512,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":4}},"4333834208":{"Equals":{"id":4333834208,"left":4333810176,"right":4333809264,"type":"SecretBoolean","source_ref_index":1}},"4333864480":{"Addition":{"id":4333864480,"left":4333863184,"right":4333864240,"type":"SecretInteger","source_ref_index":1}},"4333844144":{"Addition":{"id":4333844144,"left":4333842848,"right":4333843904,"type":"SecretInteger","source_ref_index":1}},"4333841072":{"LiteralReference":{"id":4333841072,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333837616":{"IfElse":{"id":4333837616,"this":4333836704,"arg_0":4333837040,"arg_1":4333837376,"type":"SecretInteger","source_ref_index":1}},"4333830608":{"Equals":{"id":4333830608,"left":4333810176,"right":4333808544,"type":"SecretBoolean","source_ref_index":1}},"4333813680":{"IfElse":{"id":4333813680,"this":4333812960,"arg_0":4333813248,"arg_1":4333813488,"type":"SecretInteger","source_ref_index":1}},"4333839392":{"LiteralReference":{"id":4333839392,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":0}},"4333810176":{"LiteralReference":{"id":4333810176,"refers_to":"3a980e5ac46f3aa9cc3f1d27d3b9f1f9","type":"Integer","source_ref_index":3}},"4333867072":{"Addition":{"id":4333867072,"left":4333865776,"right":4333866832,"type":"SecretInteger","source_ref_index":1}},"4333812960":{"Equals":{"id":4333812960,"left":4333810176,"right":4333808304,"type":"SecretBoolean","source_ref_index":1}},"4333843088":{"Equals":{"id":4333843088,"left":4333839920,"right":4333808304,"type":"SecretBoolean","source_ref_index":1}},"4333809984":{"InputReference":{"id":4333809984,"refers_to":"num_9","type":"SecretInteger","source_ref_index":2}},"4333869904":{"LiteralReference":{"id":4333869904,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":0}},"4333869424":{"IfElse":{"id":4333869424,"this":4333868608,"arg_0":4333868896,"arg_1":4333869184,"type":"SecretInteger","source_ref_index":1}},"4333835936":{"LiteralReference":{"id":4333835936,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333838384":{"LiteralReference":{"id":4333838384,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333835408":{"Equals":{"id":4333835408,"left":4333810176,"right":4333809504,"type":"SecretBoolean","source_ref_index":1}},"4333843376":{"LiteralReference":{"id":4333843376,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333833008":{"Equals":{"id":4333833008,"left":4333810176,"right":4333809024,"type":"SecretBoolean","source_ref_index":1}},"4333834928":{"IfElse":{"id":4333834928,"this":4333834208,"arg_0":4333834496,"arg_1":4333834736,"type":"SecretInteger","source_ref_index":1}},"4333842368":{"LiteralReference":{"id":4333842368,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333808304":{"InputReference":{"id":4333808304,"refers_to":"num_2","type":"SecretInteger","source_ref_index":2}},"4333838096":{"Equals":{"id":4333838096,"left":4333810176,"right":4333809984,"type":"SecretBoolean","source_ref_index":1}},"4333867888":{"LiteralReference":{"id":4333867888,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333867312":{"Equals":{"id":4333867312,"left":4333839920,"right":4333809744,"type":"SecretBoolean","source_ref_index":1}},"4305185312":{"InputReference":{"id":4305185312,"refers_to":"num_0","type":"SecretInteger","source_ref_index":2}},"4333838912":{"IfElse":{"id":4333838912,"this":4333838096,"arg_0":4333838384,"arg_1":4333838672,"type":"SecretInteger","source_ref_index":1}},"4333837856":{"Addition":{"id":4333837856,"left":4333836416,"right":4333837616,"type":"SecretInteger","source_ref_index":1}},"4333812048":{"LiteralReference":{"id":4333812048,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333842848":{"Addition":{"id":4333842848,"left":4333841552,"right":4333842608,"type":"SecretInteger","source_ref_index":1}},"4333840784":{"LiteralReference":{"id":4333840784,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333833968":{"Addition":{"id":4333833968,"left":4333832768,"right":4333833728,"type":"SecretInteger","source_ref_index":1}},"4333866304":{"LiteralReference":{"id":4333866304,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333837040":{"LiteralReference":{"id":4333837040,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333831136":{"LiteralReference":{"id":4333831136,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333809264":{"InputReference":{"id":4333809264,"refers_to":"num_6","type":"SecretInteger","source_ref_index":2}},"4333830368":{"Addition":{"id":4333830368,"left":4333812720,"right":4333813680,"type":"SecretInteger","source_ref_index":1}},"4333864240":{"IfElse":{"id":4333864240,"this":4333863424,"arg_0":4333863712,"arg_1":4333864000,"type":"SecretInteger","source_ref_index":1}},"4333869664":{"Addition":{"id":4333869664,"left":4333868368,"right":4333869424,"type":"SecretInteger","source_ref_index":1}},"4333868896":{"LiteralReference":{"id":4333868896,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333834496":{"LiteralReference":{"id":4333834496,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333833296":{"LiteralReference":{"id":4333833296,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333843904":{"IfElse":{"id":4333843904,"this":4333843088,"arg_0":4333843376,"arg_1":4333843664,"type":"SecretInteger","source_ref_index":1}},"4333812720":{"Addition":{"id":4333812720,"left":4333811616,"right":4333812480,"type":"SecretInteger","source_ref_index":1}},"4333831808":{"Equals":{"id":4333831808,"left":4333810176,"right":4333808784,"type":"SecretBoolean","source_ref_index":1}},"4333868368":{"Addition":{"id":4333868368,"left":4333867072,"right":4333868128,"type":"SecretInteger","source_ref_index":1}},"4333864720":{"Equals":{"id":4333864720,"left":4333839920,"right":4333809264,"type":"SecretBoolean","source_ref_index":1}},"4333862992":{"IfElse":{"id":4333862992,"this":4333845680,"arg_0":4333845968,"arg_1":4333846256,"type":"SecretInteger","source_ref_index":1}},"4333841792":{"Equals":{"id":4333841792,"left":4333839920,"right":4317576320,"type":"SecretBoolean","source_ref_index":1}},"4333868608":{"Equals":{"id":4333868608,"left":4333839920,"right":4333809984,"type":"SecretBoolean","source_ref_index":1}},"4333840496":{"Equals":{"id":4333840496,"left":4333839920,"right":4305185312,"type":"SecretBoolean","source_ref_index":1}},"4333808544":{"InputReference":{"id":4333808544,"refers_to":"num_3","type":"SecretInteger","source_ref_index":2}},"4333845200":{"IfElse":{"id":4333845200,"this":4333844384,"arg_0":4333844672,"arg_1":4333844960,"type":"SecretInteger","source_ref_index":1}},"4333838672":{"LiteralReference":{"id":4333838672,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333845968":{"LiteralReference":{"id":4333845968,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333813488":{"LiteralReference":{"id":4333813488,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333835168":{"Addition":{"id":4333835168,"left":4333833968,"right":4333834928,"type":"SecretInteger","source_ref_index":1}},"4333832096":{"LiteralReference":{"id":4333832096,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333836128":{"IfElse":{"id":4333836128,"this":4333835408,"arg_0":4333835696,"arg_1":4333835936,"type":"SecretInteger","source_ref_index":1}},"4333839920":{"LiteralReference":{"id":4333839920,"refers_to":"84e55ea59b6b9ec4c529ed7e4655425e","type":"Integer","source_ref_index":6}},"4333809504":{"InputReference":{"id":4333809504,"refers_to":"num_7","type":"SecretInteger","source_ref_index":2}},"4333836416":{"Addition":{"id":4333836416,"left":4333835168,"right":4333836128,"type":"SecretInteger","source_ref_index":1}},"4333866592":{"LiteralReference":{"id":4333866592,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333831328":{"IfElse":{"id":4333831328,"this":4333830608,"arg_0":4333830896,"arg_1":4333831136,"type":"SecretInteger","source_ref_index":1}},"4333845440":{"Addition":{"id":4333845440,"left":4333844144,"right":4333845200,"type":"SecretInteger","source_ref_index":1}},"4333811232":{"LiteralReference":{"id":4333811232,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333839152":{"Addition":{"id":4333839152,"left":4333837856,"right":4333838912,"type":"SecretInteger","source_ref_index":1}},"4333863712":{"LiteralReference":{"id":4333863712,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333842608":{"IfElse":{"id":4333842608,"this":4333841792,"arg_0":4333842080,"arg_1":4333842368,"type":"SecretInteger","source_ref_index":1}},"4333844672":{"LiteralReference":{"id":4333844672,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333865008":{"LiteralReference":{"id":4333865008,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333839632":{"LessThan":{"id":4333839632,"left":4333839392,"right":4333839152,"type":"SecretBoolean","source_ref_index":0}},"4333810656":{"Equals":{"id":4333810656,"left":4333810176,"right":4305185312,"type":"SecretBoolean","source_ref_index":1}},"4333810992":{"LiteralReference":{"id":4333810992,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333846256":{"LiteralReference":{"id":4333846256,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333845680":{"Equals":{"id":4333845680,"left":4333839920,"right":4333808784,"type":"SecretBoolean","source_ref_index":1}},"4333841312":{"IfElse":{"id":4333841312,"this":4333840496,"arg_0":4333840784,"arg_1":4333841072,"type":"SecretInteger","source_ref_index":1}},"4333812480":{"IfElse":{"id":4333812480,"this":4333811760,"arg_0":4333812048,"arg_1":4333812288,"type":"SecretInteger","source_ref_index":1}},"4333813248":{"LiteralReference":{"id":4333813248,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333833536":{"LiteralReference":{"id":4333833536,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4317576320":{"InputReference":{"id":4317576320,"refers_to":"num_1","type":"SecretInteger","source_ref_index":2}},"4333809024":{"InputReference":{"id":4333809024,"refers_to":"num_5","type":"SecretInteger","source_ref_index":2}},"4333844384":{"Equals":{"id":4333844384,"left":4333839920,"right":4333808544,"type":"SecretBoolean","source_ref_index":1}},"4333870144":{"LessThan":{"id":4333870144,"left":4333869904,"right":4333869664,"type":"SecretBoolean","source_ref_index":0}},"4333832336":{"LiteralReference":{"id":4333832336,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333840208":{"LiteralReference":{"id":4333840208,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":4}},"4333834736":{"LiteralReference":{"id":4333834736,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333863184":{"Addition":{"id":4333863184,"left":4333845440,"right":4333862992,"type":"SecretInteger","source_ref_index":1}},"4333865536":{"IfElse":{"id":4333865536,"this":4333864720,"arg_0":4333865008,"arg_1":4333865296,"type":"SecretInteger","source_ref_index":1}},"4333867600":{"LiteralReference":{"id":4333867600,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333811616":{"Addition":{"id":4333811616,"left":4333810512,"right":4333811424,"type":"SecretInteger","source_ref_index":1}},"4333832768":{"Addition":{"id":4333832768,"left":4333831568,"right":4333832528,"type":"SecretInteger","source_ref_index":1}},"4333809744":{"InputReference":{"id":4333809744,"refers_to":"num_8","type":"SecretInteger","source_ref_index":2}},"4333843664":{"LiteralReference":{"id":4333843664,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333812288":{"LiteralReference":{"id":4333812288,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333808784":{"InputReference":{"id":4333808784,"refers_to":"num_4","type":"SecretInteger","source_ref_index":2}},"4333865776":{"Addition":{"id":4333865776,"left":4333864480,"right":4333865536,"type":"SecretInteger","source_ref_index":1}},"4333842080":{"LiteralReference":{"id":4333842080,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333866832":{"IfElse":{"id":4333866832,"this":4333866016,"arg_0":4333866304,"arg_1":4333866592,"type":"SecretInteger","source_ref_index":1}},"4333835696":{"LiteralReference":{"id":4333835696,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4333832528":{"IfElse":{"id":4333832528,"this":4333831808,"arg_0":4333832096,"arg_1":4333832336,"type":"SecretInteger","source_ref_index":1}},"4333811424":{"IfElse":{"id":4333811424,"this":4333810656,"arg_0":4333810992,"arg_1":4333811232,"type":"SecretInteger","source_ref_index":1}},"4333837376":{"LiteralReference":{"id":4333837376,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333866016":{"Equals":{"id":4333866016,"left":4333839920,"right":4333809504,"type":"SecretBoolean","source_ref_index":1}},"4333864000":{"LiteralReference":{"id":4333864000,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4333830896":{"LiteralReference":{"id":4333830896,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}}},"source_files":{"funcs.py":"\"\"\"\nThis module provides common functions to work with Nada Numpy. It includes: \n- the creation and manipulation of arrays and party objects.\n- non-linear functions over arrays.\n- random operations over arrays: random generation, shuffling.\n\"\"\"\n\n# pylint:disable=too-many-lines\n\nfrom typing import Any, Callable, List, Optional, Sequence, Tuple, Union\n\nimport numpy as np\nfrom nada_dsl import (Boolean, Integer, Output, Party, PublicInteger,\n PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, UnsignedInteger)\n\nfrom nada_numpy.array import NadaArray\nfrom nada_numpy.nada_typing import AnyNadaType, NadaCleartextNumber\nfrom nada_numpy.types import Rational, SecretRational, rational\nfrom nada_numpy.utils import copy_metadata\n\n__all__ = [\n \"parties\",\n \"from_list\",\n \"ones\",\n \"ones_like\",\n \"zeros\",\n \"zeros_like\",\n \"alphas\",\n \"alphas_like\",\n \"array\",\n \"random\",\n \"output\",\n \"vstack\",\n \"hstack\",\n \"ndim\",\n \"shape\",\n \"size\",\n \"pad\",\n \"frompyfunc\",\n \"vectorize\",\n \"eye\",\n \"arange\",\n \"linspace\",\n \"split\",\n \"compress\",\n \"copy\",\n \"cumprod\",\n \"cumsum\",\n \"diagonal\",\n \"mean\",\n \"prod\",\n \"put\",\n \"ravel\",\n \"repeat\",\n \"reshape\",\n \"resize\",\n \"squeeze\",\n \"sum\",\n \"swapaxes\",\n \"take\",\n \"trace\",\n \"transpose\",\n \"sign\",\n \"abs\",\n \"exp\",\n \"polynomial\",\n \"log\",\n \"reciprocal\",\n \"inv_sqrt\",\n \"sqrt\",\n \"cossin\",\n \"sin\",\n \"cos\",\n \"tan\",\n \"tanh\",\n \"sigmoid\",\n \"gelu\",\n \"silu\",\n \"shuffle\",\n]\n\n\ndef parties(num: int, party_names: Optional[List[str]] = None) -> List[Party]:\n \"\"\"\n Create a list of Party objects with specified names.\n\n Args:\n num (int): The number of parties to create.\n party_names (List[str], optional): Party names to use. Defaults to None.\n\n Raises:\n ValueError: Raised when incorrect number of party names is supplied.\n\n Returns:\n List[Party]: A list of Party objects.\n \"\"\"\n if party_names is None:\n party_names = [f\"Party{i}\" for i in range(num)]\n\n if len(party_names) != num:\n num_supplied_parties = len(party_names)\n raise ValueError(\n f\"Incorrect number of party names. Expected {num}, received {num_supplied_parties}\"\n )\n\n return [Party(name=party_name) for party_name in party_names]\n\n\ndef __from_numpy(arr: np.ndarray, nada_type: NadaCleartextNumber) -> List:\n \"\"\"\n Recursively convert a n-dimensional NumPy array to a nested list of NadaInteger objects.\n\n Args:\n arr (np.ndarray): A NumPy array of integers.\n nada_type (type): The type of NadaInteger objects to create.\n\n Returns:\n List: A nested list of NadaInteger objects.\n \"\"\"\n if len(arr.shape) == 1:\n if isinstance(nada_type, Rational):\n return [nada_type(elem) for elem in arr] # type: ignore\n return [nada_type(int(elem)) for elem in arr] # type: ignore\n return [__from_numpy(arr[i], nada_type) for i in range(arr.shape[0])]\n\n\ndef from_list(\n lst: Union[List, np.ndarray], nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray from a list of integers.\n\n Args:\n lst (Union[List, np.ndarray]): A list of integers representing the elements of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n lst_np = np.array(lst)\n return NadaArray(np.array(__from_numpy(lst_np, nada_type)))\n\n\ndef ones(dims: Sequence[int], nada_type: NadaCleartextNumber = Integer) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with ones.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with ones.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n return from_list(np.ones(dims), nada_type)\n\n\ndef ones_like(\n a: np.ndarray | NadaArray, nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with one with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): A reference array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with ones.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n if isinstance(a, NadaArray):\n a = a.inner\n return from_list(np.ones_like(a), nada_type)\n\n\ndef zeros(dims: Sequence[int], nada_type: NadaCleartextNumber = Integer) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with zeros.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with zeros.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n return from_list(np.zeros(dims), nada_type)\n\n\ndef zeros_like(\n a: np.ndarray | NadaArray, nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with zeros with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): A reference array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with zeros.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n if isinstance(a, NadaArray):\n a = a.inner\n return from_list(np.zeros_like(a), nada_type)\n\n\ndef alphas(dims: Sequence[int], alpha: Any) -> NadaArray:\n \"\"\"\n Create a NadaArray filled with a certain constant value.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n alpha (Any): Some constant value.\n\n Returns:\n NadaArray: NadaArray filled with constant value.\n \"\"\"\n ones_array = np.ones(dims)\n return NadaArray(np.frompyfunc(lambda _: alpha, 1, 1)(ones_array))\n\n\ndef alphas_like(a: np.ndarray | NadaArray, alpha: Any) -> NadaArray:\n \"\"\"\n Create a NadaArray filled with a certain constant value\n with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): Reference array.\n alpha (Any): Some constant value.\n\n Returns:\n NadaArray: NadaArray filled with constant value.\n \"\"\"\n if isinstance(a, NadaArray):\n a = a.inner\n ones_array = np.ones_like(a)\n return NadaArray(np.frompyfunc(lambda _: alpha, 1, 1)(ones_array))\n\n\ndef array(\n dims: Sequence[int],\n party: Party,\n prefix: str,\n nada_type: Union[\n SecretInteger,\n SecretUnsignedInteger,\n PublicInteger,\n PublicUnsignedInteger,\n SecretRational,\n Rational,\n ],\n) -> NadaArray:\n \"\"\"\n Create a NadaArray with the specified dimensions and elements of the given type.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n party (Party): The party object.\n prefix (str): A prefix for naming the array elements.\n nada_type (type): The type of elements to create.\n\n Returns:\n NadaArray: The created NadaArray.\n \"\"\"\n return NadaArray.array(dims, party, prefix, nada_type)\n\n\ndef random(\n dims: Sequence[int],\n nada_type: SecretInteger | SecretUnsignedInteger | SecretRational = SecretInteger,\n) -> NadaArray:\n \"\"\"\n Create a random NadaArray with the specified dimensions.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of elements to create. Defaults to SecretInteger.\n\n Returns:\n NadaArray: A NadaArray with random values of the specified type.\n \"\"\"\n return NadaArray.random(dims, nada_type)\n\n\ndef output(\n value: Union[NadaArray, AnyNadaType], party: Party, prefix: str\n) -> List[Output]:\n \"\"\"\n Generate a list of Output objects for some provided value.\n\n Args:\n value (Union[NadaArray, AnyNadaType]): The input NadaArray.\n party (Party): The party object.\n prefix (str): The prefix for naming the Output objects.\n\n Returns:\n List[Output]: A list of Output objects.\n \"\"\"\n if isinstance(value, NadaArray):\n # pylint:disable=protected-access\n return NadaArray._output_array(value, party, prefix)\n if isinstance(value, (Rational, SecretRational)):\n value = value.value\n return [Output(value, prefix, party)]\n\n\ndef vstack(arr_list: list) -> NadaArray:\n \"\"\"\n Stack arrays in sequence vertically (row wise).\n\n Args:\n arr_list (list): A list of NadaArray objects to stack.\n\n Returns:\n NadaArray: The stacked NadaArray.\n \"\"\"\n return NadaArray(np.vstack(arr_list))\n\n\ndef hstack(arr_list: list) -> NadaArray:\n \"\"\"\n Stack arrays in sequence horizontally (column wise).\n\n Args:\n arr_list (list): A list of NadaArray objects to stack.\n\n Returns:\n NadaArray: The stacked NadaArray.\n \"\"\"\n return NadaArray(np.hstack(arr_list))\n\n\ndef ndim(arr: NadaArray) -> int:\n \"\"\"\n Returns number of array dimensions.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array dimensions.\n \"\"\"\n return arr.ndim\n\n\ndef shape(arr: NadaArray) -> Tuple[int]:\n \"\"\"\n Returns Array shape.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array shape.\n \"\"\"\n return arr.shape\n\n\ndef size(arr: NadaArray) -> int:\n \"\"\"\n Returns array size.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array size.\n \"\"\"\n return arr.size\n\n\ndef to_nada(arr: np.ndarray, nada_type: NadaCleartextNumber) -> NadaArray:\n \"\"\"\n Converts a plain-text NumPy array to the equivalent NadaArray with\n a specified compatible NadaType.\n\n Args:\n arr (np.ndarray): Input Numpy array.\n nada_type (NadaCleartextNumber): Desired clear-text NadaType.\n\n Returns:\n NadaArray: Output NadaArray.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n else:\n arr = arr.astype(int)\n return NadaArray(np.frompyfunc(nada_type, 1, 1)(arr)) # type: ignore\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.pad)\ndef pad(\n arr: NadaArray,\n pad_width: Union[Sequence[int], int],\n mode: str = \"constant\",\n **kwargs,\n) -> NadaArray:\n if mode not in {\"constant\", \"edge\", \"reflect\", \"symmetric\", \"wrap\"}:\n raise NotImplementedError(\n f\"Not currently possible to pad NadaArray in mode `{mode}`\"\n )\n\n # Override python defaults by NadaType defaults\n overriden_kwargs = {}\n if mode == \"constant\" and \"constant_values\" not in kwargs:\n if arr.is_rational:\n default = rational(0)\n elif arr.is_integer:\n default = Integer(0)\n elif arr.is_unsigned_integer:\n default = UnsignedInteger(0)\n else:\n default = Boolean(False)\n\n overriden_kwargs[\"constant_values\"] = kwargs.get(\"constant_values\", default)\n\n padded_inner = np.pad( # type: ignore\n arr.inner,\n pad_width,\n mode,\n **overriden_kwargs,\n **kwargs,\n )\n\n return NadaArray(padded_inner)\n\n\n# pylint:disable=too-few-public-methods\nclass NadaCallable:\n \"\"\"Class that wraps a vectorized NumPy function\"\"\"\n\n def __init__(self, vfunc: Callable) -> None:\n \"\"\"\n Initialization.\n\n Args:\n vfunc (Callable): Vectorized function to wrap.\n \"\"\"\n self.vfunc = vfunc\n\n def __call__(self, *args, **kwargs) -> Any:\n \"\"\"\n Routes function call to wrapped vectorized function while\n ensuring any resulting NumPy arrays are converted to NadaArrays.\n\n Returns:\n Any: Function result.\n \"\"\"\n result = self.vfunc(*args, **kwargs)\n if isinstance(result, np.ndarray):\n return NadaArray(result)\n if isinstance(result, Sequence):\n return type(result)( # type: ignore\n NadaArray(value) if isinstance(value, np.ndarray) else value\n for value in result\n )\n return result\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.frompyfunc)\ndef frompyfunc(*args, **kwargs) -> NadaCallable:\n return NadaCallable(np.frompyfunc(*args, **kwargs))\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.vectorize)\ndef vectorize(*args, **kwargs) -> NadaCallable:\n return NadaCallable(np.vectorize(*args, **kwargs))\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.eye)\ndef eye(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.eye(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.arange)\ndef arange(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.arange(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.linspace)\ndef linspace(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.linspace(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.split)\ndef split(a: NadaArray, *args, **kwargs) -> List[NadaArray]:\n return [NadaArray(arr) for arr in np.split(a.inner, *args, **kwargs)]\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.compress)\ndef compress(a: NadaArray, *args, **kwargs):\n return a.compress(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.copy)\ndef copy(a: NadaArray, *args, **kwargs):\n return a.copy(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.cumprod)\ndef cumprod(a: NadaArray, *args, **kwargs):\n return a.cumprod(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.cumsum)\ndef cumsum(a: NadaArray, *args, **kwargs):\n return a.cumsum(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.diagonal)\ndef diagonal(a: NadaArray, *args, **kwargs):\n return a.diagonal(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.diagonal)\ndef mean(a: NadaArray, *args, **kwargs):\n return a.mean(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.prod)\ndef prod(a: NadaArray, *args, **kwargs):\n return a.prod(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.put)\ndef put(a: NadaArray, *args, **kwargs):\n return a.put(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.ravel)\ndef ravel(a: NadaArray, *args, **kwargs):\n return a.ravel(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.repeat)\ndef repeat(a: NadaArray, *args, **kwargs):\n return a.repeat(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.reshape)\ndef reshape(a: NadaArray, *args, **kwargs):\n return a.reshape(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.resize)\ndef resize(a: NadaArray, *args, **kwargs):\n return a.resize(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.squeeze)\ndef squeeze(a: NadaArray, *args, **kwargs):\n return a.squeeze(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring,redefined-builtin\n@copy_metadata(np.sum)\ndef sum(a: NadaArray, *args, **kwargs):\n return a.sum(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.swapaxes)\ndef swapaxes(a: NadaArray, *args, **kwargs):\n return a.swapaxes(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.take)\ndef take(a: NadaArray, *args, **kwargs):\n return a.take(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.trace)\ndef trace(a: NadaArray, *args, **kwargs):\n return a.trace(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.transpose)\ndef transpose(a: NadaArray, *args, **kwargs):\n return a.transpose(*args, **kwargs)\n\n\n# Non-linear functions\n\n\ndef sign(arr: NadaArray) -> \"NadaArray\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n return arr.sign()\n\n\ndef abs(arr: NadaArray) -> \"NadaArray\":\n \"\"\"Computes the absolute value\"\"\"\n return arr.abs()\n\n\ndef exp(arr: NadaArray, iterations: int = 8) -> \"NadaArray\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The exponential function is computed by choosing n = 2 ** d, where d is set to `iterations`.\n The calculation is performed by computing (1 + x / n) once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n NadaArray: The approximated value of the exponential function.\n \"\"\"\n return arr.exp(iterations=iterations)\n\n\ndef polynomial(arr: NadaArray, coefficients: list) -> \"NadaArray\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n NadaArray: The result of the polynomial function applied to the input x.\n \"\"\"\n return arr.polynomial(coefficients=coefficients)\n\n\ndef log(\n arr: NadaArray,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n) -> \"NadaArray\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation of exp.\n Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of Householder approximation).\n Defaults to 8.\n\n Returns:\n NadaArray: The approximate value of the natural logarithm.\n \"\"\"\n return arr.log(\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n\n\ndef reciprocal( # pylint: disable=too-many-arguments\n arr: NadaArray,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n # pylint:disable=duplicate-code\n return arr.reciprocal(\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n\n\ndef inv_sqrt(\n arr: NadaArray,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n return arr.inv_sqrt(initial=initial, iterations=iterations, method=method)\n\n\ndef sqrt(\n arr: NadaArray,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the inverse\n square root Newton-Raphson iterations. By default, this will be set to allow\n convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n return arr.sqrt(initial=initial, iterations=iterations, method=method)\n\n\n# Trigonometry\n\n\ndef cossin(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit through the\n formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[NadaArray, NadaArray]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n return arr.cossin(iterations=iterations)\n\n\ndef cos(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the cosine.\n \"\"\"\n return arr.cos(iterations=iterations)\n\n\ndef sin(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the sine.\n \"\"\"\n return arr.sin(iterations=iterations)\n\n\ndef tan(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the tan.\n \"\"\"\n return arr.tan(iterations=iterations)\n\n\n# Activation functions\n\n\ndef tanh(\n arr: NadaArray,\n chebyshev_terms: int = 32,\n method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n return arr.tanh(chebyshev_terms=chebyshev_terms, method=method)\n\n\ndef sigmoid(\n arr: NadaArray,\n chebyshev_terms: int = 32,\n method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses\n the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n return arr.sigmoid(chebyshev_terms=chebyshev_terms, method=method)\n\n\ndef gelu(\n arr: NadaArray,\n method: str = \"tanh\",\n tanh_method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n return arr.gelu(method=method, tanh_method=tanh_method)\n\n\ndef silu(\n arr: NadaArray,\n method_sigmoid: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses the\n identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n return arr.silu(method_sigmoid=method_sigmoid)\n\n\ndef shuffle(arr: NadaArray) -> NadaArray:\n \"\"\"\n Shuffles a 1D array using the Benes network.\n\n This function rearranges the elements of a 1-dimensional array in a deterministic but seemingly\n random order based on the Benes network, a network used in certain types of sorting and\n switching circuits. The Benes network requires the input array's length to be a power of two\n (e.g., 2, 4, 8, 16, ...).\n\n Note: The resulting shuffled arrays contain the same elements as the input arrays.\n\n Args:\n NadaArray: The input array to be shuffled. This must be a 1-dimensional NumPy array.\n The length of the array must be a power of two.\n\n Returns:\n NadaArray: The shuffled version of the input array. The output is a new array where\n the elements have been rearranged according to the Benes network.\n\n Raises:\n ValueError: If the length of the input array is not a power of two.\n\n Example:\n ```python\n import nada_numpy as na\n\n # Example arrays with different data types\n parties = na.parties(2)\n a = na.array([8], parties[0], \"A\", na.Rational)\n b = na.array([8], parties[0], \"B\", na.SecretRational)\n c = na.array([8], parties[0], \"C\", PublicInteger)\n d = na.array([8], parties[0], \"D\", SecretInteger)\n\n # Shuffling the arrays\n shuffled_a = shuffle(a)\n shuffled_b = shuffle(b)\n shuffled_c = shuffle(c)\n ```\n\n Frequency analysis:\n\n This script performs a frequency analysis of a shuffle function implemented using a Benes\n network. It includes a function for shuffle, a test function for evaluating randomness,\n and an example of running the test. Below is an overview of the code and its output.\n\n 1. **Shuffle Function**:\n\n The `shuffle` function shuffles a 1D array using a Benes network approach.\n The Benes network is defined by the function `_benes_network(n)`, which should provide the\n network stages required for the shuffle.\n\n ```python\n import numpy as np\n import random\n\n def rand_bool():\n # Simulates a random boolean value\n return random.choice([0, 1]) == 0\n\n def swap_gate(a, b):\n # Conditionally swaps two values based on a random boolean\n rbool = rand_bool()\n return (b, a) if rbool else (a, b)\n\n def shuffle(array):\n # Applies Benes network shuffle to a 1D array\n if array.ndim != 1:\n raise ValueError(\"Input array must be a 1D array.\")\n\n n = array.size\n bnet = benes_network(n)\n swap_array = np.ones(n)\n\n first_numbers = np.arange(0, n, 2)\n second_numbers = np.arange(1, n, 2)\n pairs = np.column_stack((first_numbers, second_numbers))\n\n for stage in bnet:\n for ((i0, i1), (a, b)) in zip(pairs, stage):\n swap_array[i0], swap_array[i1] = swap_gate(array[a], array[b])\n array = swap_array.copy()\n\n return array\n ```\n\n 2. **Randomness Test Function:**:\n The test_shuffle_randomness function evaluates the shuffle function by performing\n multiple shuffles and counting the occurrences of each element at each position.\n\n ```python\n def test_shuffle_randomness(vector_size, num_shuffles):\n # Initializes vector and count matrix\n vector = np.arange(vector_size)\n counts = np.zeros((vector_size, vector_size), dtype=int)\n\n # Performs shuffling and counts occurrences\n for _ in range(num_shuffles):\n shuffled_vector = shuffle(vector)\n for position, element in enumerate(shuffled_vector):\n counts[int(element), position] += 1\n\n # Computes average counts and deviation\n average_counts = num_shuffles / vector_size\n deviation = np.abs(counts - average_counts)\n\n return counts, average_counts, deviation\n ```\n\n\n Running the `test_shuffle_randomness` function with a vector size of 8 and 100,000 shuffles\n provides the following results:\n\n ```python\n vector_size = 8 # Size of the vector\n num_shuffles = 100000 # Number of shuffles to perform\n\n counts, average_counts, deviation = test_shuffle_randomness(vector_size,\n num_shuffles)\n\n print(\"Counts of numbers appearances at each position:\")\n for i in range(vector_size):\n print(f\"Number {i}: {counts[i]}\")\n print(\"Expected count of number per slot:\", average_counts)\n print(\"\\nDeviation from the expected average:\")\n for i in range(vector_size):\n print(f\"Number {i}: {deviation[i]}\")\n ```\n ```bash\n >>> Counts of numbers appearances at each position:\n >>> Number 0: [12477 12409 12611 12549 12361 12548 12591 12454]\n >>> Number 1: [12506 12669 12562 12414 12311 12408 12377 12753]\n >>> Number 2: [12595 12327 12461 12607 12492 12721 12419 12378]\n >>> Number 3: [12417 12498 12586 12433 12627 12231 12638 12570]\n >>> Number 4: [12370 12544 12404 12337 12497 12743 12588 12517]\n >>> Number 5: [12559 12420 12416 12791 12508 12489 12360 12457]\n >>> Number 6: [12669 12459 12396 12394 12757 12511 12423 12391]\n >>> Number 7: [12407 12674 12564 12475 12447 12349 12604 12480]\n >>> Expected count of number per slot: 12500.0\n >>>\n >>> Deviation from the expected average:\n >>> Number 0: [ 23. 91. 111. 49. 139. 48. 91. 46.]\n >>> Number 1: [ 6. 169. 62. 86. 189. 92. 123. 253.]\n >>> Number 2: [ 95. 173. 39. 107. 8. 221. 81. 122.]\n >>> Number 3: [ 83. 2. 86. 67. 127. 269. 138. 70.]\n >>> Number 4: [130. 44. 96. 163. 3. 243. 88. 17.]\n >>> Number 5: [ 59. 80. 84. 291. 8. 11. 140. 43.]\n >>> Number 6: [169. 41. 104. 106. 257. 11. 77. 109.]\n >>> Number 7: [ 93. 174. 64. 25. 53. 151. 104. 20.]\n ```\n \"\"\"\n return arr.shuffle()\n","list_scan_linear.py":"from nada_dsl import *\nimport nada_numpy as na\n\ndef is_number_present_in_list(array: List[SecretInteger], value: Integer) -> SecretBoolean:\n result = Integer(0)\n for element in array:\n # If the element is equal to the value, add 1 to the result.\n result += (value == element).if_else(Integer(1), Integer(0))\n return (result > Integer(0))\n\ndef nada_main():\n num_parties = 10\n parties = na.parties(num_parties)\n\n secrets_list = []\n for i in range(num_parties):\n secrets_list.append(\n SecretInteger(Input(name=\"num_\" + str(i), party=parties[i]))\n )\n\n # Check if 100 is a secret value in the list\n is_present_1 = is_number_present_in_list(secrets_list, Integer(100))\n\n # Check if 99 is a secret value in the list\n is_present_2 = is_number_present_in_list(secrets_list, Integer(99))\n\n return [\n Output(is_present_1, \"is_present_1\", party=parties[0]),\n Output(is_present_2, \"is_present_2\", party=parties[0]),\n ]\n","types.py":"\"\"\"Additional special data types\"\"\"\n\n# pylint:disable=too-many-lines\n\nimport functools\nimport warnings\nfrom typing import List, Optional, Tuple, Union\n\nimport nada_dsl as dsl\nimport numpy as np\nfrom nada_dsl import (Input, Integer, Party, PublicInteger,\n PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, UnsignedInteger)\n\n_NadaRational = Union[\"Rational\", \"SecretRational\"]\n\n_NadaType = Union[\n Integer,\n PublicInteger,\n PublicUnsignedInteger,\n SecretInteger,\n SecretUnsignedInteger,\n UnsignedInteger,\n]\n\n\nclass SecretBoolean(dsl.SecretBoolean):\n \"\"\"SecretBoolean rational wrapper\"\"\"\n\n def __init__(self, value: dsl.SecretBoolean) -> None:\n \"\"\"\n Initialization.\n\n Args:\n value (dsl.SecretBoolean): SecretBoolean value.\n \"\"\"\n super().__init__(value.inner)\n\n def if_else(\n self,\n true: Union[_NadaType, \"SecretRational\", \"Rational\"],\n false: Union[_NadaType, \"SecretRational\", \"Rational\"],\n ) -> Union[SecretInteger, SecretUnsignedInteger, \"SecretRational\"]:\n \"\"\"\n If-else logic. If the boolean is True, true is returned. If not, false is returned.\n\n Args:\n true (Union[_NadaType, SecretRational, Rational]): First argument.\n false (Union[_NadaType, SecretRational, Rational]): Second argument.\n\n Raises:\n ValueError: Raised when incompatibly-scaled values are passed.\n TypeError: Raised when invalid operation is called.\n\n Returns:\n Union[SecretInteger, SecretUnsignedInteger, \"SecretRational\"]: Return value.\n \"\"\"\n first_arg = true\n second_arg = false\n if isinstance(true, (SecretRational, Rational)) and isinstance(\n false, (SecretRational, Rational)\n ):\n # Both are SecretRational or Rational objects\n if true.log_scale != false.log_scale:\n raise ValueError(\"Cannot output values with different scales.\")\n first_arg = true.value\n second_arg = false.value\n elif isinstance(true, (Rational, SecretRational)) or isinstance(\n false, (Rational, SecretRational)\n ):\n # Both are SecretRational or Rational objects\n raise TypeError(f\"Invalid operation: {self}.IfElse({true}, {false})\")\n\n result = super().if_else(first_arg, second_arg)\n\n if isinstance(true, (SecretRational, Rational)):\n # If we have a SecretBoolean, the return type will be SecretInteger,\n # thus promoted to SecretRational\n return SecretRational(result, true.log_scale, is_scaled=True)\n return result\n\n\nclass PublicBoolean(dsl.PublicBoolean):\n \"\"\"PublicBoolean rational wrapper\"\"\"\n\n def __init__(self, value: dsl.PublicBoolean) -> None:\n \"\"\"\n Initialization.\n\n Args:\n value (dsl.PublicBoolean): PublicBoolean value.\n \"\"\"\n super().__init__(value.inner)\n\n def if_else(\n self,\n true: Union[_NadaType, \"SecretRational\", \"Rational\"],\n false: Union[_NadaType, \"SecretRational\", \"Rational\"],\n ) -> Union[\n PublicInteger,\n PublicUnsignedInteger,\n SecretInteger,\n SecretUnsignedInteger,\n \"Rational\",\n \"SecretRational\",\n ]:\n \"\"\"\n If-else logic. If the boolean is True, true is returned. If not, false is returned.\n\n Args:\n true (Union[_NadaType, SecretRational, Rational]): First argument.\n false (Union[_NadaType, SecretRational, Rational]): Second argument.\n\n Raises:\n ValueError: Raised when incompatibly-scaled values are passed.\n TypeError: Raised when invalid operation is called.\n\n Returns:\n Union[PublicInteger, PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, \"Rational\", \"SecretRational\"]: Return value.\n \"\"\"\n first_arg = true\n second_arg = false\n if isinstance(true, (SecretRational, Rational)) and isinstance(\n false, (SecretRational, Rational)\n ):\n # Both are SecretRational or Rational objects\n if true.log_scale != false.log_scale:\n raise ValueError(\"Cannot output values with different scales.\")\n first_arg = true.value\n second_arg = false.value\n elif isinstance(true, (Rational, SecretRational)) or isinstance(\n false, (Rational, SecretRational)\n ):\n # Both are SecretRational or Rational objects but of different type\n raise TypeError(f\"Invalid operation: {self}.IfElse({true}, {false})\")\n\n result = super().if_else(first_arg, second_arg)\n\n if isinstance(true, SecretRational) or isinstance(false, SecretRational):\n return SecretRational(result, true.log_scale, is_scaled=True)\n if isinstance(true, Rational) and isinstance(false, Rational):\n return Rational(result, true.log_scale, is_scaled=True)\n return result\n\n\nclass Rational: # pylint:disable=too-many-public-methods\n \"\"\"Wrapper class to store scaled Integer values representing a fixed-point number.\"\"\"\n\n def __init__(\n self,\n value: Union[Integer, PublicInteger],\n log_scale: Optional[int] = None,\n is_scaled: bool = True,\n ) -> None:\n \"\"\"\n Initializes wrapper around Integer object.\n\n Args:\n value (Union[Integer, PublicInteger]): The value to be representedas a Rational.\n log_scale (int, optional): Quantization scaling factor.\n Defaults to RationalConfig.log_scale.\n is_scaled (bool, optional): Flag that represents whether the value is already scaled.\n Defaults to True.\n\n Raises:\n TypeError: If value is of an incompatible type.\n \"\"\"\n if not isinstance(value, (Integer, PublicInteger)):\n raise TypeError(f\"Cannot instantiate Rational from type `{type(value)}`.\")\n\n if log_scale is None:\n log_scale = get_log_scale()\n self._log_scale = log_scale\n\n if is_scaled is False:\n value = value * Integer(\n 1 << log_scale\n ) # TODO: replace with shift when supported\n self._value = value\n\n @property\n def log_scale(self) -> int:\n \"\"\"\n Getter for the logarithmic scale value.\n\n Returns:\n int: Logarithmic scale value.\n \"\"\"\n return self._log_scale\n\n @property\n def value(self) -> Union[Integer, PublicInteger]:\n \"\"\"\n Getter for the underlying Integer value.\n\n Returns:\n Union[Integer, PublicInteger]: The Integer value.\n \"\"\"\n return self._value\n\n def add(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot add values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n other.value + self.value, self.log_scale, is_scaled=True\n )\n return Rational(self.value + other.value, self.log_scale, is_scaled=True)\n\n def __add__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def __iadd__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def sub(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to subtract.\n\n Returns:\n Union[Rational, SecretRational]: Result of the subtraction.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot substract values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value - other.value, self.log_scale, is_scaled=True\n )\n return Rational(self.value - other.value, self.log_scale, is_scaled=True)\n\n def __sub__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def __isub__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def mul_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers.\n\n WARNING: This function does not rescale by default. Use `mul` to multiply and rescale.\n\n Args:\n other (_NadaRational): Other rational number to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the multiplication.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot multiply values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value * other.value,\n self.log_scale + other.log_scale,\n is_scaled=True,\n )\n return Rational(\n self.value * other.value,\n self.log_scale + other.log_scale,\n is_scaled=True,\n )\n\n def mul(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers and rescale the result.\n\n Args:\n other (_NadaRational): Other rational number to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the multiplication.\n \"\"\"\n c = self.mul_no_rescale(other, ignore_scale=ignore_scale)\n d = c.rescale_down()\n return d\n\n def __mul__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def __imul__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def divide_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to divide by.\n\n Returns:\n Union[Rational, SecretRational]: Result of the division.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale + get_log_scale():\n raise ValueError(\n f\"Cannot divide values where scale is: {self.log_scale} / {other.log_scale}.\"\n f\"Required scale: {self.log_scale} / {other.log_scale + get_log_scale()}\"\n )\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value / other.value,\n self.log_scale - other.log_scale,\n is_scaled=True,\n )\n return Rational(\n self.value / other.value,\n self.log_scale - other.log_scale,\n is_scaled=True,\n )\n\n def divide(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers and rescale the result.\n\n Args:\n other (_NadaRational): Other rational number to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the division.\n \"\"\"\n a = self.rescale_up()\n c = a.divide_no_rescale(other, ignore_scale)\n return c\n\n def __truediv__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __itruediv__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __pow__(self, other: int) -> \"Rational\":\n \"\"\"\n Raise a rational number to an integer power using binary exponentiation.\n\n Args:\n other (int): The exponent.\n\n Returns:\n Rational: Result of the power operation.\n\n Raises:\n TypeError: If the exponent is not an integer.\n \"\"\"\n if not isinstance(other, int):\n raise TypeError(f\"Cannot raise Rational to a power of type `{type(other)}`\")\n\n result = Rational(Integer(1), self.log_scale, is_scaled=False)\n\n if other == 0:\n return result # Any number to the power of 0 is 1\n\n base = self\n\n exponent = abs(other)\n while exponent > 0:\n if exponent % 2 == 1:\n result = result * base # type: ignore\n base *= base # type: ignore\n exponent //= 2\n\n if other < 0:\n return rational(1) / Rational( # type: ignore\n result.value, result.log_scale, is_scaled=True\n )\n\n return result\n\n def __neg__(self) -> \"Rational\":\n \"\"\"\n Negate the Rational value.\n\n Returns:\n Rational: Negated Rational value.\n \"\"\"\n return Rational(self.value * Integer(-1), self.log_scale, is_scaled=True)\n\n def __lshift__(self, other: UnsignedInteger) -> \"Rational\":\n \"\"\"\n Left shift the Rational value.\n\n Args:\n other (UnsignedInteger): The value to left shift by.\n\n Returns:\n Rational: Left shifted Rational value.\n \"\"\"\n return Rational(self.value << other, self.log_scale)\n\n def __rshift__(self, other: UnsignedInteger) -> \"Rational\":\n \"\"\"\n Right shift the Rational value.\n\n Args:\n other (UnsignedInteger): The value to right shift by.\n\n Returns:\n Rational: Right shifted Rational value.\n \"\"\"\n return Rational(self.value >> other, self.log_scale)\n\n def __lt__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is less than another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value < other.value)\n return PublicBoolean(self.value < other.value)\n\n def __gt__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is greater than another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value > other.value)\n return PublicBoolean(self.value > other.value)\n\n def __le__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is less than or equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value <= other.value)\n return PublicBoolean(self.value <= other.value)\n\n def __ge__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is greater than or equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value >= other.value)\n return PublicBoolean(self.value >= other.value)\n\n def __eq__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]: # type: ignore\n \"\"\"\n Check if this Rational is equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value == other.value)\n return PublicBoolean(self.value == other.value)\n\n def __ne__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]: # type: ignore\n \"\"\"\n Check if this Rational is not equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value != other.value)\n return PublicBoolean(self.value != other.value)\n\n def rescale_up(self, log_scale: Optional[int] = None) -> \"Rational\":\n \"\"\"\n Rescale the value in the upward direction by a scaling factor.\n\n This is equivalent to multiplying the value by `2**(log_scale)`.\n\n Args:\n log_scale (int, optional): Scaling factor to rescale the value.\n Defaults to RationalConfig.log_scale.\n\n Returns:\n Rational: Rescaled Rational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return Rational(\n self._value\n * Integer(1 << log_scale), # TODO: replace with shift when supported\n self.log_scale + log_scale,\n is_scaled=True,\n )\n\n def rescale_down(self, log_scale: Optional[int] = None) -> \"Rational\":\n \"\"\"\n Rescale the value in the downward direction by a scaling factor.\n\n This is equivalent to dividing the value by `2**(log_scale)`.\n\n Args:\n log_scale (int, optional): Scaling factor to rescale the value.\n Defaults to RationalConfig.log_scale.\n\n Returns:\n Rational: Rescaled Rational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return Rational(\n self._value\n / Integer(1 << log_scale), # TODO: replace with shift when supported\n self.log_scale - log_scale,\n is_scaled=True,\n )\n\n # Non-linear functions\n\n def sign(self) -> \"Rational\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n result = sign(self)\n if not isinstance(result, Rational):\n raise TypeError(\"sign input should be of type Rational.\")\n return result\n\n def abs(self) -> \"Rational\":\n \"\"\"Computes the absolute value\"\"\"\n\n result = fxp_abs(self)\n if not isinstance(result, Rational):\n raise TypeError(\"abs input should be of type Rational.\")\n return result\n\n def exp(self, iterations: int = 8) -> \"Rational\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The function is computed by choosing n = 2 ** d, where d is set to `iterations`.\n The calculation is performed by computing (1 + x / n) once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n Rational: The approximated value of the exponential function.\n \"\"\"\n\n result = exp(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"exp input should be of type Rational.\")\n return result\n\n def polynomial(self, coefficients: list) -> \"Rational\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the\n highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n Rational: The result of the polynomial function applied to the input x.\n \"\"\"\n\n result = polynomial(self, coefficients=coefficients)\n if not isinstance(result, Rational):\n raise TypeError(\"polynomial input should be of type Rational.\")\n return result\n\n def log(\n self,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n ) -> \"Rational\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation\n of exp. Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of Householder\n approximation). Defaults to 8.\n\n Returns:\n Rational: The approximate value of the natural logarithm.\n \"\"\"\n\n result = log(\n self,\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n if not isinstance(result, Rational):\n raise TypeError(\"log input should be of type Rational.\")\n return result\n\n def reciprocal( # pylint: disable=too-many-arguments\n self,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n\n result = reciprocal(\n self,\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n if not isinstance(result, Rational):\n raise TypeError(\"reciprocal input should be of type Rational.\")\n return result\n\n def inv_sqrt(\n self,\n initial: Optional[\"Rational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[Rational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = inv_sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"inv_sqrt input should be of type Rational.\")\n return result\n\n def sqrt(\n self,\n initial: Optional[\"Rational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[Rational, None], optional): sets the initial value for the inverse\n square root Newton-Raphson iterations. By default, this will be set to allow\n convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"sqrt input should be of type Rational.\")\n return result\n\n # Trigonometry\n\n def cossin(self, iterations: int = 10) -> Tuple[\"Rational\", \"Rational\"]:\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit through\n the formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[Rational, Rational]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n result = cossin(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"cossin input should be of type Rational.\")\n return result\n\n def cos(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the cosine.\n \"\"\"\n\n result = cos(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"cos input should be of type Rational.\")\n return result\n\n def sin(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the sine.\n \"\"\"\n\n result = sin(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"sin input should be of type Rational.\")\n return result\n\n def tan(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the tan.\n \"\"\"\n\n result = tan(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"tan input should be of type Rational.\")\n return result\n\n # Activation functions\n\n def tanh(self, chebyshev_terms: int = 32, method: str = \"reciprocal\") -> \"Rational\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n Rational: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = tanh(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"tanh input should be of type Rational.\")\n return result\n\n def sigmoid(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"Rational\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses\n the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n Rational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = sigmoid(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"sigmoid input should be of type Rational.\")\n return result\n\n def gelu(self, method: str = \"tanh\", tanh_method: str = \"reciprocal\") -> \"Rational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n Rational: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = gelu(self, method=method, tanh_method=tanh_method)\n if not isinstance(result, Rational):\n raise TypeError(\"gelu input should be of type Rational.\")\n return result\n\n def silu(\n self,\n method_sigmoid: str = \"reciprocal\",\n ) -> \"Rational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n Rational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n\n if method_sigmoid is None:\n method_sigmoid = \"reciprocal\"\n\n result = silu(self, method_sigmoid=method_sigmoid)\n if not isinstance(result, Rational):\n raise TypeError(\"silu input should be of type Rational.\")\n return result\n\n\nclass SecretRational: # pylint:disable=too-many-public-methods\n \"\"\"Wrapper class to store scaled SecretInteger values representing a fixed-point number.\"\"\"\n\n def __init__(\n self,\n value: SecretInteger,\n log_scale: Optional[int] = None,\n is_scaled: bool = True,\n ) -> None:\n \"\"\"\n Initializes wrapper around SecretInteger object.\n The object should come scaled up by default otherwise precision may be lost.\n\n Args:\n value (SecretInteger): SecretInteger input value.\n log_scale (int, optional): Quantization scaling factor.\n Defaults to RationalConfig.log_scale.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Raises:\n TypeError: If value is of an incompatible type.\n \"\"\"\n if not isinstance(value, SecretInteger):\n raise TypeError(\n f\"Cannot instantiate SecretRational from type `{type(value)}`.\"\n )\n\n if log_scale is None:\n log_scale = get_log_scale()\n self._log_scale = log_scale\n\n if is_scaled is False:\n value = value << UnsignedInteger(log_scale)\n self._value = value\n\n @property\n def log_scale(self) -> int:\n \"\"\"\n Getter for the logarithmic scale value.\n\n Returns:\n int: Logarithmic scale value.\n \"\"\"\n return self._log_scale\n\n @property\n def value(self) -> SecretInteger:\n \"\"\"\n Getter for the underlying SecretInteger value.\n\n Returns:\n SecretInteger: The SecretInteger value.\n \"\"\"\n return self._value\n\n def add(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Add two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to add.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the addition.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot add values with different scales.\")\n\n return SecretRational(self.value + other.value, self.log_scale)\n\n def __add__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Add two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def __iadd__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Add two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def sub(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Subtract two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to subtract.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the subtraction.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot substract values with different scales.\")\n\n return SecretRational(self.value - other.value, self.log_scale)\n\n def __sub__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Subtract two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def __isub__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Subtract two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def mul_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Multiply two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the multiplication.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot multiply values with different scales.\")\n\n return SecretRational(\n self.value * other.value, self.log_scale + other.log_scale\n )\n\n def mul(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Multiply two SecretRational numbers and rescale the result.\n\n Args:\n other (_NadaRational): The other SecretRational to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n SecretRational: Result of the multiplication, rescaled.\n \"\"\"\n c = self.mul_no_rescale(other, ignore_scale=ignore_scale)\n if c is NotImplemented:\n # Note that, because this function would be executed under a NadaArray,\n # the NotImplemented value will be handled by the caller (in principle NadaArray)\n # The caller will then call the mul function of the NadaArray\n # The broadcasting will execute element-wise multiplication,\n # so rescale_down will be taken care by that function\n return c\n d = c.rescale_down()\n return d\n\n def __mul__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Multiply two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def __imul__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Multiply two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def divide_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Divide two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the division.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale + get_log_scale():\n raise ValueError(\n f\"Cannot divide values where scale is: {self.log_scale} / {other.log_scale}.\"\n f\"Required scale: {self.log_scale} / {other.log_scale + get_log_scale()}\"\n )\n\n return SecretRational(\n self.value / other.value, self.log_scale - other.log_scale\n )\n\n def divide(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Divide two SecretRational numbers and rescale the result.\n\n Args:\n other (_NadaRational): The other SecretRational to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n SecretRational: Result of the division, rescaled.\n \"\"\"\n # Note: If the other value is a NadaArray, the divide-no-rescale function will\n # return NotImplemented\n # This will cause that the divide function will return NotImplemented as well\n # The NotImplemented value will be handled by the caller (in principle NadaArray)\n # The caller will then call the divide function of the NadaArray\n # The rescale up, because there is no follow up, will not be taken into consideration.\n a = self.rescale_up()\n c = a.divide_no_rescale(other, ignore_scale=ignore_scale)\n return c\n\n def __truediv__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Divide two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __itruediv__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Divide two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __pow__(self, other: int) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Raise a SecretRational to an integer power using binary exponentiation.\n\n Args:\n other (int): The exponent.\n\n Raises:\n TypeError: If the exponent is not an integer.\n\n Returns:\n Union[Rational, SecretRational]: Result of the power operation.\n \"\"\"\n if not isinstance(other, int):\n raise TypeError(\n f\"Cannot raise SecretRational to a power of type `{type(other)}`\"\n )\n\n result = Rational(Integer(1), self.log_scale, is_scaled=False)\n\n if other == 0:\n return result # Any number to the power of 0 is 1\n\n base = self\n\n exponent = abs(other)\n while exponent > 0:\n if exponent % 2 == 1:\n result = result * base # type: ignore\n base *= base # type: ignore\n exponent //= 2\n\n if other < 0:\n return rational(1) / SecretRational( # type: ignore\n result.value, result.log_scale, is_scaled=True\n )\n\n return result\n\n def __neg__(self) -> \"SecretRational\":\n \"\"\"\n Negate the SecretRational value.\n\n Returns:\n SecretRational: Negated SecretRational value.\n \"\"\"\n return SecretRational(self.value * Integer(-1), self.log_scale)\n\n def __lshift__(self, other: UnsignedInteger) -> \"SecretRational\":\n \"\"\"\n Left shift the SecretRational value.\n\n Args:\n other (UnsignedInteger): The value to left shift by.\n\n Returns:\n SecretRational: Left shifted SecretRational value.\n \"\"\"\n return SecretRational(self.value << other, self.log_scale)\n\n def __rshift__(self, other: UnsignedInteger) -> \"SecretRational\":\n \"\"\"\n Right shift the SecretRational value.\n\n Args:\n other (UnsignedInteger): The value to right shift by.\n\n Returns:\n SecretRational: Right shifted SecretRational value.\n \"\"\"\n return SecretRational(self.value >> other, self.log_scale)\n\n def __lt__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is less than another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value < other.value)\n\n def __gt__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is greater than another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value > other.value)\n\n def __le__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is less than or equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value <= other.value)\n\n def __ge__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is greater than or equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value >= other.value)\n\n def __eq__(self, other: _NadaRational) -> SecretBoolean: # type: ignore\n \"\"\"\n Check if this SecretRational is equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value == other.value)\n\n def __ne__(self, other: _NadaRational) -> SecretBoolean: # type: ignore\n \"\"\"\n Check if this SecretRational is not equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value != other.value)\n\n def public_equals(self, other: _NadaRational) -> PublicBoolean:\n \"\"\"\n Check if this SecretRational is equal to another and reveal the result.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n PublicBoolean: Result of the comparison, revealed.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return self.value.public_equals(other.value)\n\n def reveal(self) -> Rational:\n \"\"\"\n Reveal the SecretRational value.\n\n Returns:\n Rational: Revealed SecretRational value.\n \"\"\"\n return Rational(self.value.reveal(), self.log_scale)\n\n def trunc_pr(self, arg_0: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Truncate the SecretRational value.\n\n Args:\n arg_0 (_NadaRational): The value to truncate by.\n\n Returns:\n SecretRational: Truncated SecretRational value.\n \"\"\"\n return SecretRational(self.value.trunc_pr(arg_0), self.log_scale)\n\n def rescale_up(self, log_scale: Optional[int] = None) -> \"SecretRational\":\n \"\"\"\n Rescale the SecretRational value upwards by a scaling factor.\n\n Args:\n log_scale (int, optional): The scaling factor. Defaults to RationalConfig.log_scale.\n\n Returns:\n SecretRational: Rescaled SecretRational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return SecretRational(\n self._value << UnsignedInteger(log_scale),\n self.log_scale + log_scale,\n is_scaled=True,\n )\n\n def rescale_down(self, log_scale: Optional[int] = None) -> \"SecretRational\":\n \"\"\"\n Rescale the SecretRational value downwards by a scaling factor.\n\n Args:\n log_scale (int, optional): The scaling factor. Defaults to RationalConfig.\n\n Returns:\n SecretRational: Rescaled SecretRational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return SecretRational(\n self._value >> UnsignedInteger(log_scale),\n self.log_scale - log_scale,\n is_scaled=True,\n )\n\n # Non-linear functions\n\n def sign(self) -> \"SecretRational\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n result = sign(self)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sign input should be of type SecretRational.\")\n return result\n\n def abs(self) -> \"SecretRational\":\n \"\"\"Computes the absolute value\"\"\"\n\n result = fxp_abs(self)\n if not isinstance(result, SecretRational):\n raise TypeError(\"abs input should be of type SecretRational.\")\n return result\n\n def exp(self, iterations: int = 8) -> \"SecretRational\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The exponential function is computed by choosing n = 2 ** d, where d is\n set to `iterations`. The calculation is performed by computing (1 + x / n)\n once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n SecretRational: The approximated value of the exponential function.\n \"\"\"\n\n result = exp(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"exp input should be of type SecretRational.\")\n return result\n\n def polynomial(self, coefficients: list) -> \"SecretRational\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the\n highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n SecretRational: The result of the polynomial function applied to the input x.\n \"\"\"\n\n result = polynomial(self, coefficients=coefficients)\n if not isinstance(result, SecretRational):\n raise TypeError(\"polynomial input should be of type SecretRational.\")\n return result\n\n def log(\n self,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n ) -> \"SecretRational\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation of\n exp. Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of\n Householder approximation). Defaults to 8.\n\n Returns:\n SecretRational: The approximate value of the natural logarithm.\n \"\"\"\n\n result = log(\n self,\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n if not isinstance(result, SecretRational):\n raise TypeError(\"log input should be of type SecretRational.\")\n return result\n\n def reciprocal( # pylint: disable=too-many-arguments\n self,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n\n result = reciprocal(\n self,\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n if not isinstance(result, SecretRational):\n raise TypeError(\"reciprocal input should be of type SecretRational.\")\n return result\n\n def inv_sqrt(\n self,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = inv_sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"inv_sqrt input should be of type SecretRational.\")\n return result\n\n def sqrt(\n self,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n inverse square root Newton-Raphson iterations. By default, this will be set\n to allow convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sqrt input should be of type SecretRational.\")\n return result\n\n # Trigonometry\n\n def cossin(self, iterations: int = 10) -> Tuple[\"SecretRational\", \"SecretRational\"]:\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit\n through the formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[SecretRational, SecretRational]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n result = cossin(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"cossin input should be of type SecretRational.\")\n return result\n\n def cos(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the cosine.\n \"\"\"\n\n result = cos(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"cos input should be of type SecretRational.\")\n return result\n\n def sin(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the sine.\n \"\"\"\n\n result = sin(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sin input should be of type SecretRational.\")\n return result\n\n def tan(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the tan.\n \"\"\"\n\n result = tan(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"tan input should be of type SecretRational.\")\n return result\n\n # Activation functions\n\n def tanh(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = tanh(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"tanh input should be of type SecretRational.\")\n return result\n\n def sigmoid(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = sigmoid(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sigmoid input should be of type SecretRational.\")\n return result\n\n def gelu(\n self, method: str = \"tanh\", tanh_method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = gelu(self, method=method, tanh_method=tanh_method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"gelu input should be of type SecretRational.\")\n return result\n\n def silu(\n self,\n method_sigmoid: str = \"reciprocal\",\n ) -> \"SecretRational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n\n result = silu(self, method_sigmoid=method_sigmoid)\n if not isinstance(result, SecretRational):\n raise TypeError(\"silu input should be of type SecretRational.\")\n return result\n\n\ndef secret_rational(\n name: str, party: Party, log_scale: Optional[int] = None, is_scaled: bool = True\n) -> SecretRational:\n \"\"\"\n Creates a SecretRational from a variable in the Nillion network.\n\n Args:\n name (str): Name of variable in Nillion network.\n party (Party): Name of party that provided variable.\n log_scale (int, optional): Quantization scaling factor. Defaults to None.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n SecretRational: Instantiated SecretRational object.\n \"\"\"\n value = SecretInteger(Input(name=name, party=party))\n return SecretRational(value, log_scale, is_scaled)\n\n\ndef public_rational(\n name: str, party: Party, log_scale: Optional[int] = None, is_scaled: bool = True\n) -> Rational:\n \"\"\"\n Creates a Rational from a variable in the Nillion network.\n\n Args:\n name (str): Name of variable in Nillion network.\n party (Party): Name of party that provided variable.\n log_scale (int, optional): Quantization scaling factor. Defaults to None.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n Rational: Instantiated Rational object.\n \"\"\"\n value = PublicInteger(Input(name=name, party=party))\n return Rational(value, log_scale, is_scaled)\n\n\ndef rational(\n value: Union[int, float, np.floating],\n log_scale: Optional[int] = None,\n is_scaled: bool = False,\n) -> Rational:\n \"\"\"\n Creates a Rational from a number variable.\n\n Args:\n value (Union[int, float, np.floating]): Provided input value.\n log_scale (int, optional): Quantization scaling factor. Defaults to default log_scale.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n Rational: Instantiated Rational object.\n \"\"\"\n if value == 0: # no use in rescaling 0\n return Rational(Integer(0), is_scaled=True)\n\n if log_scale is None:\n log_scale = get_log_scale()\n\n if isinstance(value, np.floating):\n value = value.item()\n if isinstance(value, int):\n return Rational(Integer(value), log_scale=log_scale, is_scaled=is_scaled)\n if isinstance(value, float):\n assert (\n is_scaled is False\n ), \"Got a value of type `float` with `is_scaled` set to True. This should never occur\"\n quantized = round(value * (1 << log_scale))\n return Rational(Integer(quantized), is_scaled=True)\n raise TypeError(f\"Cannot instantiate Rational from type `{type(value)}`.\")\n\n\nclass _MetaRationalConfig(type):\n \"\"\"Rational config metaclass that defines classproperties\"\"\"\n\n _log_scale: int\n _default_log_scale: int\n\n @property\n def default_log_scale(cls) -> int:\n \"\"\"\n Getter method.\n\n Returns:\n int: Default log scale.\n \"\"\"\n return cls._default_log_scale\n\n @property\n def log_scale(cls) -> int:\n \"\"\"\n Getter method.\n\n Returns:\n int: Log scale.\n \"\"\"\n return cls._log_scale\n\n @log_scale.setter\n def log_scale(cls, new_log_scale: int) -> None:\n \"\"\"\n Setter method.\n\n Args:\n new_log_scale (int): New log scale value to reset old value with.\n \"\"\"\n if new_log_scale <= 4:\n warnings.warn(\n f\"Provided log scale `{str(new_log_scale)}` is very low.\"\n \" Expected a value higher than 4.\"\n \" Using a low quantization scale can lead to poor quantization of rational values\"\n \" and thus poor performance & unexpected results.\"\n )\n if new_log_scale >= 64:\n warnings.warn(\n f\"Provided log scale `{str(new_log_scale)}` is very high.\"\n \" Expected a value lower than 64.\"\n \" Using a high quantization scale can lead to overflows & unexpected results.\"\n )\n\n cls._log_scale = new_log_scale\n\n\n# pylint:disable=too-few-public-methods\nclass _RationalConfig(metaclass=_MetaRationalConfig):\n \"\"\"Rational config data class\"\"\"\n\n _default_log_scale: int = 16\n _log_scale: int = _default_log_scale\n\n\ndef set_log_scale(new_log_scale: int) -> None:\n \"\"\"\n Sets the default Rational log scaling factor to a new value.\n Note that this value is the LOG scale and will be used as a base-2 exponent\n during quantization.\n\n Args:\n new_log_scale (int): New log scaling factor.\n \"\"\"\n if not isinstance(new_log_scale, int):\n raise TypeError(\n f\"Cannot set log scale to type `{type(new_log_scale)}`. Expected `int`.\"\n )\n _RationalConfig.log_scale = new_log_scale\n\n\ndef get_log_scale() -> int:\n \"\"\"\n Gets the Rational log scaling factor\n Note that this value is the LOG scale and is used as a base-2 exponent during quantization.\n\n Returns:\n int: Current log scale in use.\n \"\"\"\n return _RationalConfig.log_scale\n\n\ndef reset_log_scale() -> None:\n \"\"\"Resets the Rational log scaling factor to the original default value\"\"\"\n _RationalConfig.log_scale = _RationalConfig.default_log_scale\n\n\n# Fixed-point math operations\n\n# Copyright (c) Facebook, Inc. and its affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n#\n# Part of the code is from the CrypTen Facebook Project:\n# https://github.com/facebookresearch/CrypTen/blob/main/crypten/common/functions/logic.py\n# https://github.com/facebookresearch/CrypTen/blob/main/crypten/common/functions/approximations.py\n#\n# Modifications:\n# July, 2024\n# - Nada datatypes.\n# - Relative accuracy documentation.\n# - Some performance improvements.\n# - Fixed Tanh Chebyshev method by changing '_hardtanh' implementation.\n# - Tan.\n# - Motzkin's prolynomial preprocessing approach.\n# - GeLU and SiLU functions.\n\n\ndef sign(x: _NadaRational) -> _NadaRational:\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n ltz_cond = x < rational(0)\n ltz = ltz_cond.if_else(rational(1), rational(0))\n\n return rational(1) - ltz - ltz\n\n\ndef fxp_abs(x: _NadaRational) -> _NadaRational:\n \"\"\"Computes the absolute value of a rational\"\"\"\n return x * sign(x)\n\n\ndef exp(x: _NadaRational, iterations: int = 8) -> _NadaRational:\n \"\"\"\n Approximates the exponential function using a limit approximation.\n \"\"\"\n\n iters_na = UnsignedInteger(iterations)\n\n result = rational(1) + (x >> iters_na)\n for _ in range(iterations):\n result = result**2\n return result\n\n\ndef polynomial(x: _NadaRational, coefficients: List[Rational]) -> _NadaRational:\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the highest order term.\n **Note: The constant term is not included.**\n \"\"\"\n result = coefficients[0] * x\n\n for power, coeff in enumerate(coefficients[1:], start=2):\n result += coeff * (x**power)\n\n return result\n\n\ndef log(\n x: _NadaRational,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n) -> _NadaRational:\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n \"\"\"\n\n if input_in_01:\n return log(\n x * rational(100),\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n ) - rational(4.605170)\n\n # Initialization to a decent estimate (found by qualitative inspection):\n # ln(x) = x/120 - 20exp(-2x - 1.0) + 3.0\n term1 = x * rational(1 / 120.0)\n term2 = exp(-x - x - rational(1), iterations=exp_iterations) * rational(20)\n y = term1 - term2 + rational(3.0)\n\n # 8th order Householder iterations\n for _ in range(iterations):\n h = rational(1) - x * exp(-y, iterations=exp_iterations)\n y -= polynomial(h, [rational(1 / (i + 1)) for i in range(order)])\n return y\n\n\ndef reciprocal( # pylint: disable=too-many-arguments\n x: _NadaRational,\n all_pos: bool = False,\n initial: Optional[Rational] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n \"\"\"\n if input_in_01:\n rec = reciprocal(\n x * rational(64),\n method=method,\n all_pos=True,\n initial=initial,\n iterations=iterations,\n ) * rational(64)\n return rec\n\n if not all_pos:\n sgn = sign(x)\n pos = sgn * x\n return sgn * reciprocal(\n pos, method=method, all_pos=True, initial=initial, iterations=iterations\n )\n\n if method == \"NR\":\n if initial is None:\n # Initialization to a decent estimate (found by qualitative inspection):\n # 1/x = 3exp(1 - 2x) + 0.003\n result = rational(3) * exp(\n rational(1) - x - x, iterations=exp_iters\n ) + rational(0.003)\n else:\n result = initial\n for _ in range(iterations):\n result = result + result - result * result * x\n return result\n if method == \"log\":\n return exp(-log(x, iterations=log_iters), iterations=exp_iters)\n raise ValueError(f\"Invalid method {method} given for reciprocal function\")\n\n\ndef inv_sqrt(\n x: _NadaRational,\n initial: Optional[Union[_NadaRational, None]] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n \"\"\"\n\n if method == \"NR\":\n if initial is None:\n # Initialization to a decent estimate (found by qualitative inspection):\n # exp(- x/2 - 0.2) * 2.2 + 0.2 - x/1024\n y = exp(-(x >> UnsignedInteger(1)) - rational(0.2)) * rational(\n 2.2\n ) + rational(0.2)\n y -= x >> UnsignedInteger(10) # div by 1024\n else:\n y = initial\n\n # Newton Raphson iterations for inverse square root\n for _ in range(iterations):\n y = (y * (rational(3) - x * y * y)) >> UnsignedInteger(1)\n return y\n raise ValueError(f\"Invalid method {method} given for inv_sqrt function\")\n\n\ndef sqrt(\n x: _NadaRational,\n initial: Union[_NadaRational, None] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n \"\"\"\n\n if method == \"NR\":\n return inv_sqrt(x, initial=initial, iterations=iterations, method=method) * x\n\n raise ValueError(f\"Invalid method {method} given for sqrt function\")\n\n\n# Trigonometry\n\n\ndef _eix(x: _NadaRational, iterations: int = 10) -> Tuple[_NadaRational, _NadaRational]:\n r\"\"\"Computes e^(i * x) where i is the imaginary unit through the formula:\n\n .. math::\n Re\\{e^{i * x}\\}, Im\\{e^{i * x}\\} = \\cos(x), \\sin(x)\n\n Args:\n x (Union[Rational, SecretRational]): the input value.\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[Union[Rational, SecretRational], Union[Rational, SecretRational]]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n one = rational(1)\n im = x >> UnsignedInteger(iterations)\n\n # First iteration uses knowledge that `re` is public and = 1\n re = one - im * im\n im *= rational(2)\n\n # Compute (a + bi)^2 -> (a^2 - b^2) + (2ab)i `iterations` times\n for _ in range(iterations - 1):\n a2 = re * re\n b2 = im * im\n im = im * re\n im *= rational(2)\n re = a2 - b2\n\n return re, im\n\n\ndef cossin(\n x: _NadaRational, iterations: int = 10\n) -> Tuple[_NadaRational, _NadaRational]:\n r\"\"\"\n Computes cosine and sine through e^(i * x) where i is the imaginary unit.\n \"\"\"\n return _eix(x, iterations=iterations)\n\n\ndef cos(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the cosine of x using cos(x) = Re{exp(i * x)}.\n \"\"\"\n return cossin(x, iterations=iterations)[0]\n\n\ndef sin(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the sine of x using sin(x) = Im{exp(i * x)}.\n \"\"\"\n return cossin(x, iterations=iterations)[1]\n\n\ndef tan(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the tan of x using tan(x) = sin(x) / cos(x).\n \"\"\"\n c, s = cossin(x, iterations=iterations)\n return s * reciprocal(c)\n\n\n# Activation functions\n\n\n@functools.lru_cache(maxsize=10)\ndef chebyshev_series(func, width, terms):\n \"\"\"\n Computes Chebyshev coefficients.\n \"\"\"\n n_range = np.arange(start=0, stop=terms, dtype=float)\n x = width * np.cos((n_range + 0.5) * np.pi / terms)\n y = func(x)\n cos_term = np.cos(np.outer(n_range, n_range + 0.5) * np.pi / terms)\n coeffs = (2 / terms) * np.sum(y * cos_term, axis=1)\n return coeffs\n\n\ndef tanh(\n x: _NadaRational, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the hyperbolic tangent function.\n \"\"\"\n\n if method == \"reciprocal\":\n return sigmoid(x + x, method=method) * rational(2) - rational(1)\n if method == \"chebyshev\":\n coeffs = chebyshev_series(np.tanh, 1, chebyshev_terms)[1::2]\n # transform np.array of float to na.array of rationals\n coeffs = np.vectorize(rational)(coeffs)\n out = _chebyshev_polynomials(x, chebyshev_terms).transpose() @ coeffs\n # truncate outside [-maxval, maxval]\n return _hardtanh(x, out)\n if method == \"motzkin\":\n # Using approximation from \"BOLT: Privacy-Preserving, Accurate and Efficient\n # Inference for Transformers\"\n # section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n # ltz is used for absolute value of x and to compute sign (used to generate result).\n # We don't use 'abs' and 'sign' functions to avoid computing ltz twice.\n # sign = 1 - 2 * ltz, where ltz = (x < rational(0)).if_else(rational(1), rational(0))\n sgn = rational(1) - rational(2) * (x < rational(0)).if_else(\n rational(1), rational(0)\n )\n # absolute value\n abs_x = x * sgn\n\n # Motzkin’s polynomial preprocessing\n t0 = rational(-4.259314087994767)\n t1 = rational(18.86353816972803)\n t2 = rational(-36.42402897526823)\n t3 = rational(-0.013232131886235352)\n t4 = rational(-3.3289339650097993)\n t5 = rational(-0.0024920889620412097)\n tanh_p0 = (abs_x + t0) * abs_x + t1\n tanh_p1 = (tanh_p0 + abs_x + t2) * tanh_p0 * t3 * abs_x + t4 * abs_x + t5\n\n return (abs_x > rational(2.855)).if_else(sgn, sgn * tanh_p1)\n raise ValueError(f\"Unrecognized method {method} for tanh\")\n\n\n### Auxiliary functions for tanh\n\n\ndef _chebyshev_polynomials(x: _NadaRational, terms: int) -> np.ndarray:\n \"\"\"Evaluates odd degree Chebyshev polynomials at x.\n\n Chebyshev Polynomials of the first kind are defined as:\n\n .. math::\n P_0(x) = 1, P_1(x) = x, P_n(x) = 2 P_{n - 1}(x) - P_{n-2}(x)\n\n Args:\n x (Union[\"Rational\", \"SecretRational\"]): input at which polynomials are evaluated\n terms (int): highest degree of Chebyshev polynomials.\n Must be even and at least 6.\n Returns:\n NadaArray of polynomials evaluated at x of shape `(terms, *x)`.\n\n Raises:\n ValueError: Raised if 'terrms' is odd and < 6.\n \"\"\"\n if terms % 2 != 0 or terms < 6:\n raise ValueError(\"Chebyshev terms must be even and >= 6\")\n\n # Initiate base polynomials\n # P_0\n # polynomials = np.array([x])\n # y = rational(4) * x * x - rational(2)\n # z = y - rational(1)\n # # P_1\n # polynomials = np.append(polynomials, z * x)\n\n # # Generate remaining Chebyshev polynomials using the recurrence relation\n # for k in range(2, terms // 2):\n # next_polynomial = y * polynomials[k - 1] - polynomials[k - 2]\n # polynomials = np.append(polynomials, next_polynomial)\n\n # return polynomials\n\n polynomials = [x]\n y = rational(4) * x * x - rational(2)\n z = y - rational(1)\n # P_1\n polynomials.append(z * x)\n\n # Generate remaining Chebyshev polynomials using the recurrence relation\n for k in range(2, terms // 2):\n next_polynomial = y * polynomials[k - 1] - polynomials[k - 2]\n polynomials.append(next_polynomial)\n\n return np.array(polynomials)\n\n\ndef _hardtanh(\n x: _NadaRational,\n output: _NadaRational,\n abs_const: _NadaRational = rational(1),\n abs_range: _NadaRational = rational(1),\n) -> _NadaRational:\n r\"\"\"Applies the HardTanh function element-wise.\n\n HardTanh is defined as:\n\n .. math::\n \\text{HardTanh}(x) = \\begin{cases}\n 1 & \\text{ if } x > 1 \\\\\n -1 & \\text{ if } x < -1 \\\\\n Tanh(x) & \\text{ otherwise } \\\\\n \\end{cases}\n\n The range of the linear region :math:`[-1, 1]` can be adjusted using\n :attr:`abs_range`.\n\n Args:\n x (Union[Rational, SecretRational]): the input value of the Tanh.\n output (Union[Rational, SecretRational]): the output value of the approximation of Tanh.\n abs_const (Union[Rational, SecretRational]): constant value to which |Tanh(x)| converges.\n Defaults to 1.\n abs_range (Union[Rational, SecretRational]): absolute value of the range. Defaults to 1.\n\n Returns:\n Union[Rational, SecretRational]: HardTanh output.\n \"\"\"\n # absolute value\n sgn = sign(x)\n abs_x = x * sgn\n # chekc if inside [-abs_range, abs_range] interval\n ineight_cond = abs_x < abs_range\n result = ineight_cond.if_else(output, abs_const * sgn)\n\n return result\n\n\n### End of auxiliary functions for tanh\n\n\ndef sigmoid(\n x: _NadaRational, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the sigmoid function.\n \"\"\"\n if method == \"chebyshev\":\n tanh_approx = tanh(\n x >> UnsignedInteger(1), method=method, chebyshev_terms=chebyshev_terms\n )\n return (tanh_approx >> UnsignedInteger(1)) + rational(0.5)\n if method == \"motzkin\":\n tanh_approx = tanh(\n x >> UnsignedInteger(1), method=method, chebyshev_terms=chebyshev_terms\n )\n return (tanh_approx >> UnsignedInteger(1)) + rational(0.5)\n if method == \"reciprocal\":\n # ltz is used for absolute value of x and to generate 'result'.\n # We don't use 'abs' function to avoid computing ltz twice.\n ltz_cond = x < rational(0)\n ltz = ltz_cond.if_else(rational(1), rational(0))\n # compute absolute value of x\n sgn = rational(1) - rational(2) * ltz\n pos_x = x * sgn\n\n denominator = exp(-pos_x) + rational(1)\n pos_output = reciprocal(\n denominator, all_pos=True, initial=rational(0.75), iterations=3, exp_iters=9\n )\n\n # result is equivalent to (1 - ltz).if_else(pos_output, 1 - pos_output)\n result = pos_output + ltz - rational(2) * pos_output * ltz\n return result\n raise ValueError(f\"Unrecognized method {method} for sigmoid\")\n\n\ndef gelu(\n x: _NadaRational, method: str = \"tanh\", tanh_method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the gelu function.\n \"\"\"\n\n if method == \"tanh\":\n # Using common approximation:\n # x/2 * (1 + tanh(0.797884560 * ( x + 0.04471 * x ** 3 ) ) )\n # where 0.797884560 ~= sqrt(2/pi).\n val = rational(0.797884560) * (x + rational(0.044715) * x**3)\n return (x * (rational(1) + tanh(val, method=tanh_method))) >> UnsignedInteger(1)\n if method == \"motzkin\":\n # Using approximation from \"BOLT: Privacy-Preserving, Accurate and Efficient\n # Inference for Transformers\"\n # section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n # ltz is used for absolute value of x and to compute relu.\n # We don't use 'abs' and '_relu' functions to avoid computing ltz twice.\n ltz = (x < rational(0)).if_else(rational(1), rational(0))\n # absolute value\n sgn = rational(1) - rational(2) * ltz\n abs_x = x * sgn\n # relu\n relu = x * (rational(1) - ltz)\n\n # Motzkin’s polynomial preprocessing\n g0 = rational(0.14439048359960427)\n g1 = rational(-0.7077117131613893)\n g2 = rational(4.5702822654246535)\n g3 = rational(-8.15444702051307)\n g4 = rational(16.382265425072532)\n gelu_p0 = (g0 * abs_x + g1) * abs_x + g2\n gelu_p1 = (gelu_p0 + g0 * abs_x + g3) * gelu_p0 + g4 + (x >> UnsignedInteger(1))\n\n return (abs_x > rational(2.7)).if_else(relu, gelu_p1)\n raise ValueError(f\"Unrecognized method {method} for gelu\")\n\n\ndef silu(\n x: _NadaRational,\n method_sigmoid: str = \"reciprocal\",\n) -> _NadaRational:\n \"\"\"\n Computes the gelu function\n \"\"\"\n return x * sigmoid(x, method=method_sigmoid)\n"},"source_refs":[{"file":"list_scan_linear.py","lineno":9,"offset":328,"length":32},{"file":"list_scan_linear.py","lineno":8,"offset":259,"length":68},{"file":"list_scan_linear.py","lineno":18,"offset":523,"length":72},{"file":"list_scan_linear.py","lineno":22,"offset":656,"length":72},{"file":"list_scan_linear.py","lineno":5,"offset":140,"length":23},{"file":"list_scan_linear.py","lineno":28,"offset":864,"length":63},{"file":"list_scan_linear.py","lineno":25,"offset":778,"length":71},{"file":"list_scan_linear.py","lineno":29,"offset":928,"length":63},{"file":"funcs.py","lineno":107,"offset":2341,"length":65}]}
\ No newline at end of file
diff --git a/streamlit_demo_apps/compiled_nada_programs/multiplication.nada.bin b/streamlit_demo_apps/compiled_nada_programs/multiplication.nada.bin
deleted file mode 100644
index ef670e9..0000000
Binary files a/streamlit_demo_apps/compiled_nada_programs/multiplication.nada.bin and /dev/null differ
diff --git a/streamlit_demo_apps/compiled_nada_programs/multiplication.nada.json b/streamlit_demo_apps/compiled_nada_programs/multiplication.nada.json
deleted file mode 100644
index 4c58a03..0000000
--- a/streamlit_demo_apps/compiled_nada_programs/multiplication.nada.json
+++ /dev/null
@@ -1 +0,0 @@
-{"functions":[],"parties":[{"name":"Alice","source_ref_index":4},{"name":"Bob","source_ref_index":5},{"name":"Charlie","source_ref_index":6}],"inputs":[{"type":"SecretInteger","party":"Alice","name":"num_1","doc":"","source_ref_index":2},{"type":"SecretInteger","party":"Bob","name":"num_2","doc":"","source_ref_index":1}],"literals":[],"outputs":[{"name":"product","operation_id":4343869184,"party":"Charlie","type":"SecretInteger","source_ref_index":3}],"operations":{"4343869184":{"Multiplication":{"id":4343869184,"left":4343868464,"right":4339294304,"type":"SecretInteger","source_ref_index":0}},"4339294304":{"InputReference":{"id":4339294304,"refers_to":"num_2","type":"SecretInteger","source_ref_index":1}},"4343868464":{"InputReference":{"id":4343868464,"refers_to":"num_1","type":"SecretInteger","source_ref_index":2}}},"source_files":{"multiplication.py":"from nada_dsl import *\n\ndef nada_main():\n party_alice = Party(name=\"Alice\")\n party_bob = Party(name=\"Bob\")\n party_charlie = Party(name=\"Charlie\")\n num_1 = SecretInteger(Input(name=\"num_1\", party=party_alice))\n num_2 = SecretInteger(Input(name=\"num_2\", party=party_bob))\n product = num_1 * num_2\n return [Output(product, \"product\", party_charlie)]"},"source_refs":[{"file":"multiplication.py","lineno":9,"offset":285,"length":27},{"file":"multiplication.py","lineno":8,"offset":221,"length":63},{"file":"multiplication.py","lineno":7,"offset":155,"length":65},{"file":"multiplication.py","lineno":10,"offset":0,"length":0},{"file":"multiplication.py","lineno":4,"offset":41,"length":37},{"file":"multiplication.py","lineno":5,"offset":79,"length":33},{"file":"multiplication.py","lineno":6,"offset":113,"length":41}]}
\ No newline at end of file
diff --git a/streamlit_demo_apps/compiled_nada_programs/voting.nada.bin b/streamlit_demo_apps/compiled_nada_programs/voting.nada.bin
index 6bdfe9a..dd4fc1b 100644
Binary files a/streamlit_demo_apps/compiled_nada_programs/voting.nada.bin and b/streamlit_demo_apps/compiled_nada_programs/voting.nada.bin differ
diff --git a/streamlit_demo_apps/compiled_nada_programs/voting.nada.json b/streamlit_demo_apps/compiled_nada_programs/voting.nada.json
index f86f0da..de3426d 100644
--- a/streamlit_demo_apps/compiled_nada_programs/voting.nada.json
+++ b/streamlit_demo_apps/compiled_nada_programs/voting.nada.json
@@ -1 +1 @@
-{"functions":[],"parties":[{"name":"Official","source_ref_index":6},{"name":"Voter0","source_ref_index":7},{"name":"Voter1","source_ref_index":7},{"name":"Voter2","source_ref_index":7},{"name":"Voter3","source_ref_index":7},{"name":"Voter4","source_ref_index":7},{"name":"Voter5","source_ref_index":7},{"name":"Voter6","source_ref_index":7},{"name":"Voter7","source_ref_index":7}],"inputs":[{"type":"SecretInteger","party":"Official","name":"vote_start_count","doc":"","source_ref_index":4},{"type":"SecretInteger","party":"Voter0","name":"vote_0","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter1","name":"vote_1","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter2","name":"vote_2","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter3","name":"vote_3","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter4","name":"vote_4","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter5","name":"vote_5","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter6","name":"vote_6","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter7","name":"vote_7","doc":"","source_ref_index":3}],"literals":[{"name":"0420609fa1d35394f41049df03ef341f","value":"0","type":"Integer"},{"name":"10d33944d37d5b1b833be6fd73d3033c","value":"1","type":"Integer"},{"name":"be9dc3499e38754968f0ed1e2d88d815","value":"2","type":"Integer"},{"name":"eb53046d942e0370244802bab2e0909f","value":"3","type":"Integer"}],"outputs":[{"name":"kamala_harris_votes","operation_id":4387952192,"party":"Official","type":"SecretInteger","source_ref_index":5},{"name":"donald_trump_votes","operation_id":4387995824,"party":"Official","type":"SecretInteger","source_ref_index":5},{"name":"rfk_jr_votes","operation_id":4388006480,"party":"Official","type":"SecretInteger","source_ref_index":5}],"operations":{"4387957040":{"LiteralReference":{"id":4387957040,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387953056":{"LiteralReference":{"id":4387953056,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387961216":{"LiteralReference":{"id":4387961216,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387950032":{"Equals":{"id":4387950032,"left":4387925856,"right":4387923360,"type":"SecretBoolean","source_ref_index":1}},"4387995824":{"Addition":{"id":4387995824,"left":4387961696,"right":4387962752,"type":"SecretInteger","source_ref_index":0}},"4387960400":{"Addition":{"id":4387960400,"left":4387959104,"right":4387960160,"type":"SecretInteger","source_ref_index":0}},"4387955456":{"Equals":{"id":4387955456,"left":4387924896,"right":4387952480,"type":"SecretBoolean","source_ref_index":1}},"4387996064":{"LiteralReference":{"id":4387996064,"refers_to":"eb53046d942e0370244802bab2e0909f","type":"Integer","source_ref_index":2}},"4387998944":{"Equals":{"id":4387998944,"left":4387924896,"right":4387996064,"type":"SecretBoolean","source_ref_index":1}},"4387959344":{"Equals":{"id":4387959344,"left":4387925616,"right":4387952480,"type":"SecretBoolean","source_ref_index":1}},"4387926384":{"Equals":{"id":4387926384,"left":4387924128,"right":4387923360,"type":"SecretBoolean","source_ref_index":1}},"4388001056":{"IfElse":{"id":4388001056,"this":4388000240,"arg_0":4388000528,"arg_1":4388000816,"type":"SecretInteger","source_ref_index":1}},"4388003648":{"IfElse":{"id":4388003648,"this":4388002832,"arg_0":4388003120,"arg_1":4388003408,"type":"SecretInteger","source_ref_index":1}},"4387954448":{"LiteralReference":{"id":4387954448,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387953632":{"IfElse":{"id":4387953632,"this":4387952720,"arg_0":4387953056,"arg_1":4387953392,"type":"SecretInteger","source_ref_index":1}},"4387958336":{"LiteralReference":{"id":4387958336,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387929744":{"Addition":{"id":4387929744,"left":4387928544,"right":4387929504,"type":"SecretInteger","source_ref_index":0}},"4387962224":{"LiteralReference":{"id":4387962224,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387929984":{"Equals":{"id":4387929984,"left":4387925136,"right":4387923360,"type":"SecretBoolean","source_ref_index":1}},"4388001536":{"Equals":{"id":4388001536,"left":4387925376,"right":4387996064,"type":"SecretBoolean","source_ref_index":1}},"4387999760":{"IfElse":{"id":4387999760,"this":4387998944,"arg_0":4387999232,"arg_1":4387999520,"type":"SecretInteger","source_ref_index":1}},"4387962512":{"LiteralReference":{"id":4387962512,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387946960":{"LiteralReference":{"id":4387946960,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4388003120":{"LiteralReference":{"id":4388003120,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387947920":{"LiteralReference":{"id":4387947920,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387947392":{"Addition":{"id":4387947392,"left":4387929744,"right":4387947152,"type":"SecretInteger","source_ref_index":0}},"4388002352":{"IfElse":{"id":4388002352,"this":4388001536,"arg_0":4388001824,"arg_1":4388002112,"type":"SecretInteger","source_ref_index":1}},"4387999520":{"LiteralReference":{"id":4387999520,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387948160":{"LiteralReference":{"id":4387948160,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387952192":{"Addition":{"id":4387952192,"left":4387950992,"right":4387951952,"type":"SecretInteger","source_ref_index":0}},"4387950320":{"LiteralReference":{"id":4387950320,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4388002592":{"Addition":{"id":4388002592,"left":4388001296,"right":4388002352,"type":"SecretInteger","source_ref_index":0}},"4387929072":{"LiteralReference":{"id":4387929072,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387949360":{"LiteralReference":{"id":4387949360,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4388006000":{"LiteralReference":{"id":4388006000,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387926096":{"InputReference":{"id":4387926096,"refers_to":"vote_7","type":"SecretInteger","source_ref_index":3}},"4388000528":{"LiteralReference":{"id":4388000528,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4388005184":{"Addition":{"id":4388005184,"left":4388003888,"right":4388004944,"type":"SecretInteger","source_ref_index":0}},"4387951760":{"LiteralReference":{"id":4387951760,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4388006240":{"IfElse":{"id":4388006240,"this":4388005424,"arg_0":4388005712,"arg_1":4388006000,"type":"SecretInteger","source_ref_index":1}},"4387949792":{"Addition":{"id":4387949792,"left":4387948592,"right":4387949552,"type":"SecretInteger","source_ref_index":0}},"4387951520":{"LiteralReference":{"id":4387951520,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387960160":{"IfElse":{"id":4387960160,"this":4387959344,"arg_0":4387959632,"arg_1":4387959920,"type":"SecretInteger","source_ref_index":1}},"4387956272":{"IfElse":{"id":4387956272,"this":4387955456,"arg_0":4387955744,"arg_1":4387956032,"type":"SecretInteger","source_ref_index":1}},"4387957568":{"IfElse":{"id":4387957568,"this":4387956752,"arg_0":4387957040,"arg_1":4387957328,"type":"SecretInteger","source_ref_index":1}},"4387924896":{"InputReference":{"id":4387924896,"refers_to":"vote_2","type":"SecretInteger","source_ref_index":3}},"4387955744":{"LiteralReference":{"id":4387955744,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387953392":{"LiteralReference":{"id":4387953392,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387924128":{"InputReference":{"id":4387924128,"refers_to":"vote_0","type":"SecretInteger","source_ref_index":3}},"4387952720":{"Equals":{"id":4387952720,"left":4387924128,"right":4387952480,"type":"SecretBoolean","source_ref_index":1}},"4388004944":{"IfElse":{"id":4388004944,"this":4388004128,"arg_0":4388004416,"arg_1":4388004704,"type":"SecretInteger","source_ref_index":1}},"4387997168":{"IfElse":{"id":4387997168,"this":4387996352,"arg_0":4387996640,"arg_1":4387996928,"type":"SecretInteger","source_ref_index":1}},"4387996640":{"LiteralReference":{"id":4387996640,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387925136":{"InputReference":{"id":4387925136,"refers_to":"vote_3","type":"SecretInteger","source_ref_index":3}},"4387953920":{"Addition":{"id":4387953920,"left":4387924320,"right":4387953632,"type":"SecretInteger","source_ref_index":0}},"4387997648":{"Equals":{"id":4387997648,"left":4387924656,"right":4387996064,"type":"SecretBoolean","source_ref_index":1}},"4387950560":{"LiteralReference":{"id":4387950560,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387927392":{"Addition":{"id":4387927392,"left":4387924320,"right":4387927200,"type":"SecretInteger","source_ref_index":0}},"4387962752":{"IfElse":{"id":4387962752,"this":4387961936,"arg_0":4387962224,"arg_1":4387962512,"type":"SecretInteger","source_ref_index":1}},"4388005424":{"Equals":{"id":4388005424,"left":4387926096,"right":4387996064,"type":"SecretBoolean","source_ref_index":1}},"4388000000":{"Addition":{"id":4388000000,"left":4387998704,"right":4387999760,"type":"SecretInteger","source_ref_index":0}},"4387954160":{"Equals":{"id":4387954160,"left":4387924656,"right":4387952480,"type":"SecretBoolean","source_ref_index":1}},"4387924320":{"InputReference":{"id":4387924320,"refers_to":"vote_start_count","type":"SecretInteger","source_ref_index":4}},"4387927872":{"LiteralReference":{"id":4387927872,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4388000240":{"Equals":{"id":4388000240,"left":4387925136,"right":4387996064,"type":"SecretBoolean","source_ref_index":1}},"4387949120":{"LiteralReference":{"id":4387949120,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387949552":{"IfElse":{"id":4387949552,"this":4387948832,"arg_0":4387949120,"arg_1":4387949360,"type":"SecretInteger","source_ref_index":1}},"4387950992":{"Addition":{"id":4387950992,"left":4387949792,"right":4387950752,"type":"SecretInteger","source_ref_index":0}},"4387996352":{"Equals":{"id":4387996352,"left":4387924128,"right":4387996064,"type":"SecretBoolean","source_ref_index":1}},"4388002832":{"Equals":{"id":4388002832,"left":4387925616,"right":4387996064,"type":"SecretBoolean","source_ref_index":1}},"4388005712":{"LiteralReference":{"id":4388005712,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387947632":{"Equals":{"id":4387947632,"left":4387925376,"right":4387923360,"type":"SecretBoolean","source_ref_index":1}},"4387997408":{"Addition":{"id":4387997408,"left":4387924320,"right":4387997168,"type":"SecretInteger","source_ref_index":0}},"4387925856":{"InputReference":{"id":4387925856,"refers_to":"vote_6","type":"SecretInteger","source_ref_index":3}},"4387925616":{"InputReference":{"id":4387925616,"refers_to":"vote_5","type":"SecretInteger","source_ref_index":3}},"4387927200":{"IfElse":{"id":4387927200,"this":4387926384,"arg_0":4387926720,"arg_1":4387927008,"type":"SecretInteger","source_ref_index":1}},"4387958624":{"LiteralReference":{"id":4387958624,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387924656":{"InputReference":{"id":4387924656,"refers_to":"vote_1","type":"SecretInteger","source_ref_index":3}},"4387957808":{"Addition":{"id":4387957808,"left":4387956512,"right":4387957568,"type":"SecretInteger","source_ref_index":0}},"4387928112":{"LiteralReference":{"id":4387928112,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387954736":{"LiteralReference":{"id":4387954736,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387928304":{"IfElse":{"id":4387928304,"this":4387927584,"arg_0":4387927872,"arg_1":4387928112,"type":"SecretInteger","source_ref_index":1}},"4387959920":{"LiteralReference":{"id":4387959920,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4388001296":{"Addition":{"id":4388001296,"left":4388000000,"right":4388001056,"type":"SecretInteger","source_ref_index":0}},"4387999232":{"LiteralReference":{"id":4387999232,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387950752":{"IfElse":{"id":4387950752,"this":4387950032,"arg_0":4387950320,"arg_1":4387950560,"type":"SecretInteger","source_ref_index":1}},"4387929504":{"IfElse":{"id":4387929504,"this":4387928784,"arg_0":4387929072,"arg_1":4387929312,"type":"SecretInteger","source_ref_index":1}},"4387961696":{"Addition":{"id":4387961696,"left":4387960400,"right":4387961456,"type":"SecretInteger","source_ref_index":0}},"4388004704":{"LiteralReference":{"id":4388004704,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387927584":{"Equals":{"id":4387927584,"left":4387924656,"right":4387923360,"type":"SecretBoolean","source_ref_index":1}},"4388004416":{"LiteralReference":{"id":4388004416,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4388000816":{"LiteralReference":{"id":4388000816,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387998224":{"LiteralReference":{"id":4387998224,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387960928":{"LiteralReference":{"id":4387960928,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387957328":{"LiteralReference":{"id":4387957328,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4388003888":{"Addition":{"id":4388003888,"left":4388002592,"right":4388003648,"type":"SecretInteger","source_ref_index":0}},"4387927008":{"LiteralReference":{"id":4387927008,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387996928":{"LiteralReference":{"id":4387996928,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387948832":{"Equals":{"id":4387948832,"left":4387925616,"right":4387923360,"type":"SecretBoolean","source_ref_index":1}},"4387961936":{"Equals":{"id":4387961936,"left":4387926096,"right":4387952480,"type":"SecretBoolean","source_ref_index":1}},"4388001824":{"LiteralReference":{"id":4388001824,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387958864":{"IfElse":{"id":4387958864,"this":4387958048,"arg_0":4387958336,"arg_1":4387958624,"type":"SecretInteger","source_ref_index":1}},"4387958048":{"Equals":{"id":4387958048,"left":4387925376,"right":4387952480,"type":"SecretBoolean","source_ref_index":1}},"4387959104":{"Addition":{"id":4387959104,"left":4387957808,"right":4387958864,"type":"SecretInteger","source_ref_index":0}},"4387947152":{"IfElse":{"id":4387947152,"this":4387929984,"arg_0":4387946720,"arg_1":4387946960,"type":"SecretInteger","source_ref_index":1}},"4387952480":{"LiteralReference":{"id":4387952480,"refers_to":"be9dc3499e38754968f0ed1e2d88d815","type":"Integer","source_ref_index":2}},"4388006480":{"Addition":{"id":4388006480,"left":4388005184,"right":4388006240,"type":"SecretInteger","source_ref_index":0}},"4387928784":{"Equals":{"id":4387928784,"left":4387924896,"right":4387923360,"type":"SecretBoolean","source_ref_index":1}},"4387954976":{"IfElse":{"id":4387954976,"this":4387954160,"arg_0":4387954448,"arg_1":4387954736,"type":"SecretInteger","source_ref_index":1}},"4387948352":{"IfElse":{"id":4387948352,"this":4387947632,"arg_0":4387947920,"arg_1":4387948160,"type":"SecretInteger","source_ref_index":1}},"4387925376":{"InputReference":{"id":4387925376,"refers_to":"vote_4","type":"SecretInteger","source_ref_index":3}},"4387951232":{"Equals":{"id":4387951232,"left":4387926096,"right":4387923360,"type":"SecretBoolean","source_ref_index":1}},"4387926720":{"LiteralReference":{"id":4387926720,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387961456":{"IfElse":{"id":4387961456,"this":4387960640,"arg_0":4387960928,"arg_1":4387961216,"type":"SecretInteger","source_ref_index":1}},"4387956512":{"Addition":{"id":4387956512,"left":4387955216,"right":4387956272,"type":"SecretInteger","source_ref_index":0}},"4387998464":{"IfElse":{"id":4387998464,"this":4387997648,"arg_0":4387997936,"arg_1":4387998224,"type":"SecretInteger","source_ref_index":1}},"4387997936":{"LiteralReference":{"id":4387997936,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4388004128":{"Equals":{"id":4388004128,"left":4387925856,"right":4387996064,"type":"SecretBoolean","source_ref_index":1}},"4387998704":{"Addition":{"id":4387998704,"left":4387997408,"right":4387998464,"type":"SecretInteger","source_ref_index":0}},"4387956032":{"LiteralReference":{"id":4387956032,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387951952":{"IfElse":{"id":4387951952,"this":4387951232,"arg_0":4387951520,"arg_1":4387951760,"type":"SecretInteger","source_ref_index":1}},"4387923360":{"LiteralReference":{"id":4387923360,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":2}},"4388003408":{"LiteralReference":{"id":4388003408,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387956752":{"Equals":{"id":4387956752,"left":4387925136,"right":4387952480,"type":"SecretBoolean","source_ref_index":1}},"4387948592":{"Addition":{"id":4387948592,"left":4387947392,"right":4387948352,"type":"SecretInteger","source_ref_index":0}},"4387928544":{"Addition":{"id":4387928544,"left":4387927392,"right":4387928304,"type":"SecretInteger","source_ref_index":0}},"4387955216":{"Addition":{"id":4387955216,"left":4387953920,"right":4387954976,"type":"SecretInteger","source_ref_index":0}},"4387946720":{"LiteralReference":{"id":4387946720,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4387929312":{"LiteralReference":{"id":4387929312,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387959632":{"LiteralReference":{"id":4387959632,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4388002112":{"LiteralReference":{"id":4388002112,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4387960640":{"Equals":{"id":4387960640,"left":4387925856,"right":4387952480,"type":"SecretBoolean","source_ref_index":1}}},"source_files":{"funcs.py":"\"\"\"\nThis module provides common functions to work with Nada Numpy. It includes: \n- the creation and manipulation of arrays and party objects.\n- non-linear functions over arrays.\n- random operations over arrays: random generation, shuffling.\n\"\"\"\n\n# pylint:disable=too-many-lines\n\nfrom typing import Any, Callable, List, Optional, Sequence, Tuple, Union\n\nimport numpy as np\nfrom nada_dsl import (Boolean, Integer, Output, Party, PublicInteger,\n PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, UnsignedInteger)\n\nfrom nada_numpy.array import NadaArray\nfrom nada_numpy.nada_typing import AnyNadaType, NadaCleartextNumber\nfrom nada_numpy.types import Rational, SecretRational, rational\nfrom nada_numpy.utils import copy_metadata\n\n__all__ = [\n \"parties\",\n \"from_list\",\n \"ones\",\n \"ones_like\",\n \"zeros\",\n \"zeros_like\",\n \"alphas\",\n \"alphas_like\",\n \"array\",\n \"random\",\n \"output\",\n \"vstack\",\n \"hstack\",\n \"ndim\",\n \"shape\",\n \"size\",\n \"pad\",\n \"frompyfunc\",\n \"vectorize\",\n \"eye\",\n \"arange\",\n \"linspace\",\n \"split\",\n \"compress\",\n \"copy\",\n \"cumprod\",\n \"cumsum\",\n \"diagonal\",\n \"mean\",\n \"prod\",\n \"put\",\n \"ravel\",\n \"repeat\",\n \"reshape\",\n \"resize\",\n \"squeeze\",\n \"sum\",\n \"swapaxes\",\n \"take\",\n \"trace\",\n \"transpose\",\n \"sign\",\n \"abs\",\n \"exp\",\n \"polynomial\",\n \"log\",\n \"reciprocal\",\n \"inv_sqrt\",\n \"sqrt\",\n \"cossin\",\n \"sin\",\n \"cos\",\n \"tan\",\n \"tanh\",\n \"sigmoid\",\n \"gelu\",\n \"silu\",\n \"shuffle\",\n]\n\n\ndef parties(num: int, party_names: Optional[List[str]] = None) -> List[Party]:\n \"\"\"\n Create a list of Party objects with specified names.\n\n Args:\n num (int): The number of parties to create.\n party_names (List[str], optional): Party names to use. Defaults to None.\n\n Raises:\n ValueError: Raised when incorrect number of party names is supplied.\n\n Returns:\n List[Party]: A list of Party objects.\n \"\"\"\n if party_names is None:\n party_names = [f\"Party{i}\" for i in range(num)]\n\n if len(party_names) != num:\n num_supplied_parties = len(party_names)\n raise ValueError(\n f\"Incorrect number of party names. Expected {num}, received {num_supplied_parties}\"\n )\n\n return [Party(name=party_name) for party_name in party_names]\n\n\ndef __from_numpy(arr: np.ndarray, nada_type: NadaCleartextNumber) -> List:\n \"\"\"\n Recursively convert a n-dimensional NumPy array to a nested list of NadaInteger objects.\n\n Args:\n arr (np.ndarray): A NumPy array of integers.\n nada_type (type): The type of NadaInteger objects to create.\n\n Returns:\n List: A nested list of NadaInteger objects.\n \"\"\"\n if len(arr.shape) == 1:\n if isinstance(nada_type, Rational):\n return [nada_type(elem) for elem in arr] # type: ignore\n return [nada_type(int(elem)) for elem in arr] # type: ignore\n return [__from_numpy(arr[i], nada_type) for i in range(arr.shape[0])]\n\n\ndef from_list(\n lst: Union[List, np.ndarray], nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray from a list of integers.\n\n Args:\n lst (Union[List, np.ndarray]): A list of integers representing the elements of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n lst_np = np.array(lst)\n return NadaArray(np.array(__from_numpy(lst_np, nada_type)))\n\n\ndef ones(dims: Sequence[int], nada_type: NadaCleartextNumber = Integer) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with ones.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with ones.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n return from_list(np.ones(dims), nada_type)\n\n\ndef ones_like(\n a: np.ndarray | NadaArray, nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with one with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): A reference array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with ones.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n if isinstance(a, NadaArray):\n a = a.inner\n return from_list(np.ones_like(a), nada_type)\n\n\ndef zeros(dims: Sequence[int], nada_type: NadaCleartextNumber = Integer) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with zeros.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with zeros.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n return from_list(np.zeros(dims), nada_type)\n\n\ndef zeros_like(\n a: np.ndarray | NadaArray, nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with zeros with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): A reference array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with zeros.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n if isinstance(a, NadaArray):\n a = a.inner\n return from_list(np.zeros_like(a), nada_type)\n\n\ndef alphas(dims: Sequence[int], alpha: Any) -> NadaArray:\n \"\"\"\n Create a NadaArray filled with a certain constant value.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n alpha (Any): Some constant value.\n\n Returns:\n NadaArray: NadaArray filled with constant value.\n \"\"\"\n ones_array = np.ones(dims)\n return NadaArray(np.frompyfunc(lambda _: alpha, 1, 1)(ones_array))\n\n\ndef alphas_like(a: np.ndarray | NadaArray, alpha: Any) -> NadaArray:\n \"\"\"\n Create a NadaArray filled with a certain constant value\n with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): Reference array.\n alpha (Any): Some constant value.\n\n Returns:\n NadaArray: NadaArray filled with constant value.\n \"\"\"\n if isinstance(a, NadaArray):\n a = a.inner\n ones_array = np.ones_like(a)\n return NadaArray(np.frompyfunc(lambda _: alpha, 1, 1)(ones_array))\n\n\ndef array(\n dims: Sequence[int],\n party: Party,\n prefix: str,\n nada_type: Union[\n SecretInteger,\n SecretUnsignedInteger,\n PublicInteger,\n PublicUnsignedInteger,\n SecretRational,\n Rational,\n ],\n) -> NadaArray:\n \"\"\"\n Create a NadaArray with the specified dimensions and elements of the given type.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n party (Party): The party object.\n prefix (str): A prefix for naming the array elements.\n nada_type (type): The type of elements to create.\n\n Returns:\n NadaArray: The created NadaArray.\n \"\"\"\n return NadaArray.array(dims, party, prefix, nada_type)\n\n\ndef random(\n dims: Sequence[int],\n nada_type: SecretInteger | SecretUnsignedInteger | SecretRational = SecretInteger,\n) -> NadaArray:\n \"\"\"\n Create a random NadaArray with the specified dimensions.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of elements to create. Defaults to SecretInteger.\n\n Returns:\n NadaArray: A NadaArray with random values of the specified type.\n \"\"\"\n return NadaArray.random(dims, nada_type)\n\n\ndef output(\n value: Union[NadaArray, AnyNadaType], party: Party, prefix: str\n) -> List[Output]:\n \"\"\"\n Generate a list of Output objects for some provided value.\n\n Args:\n value (Union[NadaArray, AnyNadaType]): The input NadaArray.\n party (Party): The party object.\n prefix (str): The prefix for naming the Output objects.\n\n Returns:\n List[Output]: A list of Output objects.\n \"\"\"\n if isinstance(value, NadaArray):\n # pylint:disable=protected-access\n return NadaArray._output_array(value, party, prefix)\n if isinstance(value, (Rational, SecretRational)):\n value = value.value\n return [Output(value, prefix, party)]\n\n\ndef vstack(arr_list: list) -> NadaArray:\n \"\"\"\n Stack arrays in sequence vertically (row wise).\n\n Args:\n arr_list (list): A list of NadaArray objects to stack.\n\n Returns:\n NadaArray: The stacked NadaArray.\n \"\"\"\n return NadaArray(np.vstack(arr_list))\n\n\ndef hstack(arr_list: list) -> NadaArray:\n \"\"\"\n Stack arrays in sequence horizontally (column wise).\n\n Args:\n arr_list (list): A list of NadaArray objects to stack.\n\n Returns:\n NadaArray: The stacked NadaArray.\n \"\"\"\n return NadaArray(np.hstack(arr_list))\n\n\ndef ndim(arr: NadaArray) -> int:\n \"\"\"\n Returns number of array dimensions.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array dimensions.\n \"\"\"\n return arr.ndim\n\n\ndef shape(arr: NadaArray) -> Tuple[int]:\n \"\"\"\n Returns Array shape.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array shape.\n \"\"\"\n return arr.shape\n\n\ndef size(arr: NadaArray) -> int:\n \"\"\"\n Returns array size.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array size.\n \"\"\"\n return arr.size\n\n\ndef to_nada(arr: np.ndarray, nada_type: NadaCleartextNumber) -> NadaArray:\n \"\"\"\n Converts a plain-text NumPy array to the equivalent NadaArray with\n a specified compatible NadaType.\n\n Args:\n arr (np.ndarray): Input Numpy array.\n nada_type (NadaCleartextNumber): Desired clear-text NadaType.\n\n Returns:\n NadaArray: Output NadaArray.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n else:\n arr = arr.astype(int)\n return NadaArray(np.frompyfunc(nada_type, 1, 1)(arr)) # type: ignore\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.pad)\ndef pad(\n arr: NadaArray,\n pad_width: Union[Sequence[int], int],\n mode: str = \"constant\",\n **kwargs,\n) -> NadaArray:\n if mode not in {\"constant\", \"edge\", \"reflect\", \"symmetric\", \"wrap\"}:\n raise NotImplementedError(\n f\"Not currently possible to pad NadaArray in mode `{mode}`\"\n )\n\n # Override python defaults by NadaType defaults\n overriden_kwargs = {}\n if mode == \"constant\" and \"constant_values\" not in kwargs:\n if arr.is_rational:\n default = rational(0)\n elif arr.is_integer:\n default = Integer(0)\n elif arr.is_unsigned_integer:\n default = UnsignedInteger(0)\n else:\n default = Boolean(False)\n\n overriden_kwargs[\"constant_values\"] = kwargs.get(\"constant_values\", default)\n\n padded_inner = np.pad( # type: ignore\n arr.inner,\n pad_width,\n mode,\n **overriden_kwargs,\n **kwargs,\n )\n\n return NadaArray(padded_inner)\n\n\n# pylint:disable=too-few-public-methods\nclass NadaCallable:\n \"\"\"Class that wraps a vectorized NumPy function\"\"\"\n\n def __init__(self, vfunc: Callable) -> None:\n \"\"\"\n Initialization.\n\n Args:\n vfunc (Callable): Vectorized function to wrap.\n \"\"\"\n self.vfunc = vfunc\n\n def __call__(self, *args, **kwargs) -> Any:\n \"\"\"\n Routes function call to wrapped vectorized function while\n ensuring any resulting NumPy arrays are converted to NadaArrays.\n\n Returns:\n Any: Function result.\n \"\"\"\n result = self.vfunc(*args, **kwargs)\n if isinstance(result, np.ndarray):\n return NadaArray(result)\n if isinstance(result, Sequence):\n return type(result)( # type: ignore\n NadaArray(value) if isinstance(value, np.ndarray) else value\n for value in result\n )\n return result\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.frompyfunc)\ndef frompyfunc(*args, **kwargs) -> NadaCallable:\n return NadaCallable(np.frompyfunc(*args, **kwargs))\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.vectorize)\ndef vectorize(*args, **kwargs) -> NadaCallable:\n return NadaCallable(np.vectorize(*args, **kwargs))\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.eye)\ndef eye(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.eye(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.arange)\ndef arange(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.arange(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.linspace)\ndef linspace(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.linspace(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.split)\ndef split(a: NadaArray, *args, **kwargs) -> List[NadaArray]:\n return [NadaArray(arr) for arr in np.split(a.inner, *args, **kwargs)]\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.compress)\ndef compress(a: NadaArray, *args, **kwargs):\n return a.compress(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.copy)\ndef copy(a: NadaArray, *args, **kwargs):\n return a.copy(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.cumprod)\ndef cumprod(a: NadaArray, *args, **kwargs):\n return a.cumprod(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.cumsum)\ndef cumsum(a: NadaArray, *args, **kwargs):\n return a.cumsum(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.diagonal)\ndef diagonal(a: NadaArray, *args, **kwargs):\n return a.diagonal(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.diagonal)\ndef mean(a: NadaArray, *args, **kwargs):\n return a.mean(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.prod)\ndef prod(a: NadaArray, *args, **kwargs):\n return a.prod(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.put)\ndef put(a: NadaArray, *args, **kwargs):\n return a.put(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.ravel)\ndef ravel(a: NadaArray, *args, **kwargs):\n return a.ravel(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.repeat)\ndef repeat(a: NadaArray, *args, **kwargs):\n return a.repeat(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.reshape)\ndef reshape(a: NadaArray, *args, **kwargs):\n return a.reshape(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.resize)\ndef resize(a: NadaArray, *args, **kwargs):\n return a.resize(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.squeeze)\ndef squeeze(a: NadaArray, *args, **kwargs):\n return a.squeeze(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring,redefined-builtin\n@copy_metadata(np.sum)\ndef sum(a: NadaArray, *args, **kwargs):\n return a.sum(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.swapaxes)\ndef swapaxes(a: NadaArray, *args, **kwargs):\n return a.swapaxes(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.take)\ndef take(a: NadaArray, *args, **kwargs):\n return a.take(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.trace)\ndef trace(a: NadaArray, *args, **kwargs):\n return a.trace(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.transpose)\ndef transpose(a: NadaArray, *args, **kwargs):\n return a.transpose(*args, **kwargs)\n\n\n# Non-linear functions\n\n\ndef sign(arr: NadaArray) -> \"NadaArray\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n return arr.sign()\n\n\ndef abs(arr: NadaArray) -> \"NadaArray\":\n \"\"\"Computes the absolute value\"\"\"\n return arr.abs()\n\n\ndef exp(arr: NadaArray, iterations: int = 8) -> \"NadaArray\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The exponential function is computed by choosing n = 2 ** d, where d is set to `iterations`.\n The calculation is performed by computing (1 + x / n) once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n NadaArray: The approximated value of the exponential function.\n \"\"\"\n return arr.exp(iterations=iterations)\n\n\ndef polynomial(arr: NadaArray, coefficients: list) -> \"NadaArray\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n NadaArray: The result of the polynomial function applied to the input x.\n \"\"\"\n return arr.polynomial(coefficients=coefficients)\n\n\ndef log(\n arr: NadaArray,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n) -> \"NadaArray\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation of exp.\n Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of Householder approximation).\n Defaults to 8.\n\n Returns:\n NadaArray: The approximate value of the natural logarithm.\n \"\"\"\n return arr.log(\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n\n\ndef reciprocal( # pylint: disable=too-many-arguments\n arr: NadaArray,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n # pylint:disable=duplicate-code\n return arr.reciprocal(\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n\n\ndef inv_sqrt(\n arr: NadaArray,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n return arr.inv_sqrt(initial=initial, iterations=iterations, method=method)\n\n\ndef sqrt(\n arr: NadaArray,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the inverse\n square root Newton-Raphson iterations. By default, this will be set to allow\n convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n return arr.sqrt(initial=initial, iterations=iterations, method=method)\n\n\n# Trigonometry\n\n\ndef cossin(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit through the\n formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[NadaArray, NadaArray]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n return arr.cossin(iterations=iterations)\n\n\ndef cos(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the cosine.\n \"\"\"\n return arr.cos(iterations=iterations)\n\n\ndef sin(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the sine.\n \"\"\"\n return arr.sin(iterations=iterations)\n\n\ndef tan(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the tan.\n \"\"\"\n return arr.tan(iterations=iterations)\n\n\n# Activation functions\n\n\ndef tanh(\n arr: NadaArray,\n chebyshev_terms: int = 32,\n method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n return arr.tanh(chebyshev_terms=chebyshev_terms, method=method)\n\n\ndef sigmoid(\n arr: NadaArray,\n chebyshev_terms: int = 32,\n method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses\n the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n return arr.sigmoid(chebyshev_terms=chebyshev_terms, method=method)\n\n\ndef gelu(\n arr: NadaArray,\n method: str = \"tanh\",\n tanh_method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n return arr.gelu(method=method, tanh_method=tanh_method)\n\n\ndef silu(\n arr: NadaArray,\n method_sigmoid: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses the\n identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n return arr.silu(method_sigmoid=method_sigmoid)\n\n\ndef shuffle(arr: NadaArray) -> NadaArray:\n \"\"\"\n Shuffles a 1D array using the Benes network.\n\n This function rearranges the elements of a 1-dimensional array in a deterministic but seemingly\n random order based on the Benes network, a network used in certain types of sorting and\n switching circuits. The Benes network requires the input array's length to be a power of two\n (e.g., 2, 4, 8, 16, ...).\n\n Note: The resulting shuffled arrays contain the same elements as the input arrays.\n\n Args:\n NadaArray: The input array to be shuffled. This must be a 1-dimensional NumPy array.\n The length of the array must be a power of two.\n\n Returns:\n NadaArray: The shuffled version of the input array. The output is a new array where\n the elements have been rearranged according to the Benes network.\n\n Raises:\n ValueError: If the length of the input array is not a power of two.\n\n Example:\n ```python\n import nada_numpy as na\n\n # Example arrays with different data types\n parties = na.parties(2)\n a = na.array([8], parties[0], \"A\", na.Rational)\n b = na.array([8], parties[0], \"B\", na.SecretRational)\n c = na.array([8], parties[0], \"C\", PublicInteger)\n d = na.array([8], parties[0], \"D\", SecretInteger)\n\n # Shuffling the arrays\n shuffled_a = shuffle(a)\n shuffled_b = shuffle(b)\n shuffled_c = shuffle(c)\n ```\n\n Frequency analysis:\n\n This script performs a frequency analysis of a shuffle function implemented using a Benes\n network. It includes a function for shuffle, a test function for evaluating randomness,\n and an example of running the test. Below is an overview of the code and its output.\n\n 1. **Shuffle Function**:\n\n The `shuffle` function shuffles a 1D array using a Benes network approach.\n The Benes network is defined by the function `_benes_network(n)`, which should provide the\n network stages required for the shuffle.\n\n ```python\n import numpy as np\n import random\n\n def rand_bool():\n # Simulates a random boolean value\n return random.choice([0, 1]) == 0\n\n def swap_gate(a, b):\n # Conditionally swaps two values based on a random boolean\n rbool = rand_bool()\n return (b, a) if rbool else (a, b)\n\n def shuffle(array):\n # Applies Benes network shuffle to a 1D array\n if array.ndim != 1:\n raise ValueError(\"Input array must be a 1D array.\")\n\n n = array.size\n bnet = benes_network(n)\n swap_array = np.ones(n)\n\n first_numbers = np.arange(0, n, 2)\n second_numbers = np.arange(1, n, 2)\n pairs = np.column_stack((first_numbers, second_numbers))\n\n for stage in bnet:\n for ((i0, i1), (a, b)) in zip(pairs, stage):\n swap_array[i0], swap_array[i1] = swap_gate(array[a], array[b])\n array = swap_array.copy()\n\n return array\n ```\n\n 2. **Randomness Test Function:**:\n The test_shuffle_randomness function evaluates the shuffle function by performing\n multiple shuffles and counting the occurrences of each element at each position.\n\n ```python\n def test_shuffle_randomness(vector_size, num_shuffles):\n # Initializes vector and count matrix\n vector = np.arange(vector_size)\n counts = np.zeros((vector_size, vector_size), dtype=int)\n\n # Performs shuffling and counts occurrences\n for _ in range(num_shuffles):\n shuffled_vector = shuffle(vector)\n for position, element in enumerate(shuffled_vector):\n counts[int(element), position] += 1\n\n # Computes average counts and deviation\n average_counts = num_shuffles / vector_size\n deviation = np.abs(counts - average_counts)\n\n return counts, average_counts, deviation\n ```\n\n\n Running the `test_shuffle_randomness` function with a vector size of 8 and 100,000 shuffles\n provides the following results:\n\n ```python\n vector_size = 8 # Size of the vector\n num_shuffles = 100000 # Number of shuffles to perform\n\n counts, average_counts, deviation = test_shuffle_randomness(vector_size,\n num_shuffles)\n\n print(\"Counts of numbers appearances at each position:\")\n for i in range(vector_size):\n print(f\"Number {i}: {counts[i]}\")\n print(\"Expected count of number per slot:\", average_counts)\n print(\"\\nDeviation from the expected average:\")\n for i in range(vector_size):\n print(f\"Number {i}: {deviation[i]}\")\n ```\n ```bash\n >>> Counts of numbers appearances at each position:\n >>> Number 0: [12477 12409 12611 12549 12361 12548 12591 12454]\n >>> Number 1: [12506 12669 12562 12414 12311 12408 12377 12753]\n >>> Number 2: [12595 12327 12461 12607 12492 12721 12419 12378]\n >>> Number 3: [12417 12498 12586 12433 12627 12231 12638 12570]\n >>> Number 4: [12370 12544 12404 12337 12497 12743 12588 12517]\n >>> Number 5: [12559 12420 12416 12791 12508 12489 12360 12457]\n >>> Number 6: [12669 12459 12396 12394 12757 12511 12423 12391]\n >>> Number 7: [12407 12674 12564 12475 12447 12349 12604 12480]\n >>> Expected count of number per slot: 12500.0\n >>>\n >>> Deviation from the expected average:\n >>> Number 0: [ 23. 91. 111. 49. 139. 48. 91. 46.]\n >>> Number 1: [ 6. 169. 62. 86. 189. 92. 123. 253.]\n >>> Number 2: [ 95. 173. 39. 107. 8. 221. 81. 122.]\n >>> Number 3: [ 83. 2. 86. 67. 127. 269. 138. 70.]\n >>> Number 4: [130. 44. 96. 163. 3. 243. 88. 17.]\n >>> Number 5: [ 59. 80. 84. 291. 8. 11. 140. 43.]\n >>> Number 6: [169. 41. 104. 106. 257. 11. 77. 109.]\n >>> Number 7: [ 93. 174. 64. 25. 53. 151. 104. 20.]\n ```\n \"\"\"\n return arr.shuffle()\n","types.py":"\"\"\"Additional special data types\"\"\"\n\n# pylint:disable=too-many-lines\n\nimport functools\nimport warnings\nfrom typing import List, Optional, Tuple, Union\n\nimport nada_dsl as dsl\nimport numpy as np\nfrom nada_dsl import (Input, Integer, Party, PublicInteger,\n PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, UnsignedInteger)\n\n_NadaRational = Union[\"Rational\", \"SecretRational\"]\n\n_NadaType = Union[\n Integer,\n PublicInteger,\n PublicUnsignedInteger,\n SecretInteger,\n SecretUnsignedInteger,\n UnsignedInteger,\n]\n\n\nclass SecretBoolean(dsl.SecretBoolean):\n \"\"\"SecretBoolean rational wrapper\"\"\"\n\n def __init__(self, value: dsl.SecretBoolean) -> None:\n \"\"\"\n Initialization.\n\n Args:\n value (dsl.SecretBoolean): SecretBoolean value.\n \"\"\"\n super().__init__(value.inner)\n\n def if_else(\n self,\n true: Union[_NadaType, \"SecretRational\", \"Rational\"],\n false: Union[_NadaType, \"SecretRational\", \"Rational\"],\n ) -> Union[SecretInteger, SecretUnsignedInteger, \"SecretRational\"]:\n \"\"\"\n If-else logic. If the boolean is True, true is returned. If not, false is returned.\n\n Args:\n true (Union[_NadaType, SecretRational, Rational]): First argument.\n false (Union[_NadaType, SecretRational, Rational]): Second argument.\n\n Raises:\n ValueError: Raised when incompatibly-scaled values are passed.\n TypeError: Raised when invalid operation is called.\n\n Returns:\n Union[SecretInteger, SecretUnsignedInteger, \"SecretRational\"]: Return value.\n \"\"\"\n first_arg = true\n second_arg = false\n if isinstance(true, (SecretRational, Rational)) and isinstance(\n false, (SecretRational, Rational)\n ):\n # Both are SecretRational or Rational objects\n if true.log_scale != false.log_scale:\n raise ValueError(\"Cannot output values with different scales.\")\n first_arg = true.value\n second_arg = false.value\n elif isinstance(true, (Rational, SecretRational)) or isinstance(\n false, (Rational, SecretRational)\n ):\n # Both are SecretRational or Rational objects\n raise TypeError(f\"Invalid operation: {self}.IfElse({true}, {false})\")\n\n result = super().if_else(first_arg, second_arg)\n\n if isinstance(true, (SecretRational, Rational)):\n # If we have a SecretBoolean, the return type will be SecretInteger,\n # thus promoted to SecretRational\n return SecretRational(result, true.log_scale, is_scaled=True)\n return result\n\n\nclass PublicBoolean(dsl.PublicBoolean):\n \"\"\"PublicBoolean rational wrapper\"\"\"\n\n def __init__(self, value: dsl.PublicBoolean) -> None:\n \"\"\"\n Initialization.\n\n Args:\n value (dsl.PublicBoolean): PublicBoolean value.\n \"\"\"\n super().__init__(value.inner)\n\n def if_else(\n self,\n true: Union[_NadaType, \"SecretRational\", \"Rational\"],\n false: Union[_NadaType, \"SecretRational\", \"Rational\"],\n ) -> Union[\n PublicInteger,\n PublicUnsignedInteger,\n SecretInteger,\n SecretUnsignedInteger,\n \"Rational\",\n \"SecretRational\",\n ]:\n \"\"\"\n If-else logic. If the boolean is True, true is returned. If not, false is returned.\n\n Args:\n true (Union[_NadaType, SecretRational, Rational]): First argument.\n false (Union[_NadaType, SecretRational, Rational]): Second argument.\n\n Raises:\n ValueError: Raised when incompatibly-scaled values are passed.\n TypeError: Raised when invalid operation is called.\n\n Returns:\n Union[PublicInteger, PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, \"Rational\", \"SecretRational\"]: Return value.\n \"\"\"\n first_arg = true\n second_arg = false\n if isinstance(true, (SecretRational, Rational)) and isinstance(\n false, (SecretRational, Rational)\n ):\n # Both are SecretRational or Rational objects\n if true.log_scale != false.log_scale:\n raise ValueError(\"Cannot output values with different scales.\")\n first_arg = true.value\n second_arg = false.value\n elif isinstance(true, (Rational, SecretRational)) or isinstance(\n false, (Rational, SecretRational)\n ):\n # Both are SecretRational or Rational objects but of different type\n raise TypeError(f\"Invalid operation: {self}.IfElse({true}, {false})\")\n\n result = super().if_else(first_arg, second_arg)\n\n if isinstance(true, SecretRational) or isinstance(false, SecretRational):\n return SecretRational(result, true.log_scale, is_scaled=True)\n if isinstance(true, Rational) and isinstance(false, Rational):\n return Rational(result, true.log_scale, is_scaled=True)\n return result\n\n\nclass Rational: # pylint:disable=too-many-public-methods\n \"\"\"Wrapper class to store scaled Integer values representing a fixed-point number.\"\"\"\n\n def __init__(\n self,\n value: Union[Integer, PublicInteger],\n log_scale: Optional[int] = None,\n is_scaled: bool = True,\n ) -> None:\n \"\"\"\n Initializes wrapper around Integer object.\n\n Args:\n value (Union[Integer, PublicInteger]): The value to be representedas a Rational.\n log_scale (int, optional): Quantization scaling factor.\n Defaults to RationalConfig.log_scale.\n is_scaled (bool, optional): Flag that represents whether the value is already scaled.\n Defaults to True.\n\n Raises:\n TypeError: If value is of an incompatible type.\n \"\"\"\n if not isinstance(value, (Integer, PublicInteger)):\n raise TypeError(f\"Cannot instantiate Rational from type `{type(value)}`.\")\n\n if log_scale is None:\n log_scale = get_log_scale()\n self._log_scale = log_scale\n\n if is_scaled is False:\n value = value * Integer(\n 1 << log_scale\n ) # TODO: replace with shift when supported\n self._value = value\n\n @property\n def log_scale(self) -> int:\n \"\"\"\n Getter for the logarithmic scale value.\n\n Returns:\n int: Logarithmic scale value.\n \"\"\"\n return self._log_scale\n\n @property\n def value(self) -> Union[Integer, PublicInteger]:\n \"\"\"\n Getter for the underlying Integer value.\n\n Returns:\n Union[Integer, PublicInteger]: The Integer value.\n \"\"\"\n return self._value\n\n def add(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot add values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n other.value + self.value, self.log_scale, is_scaled=True\n )\n return Rational(self.value + other.value, self.log_scale, is_scaled=True)\n\n def __add__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def __iadd__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def sub(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to subtract.\n\n Returns:\n Union[Rational, SecretRational]: Result of the subtraction.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot substract values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value - other.value, self.log_scale, is_scaled=True\n )\n return Rational(self.value - other.value, self.log_scale, is_scaled=True)\n\n def __sub__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def __isub__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def mul_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers.\n\n WARNING: This function does not rescale by default. Use `mul` to multiply and rescale.\n\n Args:\n other (_NadaRational): Other rational number to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the multiplication.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot multiply values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value * other.value,\n self.log_scale + other.log_scale,\n is_scaled=True,\n )\n return Rational(\n self.value * other.value,\n self.log_scale + other.log_scale,\n is_scaled=True,\n )\n\n def mul(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers and rescale the result.\n\n Args:\n other (_NadaRational): Other rational number to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the multiplication.\n \"\"\"\n c = self.mul_no_rescale(other, ignore_scale=ignore_scale)\n d = c.rescale_down()\n return d\n\n def __mul__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def __imul__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def divide_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to divide by.\n\n Returns:\n Union[Rational, SecretRational]: Result of the division.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale + get_log_scale():\n raise ValueError(\n f\"Cannot divide values where scale is: {self.log_scale} / {other.log_scale}.\"\n f\"Required scale: {self.log_scale} / {other.log_scale + get_log_scale()}\"\n )\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value / other.value,\n self.log_scale - other.log_scale,\n is_scaled=True,\n )\n return Rational(\n self.value / other.value,\n self.log_scale - other.log_scale,\n is_scaled=True,\n )\n\n def divide(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers and rescale the result.\n\n Args:\n other (_NadaRational): Other rational number to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the division.\n \"\"\"\n a = self.rescale_up()\n c = a.divide_no_rescale(other, ignore_scale)\n return c\n\n def __truediv__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __itruediv__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __pow__(self, other: int) -> \"Rational\":\n \"\"\"\n Raise a rational number to an integer power using binary exponentiation.\n\n Args:\n other (int): The exponent.\n\n Returns:\n Rational: Result of the power operation.\n\n Raises:\n TypeError: If the exponent is not an integer.\n \"\"\"\n if not isinstance(other, int):\n raise TypeError(f\"Cannot raise Rational to a power of type `{type(other)}`\")\n\n result = Rational(Integer(1), self.log_scale, is_scaled=False)\n\n if other == 0:\n return result # Any number to the power of 0 is 1\n\n base = self\n\n exponent = abs(other)\n while exponent > 0:\n if exponent % 2 == 1:\n result = result * base # type: ignore\n base *= base # type: ignore\n exponent //= 2\n\n if other < 0:\n return rational(1) / Rational( # type: ignore\n result.value, result.log_scale, is_scaled=True\n )\n\n return result\n\n def __neg__(self) -> \"Rational\":\n \"\"\"\n Negate the Rational value.\n\n Returns:\n Rational: Negated Rational value.\n \"\"\"\n return Rational(self.value * Integer(-1), self.log_scale, is_scaled=True)\n\n def __lshift__(self, other: UnsignedInteger) -> \"Rational\":\n \"\"\"\n Left shift the Rational value.\n\n Args:\n other (UnsignedInteger): The value to left shift by.\n\n Returns:\n Rational: Left shifted Rational value.\n \"\"\"\n return Rational(self.value << other, self.log_scale)\n\n def __rshift__(self, other: UnsignedInteger) -> \"Rational\":\n \"\"\"\n Right shift the Rational value.\n\n Args:\n other (UnsignedInteger): The value to right shift by.\n\n Returns:\n Rational: Right shifted Rational value.\n \"\"\"\n return Rational(self.value >> other, self.log_scale)\n\n def __lt__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is less than another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value < other.value)\n return PublicBoolean(self.value < other.value)\n\n def __gt__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is greater than another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value > other.value)\n return PublicBoolean(self.value > other.value)\n\n def __le__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is less than or equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value <= other.value)\n return PublicBoolean(self.value <= other.value)\n\n def __ge__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is greater than or equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value >= other.value)\n return PublicBoolean(self.value >= other.value)\n\n def __eq__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]: # type: ignore\n \"\"\"\n Check if this Rational is equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value == other.value)\n return PublicBoolean(self.value == other.value)\n\n def __ne__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]: # type: ignore\n \"\"\"\n Check if this Rational is not equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value != other.value)\n return PublicBoolean(self.value != other.value)\n\n def rescale_up(self, log_scale: Optional[int] = None) -> \"Rational\":\n \"\"\"\n Rescale the value in the upward direction by a scaling factor.\n\n This is equivalent to multiplying the value by `2**(log_scale)`.\n\n Args:\n log_scale (int, optional): Scaling factor to rescale the value.\n Defaults to RationalConfig.log_scale.\n\n Returns:\n Rational: Rescaled Rational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return Rational(\n self._value\n * Integer(1 << log_scale), # TODO: replace with shift when supported\n self.log_scale + log_scale,\n is_scaled=True,\n )\n\n def rescale_down(self, log_scale: Optional[int] = None) -> \"Rational\":\n \"\"\"\n Rescale the value in the downward direction by a scaling factor.\n\n This is equivalent to dividing the value by `2**(log_scale)`.\n\n Args:\n log_scale (int, optional): Scaling factor to rescale the value.\n Defaults to RationalConfig.log_scale.\n\n Returns:\n Rational: Rescaled Rational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return Rational(\n self._value\n / Integer(1 << log_scale), # TODO: replace with shift when supported\n self.log_scale - log_scale,\n is_scaled=True,\n )\n\n # Non-linear functions\n\n def sign(self) -> \"Rational\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n result = sign(self)\n if not isinstance(result, Rational):\n raise TypeError(\"sign input should be of type Rational.\")\n return result\n\n def abs(self) -> \"Rational\":\n \"\"\"Computes the absolute value\"\"\"\n\n result = fxp_abs(self)\n if not isinstance(result, Rational):\n raise TypeError(\"abs input should be of type Rational.\")\n return result\n\n def exp(self, iterations: int = 8) -> \"Rational\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The function is computed by choosing n = 2 ** d, where d is set to `iterations`.\n The calculation is performed by computing (1 + x / n) once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n Rational: The approximated value of the exponential function.\n \"\"\"\n\n result = exp(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"exp input should be of type Rational.\")\n return result\n\n def polynomial(self, coefficients: list) -> \"Rational\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the\n highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n Rational: The result of the polynomial function applied to the input x.\n \"\"\"\n\n result = polynomial(self, coefficients=coefficients)\n if not isinstance(result, Rational):\n raise TypeError(\"polynomial input should be of type Rational.\")\n return result\n\n def log(\n self,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n ) -> \"Rational\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation\n of exp. Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of Householder\n approximation). Defaults to 8.\n\n Returns:\n Rational: The approximate value of the natural logarithm.\n \"\"\"\n\n result = log(\n self,\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n if not isinstance(result, Rational):\n raise TypeError(\"log input should be of type Rational.\")\n return result\n\n def reciprocal( # pylint: disable=too-many-arguments\n self,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n\n result = reciprocal(\n self,\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n if not isinstance(result, Rational):\n raise TypeError(\"reciprocal input should be of type Rational.\")\n return result\n\n def inv_sqrt(\n self,\n initial: Optional[\"Rational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[Rational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = inv_sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"inv_sqrt input should be of type Rational.\")\n return result\n\n def sqrt(\n self,\n initial: Optional[\"Rational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[Rational, None], optional): sets the initial value for the inverse\n square root Newton-Raphson iterations. By default, this will be set to allow\n convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"sqrt input should be of type Rational.\")\n return result\n\n # Trigonometry\n\n def cossin(self, iterations: int = 10) -> Tuple[\"Rational\", \"Rational\"]:\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit through\n the formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[Rational, Rational]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n result = cossin(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"cossin input should be of type Rational.\")\n return result\n\n def cos(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the cosine.\n \"\"\"\n\n result = cos(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"cos input should be of type Rational.\")\n return result\n\n def sin(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the sine.\n \"\"\"\n\n result = sin(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"sin input should be of type Rational.\")\n return result\n\n def tan(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the tan.\n \"\"\"\n\n result = tan(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"tan input should be of type Rational.\")\n return result\n\n # Activation functions\n\n def tanh(self, chebyshev_terms: int = 32, method: str = \"reciprocal\") -> \"Rational\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n Rational: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = tanh(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"tanh input should be of type Rational.\")\n return result\n\n def sigmoid(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"Rational\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses\n the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n Rational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = sigmoid(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"sigmoid input should be of type Rational.\")\n return result\n\n def gelu(self, method: str = \"tanh\", tanh_method: str = \"reciprocal\") -> \"Rational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n Rational: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = gelu(self, method=method, tanh_method=tanh_method)\n if not isinstance(result, Rational):\n raise TypeError(\"gelu input should be of type Rational.\")\n return result\n\n def silu(\n self,\n method_sigmoid: str = \"reciprocal\",\n ) -> \"Rational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n Rational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n\n if method_sigmoid is None:\n method_sigmoid = \"reciprocal\"\n\n result = silu(self, method_sigmoid=method_sigmoid)\n if not isinstance(result, Rational):\n raise TypeError(\"silu input should be of type Rational.\")\n return result\n\n\nclass SecretRational: # pylint:disable=too-many-public-methods\n \"\"\"Wrapper class to store scaled SecretInteger values representing a fixed-point number.\"\"\"\n\n def __init__(\n self,\n value: SecretInteger,\n log_scale: Optional[int] = None,\n is_scaled: bool = True,\n ) -> None:\n \"\"\"\n Initializes wrapper around SecretInteger object.\n The object should come scaled up by default otherwise precision may be lost.\n\n Args:\n value (SecretInteger): SecretInteger input value.\n log_scale (int, optional): Quantization scaling factor.\n Defaults to RationalConfig.log_scale.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Raises:\n TypeError: If value is of an incompatible type.\n \"\"\"\n if not isinstance(value, SecretInteger):\n raise TypeError(\n f\"Cannot instantiate SecretRational from type `{type(value)}`.\"\n )\n\n if log_scale is None:\n log_scale = get_log_scale()\n self._log_scale = log_scale\n\n if is_scaled is False:\n value = value << UnsignedInteger(log_scale)\n self._value = value\n\n @property\n def log_scale(self) -> int:\n \"\"\"\n Getter for the logarithmic scale value.\n\n Returns:\n int: Logarithmic scale value.\n \"\"\"\n return self._log_scale\n\n @property\n def value(self) -> SecretInteger:\n \"\"\"\n Getter for the underlying SecretInteger value.\n\n Returns:\n SecretInteger: The SecretInteger value.\n \"\"\"\n return self._value\n\n def add(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Add two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to add.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the addition.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot add values with different scales.\")\n\n return SecretRational(self.value + other.value, self.log_scale)\n\n def __add__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Add two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def __iadd__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Add two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def sub(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Subtract two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to subtract.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the subtraction.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot substract values with different scales.\")\n\n return SecretRational(self.value - other.value, self.log_scale)\n\n def __sub__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Subtract two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def __isub__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Subtract two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def mul_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Multiply two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the multiplication.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot multiply values with different scales.\")\n\n return SecretRational(\n self.value * other.value, self.log_scale + other.log_scale\n )\n\n def mul(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Multiply two SecretRational numbers and rescale the result.\n\n Args:\n other (_NadaRational): The other SecretRational to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n SecretRational: Result of the multiplication, rescaled.\n \"\"\"\n c = self.mul_no_rescale(other, ignore_scale=ignore_scale)\n if c is NotImplemented:\n # Note that, because this function would be executed under a NadaArray,\n # the NotImplemented value will be handled by the caller (in principle NadaArray)\n # The caller will then call the mul function of the NadaArray\n # The broadcasting will execute element-wise multiplication,\n # so rescale_down will be taken care by that function\n return c\n d = c.rescale_down()\n return d\n\n def __mul__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Multiply two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def __imul__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Multiply two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def divide_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Divide two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the division.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale + get_log_scale():\n raise ValueError(\n f\"Cannot divide values where scale is: {self.log_scale} / {other.log_scale}.\"\n f\"Required scale: {self.log_scale} / {other.log_scale + get_log_scale()}\"\n )\n\n return SecretRational(\n self.value / other.value, self.log_scale - other.log_scale\n )\n\n def divide(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Divide two SecretRational numbers and rescale the result.\n\n Args:\n other (_NadaRational): The other SecretRational to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n SecretRational: Result of the division, rescaled.\n \"\"\"\n # Note: If the other value is a NadaArray, the divide-no-rescale function will\n # return NotImplemented\n # This will cause that the divide function will return NotImplemented as well\n # The NotImplemented value will be handled by the caller (in principle NadaArray)\n # The caller will then call the divide function of the NadaArray\n # The rescale up, because there is no follow up, will not be taken into consideration.\n a = self.rescale_up()\n c = a.divide_no_rescale(other, ignore_scale=ignore_scale)\n return c\n\n def __truediv__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Divide two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __itruediv__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Divide two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __pow__(self, other: int) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Raise a SecretRational to an integer power using binary exponentiation.\n\n Args:\n other (int): The exponent.\n\n Raises:\n TypeError: If the exponent is not an integer.\n\n Returns:\n Union[Rational, SecretRational]: Result of the power operation.\n \"\"\"\n if not isinstance(other, int):\n raise TypeError(\n f\"Cannot raise SecretRational to a power of type `{type(other)}`\"\n )\n\n result = Rational(Integer(1), self.log_scale, is_scaled=False)\n\n if other == 0:\n return result # Any number to the power of 0 is 1\n\n base = self\n\n exponent = abs(other)\n while exponent > 0:\n if exponent % 2 == 1:\n result = result * base # type: ignore\n base *= base # type: ignore\n exponent //= 2\n\n if other < 0:\n return rational(1) / SecretRational( # type: ignore\n result.value, result.log_scale, is_scaled=True\n )\n\n return result\n\n def __neg__(self) -> \"SecretRational\":\n \"\"\"\n Negate the SecretRational value.\n\n Returns:\n SecretRational: Negated SecretRational value.\n \"\"\"\n return SecretRational(self.value * Integer(-1), self.log_scale)\n\n def __lshift__(self, other: UnsignedInteger) -> \"SecretRational\":\n \"\"\"\n Left shift the SecretRational value.\n\n Args:\n other (UnsignedInteger): The value to left shift by.\n\n Returns:\n SecretRational: Left shifted SecretRational value.\n \"\"\"\n return SecretRational(self.value << other, self.log_scale)\n\n def __rshift__(self, other: UnsignedInteger) -> \"SecretRational\":\n \"\"\"\n Right shift the SecretRational value.\n\n Args:\n other (UnsignedInteger): The value to right shift by.\n\n Returns:\n SecretRational: Right shifted SecretRational value.\n \"\"\"\n return SecretRational(self.value >> other, self.log_scale)\n\n def __lt__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is less than another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value < other.value)\n\n def __gt__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is greater than another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value > other.value)\n\n def __le__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is less than or equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value <= other.value)\n\n def __ge__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is greater than or equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value >= other.value)\n\n def __eq__(self, other: _NadaRational) -> SecretBoolean: # type: ignore\n \"\"\"\n Check if this SecretRational is equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value == other.value)\n\n def __ne__(self, other: _NadaRational) -> SecretBoolean: # type: ignore\n \"\"\"\n Check if this SecretRational is not equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value != other.value)\n\n def public_equals(self, other: _NadaRational) -> PublicBoolean:\n \"\"\"\n Check if this SecretRational is equal to another and reveal the result.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n PublicBoolean: Result of the comparison, revealed.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return self.value.public_equals(other.value)\n\n def reveal(self) -> Rational:\n \"\"\"\n Reveal the SecretRational value.\n\n Returns:\n Rational: Revealed SecretRational value.\n \"\"\"\n return Rational(self.value.reveal(), self.log_scale)\n\n def trunc_pr(self, arg_0: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Truncate the SecretRational value.\n\n Args:\n arg_0 (_NadaRational): The value to truncate by.\n\n Returns:\n SecretRational: Truncated SecretRational value.\n \"\"\"\n return SecretRational(self.value.trunc_pr(arg_0), self.log_scale)\n\n def rescale_up(self, log_scale: Optional[int] = None) -> \"SecretRational\":\n \"\"\"\n Rescale the SecretRational value upwards by a scaling factor.\n\n Args:\n log_scale (int, optional): The scaling factor. Defaults to RationalConfig.log_scale.\n\n Returns:\n SecretRational: Rescaled SecretRational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return SecretRational(\n self._value << UnsignedInteger(log_scale),\n self.log_scale + log_scale,\n is_scaled=True,\n )\n\n def rescale_down(self, log_scale: Optional[int] = None) -> \"SecretRational\":\n \"\"\"\n Rescale the SecretRational value downwards by a scaling factor.\n\n Args:\n log_scale (int, optional): The scaling factor. Defaults to RationalConfig.\n\n Returns:\n SecretRational: Rescaled SecretRational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return SecretRational(\n self._value >> UnsignedInteger(log_scale),\n self.log_scale - log_scale,\n is_scaled=True,\n )\n\n # Non-linear functions\n\n def sign(self) -> \"SecretRational\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n result = sign(self)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sign input should be of type SecretRational.\")\n return result\n\n def abs(self) -> \"SecretRational\":\n \"\"\"Computes the absolute value\"\"\"\n\n result = fxp_abs(self)\n if not isinstance(result, SecretRational):\n raise TypeError(\"abs input should be of type SecretRational.\")\n return result\n\n def exp(self, iterations: int = 8) -> \"SecretRational\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The exponential function is computed by choosing n = 2 ** d, where d is\n set to `iterations`. The calculation is performed by computing (1 + x / n)\n once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n SecretRational: The approximated value of the exponential function.\n \"\"\"\n\n result = exp(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"exp input should be of type SecretRational.\")\n return result\n\n def polynomial(self, coefficients: list) -> \"SecretRational\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the\n highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n SecretRational: The result of the polynomial function applied to the input x.\n \"\"\"\n\n result = polynomial(self, coefficients=coefficients)\n if not isinstance(result, SecretRational):\n raise TypeError(\"polynomial input should be of type SecretRational.\")\n return result\n\n def log(\n self,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n ) -> \"SecretRational\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation of\n exp. Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of\n Householder approximation). Defaults to 8.\n\n Returns:\n SecretRational: The approximate value of the natural logarithm.\n \"\"\"\n\n result = log(\n self,\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n if not isinstance(result, SecretRational):\n raise TypeError(\"log input should be of type SecretRational.\")\n return result\n\n def reciprocal( # pylint: disable=too-many-arguments\n self,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n\n result = reciprocal(\n self,\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n if not isinstance(result, SecretRational):\n raise TypeError(\"reciprocal input should be of type SecretRational.\")\n return result\n\n def inv_sqrt(\n self,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = inv_sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"inv_sqrt input should be of type SecretRational.\")\n return result\n\n def sqrt(\n self,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n inverse square root Newton-Raphson iterations. By default, this will be set\n to allow convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sqrt input should be of type SecretRational.\")\n return result\n\n # Trigonometry\n\n def cossin(self, iterations: int = 10) -> Tuple[\"SecretRational\", \"SecretRational\"]:\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit\n through the formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[SecretRational, SecretRational]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n result = cossin(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"cossin input should be of type SecretRational.\")\n return result\n\n def cos(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the cosine.\n \"\"\"\n\n result = cos(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"cos input should be of type SecretRational.\")\n return result\n\n def sin(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the sine.\n \"\"\"\n\n result = sin(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sin input should be of type SecretRational.\")\n return result\n\n def tan(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the tan.\n \"\"\"\n\n result = tan(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"tan input should be of type SecretRational.\")\n return result\n\n # Activation functions\n\n def tanh(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = tanh(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"tanh input should be of type SecretRational.\")\n return result\n\n def sigmoid(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = sigmoid(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sigmoid input should be of type SecretRational.\")\n return result\n\n def gelu(\n self, method: str = \"tanh\", tanh_method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = gelu(self, method=method, tanh_method=tanh_method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"gelu input should be of type SecretRational.\")\n return result\n\n def silu(\n self,\n method_sigmoid: str = \"reciprocal\",\n ) -> \"SecretRational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n\n result = silu(self, method_sigmoid=method_sigmoid)\n if not isinstance(result, SecretRational):\n raise TypeError(\"silu input should be of type SecretRational.\")\n return result\n\n\ndef secret_rational(\n name: str, party: Party, log_scale: Optional[int] = None, is_scaled: bool = True\n) -> SecretRational:\n \"\"\"\n Creates a SecretRational from a variable in the Nillion network.\n\n Args:\n name (str): Name of variable in Nillion network.\n party (Party): Name of party that provided variable.\n log_scale (int, optional): Quantization scaling factor. Defaults to None.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n SecretRational: Instantiated SecretRational object.\n \"\"\"\n value = SecretInteger(Input(name=name, party=party))\n return SecretRational(value, log_scale, is_scaled)\n\n\ndef public_rational(\n name: str, party: Party, log_scale: Optional[int] = None, is_scaled: bool = True\n) -> Rational:\n \"\"\"\n Creates a Rational from a variable in the Nillion network.\n\n Args:\n name (str): Name of variable in Nillion network.\n party (Party): Name of party that provided variable.\n log_scale (int, optional): Quantization scaling factor. Defaults to None.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n Rational: Instantiated Rational object.\n \"\"\"\n value = PublicInteger(Input(name=name, party=party))\n return Rational(value, log_scale, is_scaled)\n\n\ndef rational(\n value: Union[int, float, np.floating],\n log_scale: Optional[int] = None,\n is_scaled: bool = False,\n) -> Rational:\n \"\"\"\n Creates a Rational from a number variable.\n\n Args:\n value (Union[int, float, np.floating]): Provided input value.\n log_scale (int, optional): Quantization scaling factor. Defaults to default log_scale.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n Rational: Instantiated Rational object.\n \"\"\"\n if value == 0: # no use in rescaling 0\n return Rational(Integer(0), is_scaled=True)\n\n if log_scale is None:\n log_scale = get_log_scale()\n\n if isinstance(value, np.floating):\n value = value.item()\n if isinstance(value, int):\n return Rational(Integer(value), log_scale=log_scale, is_scaled=is_scaled)\n if isinstance(value, float):\n assert (\n is_scaled is False\n ), \"Got a value of type `float` with `is_scaled` set to True. This should never occur\"\n quantized = round(value * (1 << log_scale))\n return Rational(Integer(quantized), is_scaled=True)\n raise TypeError(f\"Cannot instantiate Rational from type `{type(value)}`.\")\n\n\nclass _MetaRationalConfig(type):\n \"\"\"Rational config metaclass that defines classproperties\"\"\"\n\n _log_scale: int\n _default_log_scale: int\n\n @property\n def default_log_scale(cls) -> int:\n \"\"\"\n Getter method.\n\n Returns:\n int: Default log scale.\n \"\"\"\n return cls._default_log_scale\n\n @property\n def log_scale(cls) -> int:\n \"\"\"\n Getter method.\n\n Returns:\n int: Log scale.\n \"\"\"\n return cls._log_scale\n\n @log_scale.setter\n def log_scale(cls, new_log_scale: int) -> None:\n \"\"\"\n Setter method.\n\n Args:\n new_log_scale (int): New log scale value to reset old value with.\n \"\"\"\n if new_log_scale <= 4:\n warnings.warn(\n f\"Provided log scale `{str(new_log_scale)}` is very low.\"\n \" Expected a value higher than 4.\"\n \" Using a low quantization scale can lead to poor quantization of rational values\"\n \" and thus poor performance & unexpected results.\"\n )\n if new_log_scale >= 64:\n warnings.warn(\n f\"Provided log scale `{str(new_log_scale)}` is very high.\"\n \" Expected a value lower than 64.\"\n \" Using a high quantization scale can lead to overflows & unexpected results.\"\n )\n\n cls._log_scale = new_log_scale\n\n\n# pylint:disable=too-few-public-methods\nclass _RationalConfig(metaclass=_MetaRationalConfig):\n \"\"\"Rational config data class\"\"\"\n\n _default_log_scale: int = 16\n _log_scale: int = _default_log_scale\n\n\ndef set_log_scale(new_log_scale: int) -> None:\n \"\"\"\n Sets the default Rational log scaling factor to a new value.\n Note that this value is the LOG scale and will be used as a base-2 exponent\n during quantization.\n\n Args:\n new_log_scale (int): New log scaling factor.\n \"\"\"\n if not isinstance(new_log_scale, int):\n raise TypeError(\n f\"Cannot set log scale to type `{type(new_log_scale)}`. Expected `int`.\"\n )\n _RationalConfig.log_scale = new_log_scale\n\n\ndef get_log_scale() -> int:\n \"\"\"\n Gets the Rational log scaling factor\n Note that this value is the LOG scale and is used as a base-2 exponent during quantization.\n\n Returns:\n int: Current log scale in use.\n \"\"\"\n return _RationalConfig.log_scale\n\n\ndef reset_log_scale() -> None:\n \"\"\"Resets the Rational log scaling factor to the original default value\"\"\"\n _RationalConfig.log_scale = _RationalConfig.default_log_scale\n\n\n# Fixed-point math operations\n\n# Copyright (c) Facebook, Inc. and its affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n#\n# Part of the code is from the CrypTen Facebook Project:\n# https://github.com/facebookresearch/CrypTen/blob/main/crypten/common/functions/logic.py\n# https://github.com/facebookresearch/CrypTen/blob/main/crypten/common/functions/approximations.py\n#\n# Modifications:\n# July, 2024\n# - Nada datatypes.\n# - Relative accuracy documentation.\n# - Some performance improvements.\n# - Fixed Tanh Chebyshev method by changing '_hardtanh' implementation.\n# - Tan.\n# - Motzkin's prolynomial preprocessing approach.\n# - GeLU and SiLU functions.\n\n\ndef sign(x: _NadaRational) -> _NadaRational:\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n ltz_cond = x < rational(0)\n ltz = ltz_cond.if_else(rational(1), rational(0))\n\n return rational(1) - ltz - ltz\n\n\ndef fxp_abs(x: _NadaRational) -> _NadaRational:\n \"\"\"Computes the absolute value of a rational\"\"\"\n return x * sign(x)\n\n\ndef exp(x: _NadaRational, iterations: int = 8) -> _NadaRational:\n \"\"\"\n Approximates the exponential function using a limit approximation.\n \"\"\"\n\n iters_na = UnsignedInteger(iterations)\n\n result = rational(1) + (x >> iters_na)\n for _ in range(iterations):\n result = result**2\n return result\n\n\ndef polynomial(x: _NadaRational, coefficients: List[Rational]) -> _NadaRational:\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the highest order term.\n **Note: The constant term is not included.**\n \"\"\"\n result = coefficients[0] * x\n\n for power, coeff in enumerate(coefficients[1:], start=2):\n result += coeff * (x**power)\n\n return result\n\n\ndef log(\n x: _NadaRational,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n) -> _NadaRational:\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n \"\"\"\n\n if input_in_01:\n return log(\n x * rational(100),\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n ) - rational(4.605170)\n\n # Initialization to a decent estimate (found by qualitative inspection):\n # ln(x) = x/120 - 20exp(-2x - 1.0) + 3.0\n term1 = x * rational(1 / 120.0)\n term2 = exp(-x - x - rational(1), iterations=exp_iterations) * rational(20)\n y = term1 - term2 + rational(3.0)\n\n # 8th order Householder iterations\n for _ in range(iterations):\n h = rational(1) - x * exp(-y, iterations=exp_iterations)\n y -= polynomial(h, [rational(1 / (i + 1)) for i in range(order)])\n return y\n\n\ndef reciprocal( # pylint: disable=too-many-arguments\n x: _NadaRational,\n all_pos: bool = False,\n initial: Optional[Rational] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n \"\"\"\n if input_in_01:\n rec = reciprocal(\n x * rational(64),\n method=method,\n all_pos=True,\n initial=initial,\n iterations=iterations,\n ) * rational(64)\n return rec\n\n if not all_pos:\n sgn = sign(x)\n pos = sgn * x\n return sgn * reciprocal(\n pos, method=method, all_pos=True, initial=initial, iterations=iterations\n )\n\n if method == \"NR\":\n if initial is None:\n # Initialization to a decent estimate (found by qualitative inspection):\n # 1/x = 3exp(1 - 2x) + 0.003\n result = rational(3) * exp(\n rational(1) - x - x, iterations=exp_iters\n ) + rational(0.003)\n else:\n result = initial\n for _ in range(iterations):\n result = result + result - result * result * x\n return result\n if method == \"log\":\n return exp(-log(x, iterations=log_iters), iterations=exp_iters)\n raise ValueError(f\"Invalid method {method} given for reciprocal function\")\n\n\ndef inv_sqrt(\n x: _NadaRational,\n initial: Optional[Union[_NadaRational, None]] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n \"\"\"\n\n if method == \"NR\":\n if initial is None:\n # Initialization to a decent estimate (found by qualitative inspection):\n # exp(- x/2 - 0.2) * 2.2 + 0.2 - x/1024\n y = exp(-(x >> UnsignedInteger(1)) - rational(0.2)) * rational(\n 2.2\n ) + rational(0.2)\n y -= x >> UnsignedInteger(10) # div by 1024\n else:\n y = initial\n\n # Newton Raphson iterations for inverse square root\n for _ in range(iterations):\n y = (y * (rational(3) - x * y * y)) >> UnsignedInteger(1)\n return y\n raise ValueError(f\"Invalid method {method} given for inv_sqrt function\")\n\n\ndef sqrt(\n x: _NadaRational,\n initial: Union[_NadaRational, None] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n \"\"\"\n\n if method == \"NR\":\n return inv_sqrt(x, initial=initial, iterations=iterations, method=method) * x\n\n raise ValueError(f\"Invalid method {method} given for sqrt function\")\n\n\n# Trigonometry\n\n\ndef _eix(x: _NadaRational, iterations: int = 10) -> Tuple[_NadaRational, _NadaRational]:\n r\"\"\"Computes e^(i * x) where i is the imaginary unit through the formula:\n\n .. math::\n Re\\{e^{i * x}\\}, Im\\{e^{i * x}\\} = \\cos(x), \\sin(x)\n\n Args:\n x (Union[Rational, SecretRational]): the input value.\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[Union[Rational, SecretRational], Union[Rational, SecretRational]]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n one = rational(1)\n im = x >> UnsignedInteger(iterations)\n\n # First iteration uses knowledge that `re` is public and = 1\n re = one - im * im\n im *= rational(2)\n\n # Compute (a + bi)^2 -> (a^2 - b^2) + (2ab)i `iterations` times\n for _ in range(iterations - 1):\n a2 = re * re\n b2 = im * im\n im = im * re\n im *= rational(2)\n re = a2 - b2\n\n return re, im\n\n\ndef cossin(\n x: _NadaRational, iterations: int = 10\n) -> Tuple[_NadaRational, _NadaRational]:\n r\"\"\"\n Computes cosine and sine through e^(i * x) where i is the imaginary unit.\n \"\"\"\n return _eix(x, iterations=iterations)\n\n\ndef cos(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the cosine of x using cos(x) = Re{exp(i * x)}.\n \"\"\"\n return cossin(x, iterations=iterations)[0]\n\n\ndef sin(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the sine of x using sin(x) = Im{exp(i * x)}.\n \"\"\"\n return cossin(x, iterations=iterations)[1]\n\n\ndef tan(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the tan of x using tan(x) = sin(x) / cos(x).\n \"\"\"\n c, s = cossin(x, iterations=iterations)\n return s * reciprocal(c)\n\n\n# Activation functions\n\n\n@functools.lru_cache(maxsize=10)\ndef chebyshev_series(func, width, terms):\n \"\"\"\n Computes Chebyshev coefficients.\n \"\"\"\n n_range = np.arange(start=0, stop=terms, dtype=float)\n x = width * np.cos((n_range + 0.5) * np.pi / terms)\n y = func(x)\n cos_term = np.cos(np.outer(n_range, n_range + 0.5) * np.pi / terms)\n coeffs = (2 / terms) * np.sum(y * cos_term, axis=1)\n return coeffs\n\n\ndef tanh(\n x: _NadaRational, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the hyperbolic tangent function.\n \"\"\"\n\n if method == \"reciprocal\":\n return sigmoid(x + x, method=method) * rational(2) - rational(1)\n if method == \"chebyshev\":\n coeffs = chebyshev_series(np.tanh, 1, chebyshev_terms)[1::2]\n # transform np.array of float to na.array of rationals\n coeffs = np.vectorize(rational)(coeffs)\n out = _chebyshev_polynomials(x, chebyshev_terms).transpose() @ coeffs\n # truncate outside [-maxval, maxval]\n return _hardtanh(x, out)\n if method == \"motzkin\":\n # Using approximation from \"BOLT: Privacy-Preserving, Accurate and Efficient\n # Inference for Transformers\"\n # section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n # ltz is used for absolute value of x and to compute sign (used to generate result).\n # We don't use 'abs' and 'sign' functions to avoid computing ltz twice.\n # sign = 1 - 2 * ltz, where ltz = (x < rational(0)).if_else(rational(1), rational(0))\n sgn = rational(1) - rational(2) * (x < rational(0)).if_else(\n rational(1), rational(0)\n )\n # absolute value\n abs_x = x * sgn\n\n # Motzkin’s polynomial preprocessing\n t0 = rational(-4.259314087994767)\n t1 = rational(18.86353816972803)\n t2 = rational(-36.42402897526823)\n t3 = rational(-0.013232131886235352)\n t4 = rational(-3.3289339650097993)\n t5 = rational(-0.0024920889620412097)\n tanh_p0 = (abs_x + t0) * abs_x + t1\n tanh_p1 = (tanh_p0 + abs_x + t2) * tanh_p0 * t3 * abs_x + t4 * abs_x + t5\n\n return (abs_x > rational(2.855)).if_else(sgn, sgn * tanh_p1)\n raise ValueError(f\"Unrecognized method {method} for tanh\")\n\n\n### Auxiliary functions for tanh\n\n\ndef _chebyshev_polynomials(x: _NadaRational, terms: int) -> np.ndarray:\n \"\"\"Evaluates odd degree Chebyshev polynomials at x.\n\n Chebyshev Polynomials of the first kind are defined as:\n\n .. math::\n P_0(x) = 1, P_1(x) = x, P_n(x) = 2 P_{n - 1}(x) - P_{n-2}(x)\n\n Args:\n x (Union[\"Rational\", \"SecretRational\"]): input at which polynomials are evaluated\n terms (int): highest degree of Chebyshev polynomials.\n Must be even and at least 6.\n Returns:\n NadaArray of polynomials evaluated at x of shape `(terms, *x)`.\n\n Raises:\n ValueError: Raised if 'terrms' is odd and < 6.\n \"\"\"\n if terms % 2 != 0 or terms < 6:\n raise ValueError(\"Chebyshev terms must be even and >= 6\")\n\n # Initiate base polynomials\n # P_0\n # polynomials = np.array([x])\n # y = rational(4) * x * x - rational(2)\n # z = y - rational(1)\n # # P_1\n # polynomials = np.append(polynomials, z * x)\n\n # # Generate remaining Chebyshev polynomials using the recurrence relation\n # for k in range(2, terms // 2):\n # next_polynomial = y * polynomials[k - 1] - polynomials[k - 2]\n # polynomials = np.append(polynomials, next_polynomial)\n\n # return polynomials\n\n polynomials = [x]\n y = rational(4) * x * x - rational(2)\n z = y - rational(1)\n # P_1\n polynomials.append(z * x)\n\n # Generate remaining Chebyshev polynomials using the recurrence relation\n for k in range(2, terms // 2):\n next_polynomial = y * polynomials[k - 1] - polynomials[k - 2]\n polynomials.append(next_polynomial)\n\n return np.array(polynomials)\n\n\ndef _hardtanh(\n x: _NadaRational,\n output: _NadaRational,\n abs_const: _NadaRational = rational(1),\n abs_range: _NadaRational = rational(1),\n) -> _NadaRational:\n r\"\"\"Applies the HardTanh function element-wise.\n\n HardTanh is defined as:\n\n .. math::\n \\text{HardTanh}(x) = \\begin{cases}\n 1 & \\text{ if } x > 1 \\\\\n -1 & \\text{ if } x < -1 \\\\\n Tanh(x) & \\text{ otherwise } \\\\\n \\end{cases}\n\n The range of the linear region :math:`[-1, 1]` can be adjusted using\n :attr:`abs_range`.\n\n Args:\n x (Union[Rational, SecretRational]): the input value of the Tanh.\n output (Union[Rational, SecretRational]): the output value of the approximation of Tanh.\n abs_const (Union[Rational, SecretRational]): constant value to which |Tanh(x)| converges.\n Defaults to 1.\n abs_range (Union[Rational, SecretRational]): absolute value of the range. Defaults to 1.\n\n Returns:\n Union[Rational, SecretRational]: HardTanh output.\n \"\"\"\n # absolute value\n sgn = sign(x)\n abs_x = x * sgn\n # chekc if inside [-abs_range, abs_range] interval\n ineight_cond = abs_x < abs_range\n result = ineight_cond.if_else(output, abs_const * sgn)\n\n return result\n\n\n### End of auxiliary functions for tanh\n\n\ndef sigmoid(\n x: _NadaRational, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the sigmoid function.\n \"\"\"\n if method == \"chebyshev\":\n tanh_approx = tanh(\n x >> UnsignedInteger(1), method=method, chebyshev_terms=chebyshev_terms\n )\n return (tanh_approx >> UnsignedInteger(1)) + rational(0.5)\n if method == \"motzkin\":\n tanh_approx = tanh(\n x >> UnsignedInteger(1), method=method, chebyshev_terms=chebyshev_terms\n )\n return (tanh_approx >> UnsignedInteger(1)) + rational(0.5)\n if method == \"reciprocal\":\n # ltz is used for absolute value of x and to generate 'result'.\n # We don't use 'abs' function to avoid computing ltz twice.\n ltz_cond = x < rational(0)\n ltz = ltz_cond.if_else(rational(1), rational(0))\n # compute absolute value of x\n sgn = rational(1) - rational(2) * ltz\n pos_x = x * sgn\n\n denominator = exp(-pos_x) + rational(1)\n pos_output = reciprocal(\n denominator, all_pos=True, initial=rational(0.75), iterations=3, exp_iters=9\n )\n\n # result is equivalent to (1 - ltz).if_else(pos_output, 1 - pos_output)\n result = pos_output + ltz - rational(2) * pos_output * ltz\n return result\n raise ValueError(f\"Unrecognized method {method} for sigmoid\")\n\n\ndef gelu(\n x: _NadaRational, method: str = \"tanh\", tanh_method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the gelu function.\n \"\"\"\n\n if method == \"tanh\":\n # Using common approximation:\n # x/2 * (1 + tanh(0.797884560 * ( x + 0.04471 * x ** 3 ) ) )\n # where 0.797884560 ~= sqrt(2/pi).\n val = rational(0.797884560) * (x + rational(0.044715) * x**3)\n return (x * (rational(1) + tanh(val, method=tanh_method))) >> UnsignedInteger(1)\n if method == \"motzkin\":\n # Using approximation from \"BOLT: Privacy-Preserving, Accurate and Efficient\n # Inference for Transformers\"\n # section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n # ltz is used for absolute value of x and to compute relu.\n # We don't use 'abs' and '_relu' functions to avoid computing ltz twice.\n ltz = (x < rational(0)).if_else(rational(1), rational(0))\n # absolute value\n sgn = rational(1) - rational(2) * ltz\n abs_x = x * sgn\n # relu\n relu = x * (rational(1) - ltz)\n\n # Motzkin’s polynomial preprocessing\n g0 = rational(0.14439048359960427)\n g1 = rational(-0.7077117131613893)\n g2 = rational(4.5702822654246535)\n g3 = rational(-8.15444702051307)\n g4 = rational(16.382265425072532)\n gelu_p0 = (g0 * abs_x + g1) * abs_x + g2\n gelu_p1 = (gelu_p0 + g0 * abs_x + g3) * gelu_p0 + g4 + (x >> UnsignedInteger(1))\n\n return (abs_x > rational(2.7)).if_else(relu, gelu_p1)\n raise ValueError(f\"Unrecognized method {method} for gelu\")\n\n\ndef silu(\n x: _NadaRational,\n method_sigmoid: str = \"reciprocal\",\n) -> _NadaRational:\n \"\"\"\n Computes the gelu function\n \"\"\"\n return x * sigmoid(x, method=method_sigmoid)\n","voting.py":"from nada_dsl import *\nimport nada_numpy as na\n\n# Candidate mapping: candidate name to their respective candidate identifier\n# Every voter reads the candidate map and casts their vote. \n# If their vote is SecretInteger: 1, it means they cast a vote for kamala_harris\n# If their vote is SecretInteger: 2, it means they cast a vote for donald_trump\n# If their vote is SecretInteger: 3, it means they cast a vote for rfk_jr\ncandidates_map = {\"kamala_harris\": 1, \"donald_trump\": 2, \"rfk_jr\": 3}\n\ndef count_votes_for_candidate(votes: List[SecretInteger], initialValue: SecretInteger, candidate_id: Integer) -> SecretInteger:\n total_votes_for_candidate = initialValue\n for vote in votes:\n votes_to_add = (vote == candidate_id).if_else(Integer(1), Integer(0))\n # print(type(votes_to_add)) # every voter's vote is kept secret: \n total_votes_for_candidate = total_votes_for_candidate + votes_to_add\n \n return total_votes_for_candidate\n\ndef nada_main():\n num_voters = 8\n # Creates parties with names Voter0, Voter1 ... Voter7\n voters = na.parties(num_voters, [f\"Voter{i}\" for i in range(num_voters)])\n party_official = Party(name=\"Official\")\n vote_start_count = SecretInteger(Input(name=\"vote_start_count\", party=party_official))\n\n votes_list = []\n for i in range(num_voters):\n votes_list.append(\n # Voter0 inputs vote_0\n SecretInteger(Input(name=\"vote_\" + str(i), party=voters[i]))\n )\n\n # Get the total votes per candidate\n candidate_totals = {\n name: count_votes_for_candidate(votes_list, vote_start_count, Integer(candidate_id))\n for name, candidate_id in candidates_map.items()\n }\n\n vote_count_results = [\n Output(candidate_totals[name], f\"{name}_votes\", party=party_official)\n for name in candidates_map.keys()\n ]\n return vote_count_results\n\nif __name__ == \"__main__\":\n nada_main()"},"source_refs":[{"file":"voting.py","lineno":16,"offset":889,"length":76},{"file":"voting.py","lineno":14,"offset":688,"length":77},{"file":"voting.py","lineno":36,"offset":1581,"length":92},{"file":"voting.py","lineno":31,"offset":1432,"length":72},{"file":"voting.py","lineno":25,"offset":1226,"length":90},{"file":"voting.py","lineno":41,"offset":1765,"length":77},{"file":"voting.py","lineno":24,"offset":1182,"length":43},{"file":"funcs.py","lineno":107,"offset":2341,"length":65}]}
\ No newline at end of file
+{"functions":[],"parties":[{"name":"Official","source_ref_index":6},{"name":"Voter0","source_ref_index":7},{"name":"Voter1","source_ref_index":7},{"name":"Voter2","source_ref_index":7},{"name":"Voter3","source_ref_index":7},{"name":"Voter4","source_ref_index":7},{"name":"Voter5","source_ref_index":7},{"name":"Voter6","source_ref_index":7},{"name":"Voter7","source_ref_index":7}],"inputs":[{"type":"SecretInteger","party":"Official","name":"vote_start_count","doc":"","source_ref_index":4},{"type":"SecretInteger","party":"Voter0","name":"vote_0","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter1","name":"vote_1","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter2","name":"vote_2","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter3","name":"vote_3","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter4","name":"vote_4","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter5","name":"vote_5","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter6","name":"vote_6","doc":"","source_ref_index":3},{"type":"SecretInteger","party":"Voter7","name":"vote_7","doc":"","source_ref_index":3}],"literals":[{"name":"0420609fa1d35394f41049df03ef341f","value":"0","type":"Integer"},{"name":"10d33944d37d5b1b833be6fd73d3033c","value":"1","type":"Integer"},{"name":"be9dc3499e38754968f0ed1e2d88d815","value":"2","type":"Integer"},{"name":"eb53046d942e0370244802bab2e0909f","value":"3","type":"Integer"}],"outputs":[{"name":"kamala_harris_votes","operation_id":4354414144,"party":"Official","type":"SecretInteger","source_ref_index":5},{"name":"donald_trump_votes","operation_id":4354457776,"party":"Official","type":"SecretInteger","source_ref_index":5},{"name":"rfk_jr_votes","operation_id":4354468432,"party":"Official","type":"SecretInteger","source_ref_index":5}],"operations":{"4354410112":{"LiteralReference":{"id":4354410112,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354422592":{"Equals":{"id":4354422592,"left":4354387760,"right":4354414432,"type":"SecretBoolean","source_ref_index":1}},"4354462768":{"LiteralReference":{"id":4354462768,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354462192":{"Equals":{"id":4354462192,"left":4354387040,"right":4354458016,"type":"SecretBoolean","source_ref_index":1}},"4354465600":{"IfElse":{"id":4354465600,"this":4354464784,"arg_0":4354465072,"arg_1":4354465360,"type":"SecretInteger","source_ref_index":1}},"4354416928":{"IfElse":{"id":4354416928,"this":4354416112,"arg_0":4354416400,"arg_1":4354416688,"type":"SecretInteger","source_ref_index":1}},"4354457776":{"Addition":{"id":4354457776,"left":4354423648,"right":4354424704,"type":"SecretInteger","source_ref_index":0}},"4354460176":{"LiteralReference":{"id":4354460176,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354460896":{"Equals":{"id":4354460896,"left":4354386800,"right":4354458016,"type":"SecretBoolean","source_ref_index":1}},"4354386800":{"InputReference":{"id":4354386800,"refers_to":"vote_2","type":"SecretInteger","source_ref_index":3}},"4354413712":{"LiteralReference":{"id":4354413712,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354467136":{"Addition":{"id":4354467136,"left":4354465840,"right":4354466896,"type":"SecretInteger","source_ref_index":0}},"4354390256":{"IfElse":{"id":4354390256,"this":4354389536,"arg_0":4354389824,"arg_1":4354390064,"type":"SecretInteger","source_ref_index":1}},"4354389824":{"LiteralReference":{"id":4354389824,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354465360":{"LiteralReference":{"id":4354465360,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354462480":{"LiteralReference":{"id":4354462480,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354461712":{"IfElse":{"id":4354461712,"this":4354460896,"arg_0":4354461184,"arg_1":4354461472,"type":"SecretInteger","source_ref_index":1}},"4354420000":{"Equals":{"id":4354420000,"left":4354387280,"right":4354414432,"type":"SecretBoolean","source_ref_index":1}},"4354468192":{"IfElse":{"id":4354468192,"this":4354467376,"arg_0":4354467664,"arg_1":4354467952,"type":"SecretInteger","source_ref_index":1}},"4354389344":{"Addition":{"id":4354389344,"left":4354386272,"right":4354389104,"type":"SecretInteger","source_ref_index":0}},"4354463776":{"LiteralReference":{"id":4354463776,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354387040":{"InputReference":{"id":4354387040,"refers_to":"vote_3","type":"SecretInteger","source_ref_index":3}},"4354413904":{"IfElse":{"id":4354413904,"this":4354413184,"arg_0":4354413472,"arg_1":4354413712,"type":"SecretInteger","source_ref_index":1}},"4354420288":{"LiteralReference":{"id":4354420288,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354416112":{"Equals":{"id":4354416112,"left":4354386560,"right":4354414432,"type":"SecretBoolean","source_ref_index":1}},"4341447808":{"InputReference":{"id":4341447808,"refers_to":"vote_0","type":"SecretInteger","source_ref_index":3}},"4354464544":{"Addition":{"id":4354464544,"left":4354463248,"right":4354464304,"type":"SecretInteger","source_ref_index":0}},"4354423648":{"Addition":{"id":4354423648,"left":4354422352,"right":4354423408,"type":"SecretInteger","source_ref_index":0}},"4354389104":{"IfElse":{"id":4354389104,"this":4354388384,"arg_0":4354388720,"arg_1":4354388864,"type":"SecretInteger","source_ref_index":1}},"4354411984":{"Equals":{"id":4354411984,"left":4354387760,"right":4354388288,"type":"SecretBoolean","source_ref_index":1}},"4354461472":{"LiteralReference":{"id":4354461472,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354422112":{"IfElse":{"id":4354422112,"this":4354421296,"arg_0":4354421584,"arg_1":4354421872,"type":"SecretInteger","source_ref_index":1}},"4354467664":{"LiteralReference":{"id":4354467664,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354463488":{"Equals":{"id":4354463488,"left":4354387280,"right":4354458016,"type":"SecretBoolean","source_ref_index":1}},"4354387760":{"InputReference":{"id":4354387760,"refers_to":"vote_6","type":"SecretInteger","source_ref_index":3}},"4354421584":{"LiteralReference":{"id":4354421584,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354391936":{"Equals":{"id":4354391936,"left":4354387040,"right":4354388288,"type":"SecretBoolean","source_ref_index":1}},"4354388720":{"LiteralReference":{"id":4354388720,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354458304":{"Equals":{"id":4354458304,"left":4341447808,"right":4354458016,"type":"SecretBoolean","source_ref_index":1}},"4354464064":{"LiteralReference":{"id":4354464064,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354415584":{"IfElse":{"id":4354415584,"this":4354414672,"arg_0":4354415008,"arg_1":4354415344,"type":"SecretInteger","source_ref_index":1}},"4354466368":{"LiteralReference":{"id":4354466368,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354410304":{"IfElse":{"id":4354410304,"this":4354409584,"arg_0":4354409872,"arg_1":4354410112,"type":"SecretInteger","source_ref_index":1}},"4354418992":{"LiteralReference":{"id":4354418992,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354388288":{"LiteralReference":{"id":4354388288,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":2}},"4354412704":{"IfElse":{"id":4354412704,"this":4354411984,"arg_0":4354412272,"arg_1":4354412512,"type":"SecretInteger","source_ref_index":1}},"4354417408":{"Equals":{"id":4354417408,"left":4354386800,"right":4354414432,"type":"SecretBoolean","source_ref_index":1}},"4354418224":{"IfElse":{"id":4354418224,"this":4354417408,"arg_0":4354417696,"arg_1":4354417984,"type":"SecretInteger","source_ref_index":1}},"4354463008":{"IfElse":{"id":4354463008,"this":4354462192,"arg_0":4354462480,"arg_1":4354462768,"type":"SecretInteger","source_ref_index":1}},"4354409104":{"IfElse":{"id":4354409104,"this":4354391936,"arg_0":4354408672,"arg_1":4354408912,"type":"SecretInteger","source_ref_index":1}},"4354388864":{"LiteralReference":{"id":4354388864,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354423168":{"LiteralReference":{"id":4354423168,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354417696":{"LiteralReference":{"id":4354417696,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354409872":{"LiteralReference":{"id":4354409872,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354415344":{"LiteralReference":{"id":4354415344,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354459600":{"Equals":{"id":4354459600,"left":4354386560,"right":4354458016,"type":"SecretBoolean","source_ref_index":1}},"4354467376":{"Equals":{"id":4354467376,"left":4354388000,"right":4354458016,"type":"SecretBoolean","source_ref_index":1}},"4354424704":{"IfElse":{"id":4354424704,"this":4354423888,"arg_0":4354424176,"arg_1":4354424464,"type":"SecretInteger","source_ref_index":1}},"4354466656":{"LiteralReference":{"id":4354466656,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354420816":{"IfElse":{"id":4354420816,"this":4354420000,"arg_0":4354420288,"arg_1":4354420576,"type":"SecretInteger","source_ref_index":1}},"4354408912":{"LiteralReference":{"id":4354408912,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354423888":{"Equals":{"id":4354423888,"left":4354388000,"right":4354414432,"type":"SecretBoolean","source_ref_index":1}},"4354390064":{"LiteralReference":{"id":4354390064,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354416400":{"LiteralReference":{"id":4354416400,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354389536":{"Equals":{"id":4354389536,"left":4354386560,"right":4354388288,"type":"SecretBoolean","source_ref_index":1}},"4354458016":{"LiteralReference":{"id":4354458016,"refers_to":"eb53046d942e0370244802bab2e0909f","type":"Integer","source_ref_index":2}},"4354466080":{"Equals":{"id":4354466080,"left":4354387760,"right":4354458016,"type":"SecretBoolean","source_ref_index":1}},"4354411504":{"IfElse":{"id":4354411504,"this":4354410784,"arg_0":4354411072,"arg_1":4354411312,"type":"SecretInteger","source_ref_index":1}},"4354409344":{"Addition":{"id":4354409344,"left":4354391696,"right":4354409104,"type":"SecretInteger","source_ref_index":0}},"4354467952":{"LiteralReference":{"id":4354467952,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354424176":{"LiteralReference":{"id":4354424176,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354423408":{"IfElse":{"id":4354423408,"this":4354422592,"arg_0":4354422880,"arg_1":4354423168,"type":"SecretInteger","source_ref_index":1}},"4354417984":{"LiteralReference":{"id":4354417984,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354422880":{"LiteralReference":{"id":4354422880,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354468432":{"Addition":{"id":4354468432,"left":4354467136,"right":4354468192,"type":"SecretInteger","source_ref_index":0}},"4354459120":{"IfElse":{"id":4354459120,"this":4354458304,"arg_0":4354458592,"arg_1":4354458880,"type":"SecretInteger","source_ref_index":1}},"4354410784":{"Equals":{"id":4354410784,"left":4354387520,"right":4354388288,"type":"SecretBoolean","source_ref_index":1}},"4354390736":{"Equals":{"id":4354390736,"left":4354386800,"right":4354388288,"type":"SecretBoolean","source_ref_index":1}},"4354413472":{"LiteralReference":{"id":4354413472,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354458880":{"LiteralReference":{"id":4354458880,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354464784":{"Equals":{"id":4354464784,"left":4354387520,"right":4354458016,"type":"SecretBoolean","source_ref_index":1}},"4354416688":{"LiteralReference":{"id":4354416688,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354408672":{"LiteralReference":{"id":4354408672,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354460416":{"IfElse":{"id":4354460416,"this":4354459600,"arg_0":4354459888,"arg_1":4354460176,"type":"SecretInteger","source_ref_index":1}},"4354459360":{"Addition":{"id":4354459360,"left":4354386272,"right":4354459120,"type":"SecretInteger","source_ref_index":0}},"4354421872":{"LiteralReference":{"id":4354421872,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354391696":{"Addition":{"id":4354391696,"left":4354390496,"right":4354391456,"type":"SecretInteger","source_ref_index":0}},"4354419520":{"IfElse":{"id":4354419520,"this":4354418704,"arg_0":4354418992,"arg_1":4354419280,"type":"SecretInteger","source_ref_index":1}},"4354412944":{"Addition":{"id":4354412944,"left":4354411744,"right":4354412704,"type":"SecretInteger","source_ref_index":0}},"4354422352":{"Addition":{"id":4354422352,"left":4354421056,"right":4354422112,"type":"SecretInteger","source_ref_index":0}},"4354414432":{"LiteralReference":{"id":4354414432,"refers_to":"be9dc3499e38754968f0ed1e2d88d815","type":"Integer","source_ref_index":2}},"4354465072":{"LiteralReference":{"id":4354465072,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354414144":{"Addition":{"id":4354414144,"left":4354412944,"right":4354413904,"type":"SecretInteger","source_ref_index":0}},"4354418464":{"Addition":{"id":4354418464,"left":4354417168,"right":4354418224,"type":"SecretInteger","source_ref_index":0}},"4354391264":{"LiteralReference":{"id":4354391264,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354464304":{"IfElse":{"id":4354464304,"this":4354463488,"arg_0":4354463776,"arg_1":4354464064,"type":"SecretInteger","source_ref_index":1}},"4354409584":{"Equals":{"id":4354409584,"left":4354387280,"right":4354388288,"type":"SecretBoolean","source_ref_index":1}},"4354461184":{"LiteralReference":{"id":4354461184,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354465840":{"Addition":{"id":4354465840,"left":4354464544,"right":4354465600,"type":"SecretInteger","source_ref_index":0}},"4354388000":{"InputReference":{"id":4354388000,"refers_to":"vote_7","type":"SecretInteger","source_ref_index":3}},"4354415872":{"Addition":{"id":4354415872,"left":4354386272,"right":4354415584,"type":"SecretInteger","source_ref_index":0}},"4354419280":{"LiteralReference":{"id":4354419280,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354391024":{"LiteralReference":{"id":4354391024,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354390496":{"Addition":{"id":4354390496,"left":4354389344,"right":4354390256,"type":"SecretInteger","source_ref_index":0}},"4354415008":{"LiteralReference":{"id":4354415008,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354414672":{"Equals":{"id":4354414672,"left":4341447808,"right":4354414432,"type":"SecretBoolean","source_ref_index":1}},"4354466896":{"IfElse":{"id":4354466896,"this":4354466080,"arg_0":4354466368,"arg_1":4354466656,"type":"SecretInteger","source_ref_index":1}},"4354388384":{"Equals":{"id":4354388384,"left":4341447808,"right":4354388288,"type":"SecretBoolean","source_ref_index":1}},"4354410544":{"Addition":{"id":4354410544,"left":4354409344,"right":4354410304,"type":"SecretInteger","source_ref_index":0}},"4354387280":{"InputReference":{"id":4354387280,"refers_to":"vote_4","type":"SecretInteger","source_ref_index":3}},"4354460656":{"Addition":{"id":4354460656,"left":4354459360,"right":4354460416,"type":"SecretInteger","source_ref_index":0}},"4354459888":{"LiteralReference":{"id":4354459888,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354420576":{"LiteralReference":{"id":4354420576,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354463248":{"Addition":{"id":4354463248,"left":4354461952,"right":4354463008,"type":"SecretInteger","source_ref_index":0}},"4354458592":{"LiteralReference":{"id":4354458592,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354411072":{"LiteralReference":{"id":4354411072,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354424464":{"LiteralReference":{"id":4354424464,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354387520":{"InputReference":{"id":4354387520,"refers_to":"vote_5","type":"SecretInteger","source_ref_index":3}},"4354411744":{"Addition":{"id":4354411744,"left":4354410544,"right":4354411504,"type":"SecretInteger","source_ref_index":0}},"4354418704":{"Equals":{"id":4354418704,"left":4354387040,"right":4354414432,"type":"SecretBoolean","source_ref_index":1}},"4354412512":{"LiteralReference":{"id":4354412512,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354412272":{"LiteralReference":{"id":4354412272,"refers_to":"10d33944d37d5b1b833be6fd73d3033c","type":"Integer","source_ref_index":1}},"4354386272":{"InputReference":{"id":4354386272,"refers_to":"vote_start_count","type":"SecretInteger","source_ref_index":4}},"4354421056":{"Addition":{"id":4354421056,"left":4354419760,"right":4354420816,"type":"SecretInteger","source_ref_index":0}},"4354461952":{"Addition":{"id":4354461952,"left":4354460656,"right":4354461712,"type":"SecretInteger","source_ref_index":0}},"4354391456":{"IfElse":{"id":4354391456,"this":4354390736,"arg_0":4354391024,"arg_1":4354391264,"type":"SecretInteger","source_ref_index":1}},"4354417168":{"Addition":{"id":4354417168,"left":4354415872,"right":4354416928,"type":"SecretInteger","source_ref_index":0}},"4354421296":{"Equals":{"id":4354421296,"left":4354387520,"right":4354414432,"type":"SecretBoolean","source_ref_index":1}},"4354411312":{"LiteralReference":{"id":4354411312,"refers_to":"0420609fa1d35394f41049df03ef341f","type":"Integer","source_ref_index":1}},"4354386560":{"InputReference":{"id":4354386560,"refers_to":"vote_1","type":"SecretInteger","source_ref_index":3}},"4354413184":{"Equals":{"id":4354413184,"left":4354388000,"right":4354388288,"type":"SecretBoolean","source_ref_index":1}},"4354419760":{"Addition":{"id":4354419760,"left":4354418464,"right":4354419520,"type":"SecretInteger","source_ref_index":0}}},"source_files":{"funcs.py":"\"\"\"\nThis module provides common functions to work with Nada Numpy. It includes: \n- the creation and manipulation of arrays and party objects.\n- non-linear functions over arrays.\n- random operations over arrays: random generation, shuffling.\n\"\"\"\n\n# pylint:disable=too-many-lines\n\nfrom typing import Any, Callable, List, Optional, Sequence, Tuple, Union\n\nimport numpy as np\nfrom nada_dsl import (Boolean, Integer, Output, Party, PublicInteger,\n PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, UnsignedInteger)\n\nfrom nada_numpy.array import NadaArray\nfrom nada_numpy.nada_typing import AnyNadaType, NadaCleartextNumber\nfrom nada_numpy.types import Rational, SecretRational, rational\nfrom nada_numpy.utils import copy_metadata\n\n__all__ = [\n \"parties\",\n \"from_list\",\n \"ones\",\n \"ones_like\",\n \"zeros\",\n \"zeros_like\",\n \"alphas\",\n \"alphas_like\",\n \"array\",\n \"random\",\n \"output\",\n \"vstack\",\n \"hstack\",\n \"ndim\",\n \"shape\",\n \"size\",\n \"pad\",\n \"frompyfunc\",\n \"vectorize\",\n \"eye\",\n \"arange\",\n \"linspace\",\n \"split\",\n \"compress\",\n \"copy\",\n \"cumprod\",\n \"cumsum\",\n \"diagonal\",\n \"mean\",\n \"prod\",\n \"put\",\n \"ravel\",\n \"repeat\",\n \"reshape\",\n \"resize\",\n \"squeeze\",\n \"sum\",\n \"swapaxes\",\n \"take\",\n \"trace\",\n \"transpose\",\n \"sign\",\n \"abs\",\n \"exp\",\n \"polynomial\",\n \"log\",\n \"reciprocal\",\n \"inv_sqrt\",\n \"sqrt\",\n \"cossin\",\n \"sin\",\n \"cos\",\n \"tan\",\n \"tanh\",\n \"sigmoid\",\n \"gelu\",\n \"silu\",\n \"shuffle\",\n]\n\n\ndef parties(num: int, party_names: Optional[List[str]] = None) -> List[Party]:\n \"\"\"\n Create a list of Party objects with specified names.\n\n Args:\n num (int): The number of parties to create.\n party_names (List[str], optional): Party names to use. Defaults to None.\n\n Raises:\n ValueError: Raised when incorrect number of party names is supplied.\n\n Returns:\n List[Party]: A list of Party objects.\n \"\"\"\n if party_names is None:\n party_names = [f\"Party{i}\" for i in range(num)]\n\n if len(party_names) != num:\n num_supplied_parties = len(party_names)\n raise ValueError(\n f\"Incorrect number of party names. Expected {num}, received {num_supplied_parties}\"\n )\n\n return [Party(name=party_name) for party_name in party_names]\n\n\ndef __from_numpy(arr: np.ndarray, nada_type: NadaCleartextNumber) -> List:\n \"\"\"\n Recursively convert a n-dimensional NumPy array to a nested list of NadaInteger objects.\n\n Args:\n arr (np.ndarray): A NumPy array of integers.\n nada_type (type): The type of NadaInteger objects to create.\n\n Returns:\n List: A nested list of NadaInteger objects.\n \"\"\"\n if len(arr.shape) == 1:\n if isinstance(nada_type, Rational):\n return [nada_type(elem) for elem in arr] # type: ignore\n return [nada_type(int(elem)) for elem in arr] # type: ignore\n return [__from_numpy(arr[i], nada_type) for i in range(arr.shape[0])]\n\n\ndef from_list(\n lst: Union[List, np.ndarray], nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray from a list of integers.\n\n Args:\n lst (Union[List, np.ndarray]): A list of integers representing the elements of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n lst_np = np.array(lst)\n return NadaArray(np.array(__from_numpy(lst_np, nada_type)))\n\n\ndef ones(dims: Sequence[int], nada_type: NadaCleartextNumber = Integer) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with ones.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with ones.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n return from_list(np.ones(dims), nada_type)\n\n\ndef ones_like(\n a: np.ndarray | NadaArray, nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with one with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): A reference array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with ones.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n if isinstance(a, NadaArray):\n a = a.inner\n return from_list(np.ones_like(a), nada_type)\n\n\ndef zeros(dims: Sequence[int], nada_type: NadaCleartextNumber = Integer) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with zeros.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with zeros.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n return from_list(np.zeros(dims), nada_type)\n\n\ndef zeros_like(\n a: np.ndarray | NadaArray, nada_type: NadaCleartextNumber = Integer\n) -> NadaArray:\n \"\"\"\n Create a cleartext NadaArray filled with zeros with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): A reference array.\n nada_type (type, optional): The type of NadaInteger objects to create. Defaults to Integer.\n\n Returns:\n NadaArray: The created NadaArray filled with zeros.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n if isinstance(a, NadaArray):\n a = a.inner\n return from_list(np.zeros_like(a), nada_type)\n\n\ndef alphas(dims: Sequence[int], alpha: Any) -> NadaArray:\n \"\"\"\n Create a NadaArray filled with a certain constant value.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n alpha (Any): Some constant value.\n\n Returns:\n NadaArray: NadaArray filled with constant value.\n \"\"\"\n ones_array = np.ones(dims)\n return NadaArray(np.frompyfunc(lambda _: alpha, 1, 1)(ones_array))\n\n\ndef alphas_like(a: np.ndarray | NadaArray, alpha: Any) -> NadaArray:\n \"\"\"\n Create a NadaArray filled with a certain constant value\n with the same shape and type as a given array.\n\n Args:\n a (np.ndarray | NadaArray): Reference array.\n alpha (Any): Some constant value.\n\n Returns:\n NadaArray: NadaArray filled with constant value.\n \"\"\"\n if isinstance(a, NadaArray):\n a = a.inner\n ones_array = np.ones_like(a)\n return NadaArray(np.frompyfunc(lambda _: alpha, 1, 1)(ones_array))\n\n\ndef array(\n dims: Sequence[int],\n party: Party,\n prefix: str,\n nada_type: Union[\n SecretInteger,\n SecretUnsignedInteger,\n PublicInteger,\n PublicUnsignedInteger,\n SecretRational,\n Rational,\n ],\n) -> NadaArray:\n \"\"\"\n Create a NadaArray with the specified dimensions and elements of the given type.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n party (Party): The party object.\n prefix (str): A prefix for naming the array elements.\n nada_type (type): The type of elements to create.\n\n Returns:\n NadaArray: The created NadaArray.\n \"\"\"\n return NadaArray.array(dims, party, prefix, nada_type)\n\n\ndef random(\n dims: Sequence[int],\n nada_type: SecretInteger | SecretUnsignedInteger | SecretRational = SecretInteger,\n) -> NadaArray:\n \"\"\"\n Create a random NadaArray with the specified dimensions.\n\n Args:\n dims (Sequence[int]): A list of integers representing the dimensions of the array.\n nada_type (type, optional): The type of elements to create. Defaults to SecretInteger.\n\n Returns:\n NadaArray: A NadaArray with random values of the specified type.\n \"\"\"\n return NadaArray.random(dims, nada_type)\n\n\ndef output(\n value: Union[NadaArray, AnyNadaType], party: Party, prefix: str\n) -> List[Output]:\n \"\"\"\n Generate a list of Output objects for some provided value.\n\n Args:\n value (Union[NadaArray, AnyNadaType]): The input NadaArray.\n party (Party): The party object.\n prefix (str): The prefix for naming the Output objects.\n\n Returns:\n List[Output]: A list of Output objects.\n \"\"\"\n if isinstance(value, NadaArray):\n # pylint:disable=protected-access\n return NadaArray._output_array(value, party, prefix)\n if isinstance(value, (Rational, SecretRational)):\n value = value.value\n return [Output(value, prefix, party)]\n\n\ndef vstack(arr_list: list) -> NadaArray:\n \"\"\"\n Stack arrays in sequence vertically (row wise).\n\n Args:\n arr_list (list): A list of NadaArray objects to stack.\n\n Returns:\n NadaArray: The stacked NadaArray.\n \"\"\"\n return NadaArray(np.vstack(arr_list))\n\n\ndef hstack(arr_list: list) -> NadaArray:\n \"\"\"\n Stack arrays in sequence horizontally (column wise).\n\n Args:\n arr_list (list): A list of NadaArray objects to stack.\n\n Returns:\n NadaArray: The stacked NadaArray.\n \"\"\"\n return NadaArray(np.hstack(arr_list))\n\n\ndef ndim(arr: NadaArray) -> int:\n \"\"\"\n Returns number of array dimensions.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array dimensions.\n \"\"\"\n return arr.ndim\n\n\ndef shape(arr: NadaArray) -> Tuple[int]:\n \"\"\"\n Returns Array shape.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array shape.\n \"\"\"\n return arr.shape\n\n\ndef size(arr: NadaArray) -> int:\n \"\"\"\n Returns array size.\n\n Args:\n arr (NadaArray): Input array.\n\n Returns:\n bool: Array size.\n \"\"\"\n return arr.size\n\n\ndef to_nada(arr: np.ndarray, nada_type: NadaCleartextNumber) -> NadaArray:\n \"\"\"\n Converts a plain-text NumPy array to the equivalent NadaArray with\n a specified compatible NadaType.\n\n Args:\n arr (np.ndarray): Input Numpy array.\n nada_type (NadaCleartextNumber): Desired clear-text NadaType.\n\n Returns:\n NadaArray: Output NadaArray.\n \"\"\"\n if nada_type == Rational:\n nada_type = rational\n else:\n arr = arr.astype(int)\n return NadaArray(np.frompyfunc(nada_type, 1, 1)(arr)) # type: ignore\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.pad)\ndef pad(\n arr: NadaArray,\n pad_width: Union[Sequence[int], int],\n mode: str = \"constant\",\n **kwargs,\n) -> NadaArray:\n if mode not in {\"constant\", \"edge\", \"reflect\", \"symmetric\", \"wrap\"}:\n raise NotImplementedError(\n f\"Not currently possible to pad NadaArray in mode `{mode}`\"\n )\n\n # Override python defaults by NadaType defaults\n overriden_kwargs = {}\n if mode == \"constant\" and \"constant_values\" not in kwargs:\n if arr.is_rational:\n default = rational(0)\n elif arr.is_integer:\n default = Integer(0)\n elif arr.is_unsigned_integer:\n default = UnsignedInteger(0)\n else:\n default = Boolean(False)\n\n overriden_kwargs[\"constant_values\"] = kwargs.get(\"constant_values\", default)\n\n padded_inner = np.pad( # type: ignore\n arr.inner,\n pad_width,\n mode,\n **overriden_kwargs,\n **kwargs,\n )\n\n return NadaArray(padded_inner)\n\n\n# pylint:disable=too-few-public-methods\nclass NadaCallable:\n \"\"\"Class that wraps a vectorized NumPy function\"\"\"\n\n def __init__(self, vfunc: Callable) -> None:\n \"\"\"\n Initialization.\n\n Args:\n vfunc (Callable): Vectorized function to wrap.\n \"\"\"\n self.vfunc = vfunc\n\n def __call__(self, *args, **kwargs) -> Any:\n \"\"\"\n Routes function call to wrapped vectorized function while\n ensuring any resulting NumPy arrays are converted to NadaArrays.\n\n Returns:\n Any: Function result.\n \"\"\"\n result = self.vfunc(*args, **kwargs)\n if isinstance(result, np.ndarray):\n return NadaArray(result)\n if isinstance(result, Sequence):\n return type(result)( # type: ignore\n NadaArray(value) if isinstance(value, np.ndarray) else value\n for value in result\n )\n return result\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.frompyfunc)\ndef frompyfunc(*args, **kwargs) -> NadaCallable:\n return NadaCallable(np.frompyfunc(*args, **kwargs))\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.vectorize)\ndef vectorize(*args, **kwargs) -> NadaCallable:\n return NadaCallable(np.vectorize(*args, **kwargs))\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.eye)\ndef eye(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.eye(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.arange)\ndef arange(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.arange(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.linspace)\ndef linspace(*args, nada_type: NadaCleartextNumber, **kwargs) -> NadaArray:\n return to_nada(np.linspace(*args, **kwargs), nada_type)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.split)\ndef split(a: NadaArray, *args, **kwargs) -> List[NadaArray]:\n return [NadaArray(arr) for arr in np.split(a.inner, *args, **kwargs)]\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.compress)\ndef compress(a: NadaArray, *args, **kwargs):\n return a.compress(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.copy)\ndef copy(a: NadaArray, *args, **kwargs):\n return a.copy(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.cumprod)\ndef cumprod(a: NadaArray, *args, **kwargs):\n return a.cumprod(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.cumsum)\ndef cumsum(a: NadaArray, *args, **kwargs):\n return a.cumsum(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.diagonal)\ndef diagonal(a: NadaArray, *args, **kwargs):\n return a.diagonal(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.diagonal)\ndef mean(a: NadaArray, *args, **kwargs):\n return a.mean(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.prod)\ndef prod(a: NadaArray, *args, **kwargs):\n return a.prod(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.put)\ndef put(a: NadaArray, *args, **kwargs):\n return a.put(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.ravel)\ndef ravel(a: NadaArray, *args, **kwargs):\n return a.ravel(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.repeat)\ndef repeat(a: NadaArray, *args, **kwargs):\n return a.repeat(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.reshape)\ndef reshape(a: NadaArray, *args, **kwargs):\n return a.reshape(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.resize)\ndef resize(a: NadaArray, *args, **kwargs):\n return a.resize(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.squeeze)\ndef squeeze(a: NadaArray, *args, **kwargs):\n return a.squeeze(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring,redefined-builtin\n@copy_metadata(np.sum)\ndef sum(a: NadaArray, *args, **kwargs):\n return a.sum(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.swapaxes)\ndef swapaxes(a: NadaArray, *args, **kwargs):\n return a.swapaxes(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.take)\ndef take(a: NadaArray, *args, **kwargs):\n return a.take(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.trace)\ndef trace(a: NadaArray, *args, **kwargs):\n return a.trace(*args, **kwargs)\n\n\n# pylint:disable=missing-function-docstring\n@copy_metadata(np.transpose)\ndef transpose(a: NadaArray, *args, **kwargs):\n return a.transpose(*args, **kwargs)\n\n\n# Non-linear functions\n\n\ndef sign(arr: NadaArray) -> \"NadaArray\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n return arr.sign()\n\n\ndef abs(arr: NadaArray) -> \"NadaArray\":\n \"\"\"Computes the absolute value\"\"\"\n return arr.abs()\n\n\ndef exp(arr: NadaArray, iterations: int = 8) -> \"NadaArray\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The exponential function is computed by choosing n = 2 ** d, where d is set to `iterations`.\n The calculation is performed by computing (1 + x / n) once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n NadaArray: The approximated value of the exponential function.\n \"\"\"\n return arr.exp(iterations=iterations)\n\n\ndef polynomial(arr: NadaArray, coefficients: list) -> \"NadaArray\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n NadaArray: The result of the polynomial function applied to the input x.\n \"\"\"\n return arr.polynomial(coefficients=coefficients)\n\n\ndef log(\n arr: NadaArray,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n) -> \"NadaArray\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation of exp.\n Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of Householder approximation).\n Defaults to 8.\n\n Returns:\n NadaArray: The approximate value of the natural logarithm.\n \"\"\"\n return arr.log(\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n\n\ndef reciprocal( # pylint: disable=too-many-arguments\n arr: NadaArray,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n # pylint:disable=duplicate-code\n return arr.reciprocal(\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n\n\ndef inv_sqrt(\n arr: NadaArray,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n return arr.inv_sqrt(initial=initial, iterations=iterations, method=method)\n\n\ndef sqrt(\n arr: NadaArray,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> \"NadaArray\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the inverse\n square root Newton-Raphson iterations. By default, this will be set to allow\n convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n NadaArray: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n return arr.sqrt(initial=initial, iterations=iterations, method=method)\n\n\n# Trigonometry\n\n\ndef cossin(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit through the\n formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[NadaArray, NadaArray]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n return arr.cossin(iterations=iterations)\n\n\ndef cos(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the cosine.\n \"\"\"\n return arr.cos(iterations=iterations)\n\n\ndef sin(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the sine.\n \"\"\"\n return arr.sin(iterations=iterations)\n\n\ndef tan(arr: NadaArray, iterations: int = 10) -> \"NadaArray\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n NadaArray: The approximate value of the tan.\n \"\"\"\n return arr.tan(iterations=iterations)\n\n\n# Activation functions\n\n\ndef tanh(\n arr: NadaArray,\n chebyshev_terms: int = 32,\n method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n return arr.tanh(chebyshev_terms=chebyshev_terms, method=method)\n\n\ndef sigmoid(\n arr: NadaArray,\n chebyshev_terms: int = 32,\n method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses\n the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n return arr.sigmoid(chebyshev_terms=chebyshev_terms, method=method)\n\n\ndef gelu(\n arr: NadaArray,\n method: str = \"tanh\",\n tanh_method: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n return arr.gelu(method=method, tanh_method=tanh_method)\n\n\ndef silu(\n arr: NadaArray,\n method_sigmoid: str = \"reciprocal\",\n) -> \"NadaArray\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses the\n identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n NadaArray: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n return arr.silu(method_sigmoid=method_sigmoid)\n\n\ndef shuffle(arr: NadaArray) -> NadaArray:\n \"\"\"\n Shuffles a 1D array using the Benes network.\n\n This function rearranges the elements of a 1-dimensional array in a deterministic but seemingly\n random order based on the Benes network, a network used in certain types of sorting and\n switching circuits. The Benes network requires the input array's length to be a power of two\n (e.g., 2, 4, 8, 16, ...).\n\n Note: The resulting shuffled arrays contain the same elements as the input arrays.\n\n Args:\n NadaArray: The input array to be shuffled. This must be a 1-dimensional NumPy array.\n The length of the array must be a power of two.\n\n Returns:\n NadaArray: The shuffled version of the input array. The output is a new array where\n the elements have been rearranged according to the Benes network.\n\n Raises:\n ValueError: If the length of the input array is not a power of two.\n\n Example:\n ```python\n import nada_numpy as na\n\n # Example arrays with different data types\n parties = na.parties(2)\n a = na.array([8], parties[0], \"A\", na.Rational)\n b = na.array([8], parties[0], \"B\", na.SecretRational)\n c = na.array([8], parties[0], \"C\", PublicInteger)\n d = na.array([8], parties[0], \"D\", SecretInteger)\n\n # Shuffling the arrays\n shuffled_a = shuffle(a)\n shuffled_b = shuffle(b)\n shuffled_c = shuffle(c)\n ```\n\n Frequency analysis:\n\n This script performs a frequency analysis of a shuffle function implemented using a Benes\n network. It includes a function for shuffle, a test function for evaluating randomness,\n and an example of running the test. Below is an overview of the code and its output.\n\n 1. **Shuffle Function**:\n\n The `shuffle` function shuffles a 1D array using a Benes network approach.\n The Benes network is defined by the function `_benes_network(n)`, which should provide the\n network stages required for the shuffle.\n\n ```python\n import numpy as np\n import random\n\n def rand_bool():\n # Simulates a random boolean value\n return random.choice([0, 1]) == 0\n\n def swap_gate(a, b):\n # Conditionally swaps two values based on a random boolean\n rbool = rand_bool()\n return (b, a) if rbool else (a, b)\n\n def shuffle(array):\n # Applies Benes network shuffle to a 1D array\n if array.ndim != 1:\n raise ValueError(\"Input array must be a 1D array.\")\n\n n = array.size\n bnet = benes_network(n)\n swap_array = np.ones(n)\n\n first_numbers = np.arange(0, n, 2)\n second_numbers = np.arange(1, n, 2)\n pairs = np.column_stack((first_numbers, second_numbers))\n\n for stage in bnet:\n for ((i0, i1), (a, b)) in zip(pairs, stage):\n swap_array[i0], swap_array[i1] = swap_gate(array[a], array[b])\n array = swap_array.copy()\n\n return array\n ```\n\n 2. **Randomness Test Function:**:\n The test_shuffle_randomness function evaluates the shuffle function by performing\n multiple shuffles and counting the occurrences of each element at each position.\n\n ```python\n def test_shuffle_randomness(vector_size, num_shuffles):\n # Initializes vector and count matrix\n vector = np.arange(vector_size)\n counts = np.zeros((vector_size, vector_size), dtype=int)\n\n # Performs shuffling and counts occurrences\n for _ in range(num_shuffles):\n shuffled_vector = shuffle(vector)\n for position, element in enumerate(shuffled_vector):\n counts[int(element), position] += 1\n\n # Computes average counts and deviation\n average_counts = num_shuffles / vector_size\n deviation = np.abs(counts - average_counts)\n\n return counts, average_counts, deviation\n ```\n\n\n Running the `test_shuffle_randomness` function with a vector size of 8 and 100,000 shuffles\n provides the following results:\n\n ```python\n vector_size = 8 # Size of the vector\n num_shuffles = 100000 # Number of shuffles to perform\n\n counts, average_counts, deviation = test_shuffle_randomness(vector_size,\n num_shuffles)\n\n print(\"Counts of numbers appearances at each position:\")\n for i in range(vector_size):\n print(f\"Number {i}: {counts[i]}\")\n print(\"Expected count of number per slot:\", average_counts)\n print(\"\\nDeviation from the expected average:\")\n for i in range(vector_size):\n print(f\"Number {i}: {deviation[i]}\")\n ```\n ```bash\n >>> Counts of numbers appearances at each position:\n >>> Number 0: [12477 12409 12611 12549 12361 12548 12591 12454]\n >>> Number 1: [12506 12669 12562 12414 12311 12408 12377 12753]\n >>> Number 2: [12595 12327 12461 12607 12492 12721 12419 12378]\n >>> Number 3: [12417 12498 12586 12433 12627 12231 12638 12570]\n >>> Number 4: [12370 12544 12404 12337 12497 12743 12588 12517]\n >>> Number 5: [12559 12420 12416 12791 12508 12489 12360 12457]\n >>> Number 6: [12669 12459 12396 12394 12757 12511 12423 12391]\n >>> Number 7: [12407 12674 12564 12475 12447 12349 12604 12480]\n >>> Expected count of number per slot: 12500.0\n >>>\n >>> Deviation from the expected average:\n >>> Number 0: [ 23. 91. 111. 49. 139. 48. 91. 46.]\n >>> Number 1: [ 6. 169. 62. 86. 189. 92. 123. 253.]\n >>> Number 2: [ 95. 173. 39. 107. 8. 221. 81. 122.]\n >>> Number 3: [ 83. 2. 86. 67. 127. 269. 138. 70.]\n >>> Number 4: [130. 44. 96. 163. 3. 243. 88. 17.]\n >>> Number 5: [ 59. 80. 84. 291. 8. 11. 140. 43.]\n >>> Number 6: [169. 41. 104. 106. 257. 11. 77. 109.]\n >>> Number 7: [ 93. 174. 64. 25. 53. 151. 104. 20.]\n ```\n \"\"\"\n return arr.shuffle()\n","types.py":"\"\"\"Additional special data types\"\"\"\n\n# pylint:disable=too-many-lines\n\nimport functools\nimport warnings\nfrom typing import List, Optional, Tuple, Union\n\nimport nada_dsl as dsl\nimport numpy as np\nfrom nada_dsl import (Input, Integer, Party, PublicInteger,\n PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, UnsignedInteger)\n\n_NadaRational = Union[\"Rational\", \"SecretRational\"]\n\n_NadaType = Union[\n Integer,\n PublicInteger,\n PublicUnsignedInteger,\n SecretInteger,\n SecretUnsignedInteger,\n UnsignedInteger,\n]\n\n\nclass SecretBoolean(dsl.SecretBoolean):\n \"\"\"SecretBoolean rational wrapper\"\"\"\n\n def __init__(self, value: dsl.SecretBoolean) -> None:\n \"\"\"\n Initialization.\n\n Args:\n value (dsl.SecretBoolean): SecretBoolean value.\n \"\"\"\n super().__init__(value.inner)\n\n def if_else(\n self,\n true: Union[_NadaType, \"SecretRational\", \"Rational\"],\n false: Union[_NadaType, \"SecretRational\", \"Rational\"],\n ) -> Union[SecretInteger, SecretUnsignedInteger, \"SecretRational\"]:\n \"\"\"\n If-else logic. If the boolean is True, true is returned. If not, false is returned.\n\n Args:\n true (Union[_NadaType, SecretRational, Rational]): First argument.\n false (Union[_NadaType, SecretRational, Rational]): Second argument.\n\n Raises:\n ValueError: Raised when incompatibly-scaled values are passed.\n TypeError: Raised when invalid operation is called.\n\n Returns:\n Union[SecretInteger, SecretUnsignedInteger, \"SecretRational\"]: Return value.\n \"\"\"\n first_arg = true\n second_arg = false\n if isinstance(true, (SecretRational, Rational)) and isinstance(\n false, (SecretRational, Rational)\n ):\n # Both are SecretRational or Rational objects\n if true.log_scale != false.log_scale:\n raise ValueError(\"Cannot output values with different scales.\")\n first_arg = true.value\n second_arg = false.value\n elif isinstance(true, (Rational, SecretRational)) or isinstance(\n false, (Rational, SecretRational)\n ):\n # Both are SecretRational or Rational objects\n raise TypeError(f\"Invalid operation: {self}.IfElse({true}, {false})\")\n\n result = super().if_else(first_arg, second_arg)\n\n if isinstance(true, (SecretRational, Rational)):\n # If we have a SecretBoolean, the return type will be SecretInteger,\n # thus promoted to SecretRational\n return SecretRational(result, true.log_scale, is_scaled=True)\n return result\n\n\nclass PublicBoolean(dsl.PublicBoolean):\n \"\"\"PublicBoolean rational wrapper\"\"\"\n\n def __init__(self, value: dsl.PublicBoolean) -> None:\n \"\"\"\n Initialization.\n\n Args:\n value (dsl.PublicBoolean): PublicBoolean value.\n \"\"\"\n super().__init__(value.inner)\n\n def if_else(\n self,\n true: Union[_NadaType, \"SecretRational\", \"Rational\"],\n false: Union[_NadaType, \"SecretRational\", \"Rational\"],\n ) -> Union[\n PublicInteger,\n PublicUnsignedInteger,\n SecretInteger,\n SecretUnsignedInteger,\n \"Rational\",\n \"SecretRational\",\n ]:\n \"\"\"\n If-else logic. If the boolean is True, true is returned. If not, false is returned.\n\n Args:\n true (Union[_NadaType, SecretRational, Rational]): First argument.\n false (Union[_NadaType, SecretRational, Rational]): Second argument.\n\n Raises:\n ValueError: Raised when incompatibly-scaled values are passed.\n TypeError: Raised when invalid operation is called.\n\n Returns:\n Union[PublicInteger, PublicUnsignedInteger, SecretInteger,\n SecretUnsignedInteger, \"Rational\", \"SecretRational\"]: Return value.\n \"\"\"\n first_arg = true\n second_arg = false\n if isinstance(true, (SecretRational, Rational)) and isinstance(\n false, (SecretRational, Rational)\n ):\n # Both are SecretRational or Rational objects\n if true.log_scale != false.log_scale:\n raise ValueError(\"Cannot output values with different scales.\")\n first_arg = true.value\n second_arg = false.value\n elif isinstance(true, (Rational, SecretRational)) or isinstance(\n false, (Rational, SecretRational)\n ):\n # Both are SecretRational or Rational objects but of different type\n raise TypeError(f\"Invalid operation: {self}.IfElse({true}, {false})\")\n\n result = super().if_else(first_arg, second_arg)\n\n if isinstance(true, SecretRational) or isinstance(false, SecretRational):\n return SecretRational(result, true.log_scale, is_scaled=True)\n if isinstance(true, Rational) and isinstance(false, Rational):\n return Rational(result, true.log_scale, is_scaled=True)\n return result\n\n\nclass Rational: # pylint:disable=too-many-public-methods\n \"\"\"Wrapper class to store scaled Integer values representing a fixed-point number.\"\"\"\n\n def __init__(\n self,\n value: Union[Integer, PublicInteger],\n log_scale: Optional[int] = None,\n is_scaled: bool = True,\n ) -> None:\n \"\"\"\n Initializes wrapper around Integer object.\n\n Args:\n value (Union[Integer, PublicInteger]): The value to be representedas a Rational.\n log_scale (int, optional): Quantization scaling factor.\n Defaults to RationalConfig.log_scale.\n is_scaled (bool, optional): Flag that represents whether the value is already scaled.\n Defaults to True.\n\n Raises:\n TypeError: If value is of an incompatible type.\n \"\"\"\n if not isinstance(value, (Integer, PublicInteger)):\n raise TypeError(f\"Cannot instantiate Rational from type `{type(value)}`.\")\n\n if log_scale is None:\n log_scale = get_log_scale()\n self._log_scale = log_scale\n\n if is_scaled is False:\n value = value * Integer(\n 1 << log_scale\n ) # TODO: replace with shift when supported\n self._value = value\n\n @property\n def log_scale(self) -> int:\n \"\"\"\n Getter for the logarithmic scale value.\n\n Returns:\n int: Logarithmic scale value.\n \"\"\"\n return self._log_scale\n\n @property\n def value(self) -> Union[Integer, PublicInteger]:\n \"\"\"\n Getter for the underlying Integer value.\n\n Returns:\n Union[Integer, PublicInteger]: The Integer value.\n \"\"\"\n return self._value\n\n def add(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot add values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n other.value + self.value, self.log_scale, is_scaled=True\n )\n return Rational(self.value + other.value, self.log_scale, is_scaled=True)\n\n def __add__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def __iadd__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Add two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def sub(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to subtract.\n\n Returns:\n Union[Rational, SecretRational]: Result of the subtraction.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot substract values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value - other.value, self.log_scale, is_scaled=True\n )\n return Rational(self.value - other.value, self.log_scale, is_scaled=True)\n\n def __sub__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def __isub__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Subtract two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def mul_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers.\n\n WARNING: This function does not rescale by default. Use `mul` to multiply and rescale.\n\n Args:\n other (_NadaRational): Other rational number to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the multiplication.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot multiply values with different scales.\")\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value * other.value,\n self.log_scale + other.log_scale,\n is_scaled=True,\n )\n return Rational(\n self.value * other.value,\n self.log_scale + other.log_scale,\n is_scaled=True,\n )\n\n def mul(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers and rescale the result.\n\n Args:\n other (_NadaRational): Other rational number to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the multiplication.\n \"\"\"\n c = self.mul_no_rescale(other, ignore_scale=ignore_scale)\n d = c.rescale_down()\n return d\n\n def __mul__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def __imul__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Multiply two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def divide_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to divide by.\n\n Returns:\n Union[Rational, SecretRational]: Result of the division.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale + get_log_scale():\n raise ValueError(\n f\"Cannot divide values where scale is: {self.log_scale} / {other.log_scale}.\"\n f\"Required scale: {self.log_scale} / {other.log_scale + get_log_scale()}\"\n )\n\n if isinstance(other, SecretRational):\n return SecretRational(\n self.value / other.value,\n self.log_scale - other.log_scale,\n is_scaled=True,\n )\n return Rational(\n self.value / other.value,\n self.log_scale - other.log_scale,\n is_scaled=True,\n )\n\n def divide(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers and rescale the result.\n\n Args:\n other (_NadaRational): Other rational number to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n Union[Rational, SecretRational]: Result of the division.\n \"\"\"\n a = self.rescale_up()\n c = a.divide_no_rescale(other, ignore_scale)\n return c\n\n def __truediv__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __itruediv__(self, other: _NadaRational) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Divide two rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __pow__(self, other: int) -> \"Rational\":\n \"\"\"\n Raise a rational number to an integer power using binary exponentiation.\n\n Args:\n other (int): The exponent.\n\n Returns:\n Rational: Result of the power operation.\n\n Raises:\n TypeError: If the exponent is not an integer.\n \"\"\"\n if not isinstance(other, int):\n raise TypeError(f\"Cannot raise Rational to a power of type `{type(other)}`\")\n\n result = Rational(Integer(1), self.log_scale, is_scaled=False)\n\n if other == 0:\n return result # Any number to the power of 0 is 1\n\n base = self\n\n exponent = abs(other)\n while exponent > 0:\n if exponent % 2 == 1:\n result = result * base # type: ignore\n base *= base # type: ignore\n exponent //= 2\n\n if other < 0:\n return rational(1) / Rational( # type: ignore\n result.value, result.log_scale, is_scaled=True\n )\n\n return result\n\n def __neg__(self) -> \"Rational\":\n \"\"\"\n Negate the Rational value.\n\n Returns:\n Rational: Negated Rational value.\n \"\"\"\n return Rational(self.value * Integer(-1), self.log_scale, is_scaled=True)\n\n def __lshift__(self, other: UnsignedInteger) -> \"Rational\":\n \"\"\"\n Left shift the Rational value.\n\n Args:\n other (UnsignedInteger): The value to left shift by.\n\n Returns:\n Rational: Left shifted Rational value.\n \"\"\"\n return Rational(self.value << other, self.log_scale)\n\n def __rshift__(self, other: UnsignedInteger) -> \"Rational\":\n \"\"\"\n Right shift the Rational value.\n\n Args:\n other (UnsignedInteger): The value to right shift by.\n\n Returns:\n Rational: Right shifted Rational value.\n \"\"\"\n return Rational(self.value >> other, self.log_scale)\n\n def __lt__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is less than another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value < other.value)\n return PublicBoolean(self.value < other.value)\n\n def __gt__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is greater than another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value > other.value)\n return PublicBoolean(self.value > other.value)\n\n def __le__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is less than or equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value <= other.value)\n return PublicBoolean(self.value <= other.value)\n\n def __ge__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]:\n \"\"\"\n Check if this Rational is greater than or equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value >= other.value)\n return PublicBoolean(self.value >= other.value)\n\n def __eq__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]: # type: ignore\n \"\"\"\n Check if this Rational is equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value == other.value)\n return PublicBoolean(self.value == other.value)\n\n def __ne__(self, other: _NadaRational) -> Union[PublicBoolean, SecretBoolean]: # type: ignore\n \"\"\"\n Check if this Rational is not equal to another.\n\n Args:\n other (_NadaRational): The other value to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n Union[PublicBoolean, SecretBoolean]: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n if isinstance(other, SecretRational):\n return SecretBoolean(self.value != other.value)\n return PublicBoolean(self.value != other.value)\n\n def rescale_up(self, log_scale: Optional[int] = None) -> \"Rational\":\n \"\"\"\n Rescale the value in the upward direction by a scaling factor.\n\n This is equivalent to multiplying the value by `2**(log_scale)`.\n\n Args:\n log_scale (int, optional): Scaling factor to rescale the value.\n Defaults to RationalConfig.log_scale.\n\n Returns:\n Rational: Rescaled Rational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return Rational(\n self._value\n * Integer(1 << log_scale), # TODO: replace with shift when supported\n self.log_scale + log_scale,\n is_scaled=True,\n )\n\n def rescale_down(self, log_scale: Optional[int] = None) -> \"Rational\":\n \"\"\"\n Rescale the value in the downward direction by a scaling factor.\n\n This is equivalent to dividing the value by `2**(log_scale)`.\n\n Args:\n log_scale (int, optional): Scaling factor to rescale the value.\n Defaults to RationalConfig.log_scale.\n\n Returns:\n Rational: Rescaled Rational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return Rational(\n self._value\n / Integer(1 << log_scale), # TODO: replace with shift when supported\n self.log_scale - log_scale,\n is_scaled=True,\n )\n\n # Non-linear functions\n\n def sign(self) -> \"Rational\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n result = sign(self)\n if not isinstance(result, Rational):\n raise TypeError(\"sign input should be of type Rational.\")\n return result\n\n def abs(self) -> \"Rational\":\n \"\"\"Computes the absolute value\"\"\"\n\n result = fxp_abs(self)\n if not isinstance(result, Rational):\n raise TypeError(\"abs input should be of type Rational.\")\n return result\n\n def exp(self, iterations: int = 8) -> \"Rational\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The function is computed by choosing n = 2 ** d, where d is set to `iterations`.\n The calculation is performed by computing (1 + x / n) once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n Rational: The approximated value of the exponential function.\n \"\"\"\n\n result = exp(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"exp input should be of type Rational.\")\n return result\n\n def polynomial(self, coefficients: list) -> \"Rational\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the\n highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n Rational: The result of the polynomial function applied to the input x.\n \"\"\"\n\n result = polynomial(self, coefficients=coefficients)\n if not isinstance(result, Rational):\n raise TypeError(\"polynomial input should be of type Rational.\")\n return result\n\n def log(\n self,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n ) -> \"Rational\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation\n of exp. Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of Householder\n approximation). Defaults to 8.\n\n Returns:\n Rational: The approximate value of the natural logarithm.\n \"\"\"\n\n result = log(\n self,\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n if not isinstance(result, Rational):\n raise TypeError(\"log input should be of type Rational.\")\n return result\n\n def reciprocal( # pylint: disable=too-many-arguments\n self,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n\n result = reciprocal(\n self,\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n if not isinstance(result, Rational):\n raise TypeError(\"reciprocal input should be of type Rational.\")\n return result\n\n def inv_sqrt(\n self,\n initial: Optional[\"Rational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[Rational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = inv_sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"inv_sqrt input should be of type Rational.\")\n return result\n\n def sqrt(\n self,\n initial: Optional[\"Rational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"Rational\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[Rational, None], optional): sets the initial value for the inverse\n square root Newton-Raphson iterations. By default, this will be set to allow\n convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n Rational: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"sqrt input should be of type Rational.\")\n return result\n\n # Trigonometry\n\n def cossin(self, iterations: int = 10) -> Tuple[\"Rational\", \"Rational\"]:\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit through\n the formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[Rational, Rational]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n result = cossin(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"cossin input should be of type Rational.\")\n return result\n\n def cos(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the cosine.\n \"\"\"\n\n result = cos(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"cos input should be of type Rational.\")\n return result\n\n def sin(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the sine.\n \"\"\"\n\n result = sin(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"sin input should be of type Rational.\")\n return result\n\n def tan(self, iterations: int = 10) -> \"Rational\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Rational: The approximate value of the tan.\n \"\"\"\n\n result = tan(self, iterations=iterations)\n if not isinstance(result, Rational):\n raise TypeError(\"tan input should be of type Rational.\")\n return result\n\n # Activation functions\n\n def tanh(self, chebyshev_terms: int = 32, method: str = \"reciprocal\") -> \"Rational\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n Rational: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = tanh(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"tanh input should be of type Rational.\")\n return result\n\n def sigmoid(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"Rational\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique. It uses\n the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n Rational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = sigmoid(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, Rational):\n raise TypeError(\"sigmoid input should be of type Rational.\")\n return result\n\n def gelu(self, method: str = \"tanh\", tanh_method: str = \"reciprocal\") -> \"Rational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n Rational: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = gelu(self, method=method, tanh_method=tanh_method)\n if not isinstance(result, Rational):\n raise TypeError(\"gelu input should be of type Rational.\")\n return result\n\n def silu(\n self,\n method_sigmoid: str = \"reciprocal\",\n ) -> \"Rational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n Rational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n\n if method_sigmoid is None:\n method_sigmoid = \"reciprocal\"\n\n result = silu(self, method_sigmoid=method_sigmoid)\n if not isinstance(result, Rational):\n raise TypeError(\"silu input should be of type Rational.\")\n return result\n\n\nclass SecretRational: # pylint:disable=too-many-public-methods\n \"\"\"Wrapper class to store scaled SecretInteger values representing a fixed-point number.\"\"\"\n\n def __init__(\n self,\n value: SecretInteger,\n log_scale: Optional[int] = None,\n is_scaled: bool = True,\n ) -> None:\n \"\"\"\n Initializes wrapper around SecretInteger object.\n The object should come scaled up by default otherwise precision may be lost.\n\n Args:\n value (SecretInteger): SecretInteger input value.\n log_scale (int, optional): Quantization scaling factor.\n Defaults to RationalConfig.log_scale.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Raises:\n TypeError: If value is of an incompatible type.\n \"\"\"\n if not isinstance(value, SecretInteger):\n raise TypeError(\n f\"Cannot instantiate SecretRational from type `{type(value)}`.\"\n )\n\n if log_scale is None:\n log_scale = get_log_scale()\n self._log_scale = log_scale\n\n if is_scaled is False:\n value = value << UnsignedInteger(log_scale)\n self._value = value\n\n @property\n def log_scale(self) -> int:\n \"\"\"\n Getter for the logarithmic scale value.\n\n Returns:\n int: Logarithmic scale value.\n \"\"\"\n return self._log_scale\n\n @property\n def value(self) -> SecretInteger:\n \"\"\"\n Getter for the underlying SecretInteger value.\n\n Returns:\n SecretInteger: The SecretInteger value.\n \"\"\"\n return self._value\n\n def add(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Add two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to add.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the addition.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot add values with different scales.\")\n\n return SecretRational(self.value + other.value, self.log_scale)\n\n def __add__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Add two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def __iadd__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Add two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.add(other)\n\n def sub(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Subtract two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to subtract.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the subtraction.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot substract values with different scales.\")\n\n return SecretRational(self.value - other.value, self.log_scale)\n\n def __sub__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Subtract two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def __isub__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Subtract two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.sub(other)\n\n def mul_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Multiply two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the multiplication.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale:\n raise ValueError(\"Cannot multiply values with different scales.\")\n\n return SecretRational(\n self.value * other.value, self.log_scale + other.log_scale\n )\n\n def mul(self, other: _NadaRational, ignore_scale: bool = False) -> \"SecretRational\":\n \"\"\"\n Multiply two SecretRational numbers and rescale the result.\n\n Args:\n other (_NadaRational): The other SecretRational to multiply.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n SecretRational: Result of the multiplication, rescaled.\n \"\"\"\n c = self.mul_no_rescale(other, ignore_scale=ignore_scale)\n if c is NotImplemented:\n # Note that, because this function would be executed under a NadaArray,\n # the NotImplemented value will be handled by the caller (in principle NadaArray)\n # The caller will then call the mul function of the NadaArray\n # The broadcasting will execute element-wise multiplication,\n # so rescale_down will be taken care by that function\n return c\n d = c.rescale_down()\n return d\n\n def __mul__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Multiply two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def __imul__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Multiply two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.mul(other)\n\n def divide_no_rescale(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Divide two SecretRational numbers.\n\n Args:\n other (_NadaRational): The other SecretRational to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Raises:\n TypeError: If the other value is of an incompatible type.\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretRational: Result of the division.\n \"\"\"\n if not isinstance(other, (Rational, SecretRational)):\n return NotImplemented\n\n if not ignore_scale and self.log_scale != other.log_scale + get_log_scale():\n raise ValueError(\n f\"Cannot divide values where scale is: {self.log_scale} / {other.log_scale}.\"\n f\"Required scale: {self.log_scale} / {other.log_scale + get_log_scale()}\"\n )\n\n return SecretRational(\n self.value / other.value, self.log_scale - other.log_scale\n )\n\n def divide(\n self, other: _NadaRational, ignore_scale: bool = False\n ) -> \"SecretRational\":\n \"\"\"\n Divide two SecretRational numbers and rescale the result.\n\n Args:\n other (_NadaRational): The other SecretRational to divide by.\n ignore_scale (bool, optional): Flag to disable scale checking. Disabling\n auto-scaling can lead to significant performance gains as it allows\n \"bundling\" scaling ops. However, it is advanced feature and can lead\n to unexpected results if used incorrectly. Defaults to False.\n\n Returns:\n SecretRational: Result of the division, rescaled.\n \"\"\"\n # Note: If the other value is a NadaArray, the divide-no-rescale function will\n # return NotImplemented\n # This will cause that the divide function will return NotImplemented as well\n # The NotImplemented value will be handled by the caller (in principle NadaArray)\n # The caller will then call the divide function of the NadaArray\n # The rescale up, because there is no follow up, will not be taken into consideration.\n a = self.rescale_up()\n c = a.divide_no_rescale(other, ignore_scale=ignore_scale)\n return c\n\n def __truediv__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Divide two secret rational numbers.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __itruediv__(self, other: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Divide two secret rational numbers inplace.\n\n Args:\n other (_NadaRational): Other rational number to add.\n\n Returns:\n Union[Rational, SecretRational]: Result of the addition.\n \"\"\"\n return self.divide(other)\n\n def __pow__(self, other: int) -> Union[\"Rational\", \"SecretRational\"]:\n \"\"\"\n Raise a SecretRational to an integer power using binary exponentiation.\n\n Args:\n other (int): The exponent.\n\n Raises:\n TypeError: If the exponent is not an integer.\n\n Returns:\n Union[Rational, SecretRational]: Result of the power operation.\n \"\"\"\n if not isinstance(other, int):\n raise TypeError(\n f\"Cannot raise SecretRational to a power of type `{type(other)}`\"\n )\n\n result = Rational(Integer(1), self.log_scale, is_scaled=False)\n\n if other == 0:\n return result # Any number to the power of 0 is 1\n\n base = self\n\n exponent = abs(other)\n while exponent > 0:\n if exponent % 2 == 1:\n result = result * base # type: ignore\n base *= base # type: ignore\n exponent //= 2\n\n if other < 0:\n return rational(1) / SecretRational( # type: ignore\n result.value, result.log_scale, is_scaled=True\n )\n\n return result\n\n def __neg__(self) -> \"SecretRational\":\n \"\"\"\n Negate the SecretRational value.\n\n Returns:\n SecretRational: Negated SecretRational value.\n \"\"\"\n return SecretRational(self.value * Integer(-1), self.log_scale)\n\n def __lshift__(self, other: UnsignedInteger) -> \"SecretRational\":\n \"\"\"\n Left shift the SecretRational value.\n\n Args:\n other (UnsignedInteger): The value to left shift by.\n\n Returns:\n SecretRational: Left shifted SecretRational value.\n \"\"\"\n return SecretRational(self.value << other, self.log_scale)\n\n def __rshift__(self, other: UnsignedInteger) -> \"SecretRational\":\n \"\"\"\n Right shift the SecretRational value.\n\n Args:\n other (UnsignedInteger): The value to right shift by.\n\n Returns:\n SecretRational: Right shifted SecretRational value.\n \"\"\"\n return SecretRational(self.value >> other, self.log_scale)\n\n def __lt__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is less than another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value < other.value)\n\n def __gt__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is greater than another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value > other.value)\n\n def __le__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is less than or equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value <= other.value)\n\n def __ge__(self, other: _NadaRational) -> SecretBoolean:\n \"\"\"\n Check if this SecretRational is greater than or equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value >= other.value)\n\n def __eq__(self, other: _NadaRational) -> SecretBoolean: # type: ignore\n \"\"\"\n Check if this SecretRational is equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value == other.value)\n\n def __ne__(self, other: _NadaRational) -> SecretBoolean: # type: ignore\n \"\"\"\n Check if this SecretRational is not equal to another.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n SecretBoolean: Result of the comparison.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return SecretBoolean(self.value != other.value)\n\n def public_equals(self, other: _NadaRational) -> PublicBoolean:\n \"\"\"\n Check if this SecretRational is equal to another and reveal the result.\n\n Args:\n other (_NadaRational): The other SecretRational to compare against.\n\n Raises:\n ValueError: If the log scales of the two values are different.\n\n Returns:\n PublicBoolean: Result of the comparison, revealed.\n \"\"\"\n if self.log_scale != other.log_scale:\n raise ValueError(\"Cannot compare values with different scales.\")\n return self.value.public_equals(other.value)\n\n def reveal(self) -> Rational:\n \"\"\"\n Reveal the SecretRational value.\n\n Returns:\n Rational: Revealed SecretRational value.\n \"\"\"\n return Rational(self.value.reveal(), self.log_scale)\n\n def trunc_pr(self, arg_0: _NadaRational) -> \"SecretRational\":\n \"\"\"\n Truncate the SecretRational value.\n\n Args:\n arg_0 (_NadaRational): The value to truncate by.\n\n Returns:\n SecretRational: Truncated SecretRational value.\n \"\"\"\n return SecretRational(self.value.trunc_pr(arg_0), self.log_scale)\n\n def rescale_up(self, log_scale: Optional[int] = None) -> \"SecretRational\":\n \"\"\"\n Rescale the SecretRational value upwards by a scaling factor.\n\n Args:\n log_scale (int, optional): The scaling factor. Defaults to RationalConfig.log_scale.\n\n Returns:\n SecretRational: Rescaled SecretRational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return SecretRational(\n self._value << UnsignedInteger(log_scale),\n self.log_scale + log_scale,\n is_scaled=True,\n )\n\n def rescale_down(self, log_scale: Optional[int] = None) -> \"SecretRational\":\n \"\"\"\n Rescale the SecretRational value downwards by a scaling factor.\n\n Args:\n log_scale (int, optional): The scaling factor. Defaults to RationalConfig.\n\n Returns:\n SecretRational: Rescaled SecretRational value.\n \"\"\"\n if log_scale is None:\n log_scale = get_log_scale()\n\n return SecretRational(\n self._value >> UnsignedInteger(log_scale),\n self.log_scale - log_scale,\n is_scaled=True,\n )\n\n # Non-linear functions\n\n def sign(self) -> \"SecretRational\":\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n result = sign(self)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sign input should be of type SecretRational.\")\n return result\n\n def abs(self) -> \"SecretRational\":\n \"\"\"Computes the absolute value\"\"\"\n\n result = fxp_abs(self)\n if not isinstance(result, SecretRational):\n raise TypeError(\"abs input should be of type SecretRational.\")\n return result\n\n def exp(self, iterations: int = 8) -> \"SecretRational\":\n \"\"\"\n Approximates the exponential function using a limit approximation.\n\n The exponential function is approximated using the following limit:\n\n exp(x) = lim_{n -> ∞} (1 + x / n) ^ n\n\n The exponential function is computed by choosing n = 2 ** d, where d is\n set to `iterations`. The calculation is performed by computing (1 + x / n)\n once and then squaring it `d` times.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [-2, 2] | <1% |\n | [-7, 7] | <10% |\n | [-8, 15] | <35% |\n + ---------------------------------- +\n\n Args:\n iterations (int, optional): The number of iterations for the limit approximation.\n Defaults to 8.\n\n Returns:\n SecretRational: The approximated value of the exponential function.\n \"\"\"\n\n result = exp(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"exp input should be of type SecretRational.\")\n return result\n\n def polynomial(self, coefficients: list) -> \"SecretRational\":\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the\n highest order term.\n **Note: The constant term is not included.**\n\n Args:\n coefficients (list): The coefficients of the polynomial, ordered by increasing degree.\n\n Returns:\n SecretRational: The result of the polynomial function applied to the input x.\n \"\"\"\n\n result = polynomial(self, coefficients=coefficients)\n if not isinstance(result, SecretRational):\n raise TypeError(\"polynomial input should be of type SecretRational.\")\n return result\n\n def log(\n self,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n ) -> \"SecretRational\":\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n This approximation is accurate within 2% relative error on the interval [0.0001, 250].\n\n The iterations are computed as follows:\n\n h = 1 - x * exp(-y_n)\n y_{n+1} = y_n - sum(h^k / k for k in range(1, order + 1))\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------- +\n | Input range x | Relative error |\n + ------------------------------------- +\n | [0.001, 200] | <1% |\n | [0.00003, 253] | <10% |\n | [0.0000001, 253] | <40% |\n | [253, +∞[ | Unstable |\n + ------------------------------------- +\n\n Args:\n input_in_01 (bool, optional): Indicates if the input is within the domain [0, 1].\n This setting optimizes the function for this domain, useful for computing\n log-probabilities in entropy functions.\n\n To shift the domain of convergence, a constant 'a' is used with the identity:\n\n ln(u) = ln(au) - ln(a)\n\n Given the convergence domain for log() function is approximately [1e-4, 1e2],\n we set a = 100.\n Defaults to False.\n iterations (int, optional): Number of Householder iterations for the approximation.\n Defaults to 2.\n exp_iterations (int, optional): Number of iterations for the limit approximation of\n exp. Defaults to 8.\n order (int, optional): Number of polynomial terms used (order of\n Householder approximation). Defaults to 8.\n\n Returns:\n SecretRational: The approximate value of the natural logarithm.\n \"\"\"\n\n result = log(\n self,\n input_in_01=input_in_01,\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n )\n if not isinstance(result, SecretRational):\n raise TypeError(\"log input should be of type SecretRational.\")\n return result\n\n def reciprocal( # pylint: disable=too-many-arguments\n self,\n all_pos: bool = False,\n initial: Optional[\"Rational\"] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n\n Methods:\n 'NR' : `Newton-Raphson`_ method computes the reciprocal using iterations\n of :math:`x_{i+1} = (2x_i - x * x_i^2)` and uses\n :math:`3*exp(1 - 2x) + 0.003` as an initial guess by default.\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.1, 64] | <0% |\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n 'log' : Computes the reciprocal of the input from the observation that:\n :math:`x^{-1} = exp(-log(x))`\n\n Approximation accuracy range (with 16 bit precision):\n + ------------------------------------ +\n | Input range |x| | Relative error |\n + ------------------------------------ +\n | [0.0003, 253] | <15% |\n | [0.00001, 253] | <90% |\n | [253, +∞[ | Unstable |\n + ------------------------------------ +\n\n Args:\n all_pos (bool, optional): determines whether all elements of the\n input are known to be positive, which optimizes the step of\n computing the sign of the input. Defaults to False.\n initial (Rational, optional): sets the initial value for the\n Newton-Raphson method. By default, this will be set to :math:\n `3*exp(-(x-.5)) + 0.003` as this allows the method to converge over\n a fairly large domain.\n input_in_01 (bool, optional) : Allows a user to indicate that the input is\n in the range [0, 1], causing the function optimize for this range.\n This is useful for improving the accuracy of functions on\n probabilities (e.g. entropy functions).\n iterations (int, optional): determines the number of Newton-Raphson iterations to run\n for the `NR` method. Defaults to 10.\n log_iters (int, optional): determines the number of Householder\n iterations to run when computing logarithms for the `log` method. Defaults to 1.\n exp_iters (int, optional): determines the number of exp\n iterations to run when computing exp. Defaults to 8.\n method (str, optional): method used to compute reciprocal. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the reciprocal\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Newton%27s_method\n \"\"\"\n\n result = reciprocal(\n self,\n all_pos=all_pos,\n initial=initial,\n input_in_01=input_in_01,\n iterations=iterations,\n log_iters=log_iters,\n exp_iters=exp_iters,\n method=method,\n )\n if not isinstance(result, SecretRational):\n raise TypeError(\"reciprocal input should be of type SecretRational.\")\n return result\n\n def inv_sqrt(\n self,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n Newton-Raphson iterations. By default, this will be set to allow the\n method to converge over a fairly large domain.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n method (str, optional): method used to compute inv_sqrt. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the inv_sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = inv_sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"inv_sqrt input should be of type SecretRational.\")\n return result\n\n def sqrt(\n self,\n initial: Optional[\"SecretRational\"] = None,\n iterations: int = 5,\n method: str = \"NR\",\n ) -> \"SecretRational\":\n r\"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n\n Approximation accuracy range (with 16 bit precision):\n + ---------------------------------- +\n | Input range x | Relative error |\n + ---------------------------------- +\n | [0.1, 170] | <0% |\n | [0.001, 200] | <50% |\n | [200, +∞[ | Unstable |\n + ---------------------------------- +\n\n Args:\n initial (Union[SecretRational, None], optional): sets the initial value for the\n inverse square root Newton-Raphson iterations. By default, this will be set\n to allow convergence over a fairly large domain. Defaults to None.\n iterations (int, optional): determines the number of Newton-Raphson iterations to run.\n Defaults to 5.\n method (str, optional): method used to compute sqrt. Defaults to \"NR\".\n\n Returns:\n SecretRational: The approximate value of the sqrt.\n\n .. _Newton-Raphson:\n https://en.wikipedia.org/wiki/Fast_inverse_square_root#Newton's_method\n \"\"\"\n\n result = sqrt(self, initial=initial, iterations=iterations, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sqrt input should be of type SecretRational.\")\n return result\n\n # Trigonometry\n\n def cossin(self, iterations: int = 10) -> Tuple[\"SecretRational\", \"SecretRational\"]:\n r\"\"\"Computes cosine and sine through e^(i * input) where i is the imaginary unit\n through the formula:\n\n .. math::\n Re\\{e^{i * input}\\}, Im\\{e^{i * input}\\} = \\cos(input), \\sin(input)\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[SecretRational, SecretRational]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n result = cossin(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"cossin input should be of type SecretRational.\")\n return result\n\n def cos(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the cosine of the input using cos(x) = Re{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the cosine.\n \"\"\"\n\n result = cos(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"cos input should be of type SecretRational.\")\n return result\n\n def sin(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the sine of the input using sin(x) = Im{exp(i * x)}.\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the sine.\n \"\"\"\n\n result = sin(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sin input should be of type SecretRational.\")\n return result\n\n def tan(self, iterations: int = 10) -> \"SecretRational\":\n r\"\"\"Computes the tan of the input using tan(x) = sin(x) / cos(x).\n\n Note: unstable outside [-30, 30]\n\n Args:\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n SecretRational: The approximate value of the tan.\n \"\"\"\n\n result = tan(self, iterations=iterations)\n if not isinstance(result, SecretRational):\n raise TypeError(\"tan input should be of type SecretRational.\")\n return result\n\n # Activation functions\n\n def tanh(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the hyperbolic tangent function using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Methods:\n If a valid method is given, this function will compute tanh using that method:\n\n \"reciprocal\" - computes tanh using the identity\n\n .. math::\n tanh(x) = 2\\sigma(2x) - 1\n\n Note: stable for x in [-250, 250]. Unstable otherwise.\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with truncation.\n\n .. math::\n tanh(x) = \\sum_{j=1}^chebyshev_terms c_{2j - 1} P_{2j - 1} (x / maxval)\n\n where c_i is the ith Chebyshev series coefficient and P_i is ith polynomial.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to +/-1 outside [-1, 1].\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute tanh function. Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The tanh evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = tanh(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"tanh input should be of type SecretRational.\")\n return result\n\n def sigmoid(\n self, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the sigmoid function using the following definition\n\n .. math::\n \\sigma(x) = (1 + e^{-x})^{-1}\n\n Methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n chebyshev_terms (int, optional): highest degree of Chebyshev polynomials.\n Must be even and at least 6. Defaults to 32.\n method (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = sigmoid(self, chebyshev_terms=chebyshev_terms, method=method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"sigmoid input should be of type SecretRational.\")\n return result\n\n def gelu(\n self, method: str = \"tanh\", tanh_method: str = \"reciprocal\"\n ) -> \"SecretRational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n gelu(x) = x/2 * (1 + tanh(\\sqrt{2/\\pi} * (x + 0.04471 * x^3)))\n\n Methods:\n If a valid method is given, this function will compute gelu\n using that method:\n\n \"tanh\" - computes gelu using the common approximation function\n\n Note: stable for x in [-18, 18]. Unstable otherwise.\n\n \"motzkin\" - computes gelu via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n Note: stable for all input range as the approximation is truncated\n to relu function outside [-2.7, 2.7].\n\n Args:\n method (str, optional): method used to compute gelu function. Defaults to \"tanh\".\n tanh_method (str, optional): method used for tanh function. Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The gelu evaluation.\n\n Raises:\n ValueError: Raised if method type is not supported.\n \"\"\"\n\n result = gelu(self, method=method, tanh_method=tanh_method)\n if not isinstance(result, SecretRational):\n raise TypeError(\"gelu input should be of type SecretRational.\")\n return result\n\n def silu(\n self,\n method_sigmoid: str = \"reciprocal\",\n ) -> \"SecretRational\":\n r\"\"\"Computes the gelu function using the following definition\n\n .. math::\n silu(x) = x * sigmoid(x)\n\n Sigmoid methods:\n If a valid method is given, this function will compute sigmoid\n using that method:\n\n \"chebyshev\" - computes tanh via Chebyshev approximation with\n truncation and uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"motzkin\" - computes tanh via approximation from the paper\n \"BOLT: Privacy-Preserving, Accurate and Efficient Inference for Transformers\"\n on section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n It uses the identity:\n\n .. math::\n \\sigma(x) = \\frac{1}{2}tanh(\\frac{x}{2}) + \\frac{1}{2}\n\n Note: stable for all input range as the approximation is truncated\n to 0/1 outside [-1, 1].\n\n \"reciprocal\" - computes sigmoid using :math:`1 + e^{-x}` and computing\n the reciprocal\n\n Note: stable for x in [-500, 500]. Unstable otherwise.\n\n Args:\n method_sigmoid (str, optional): method used to compute sigmoid function.\n Defaults to \"reciprocal\".\n\n Returns:\n SecretRational: The sigmoid evaluation.\n\n Raises:\n ValueError: Raised if sigmoid method type is not supported.\n \"\"\"\n\n result = silu(self, method_sigmoid=method_sigmoid)\n if not isinstance(result, SecretRational):\n raise TypeError(\"silu input should be of type SecretRational.\")\n return result\n\n\ndef secret_rational(\n name: str, party: Party, log_scale: Optional[int] = None, is_scaled: bool = True\n) -> SecretRational:\n \"\"\"\n Creates a SecretRational from a variable in the Nillion network.\n\n Args:\n name (str): Name of variable in Nillion network.\n party (Party): Name of party that provided variable.\n log_scale (int, optional): Quantization scaling factor. Defaults to None.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n SecretRational: Instantiated SecretRational object.\n \"\"\"\n value = SecretInteger(Input(name=name, party=party))\n return SecretRational(value, log_scale, is_scaled)\n\n\ndef public_rational(\n name: str, party: Party, log_scale: Optional[int] = None, is_scaled: bool = True\n) -> Rational:\n \"\"\"\n Creates a Rational from a variable in the Nillion network.\n\n Args:\n name (str): Name of variable in Nillion network.\n party (Party): Name of party that provided variable.\n log_scale (int, optional): Quantization scaling factor. Defaults to None.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n Rational: Instantiated Rational object.\n \"\"\"\n value = PublicInteger(Input(name=name, party=party))\n return Rational(value, log_scale, is_scaled)\n\n\ndef rational(\n value: Union[int, float, np.floating],\n log_scale: Optional[int] = None,\n is_scaled: bool = False,\n) -> Rational:\n \"\"\"\n Creates a Rational from a number variable.\n\n Args:\n value (Union[int, float, np.floating]): Provided input value.\n log_scale (int, optional): Quantization scaling factor. Defaults to default log_scale.\n is_scaled (bool, optional): Flag that indicates whether provided value has already been\n scaled by log_scale factor. Defaults to True.\n\n Returns:\n Rational: Instantiated Rational object.\n \"\"\"\n if value == 0: # no use in rescaling 0\n return Rational(Integer(0), is_scaled=True)\n\n if log_scale is None:\n log_scale = get_log_scale()\n\n if isinstance(value, np.floating):\n value = value.item()\n if isinstance(value, int):\n return Rational(Integer(value), log_scale=log_scale, is_scaled=is_scaled)\n if isinstance(value, float):\n assert (\n is_scaled is False\n ), \"Got a value of type `float` with `is_scaled` set to True. This should never occur\"\n quantized = round(value * (1 << log_scale))\n return Rational(Integer(quantized), is_scaled=True)\n raise TypeError(f\"Cannot instantiate Rational from type `{type(value)}`.\")\n\n\nclass _MetaRationalConfig(type):\n \"\"\"Rational config metaclass that defines classproperties\"\"\"\n\n _log_scale: int\n _default_log_scale: int\n\n @property\n def default_log_scale(cls) -> int:\n \"\"\"\n Getter method.\n\n Returns:\n int: Default log scale.\n \"\"\"\n return cls._default_log_scale\n\n @property\n def log_scale(cls) -> int:\n \"\"\"\n Getter method.\n\n Returns:\n int: Log scale.\n \"\"\"\n return cls._log_scale\n\n @log_scale.setter\n def log_scale(cls, new_log_scale: int) -> None:\n \"\"\"\n Setter method.\n\n Args:\n new_log_scale (int): New log scale value to reset old value with.\n \"\"\"\n if new_log_scale <= 4:\n warnings.warn(\n f\"Provided log scale `{str(new_log_scale)}` is very low.\"\n \" Expected a value higher than 4.\"\n \" Using a low quantization scale can lead to poor quantization of rational values\"\n \" and thus poor performance & unexpected results.\"\n )\n if new_log_scale >= 64:\n warnings.warn(\n f\"Provided log scale `{str(new_log_scale)}` is very high.\"\n \" Expected a value lower than 64.\"\n \" Using a high quantization scale can lead to overflows & unexpected results.\"\n )\n\n cls._log_scale = new_log_scale\n\n\n# pylint:disable=too-few-public-methods\nclass _RationalConfig(metaclass=_MetaRationalConfig):\n \"\"\"Rational config data class\"\"\"\n\n _default_log_scale: int = 16\n _log_scale: int = _default_log_scale\n\n\ndef set_log_scale(new_log_scale: int) -> None:\n \"\"\"\n Sets the default Rational log scaling factor to a new value.\n Note that this value is the LOG scale and will be used as a base-2 exponent\n during quantization.\n\n Args:\n new_log_scale (int): New log scaling factor.\n \"\"\"\n if not isinstance(new_log_scale, int):\n raise TypeError(\n f\"Cannot set log scale to type `{type(new_log_scale)}`. Expected `int`.\"\n )\n _RationalConfig.log_scale = new_log_scale\n\n\ndef get_log_scale() -> int:\n \"\"\"\n Gets the Rational log scaling factor\n Note that this value is the LOG scale and is used as a base-2 exponent during quantization.\n\n Returns:\n int: Current log scale in use.\n \"\"\"\n return _RationalConfig.log_scale\n\n\ndef reset_log_scale() -> None:\n \"\"\"Resets the Rational log scaling factor to the original default value\"\"\"\n _RationalConfig.log_scale = _RationalConfig.default_log_scale\n\n\n# Fixed-point math operations\n\n# Copyright (c) Facebook, Inc. and its affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n#\n# Part of the code is from the CrypTen Facebook Project:\n# https://github.com/facebookresearch/CrypTen/blob/main/crypten/common/functions/logic.py\n# https://github.com/facebookresearch/CrypTen/blob/main/crypten/common/functions/approximations.py\n#\n# Modifications:\n# July, 2024\n# - Nada datatypes.\n# - Relative accuracy documentation.\n# - Some performance improvements.\n# - Fixed Tanh Chebyshev method by changing '_hardtanh' implementation.\n# - Tan.\n# - Motzkin's prolynomial preprocessing approach.\n# - GeLU and SiLU functions.\n\n\ndef sign(x: _NadaRational) -> _NadaRational:\n \"\"\"Computes the sign value (0 is considered positive)\"\"\"\n\n ltz_cond = x < rational(0)\n ltz = ltz_cond.if_else(rational(1), rational(0))\n\n return rational(1) - ltz - ltz\n\n\ndef fxp_abs(x: _NadaRational) -> _NadaRational:\n \"\"\"Computes the absolute value of a rational\"\"\"\n return x * sign(x)\n\n\ndef exp(x: _NadaRational, iterations: int = 8) -> _NadaRational:\n \"\"\"\n Approximates the exponential function using a limit approximation.\n \"\"\"\n\n iters_na = UnsignedInteger(iterations)\n\n result = rational(1) + (x >> iters_na)\n for _ in range(iterations):\n result = result**2\n return result\n\n\ndef polynomial(x: _NadaRational, coefficients: List[Rational]) -> _NadaRational:\n \"\"\"\n Computes a polynomial function on a value with given coefficients.\n\n The coefficients can be provided as a list of values.\n They should be ordered from the linear term (order 1) first, ending with the highest order term.\n **Note: The constant term is not included.**\n \"\"\"\n result = coefficients[0] * x\n\n for power, coeff in enumerate(coefficients[1:], start=2):\n result += coeff * (x**power)\n\n return result\n\n\ndef log(\n x: _NadaRational,\n input_in_01: bool = False,\n iterations: int = 2,\n exp_iterations: int = 8,\n order: int = 8,\n) -> _NadaRational:\n \"\"\"\n Approximates the natural logarithm using 8th order modified Householder iterations.\n \"\"\"\n\n if input_in_01:\n return log(\n x * rational(100),\n iterations=iterations,\n exp_iterations=exp_iterations,\n order=order,\n ) - rational(4.605170)\n\n # Initialization to a decent estimate (found by qualitative inspection):\n # ln(x) = x/120 - 20exp(-2x - 1.0) + 3.0\n term1 = x * rational(1 / 120.0)\n term2 = exp(-x - x - rational(1), iterations=exp_iterations) * rational(20)\n y = term1 - term2 + rational(3.0)\n\n # 8th order Householder iterations\n for _ in range(iterations):\n h = rational(1) - x * exp(-y, iterations=exp_iterations)\n y -= polynomial(h, [rational(1 / (i + 1)) for i in range(order)])\n return y\n\n\ndef reciprocal( # pylint: disable=too-many-arguments\n x: _NadaRational,\n all_pos: bool = False,\n initial: Optional[Rational] = None,\n input_in_01: bool = False,\n iterations: int = 10,\n log_iters: int = 1,\n exp_iters: int = 8,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Approximates the reciprocal of a number through two possible methods: Newton-Raphson\n and log.\n \"\"\"\n if input_in_01:\n rec = reciprocal(\n x * rational(64),\n method=method,\n all_pos=True,\n initial=initial,\n iterations=iterations,\n ) * rational(64)\n return rec\n\n if not all_pos:\n sgn = sign(x)\n pos = sgn * x\n return sgn * reciprocal(\n pos, method=method, all_pos=True, initial=initial, iterations=iterations\n )\n\n if method == \"NR\":\n if initial is None:\n # Initialization to a decent estimate (found by qualitative inspection):\n # 1/x = 3exp(1 - 2x) + 0.003\n result = rational(3) * exp(\n rational(1) - x - x, iterations=exp_iters\n ) + rational(0.003)\n else:\n result = initial\n for _ in range(iterations):\n result = result + result - result * result * x\n return result\n if method == \"log\":\n return exp(-log(x, iterations=log_iters), iterations=exp_iters)\n raise ValueError(f\"Invalid method {method} given for reciprocal function\")\n\n\ndef inv_sqrt(\n x: _NadaRational,\n initial: Optional[Union[_NadaRational, None]] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Computes the inverse square root of the input using the Newton-Raphson method.\n \"\"\"\n\n if method == \"NR\":\n if initial is None:\n # Initialization to a decent estimate (found by qualitative inspection):\n # exp(- x/2 - 0.2) * 2.2 + 0.2 - x/1024\n y = exp(-(x >> UnsignedInteger(1)) - rational(0.2)) * rational(\n 2.2\n ) + rational(0.2)\n y -= x >> UnsignedInteger(10) # div by 1024\n else:\n y = initial\n\n # Newton Raphson iterations for inverse square root\n for _ in range(iterations):\n y = (y * (rational(3) - x * y * y)) >> UnsignedInteger(1)\n return y\n raise ValueError(f\"Invalid method {method} given for inv_sqrt function\")\n\n\ndef sqrt(\n x: _NadaRational,\n initial: Union[_NadaRational, None] = None,\n iterations: int = 5,\n method: str = \"NR\",\n) -> _NadaRational:\n \"\"\"\n Computes the square root of the input by computing its inverse square root using\n the Newton-Raphson method and multiplying by the input.\n \"\"\"\n\n if method == \"NR\":\n return inv_sqrt(x, initial=initial, iterations=iterations, method=method) * x\n\n raise ValueError(f\"Invalid method {method} given for sqrt function\")\n\n\n# Trigonometry\n\n\ndef _eix(x: _NadaRational, iterations: int = 10) -> Tuple[_NadaRational, _NadaRational]:\n r\"\"\"Computes e^(i * x) where i is the imaginary unit through the formula:\n\n .. math::\n Re\\{e^{i * x}\\}, Im\\{e^{i * x}\\} = \\cos(x), \\sin(x)\n\n Args:\n x (Union[Rational, SecretRational]): the input value.\n iterations (int, optional): determines the number of iterations to run. Defaults to 10.\n\n Returns:\n Tuple[Union[Rational, SecretRational], Union[Rational, SecretRational]]:\n A tuple where the first element is cos and the second element is the sin.\n \"\"\"\n\n one = rational(1)\n im = x >> UnsignedInteger(iterations)\n\n # First iteration uses knowledge that `re` is public and = 1\n re = one - im * im\n im *= rational(2)\n\n # Compute (a + bi)^2 -> (a^2 - b^2) + (2ab)i `iterations` times\n for _ in range(iterations - 1):\n a2 = re * re\n b2 = im * im\n im = im * re\n im *= rational(2)\n re = a2 - b2\n\n return re, im\n\n\ndef cossin(\n x: _NadaRational, iterations: int = 10\n) -> Tuple[_NadaRational, _NadaRational]:\n r\"\"\"\n Computes cosine and sine through e^(i * x) where i is the imaginary unit.\n \"\"\"\n return _eix(x, iterations=iterations)\n\n\ndef cos(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the cosine of x using cos(x) = Re{exp(i * x)}.\n \"\"\"\n return cossin(x, iterations=iterations)[0]\n\n\ndef sin(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the sine of x using sin(x) = Im{exp(i * x)}.\n \"\"\"\n return cossin(x, iterations=iterations)[1]\n\n\ndef tan(x: _NadaRational, iterations: int = 10) -> _NadaRational:\n r\"\"\"\n Computes the tan of x using tan(x) = sin(x) / cos(x).\n \"\"\"\n c, s = cossin(x, iterations=iterations)\n return s * reciprocal(c)\n\n\n# Activation functions\n\n\n@functools.lru_cache(maxsize=10)\ndef chebyshev_series(func, width, terms):\n \"\"\"\n Computes Chebyshev coefficients.\n \"\"\"\n n_range = np.arange(start=0, stop=terms, dtype=float)\n x = width * np.cos((n_range + 0.5) * np.pi / terms)\n y = func(x)\n cos_term = np.cos(np.outer(n_range, n_range + 0.5) * np.pi / terms)\n coeffs = (2 / terms) * np.sum(y * cos_term, axis=1)\n return coeffs\n\n\ndef tanh(\n x: _NadaRational, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the hyperbolic tangent function.\n \"\"\"\n\n if method == \"reciprocal\":\n return sigmoid(x + x, method=method) * rational(2) - rational(1)\n if method == \"chebyshev\":\n coeffs = chebyshev_series(np.tanh, 1, chebyshev_terms)[1::2]\n # transform np.array of float to na.array of rationals\n coeffs = np.vectorize(rational)(coeffs)\n out = _chebyshev_polynomials(x, chebyshev_terms).transpose() @ coeffs\n # truncate outside [-maxval, maxval]\n return _hardtanh(x, out)\n if method == \"motzkin\":\n # Using approximation from \"BOLT: Privacy-Preserving, Accurate and Efficient\n # Inference for Transformers\"\n # section 5.3 based on the Motzkin’s polynomial preprocessing technique.\n\n # ltz is used for absolute value of x and to compute sign (used to generate result).\n # We don't use 'abs' and 'sign' functions to avoid computing ltz twice.\n # sign = 1 - 2 * ltz, where ltz = (x < rational(0)).if_else(rational(1), rational(0))\n sgn = rational(1) - rational(2) * (x < rational(0)).if_else(\n rational(1), rational(0)\n )\n # absolute value\n abs_x = x * sgn\n\n # Motzkin’s polynomial preprocessing\n t0 = rational(-4.259314087994767)\n t1 = rational(18.86353816972803)\n t2 = rational(-36.42402897526823)\n t3 = rational(-0.013232131886235352)\n t4 = rational(-3.3289339650097993)\n t5 = rational(-0.0024920889620412097)\n tanh_p0 = (abs_x + t0) * abs_x + t1\n tanh_p1 = (tanh_p0 + abs_x + t2) * tanh_p0 * t3 * abs_x + t4 * abs_x + t5\n\n return (abs_x > rational(2.855)).if_else(sgn, sgn * tanh_p1)\n raise ValueError(f\"Unrecognized method {method} for tanh\")\n\n\n### Auxiliary functions for tanh\n\n\ndef _chebyshev_polynomials(x: _NadaRational, terms: int) -> np.ndarray:\n \"\"\"Evaluates odd degree Chebyshev polynomials at x.\n\n Chebyshev Polynomials of the first kind are defined as:\n\n .. math::\n P_0(x) = 1, P_1(x) = x, P_n(x) = 2 P_{n - 1}(x) - P_{n-2}(x)\n\n Args:\n x (Union[\"Rational\", \"SecretRational\"]): input at which polynomials are evaluated\n terms (int): highest degree of Chebyshev polynomials.\n Must be even and at least 6.\n Returns:\n NadaArray of polynomials evaluated at x of shape `(terms, *x)`.\n\n Raises:\n ValueError: Raised if 'terrms' is odd and < 6.\n \"\"\"\n if terms % 2 != 0 or terms < 6:\n raise ValueError(\"Chebyshev terms must be even and >= 6\")\n\n # Initiate base polynomials\n # P_0\n # polynomials = np.array([x])\n # y = rational(4) * x * x - rational(2)\n # z = y - rational(1)\n # # P_1\n # polynomials = np.append(polynomials, z * x)\n\n # # Generate remaining Chebyshev polynomials using the recurrence relation\n # for k in range(2, terms // 2):\n # next_polynomial = y * polynomials[k - 1] - polynomials[k - 2]\n # polynomials = np.append(polynomials, next_polynomial)\n\n # return polynomials\n\n polynomials = [x]\n y = rational(4) * x * x - rational(2)\n z = y - rational(1)\n # P_1\n polynomials.append(z * x)\n\n # Generate remaining Chebyshev polynomials using the recurrence relation\n for k in range(2, terms // 2):\n next_polynomial = y * polynomials[k - 1] - polynomials[k - 2]\n polynomials.append(next_polynomial)\n\n return np.array(polynomials)\n\n\ndef _hardtanh(\n x: _NadaRational,\n output: _NadaRational,\n abs_const: _NadaRational = rational(1),\n abs_range: _NadaRational = rational(1),\n) -> _NadaRational:\n r\"\"\"Applies the HardTanh function element-wise.\n\n HardTanh is defined as:\n\n .. math::\n \\text{HardTanh}(x) = \\begin{cases}\n 1 & \\text{ if } x > 1 \\\\\n -1 & \\text{ if } x < -1 \\\\\n Tanh(x) & \\text{ otherwise } \\\\\n \\end{cases}\n\n The range of the linear region :math:`[-1, 1]` can be adjusted using\n :attr:`abs_range`.\n\n Args:\n x (Union[Rational, SecretRational]): the input value of the Tanh.\n output (Union[Rational, SecretRational]): the output value of the approximation of Tanh.\n abs_const (Union[Rational, SecretRational]): constant value to which |Tanh(x)| converges.\n Defaults to 1.\n abs_range (Union[Rational, SecretRational]): absolute value of the range. Defaults to 1.\n\n Returns:\n Union[Rational, SecretRational]: HardTanh output.\n \"\"\"\n # absolute value\n sgn = sign(x)\n abs_x = x * sgn\n # chekc if inside [-abs_range, abs_range] interval\n ineight_cond = abs_x < abs_range\n result = ineight_cond.if_else(output, abs_const * sgn)\n\n return result\n\n\n### End of auxiliary functions for tanh\n\n\ndef sigmoid(\n x: _NadaRational, chebyshev_terms: int = 32, method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the sigmoid function.\n \"\"\"\n if method == \"chebyshev\":\n tanh_approx = tanh(\n x >> UnsignedInteger(1), method=method, chebyshev_terms=chebyshev_terms\n )\n return (tanh_approx >> UnsignedInteger(1)) + rational(0.5)\n if method == \"motzkin\":\n tanh_approx = tanh(\n x >> UnsignedInteger(1), method=method, chebyshev_terms=chebyshev_terms\n )\n return (tanh_approx >> UnsignedInteger(1)) + rational(0.5)\n if method == \"reciprocal\":\n # ltz is used for absolute value of x and to generate 'result'.\n # We don't use 'abs' function to avoid computing ltz twice.\n ltz_cond = x < rational(0)\n ltz = ltz_cond.if_else(rational(1), rational(0))\n # compute absolute value of x\n sgn = rational(1) - rational(2) * ltz\n pos_x = x * sgn\n\n denominator = exp(-pos_x) + rational(1)\n pos_output = reciprocal(\n denominator, all_pos=True, initial=rational(0.75), iterations=3, exp_iters=9\n )\n\n # result is equivalent to (1 - ltz).if_else(pos_output, 1 - pos_output)\n result = pos_output + ltz - rational(2) * pos_output * ltz\n return result\n raise ValueError(f\"Unrecognized method {method} for sigmoid\")\n\n\ndef gelu(\n x: _NadaRational, method: str = \"tanh\", tanh_method: str = \"reciprocal\"\n) -> _NadaRational:\n \"\"\"\n Computes the gelu function.\n \"\"\"\n\n if method == \"tanh\":\n # Using common approximation:\n # x/2 * (1 + tanh(0.797884560 * ( x + 0.04471 * x ** 3 ) ) )\n # where 0.797884560 ~= sqrt(2/pi).\n val = rational(0.797884560) * (x + rational(0.044715) * x**3)\n return (x * (rational(1) + tanh(val, method=tanh_method))) >> UnsignedInteger(1)\n if method == \"motzkin\":\n # Using approximation from \"BOLT: Privacy-Preserving, Accurate and Efficient\n # Inference for Transformers\"\n # section 5.2 based on the Motzkin’s polynomial preprocessing technique.\n\n # ltz is used for absolute value of x and to compute relu.\n # We don't use 'abs' and '_relu' functions to avoid computing ltz twice.\n ltz = (x < rational(0)).if_else(rational(1), rational(0))\n # absolute value\n sgn = rational(1) - rational(2) * ltz\n abs_x = x * sgn\n # relu\n relu = x * (rational(1) - ltz)\n\n # Motzkin’s polynomial preprocessing\n g0 = rational(0.14439048359960427)\n g1 = rational(-0.7077117131613893)\n g2 = rational(4.5702822654246535)\n g3 = rational(-8.15444702051307)\n g4 = rational(16.382265425072532)\n gelu_p0 = (g0 * abs_x + g1) * abs_x + g2\n gelu_p1 = (gelu_p0 + g0 * abs_x + g3) * gelu_p0 + g4 + (x >> UnsignedInteger(1))\n\n return (abs_x > rational(2.7)).if_else(relu, gelu_p1)\n raise ValueError(f\"Unrecognized method {method} for gelu\")\n\n\ndef silu(\n x: _NadaRational,\n method_sigmoid: str = \"reciprocal\",\n) -> _NadaRational:\n \"\"\"\n Computes the gelu function\n \"\"\"\n return x * sigmoid(x, method=method_sigmoid)\n","voting.py":"from nada_dsl import *\nimport nada_numpy as na\n\n# Candidate mapping: candidate name to their respective candidate identifier\n# Every voter reads the candidate map and casts their vote. \n# If their vote is SecretInteger: 1, it means they cast a vote for kamala_harris\n# If their vote is SecretInteger: 2, it means they cast a vote for donald_trump\n# If their vote is SecretInteger: 3, it means they cast a vote for rfk_jr\ncandidates_map = {\"kamala_harris\": 1, \"donald_trump\": 2, \"rfk_jr\": 3}\n\ndef count_votes_for_candidate(votes: List[SecretInteger], initialValue: SecretInteger, candidate_id: Integer) -> SecretInteger:\n total_votes_for_candidate = initialValue\n for vote in votes:\n votes_to_add = (vote == candidate_id).if_else(Integer(1), Integer(0))\n # print(type(votes_to_add)) # every voter's vote is kept secret: \n total_votes_for_candidate = total_votes_for_candidate + votes_to_add\n \n return total_votes_for_candidate\n\ndef nada_main():\n num_voters = 8\n # Creates parties with names Voter0, Voter1 ... Voter7\n voters = na.parties(num_voters, [f\"Voter{i}\" for i in range(num_voters)])\n party_official = Party(name=\"Official\")\n vote_start_count = SecretInteger(Input(name=\"vote_start_count\", party=party_official))\n\n votes_list = []\n for i in range(num_voters):\n votes_list.append(\n # Voter0 inputs vote_0\n SecretInteger(Input(name=\"vote_\" + str(i), party=voters[i]))\n )\n\n # Get the total votes per candidate\n candidate_totals = {\n name: count_votes_for_candidate(votes_list, vote_start_count, Integer(candidate_id))\n for name, candidate_id in candidates_map.items()\n }\n\n vote_count_results = [\n Output(candidate_totals[name], f\"{name}_votes\", party=party_official)\n for name in candidates_map.keys()\n ]\n return vote_count_results\n\nif __name__ == \"__main__\":\n nada_main()"},"source_refs":[{"file":"voting.py","lineno":16,"offset":889,"length":76},{"file":"voting.py","lineno":14,"offset":688,"length":77},{"file":"voting.py","lineno":36,"offset":1581,"length":92},{"file":"voting.py","lineno":31,"offset":1432,"length":72},{"file":"voting.py","lineno":25,"offset":1226,"length":90},{"file":"voting.py","lineno":41,"offset":1765,"length":77},{"file":"voting.py","lineno":24,"offset":1182,"length":43},{"file":"funcs.py","lineno":107,"offset":2341,"length":65}]}
\ No newline at end of file