├── .gitignore ├── .pre-commit-config.yaml ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── code ├── .keep ├── factoring.py └── sudoku_solver.py ├── np_completeness ├── anims.py ├── anims_circuit.py ├── anims_circuit_horrible.py ├── anims_np.py ├── audio │ ├── bfs │ │ ├── bfs_000.wav │ │ ├── bfs_001.wav │ │ ├── bfs_002.wav │ │ ├── bfs_003.wav │ │ ├── bfs_004.wav │ │ ├── bfs_005.wav │ │ ├── bfs_006.wav │ │ ├── bfs_007.wav │ │ ├── bfs_008.wav │ │ ├── bfs_009.wav │ │ ├── bfs_010.wav │ │ ├── bfs_011.wav │ │ ├── bfs_012.wav │ │ ├── bfs_013.wav │ │ ├── bfs_014.wav │ │ ├── bfs_015.wav │ │ ├── bfs_016.wav │ │ ├── bfs_017.wav │ │ ├── bfs_018.wav │ │ ├── bfs_019.wav │ │ ├── bfs_020.wav │ │ └── bfs_021.wav │ ├── click │ │ ├── click_0.wav │ │ ├── click_1.wav │ │ ├── click_2.wav │ │ └── click_3.wav │ ├── cube │ │ ├── full_track.mp3 │ │ ├── r1.wav │ │ ├── r10.wav │ │ ├── r11.wav │ │ ├── r12.wav │ │ ├── r13.wav │ │ ├── r14.wav │ │ ├── r15.wav │ │ ├── r16.wav │ │ ├── r17.wav │ │ ├── r18.wav │ │ ├── r19.wav │ │ ├── r2.wav │ │ ├── r20.wav │ │ ├── r3.wav │ │ ├── r4.wav │ │ ├── r5.wav │ │ ├── r6.wav │ │ ├── r7.wav │ │ ├── r8.wav │ │ └── r9.wav │ ├── gong.wav │ ├── polylog_failure.wav │ ├── polylog_success.wav │ ├── pop │ │ ├── pop_0.wav │ │ ├── pop_1.wav │ │ ├── pop_2.wav │ │ ├── pop_3.wav │ │ ├── pop_4.wav │ │ ├── pop_5.wav │ │ └── pop_6.wav │ ├── rising-falling.flac │ ├── whoops │ │ └── whoops1.mp3 │ └── whoosh │ │ ├── whoosh_0.wav │ │ ├── whoosh_1.wav │ │ ├── whoosh_2.wav │ │ └── whoosh_3.wav ├── circ.ipe ├── ffmpeg.sh ├── img │ ├── 8008.jpg │ ├── DEC_F-11_Data_chip_die.JPG │ ├── arrow.png │ ├── chess.jpg │ ├── cook.webp │ ├── cpu2.JPG │ ├── cpu3.jpg │ ├── crown.svg │ ├── emoji-cool.png │ ├── emoji-hot.png │ ├── example_code.png │ ├── jagger.jpg │ ├── karp.jpeg │ ├── levin.jpeg │ ├── logo-solarized.svg │ ├── richards.jpg │ ├── sha.png │ ├── sus.png │ ├── sus2.png │ └── thumbnail2.png └── utils │ ├── bundle_rendered_videos.py │ ├── circuit.py │ ├── cnf_constraints.py │ ├── coloring_circuits.py │ ├── gate.py │ ├── manim_circuit.py │ ├── specific_circuits.py │ ├── util_cliparts.py │ └── util_general.py ├── poetry.lock ├── postproduction ├── audio │ └── Thannoid.mp3 └── images │ └── .keep ├── pyproject.toml ├── render_all.sh └── tests ├── __init__.py ├── test_circuit.py ├── test_manim_circuit.py └── test_specific_circuits.py /.gitignore: -------------------------------------------------------------------------------- 1 | media/ 2 | .idea 3 | 4 | postproduction/rendered-files 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | *.code-workspace 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v3.2.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: check-yaml 9 | - id: check-added-large-files 10 | args: [--maxkb=2048] 11 | - repo: https://github.com/astral-sh/ruff-pre-commit 12 | rev: v0.1.9 13 | hooks: 14 | # Run the linter. 15 | - id: ruff 16 | args: [--fix] 17 | # Run the formatter - it's like Black + isort. 18 | - id: ruff-format 19 | - repo: local 20 | hooks: 21 | - id: check-poetry-lock 22 | name: Check that poetry.lock is consistent with pyproject.toml 23 | entry: bash -c 'poetry check --lock || (echo "poetry.lock is out of sync with pyproject.toml. Please run \`poetry lock\` and commit the file." && exit 1)' 24 | stages: [commit] 25 | language: system 26 | # files: "(pyproject.toml|poetry.lock)" 27 | pass_filenames: false 28 | - id: pyright 29 | name: Pyright type checker 30 | language: system 31 | types_or: [python, pyi] 32 | entry: poetry run pyright 33 | stages: [push] 34 | pass_filenames: false 35 | - id: pytest 36 | name: Pytest 37 | language: system 38 | types_or: [python, pyi] 39 | entry: poetry run pytest 40 | stages: [push] 41 | pass_filenames: false 42 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "charliermarsh.ruff", 4 | "ms-python.python" 5 | ] 6 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "editor.defaultFormatter": "charliermarsh.ruff", 4 | "editor.formatOnSave": true 5 | } 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2024 Polylog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NP-completeness 2 | 3 | Video about NP-completeness, circuit SAT and "reversing time" 4 | 5 | ## Setup 6 | 7 | The dependencies are managed via [Poetry](https://python-poetry.org/). 8 | To install the project, first get Poetry via `pip install poetry`. 9 | Next, run `poetry install` to install the dependencies. 10 | Poetry uses a lockfile (`poetry.lock`) to keep track of the *exact* versions 11 | of packages to be installed. 12 | 13 | If you have a [virtualenv](https://virtualenv.pypa.io/en/latest/) 14 | or [Conda env](https://docs.anaconda.com/miniconda/) activated, 15 | Poetry will install the dependencies into this environment. 16 | Otherwise, it will [create a new virtualenv for you](https://python-poetry.org/docs/configuration/#virtualenvsin-project). 17 | 18 | You can then run commands in this environment by starting your command with `poetry run`, 19 | e.g. `poetry run manim -pql anims.py Polylogo`. 20 | If you don't want to prefix everything with `poetry run`, you can either 21 | - run `poetry shell`, which creates a subshell with the environment activated 22 | - identify the location of the virtualenv via `poetry run which python`. 23 | You should get a path like `$SOME_PATH/bin/python`. 24 | You can run `source $SOME_PATH/bin/activate` to activate the virtualenv, 25 | just [like you would normally](https://docs.python.org/3/library/venv.html#how-venvs-work). 26 | 27 | ## Pre-commit hooks 28 | 29 | To install pre-commit hooks that check the code for correct formatting etc.: 30 | 31 | ```bash 32 | pip install pre-commit 33 | pre-commit install 34 | pre-commit install --hook pre-push 35 | ``` 36 | -------------------------------------------------------------------------------- /code/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/code/.keep -------------------------------------------------------------------------------- /code/factoring.py: -------------------------------------------------------------------------------- 1 | from pysat.formula import CNF 2 | from pysat.solvers import Glucose3 3 | 4 | DEBUG = False # Set this to True for verbose output 5 | 6 | 7 | class Wire: 8 | counter = 1 9 | 10 | def __init__(self): 11 | self.id = Wire.counter 12 | Wire.counter += 1 13 | 14 | 15 | def print_debug(message): 16 | if DEBUG: 17 | print(message) 18 | 19 | 20 | def create_circuit(k): 21 | a = [Wire() for _ in range(k)] 22 | b = [Wire() for _ in range(k)] 23 | intermediate = [[Wire() for _ in range(k)] for _ in range(k)] 24 | sum_wires = [[Wire() for _ in range(2 * k)] for _ in range(k - 1)] 25 | carry_wires = [[Wire() for _ in range(2 * k)] for _ in range(k - 1)] 26 | output = [Wire() for _ in range(2 * k)] 27 | true_wire = Wire() 28 | false_wire = Wire() 29 | return { 30 | "inputs": (a, b), 31 | "intermediate": intermediate, 32 | "sum_wires": sum_wires, 33 | "carry_wires": carry_wires, 34 | "output": output, 35 | "true": true_wire, 36 | "false": false_wire, 37 | } 38 | 39 | 40 | def add_clause(cnf, literals, description): 41 | cnf.append(literals) 42 | print_debug(f"{description}: {literals}") 43 | 44 | 45 | def add_gate(cnf, output, inputs, operation): 46 | if operation == "AND": 47 | add_clause( 48 | cnf, 49 | [-x.id for x in inputs] + [output.id], 50 | "AND gate: if all inputs are true, output has to be true", 51 | ) 52 | for x in inputs: 53 | add_clause( 54 | cnf, 55 | [x.id, -output.id], 56 | "AND gate: if any input is false, output must be false", 57 | ) 58 | elif operation == "OR": 59 | add_clause( 60 | cnf, 61 | [x.id for x in inputs] + [-output.id], 62 | "OR gate: if all inputs are false, output has to be false", 63 | ) 64 | for x in inputs: 65 | add_clause( 66 | cnf, 67 | [-x.id, output.id], 68 | "OR gate: if any input is true, output must be true", 69 | ) 70 | elif operation == "XOR": 71 | if len(inputs) == 2: 72 | x, y = inputs[0].id, inputs[1].id 73 | z = output.id 74 | add_clause( 75 | cnf, 76 | [x, y, -z], 77 | "XOR gate (2 inputs): if both inputs are false, output is false", 78 | ) 79 | add_clause( 80 | cnf, 81 | [x, -y, z], 82 | "XOR gate (2 inputs): if inputs are different (01), output is true", 83 | ) 84 | add_clause( 85 | cnf, 86 | [-x, y, z], 87 | "XOR gate (2 inputs): if inputs are different (10), output is true", 88 | ) 89 | add_clause( 90 | cnf, 91 | [-x, -y, -z], 92 | "XOR gate (2 inputs): if both inputs are true, output is false", 93 | ) 94 | elif len(inputs) == 3: 95 | x, y, w = inputs[0].id, inputs[1].id, inputs[2].id 96 | z = output.id 97 | add_clause( 98 | cnf, 99 | [x, y, w, -z], 100 | "XOR gate (3 inputs): if all inputs are false, output is false", 101 | ) 102 | add_clause( 103 | cnf, 104 | [x, y, -w, z], 105 | "XOR gate (3 inputs): if one input is true (001), output is true", 106 | ) 107 | add_clause( 108 | cnf, 109 | [x, -y, w, z], 110 | "XOR gate (3 inputs): if one input is true (010), output is true", 111 | ) 112 | add_clause( 113 | cnf, 114 | [-x, y, w, z], 115 | "XOR gate (3 inputs): if one input is true (100), output is true", 116 | ) 117 | add_clause( 118 | cnf, 119 | [x, -y, -w, -z], 120 | "XOR gate (3 inputs): if two inputs are true (011), output is false", 121 | ) 122 | add_clause( 123 | cnf, 124 | [-x, y, -w, -z], 125 | "XOR gate (3 inputs): if two inputs are true (101), output is false", 126 | ) 127 | add_clause( 128 | cnf, 129 | [-x, -y, w, -z], 130 | "XOR gate (3 inputs): if two inputs are true (110), output is false", 131 | ) 132 | add_clause( 133 | cnf, 134 | [-x, -y, -w, z], 135 | "XOR gate (3 inputs): if all inputs are true, output is true", 136 | ) 137 | else: 138 | raise ValueError("XOR gate currently supports only 2 or 3 inputs") 139 | elif operation == "MAJ": 140 | if len(inputs) != 3: 141 | raise ValueError("MAJ gate requires exactly 3 inputs") 142 | x, y, w = inputs[0].id, inputs[1].id, inputs[2].id 143 | z = output.id 144 | add_clause(cnf, [-x, -y, z], "MAJ gate: if two inputs are true, output is true") 145 | add_clause(cnf, [-x, -w, z], "MAJ gate: if two inputs are true, output is true") 146 | add_clause(cnf, [-y, -w, z], "MAJ gate: if two inputs are true, output is true") 147 | add_clause( 148 | cnf, [x, y, -z], "MAJ gate: if two inputs are false, output is false" 149 | ) 150 | add_clause( 151 | cnf, [x, w, -z], "MAJ gate: if two inputs are false, output is false" 152 | ) 153 | add_clause( 154 | cnf, [y, w, -z], "MAJ gate: if two inputs are false, output is false" 155 | ) 156 | 157 | 158 | def circuit_to_cnf(circuit, n): 159 | cnf = CNF() 160 | k = len(circuit["inputs"][0]) 161 | 162 | add_clause(cnf, [circuit["true"].id], "Setting true wire to true") 163 | add_clause(cnf, [-circuit["false"].id], "Setting false wire to false") 164 | 165 | # AND gates for intermediate results 166 | for i in range(k): 167 | for j in range(k): 168 | add_gate( 169 | cnf, 170 | circuit["intermediate"][i][j], 171 | [circuit["inputs"][0][i], circuit["inputs"][1][j]], 172 | "AND", 173 | ) 174 | 175 | # Addition logic (XOR and carry) 176 | for i in range(k - 1): 177 | for j in range(2 * k): 178 | x = ( 179 | circuit["intermediate"][0][j] 180 | if i == 0 and j < k 181 | else circuit["sum_wires"][i - 1][j] 182 | if i > 0 183 | else circuit["false"] 184 | ) 185 | y = ( 186 | circuit["intermediate"][i + 1][j - (i + 1)] 187 | if j >= (i + 1) and j < k + (i + 1) 188 | else circuit["false"] 189 | ) 190 | c = circuit["false"] if j == 0 else circuit["carry_wires"][i][j - 1] 191 | 192 | add_gate(cnf, circuit["sum_wires"][i][j], [x, y, c], "XOR") 193 | add_gate(cnf, circuit["carry_wires"][i][j], [x, y, c], "MAJ") 194 | 195 | # Define the output wires 196 | print_debug("\nConnecting sum wires to output:") 197 | for i in range(2 * k): 198 | add_gate(cnf, circuit["output"][i], [circuit["sum_wires"][k - 2][i]], "AND") 199 | 200 | # Require that the input cannot be 1 201 | add_clause( 202 | cnf, 203 | [x.id for x in circuit["inputs"][0][1:]] + [-circuit["inputs"][0][0].id], 204 | "a != 1", 205 | ) 206 | add_clause( 207 | cnf, 208 | [x.id for x in circuit["inputs"][1][1:]] + [-circuit["inputs"][1][0].id], 209 | "b != 1", 210 | ) 211 | 212 | print_debug("\nSetting output to n:") 213 | for i in range(2 * k): 214 | clause = ( 215 | [circuit["output"][i].id] if (n >> i) & 1 else [-circuit["output"][i].id] 216 | ) 217 | add_clause(cnf, clause, f"Setting output bit {i}") 218 | 219 | return cnf 220 | 221 | 222 | def factor(n): 223 | k = n.bit_length() 224 | print(f"Bit length of {n}: {k}") 225 | circuit = create_circuit(k) 226 | cnf = circuit_to_cnf(circuit, n) 227 | 228 | print(f"\nNumber of clauses: {len(cnf.clauses)}") 229 | print(f"Number of variables: {Wire.counter - 1}") 230 | 231 | solver = Glucose3() 232 | for clause in cnf.clauses: 233 | solver.add_clause([lit for lit in clause if lit != 0]) 234 | 235 | print("\nSolving SAT problem...") 236 | if solver.solve(): 237 | print("SAT problem solved successfully") 238 | model = solver.get_model() 239 | 240 | if DEBUG: 241 | for i, val in enumerate(model, 1): 242 | print(f"Variable {i}: {val}") 243 | 244 | a = sum( 245 | 1 << i 246 | for i in range(k) 247 | if circuit["inputs"][0][i].id <= len(model) 248 | and model[circuit["inputs"][0][i].id - 1] > 0 249 | ) 250 | b = sum( 251 | 1 << i 252 | for i in range(k) 253 | if circuit["inputs"][1][i].id <= len(model) 254 | and model[circuit["inputs"][1][i].id - 1] > 0 255 | ) 256 | print(f"Raw factors: a = {a}, b = {b}") 257 | return a, b 258 | else: 259 | print("SAT solver couldn't find a solution") 260 | return None 261 | 262 | 263 | def main(): 264 | instances = [ 265 | 283 * 293, 266 | 1663 * 1667, 267 | 5449 * 5471, 268 | 7907 * 7919, 269 | 33391 * 35317, 270 | 106033 * 108301, 271 | ] 272 | 273 | for n in instances: # range(2, 100): 274 | try: 275 | print(f"\nAttempting to factor {n}") 276 | factors = factor(n) 277 | if factors: 278 | a, b = factors 279 | print(f"Factors of {n}: {a} and {b}") 280 | print(f"Verification: {a} * {b} = {a * b}") 281 | else: 282 | print(f"No factors found for {n}") 283 | except Exception as e: 284 | print(f"An error occurred: {e}") 285 | import traceback 286 | 287 | traceback.print_exc() 288 | print("\n" + "=" * 50 + "\n") 289 | 290 | 291 | if __name__ == "__main__": 292 | main() 293 | -------------------------------------------------------------------------------- /code/sudoku_solver.py: -------------------------------------------------------------------------------- 1 | from pysat.formula import CNF 2 | from pysat.solvers import Glucose3 3 | 4 | 5 | # Helper function to get variable number 6 | def var(i, j, k): 7 | return 81 * (i - 1) + 9 * (j - 1) + k 8 | 9 | 10 | def encode_sudoku(puzzle): 11 | cnf = CNF() 12 | 13 | # Each cell contains exactly one number 14 | for i in range(1, 10): 15 | for j in range(1, 10): 16 | cnf.append([var(i, j, k) for k in range(1, 10)]) 17 | for k in range(1, 10): 18 | for l in range(k + 1, 10): 19 | cnf.append([-var(i, j, k), -var(i, j, l)]) 20 | 21 | # Row constraints 22 | for i in range(1, 10): 23 | for k in range(1, 10): 24 | for j in range(1, 10): 25 | for l in range(j + 1, 10): 26 | cnf.append([-var(i, j, k), -var(i, l, k)]) 27 | 28 | # Column constraints 29 | for j in range(1, 10): 30 | for k in range(1, 10): 31 | for i in range(1, 10): 32 | for l in range(i + 1, 10): 33 | cnf.append([-var(i, j, k), -var(l, j, k)]) 34 | 35 | # 3x3 sub-grid constraints 36 | for k in range(1, 10): 37 | for a in range(0, 3): 38 | for b in range(0, 3): 39 | for i in range(1, 4): 40 | for j in range(1, 4): 41 | for i2 in range(i + 1, 4): 42 | for j2 in range(1, 4): 43 | cnf.append( 44 | [ 45 | -var(3 * a + i, 3 * b + j, k), 46 | -var(3 * a + i2, 3 * b + j2, k), 47 | ] 48 | ) 49 | 50 | # Initial clues 51 | for i in range(9): 52 | for j in range(9): 53 | if puzzle[i][j] != 0: 54 | cnf.append([var(i + 1, j + 1, puzzle[i][j])]) 55 | 56 | return cnf 57 | 58 | 59 | def solve_sudoku(puzzle): 60 | cnf = encode_sudoku(puzzle) 61 | solver = Glucose3() 62 | solver.append_formula(cnf.clauses) 63 | 64 | if solver.solve(): 65 | model = solver.get_model() 66 | solution = [[0 for _ in range(9)] for _ in range(9)] 67 | for i in range(1, 10): 68 | for j in range(1, 10): 69 | for k in range(1, 10): 70 | if model[var(i, j, k) - 1] > 0: 71 | solution[i - 1][j - 1] = k 72 | return solution 73 | else: 74 | return None 75 | 76 | 77 | def print_sudoku(puzzle): 78 | for i in range(9): 79 | if i % 3 == 0 and i != 0: 80 | print("- - - - - - - - - - - -") 81 | for j in range(9): 82 | if j % 3 == 0 and j != 0: 83 | print("|", end=" ") 84 | print(puzzle[i][j], end=" ") 85 | print() 86 | 87 | 88 | # Example usage 89 | puzzle = [ 90 | [5, 3, 0, 0, 7, 0, 0, 0, 0], 91 | [6, 0, 0, 1, 9, 5, 0, 0, 0], 92 | [0, 9, 8, 0, 0, 0, 0, 6, 0], 93 | [8, 0, 0, 0, 6, 0, 0, 0, 3], 94 | [4, 0, 0, 8, 0, 3, 0, 0, 1], 95 | [7, 0, 0, 0, 2, 0, 0, 0, 6], 96 | [0, 6, 0, 0, 0, 0, 2, 8, 0], 97 | [0, 0, 0, 4, 1, 9, 0, 0, 5], 98 | [0, 0, 0, 0, 8, 0, 0, 7, 9], 99 | ] 100 | 101 | # harder puzzle 102 | puzzle = [ 103 | [0, 0, 5, 3, 0, 0, 0, 0, 0], 104 | [8, 0, 0, 0, 0, 0, 0, 2, 0], 105 | [0, 7, 0, 0, 1, 0, 5, 0, 0], 106 | [4, 0, 0, 0, 0, 5, 3, 0, 0], 107 | [0, 1, 0, 0, 7, 0, 0, 0, 6], 108 | [0, 0, 3, 2, 0, 0, 0, 8, 0], 109 | [6, 0, 0, 5, 0, 0, 0, 0, 9], 110 | [0, 0, 4, 0, 0, 0, 0, 3, 0], 111 | [0, 0, 0, 0, 0, 9, 7, 0, 0], 112 | ] 113 | 114 | print("Input Sudoku:") 115 | print_sudoku(puzzle) 116 | 117 | solution = solve_sudoku(puzzle) 118 | 119 | if solution: 120 | print("\nSolution:") 121 | print_sudoku(solution) 122 | else: 123 | print("\nNo solution exists.") 124 | -------------------------------------------------------------------------------- /np_completeness/anims.py: -------------------------------------------------------------------------------- 1 | # type: ignore[reportArgumentType] 2 | # vasek on windows: run $env:PYTHONPATH = "C:\Users\admin\np-completeness" and ask VV how to fix this 3 | # manim -pql --disable_caching --fps 15 -r 290,180 anims.py Polylogo 4 | from manim import * 5 | 6 | from np_completeness.utils.util_general import * 7 | # from utils.util_general import * 8 | 9 | CROWN_BUFF = 0.1 10 | CROWN_SCALE = 0.25 11 | BOX_COLOR = BASE3 12 | 13 | 14 | class Polylogo(Scene): 15 | def construct(self): 16 | default() 17 | authors = Tex( 18 | r"\textbf{Richard Hladík, Gabor Hollbeck, Václav Rozhoň, Václav Volhejn}", 19 | color=text_color, 20 | font_size=40, 21 | ).shift(3 * DOWN + 0 * LEFT) 22 | 23 | channel_name = Tex(r"polylog", color=text_color) 24 | channel_name.scale(4).shift(1 * UP) 25 | channel_name_without_o = Tex(r"p\hskip 5.28pt lylog", color=text_color) 26 | channel_name_without_o.scale(4).shift(1 * UP) 27 | 28 | logo_solarized = ( 29 | SVGMobject("img/logo-solarized.svg") 30 | .scale(0.55) 31 | .move_to(2 * LEFT + 0.95 * UP + 0.49 * RIGHT) 32 | ) 33 | self.play( 34 | Write(authors), 35 | Write(channel_name), 36 | ) 37 | self.play(FadeIn(logo_solarized)) 38 | self.add(channel_name_without_o) 39 | self.remove(channel_name) 40 | 41 | self.wait() 42 | 43 | self.play(*[FadeOut(o) for o in self.mobjects]) 44 | self.wait() 45 | 46 | 47 | class SATChecking(Scene): 48 | def construct(self): 49 | default() 50 | Tex = coltex 51 | 52 | variables = Group( 53 | coltex(r"Variables:"), 54 | *[ 55 | coltex(str) 56 | for str in [ 57 | x1_str + eq_str + one_str, 58 | x2_str + eq_str + zero_str, 59 | x3_str + eq_str + zero_str, 60 | x4_str + eq_str + one_str, 61 | ] 62 | ], 63 | ).arrange_in_grid(cols=1, cell_alignment=LEFT) 64 | 65 | constraints = Group( 66 | coltex(r"Constraints:"), 67 | *[ 68 | coltex(str) 69 | for str in [ 70 | x1_str + or_str + left_str + not_str + x3_str + right_str, 71 | not_str + x2_str, 72 | x2_str 73 | + or_str 74 | + left_str 75 | + not_str 76 | + x3_str 77 | + right_str 78 | + or_str 79 | + left_str 80 | + not_str 81 | + x4_str 82 | + right_str, 83 | ] 84 | ], 85 | ).arrange_in_grid(cols=1, cell_alignment=LEFT) 86 | 87 | vc = Group(variables, constraints).arrange_in_grid( 88 | cols=2, cell_alignment=UL, buff=2 89 | ) 90 | constraints[1:].shift(0.5 * DOWN) 91 | 92 | self.add(constraints, *[v[0] for v in variables], variables[0]) 93 | 94 | self.play(AnimationGroup(*[Write(v[1:]) for v in variables[1:]], lag_ratio=0.5)) 95 | self.wait() 96 | 97 | ticks = Group( 98 | *[ 99 | Text("✓", color=GREEN) 100 | .scale_to_fit_height(0.75) 101 | .next_to(text, LEFT, buff=0.5) 102 | for text in constraints[1:] 103 | ] 104 | ) 105 | self.play(AnimationGroup(*[Write(tick) for tick in ticks], lag_ratio=0.99)) 106 | self.wait() 107 | 108 | 109 | class IntroSAT(Scene): 110 | def construct(self): 111 | default() 112 | 113 | # The story of P vs NP is the story of the satisfiability problem, also known as SAT. In this problem, we are given an input that looks like this. 114 | Tex = coltex 115 | 116 | header_tex = Tex(r"Satisfiability (SAT)").scale(1.5).to_edge(UP) 117 | self.play(Write(header_tex)) 118 | self.wait() 119 | 120 | variables = Group( 121 | Tex(r"Variables:"), 122 | *[ 123 | Tex(str) 124 | for str in [ 125 | x1_str + eq_str + true_str, 126 | x2_str + eq_str + false_str, 127 | x3_str + eq_str + false_str, 128 | x4_str + eq_str + true_str, 129 | ] 130 | ], 131 | ).arrange_in_grid(cols=1, cell_alignment=LEFT) 132 | 133 | constraints = Group( 134 | Tex(r"Constraints:"), 135 | *[ 136 | Tex(str) 137 | for str in [ 138 | x1_str + or_str + left_str + not_str + x3_str + right_str, 139 | not_str + x2_str, 140 | x2_str 141 | + or_str 142 | + left_str 143 | + not_str 144 | + x3_str 145 | + right_str 146 | + or_str 147 | + left_str 148 | + not_str 149 | + x4_str 150 | + right_str, 151 | ] 152 | ], 153 | ).arrange_in_grid(cols=1, cell_alignment=LEFT) 154 | 155 | SIMPLIFIED_SCALE = 1 156 | plugged_constraints = Group( 157 | *[ 158 | Tex(str).scale(SIMPLIFIED_SCALE) 159 | for str in [ 160 | one_str + or_str + left_str + not_str + zero_str + right_str, 161 | not_str + zero_str, 162 | zero_str 163 | + or_str 164 | + left_str 165 | + not_str 166 | + zero_str 167 | + right_str 168 | + or_str 169 | + left_str 170 | + not_str 171 | + one_str 172 | + right_str, 173 | ] 174 | ] 175 | ) 176 | 177 | simplified_constraints = Group( 178 | *[ 179 | Tex(str).scale(SIMPLIFIED_SCALE) 180 | for str in [ 181 | one_str + or_str + one_str, 182 | one_str, 183 | zero_str + or_str + one_str + or_str + zero_str, 184 | ] 185 | ] 186 | ) 187 | 188 | vc = Group(variables, constraints).arrange_in_grid( 189 | cols=2, cell_alignment=UL, buff=2 190 | ) 191 | constraints[1:].shift(0.5 * DOWN) 192 | for i in range(3): 193 | for c in [plugged_constraints[i], simplified_constraints[i]]: 194 | c.move_to(constraints[i + 1]).align_to(constraints[i + 1], LEFT) 195 | if i == 0 or i == 2: 196 | simplified_constraints[i].shift( 197 | plugged_constraints[i][1].get_center() 198 | - simplified_constraints[i][1].get_center() 199 | ) 200 | 201 | self.play( 202 | AnimationGroup( 203 | Write(variables[0]), 204 | *[Write(v[0]) for v in variables[1:]], 205 | lag_ratio=0.5, 206 | ) 207 | ) 208 | self.wait() 209 | 210 | self.play(AnimationGroup(*[Write(v[1:]) for v in variables[1:]], lag_ratio=0.5)) 211 | self.wait() 212 | 213 | # l = 1 214 | # for i in range(l): 215 | # if i < l - 1: 216 | # self.play( 217 | # *[ 218 | # Transform( 219 | # v[2], 220 | # Tex( 221 | # (true_str if random.randint(0, 1) == 0 else false_str) 222 | # ).move_to(v[2]), 223 | # ) 224 | # for v in variables[1:] 225 | # ] 226 | # ) 227 | # else: 228 | # final_vals = [true_str, false_str, false_str, true_str] 229 | # self.play( 230 | # AnimationGroup(*[ 231 | # Transform(v[2], Tex(val).move_to(v[2])) 232 | # for v, val in zip(variables[1:], final_vals) 233 | # ], 234 | # lag_ratio=0.5) 235 | # ) 236 | # self.wait(0.5) 237 | # self.wait(0.5) 238 | 239 | # change true/false to 1/0 240 | self.play( 241 | *[ 242 | Transform( 243 | v[2], Tex(str(val), color=text_color).next_to(v[1], RIGHT, buff=0.2) 244 | ) 245 | for val, v in zip([1, 0, 0, 1], variables[1:]) 246 | ] 247 | ) 248 | self.wait() 249 | 250 | self.play(*[FadeOut(v[1:]) for v in variables[1:]]) 251 | self.play(AnimationGroup(*[Write(c) for c in constraints], lag_ratio=0.5)) 252 | self.wait() 253 | 254 | crazy_formula_tex = Tex( 255 | r"$(x_1 \rightarrow (\text{NOT}\, x_3)) \leftrightarrow ((x_2 \,\text{NAND}\, x_3) \,\text{XOR}\, x_4) $" 256 | ).to_edge(DOWN, buff=1.3) 257 | self.play( 258 | Write(crazy_formula_tex), 259 | ) 260 | self.wait() 261 | self.play( 262 | FadeOut(crazy_formula_tex), 263 | ) 264 | 265 | cnf_sat_tex = Tex(r"CNF-SAT").scale(1.25).to_edge(DOWN, buff=1.3) 266 | arrow = Arrow( 267 | cnf_sat_tex.get_right(), constraints[-1].get_bottom(), color=text_color 268 | ) 269 | self.play( 270 | Write(cnf_sat_tex), 271 | Create(arrow), 272 | ) 273 | self.wait() 274 | self.play( 275 | FadeOut(cnf_sat_tex), 276 | FadeOut(arrow), 277 | ) 278 | self.wait() 279 | 280 | rects = [ 281 | SurroundingRectangle(tx, color=RED) 282 | for tx in [ 283 | constraints[1][0], 284 | constraints[1][4], 285 | constraints[2][1], 286 | constraints[3][0], 287 | constraints[3][4], 288 | constraints[3][9], 289 | ] 290 | ] 291 | 292 | self.play( 293 | *[Create(rect) for rect in rects], 294 | ) 295 | self.wait() 296 | 297 | self.play( 298 | Transform(rects[1], SurroundingRectangle(constraints[1][2:], color=RED)), 299 | Transform(rects[2], SurroundingRectangle(constraints[2], color=RED)), 300 | Transform(rects[4], SurroundingRectangle(constraints[3][2:6], color=RED)), 301 | Transform(rects[5], SurroundingRectangle(constraints[3][7:], color=RED)), 302 | ) 303 | self.wait() 304 | 305 | ors = [constraints[1][1], constraints[3][1], constraints[3][6]] 306 | self.play( 307 | *[Indicate(or_, color=BLUE) for or_ in ors], 308 | ) 309 | self.wait() 310 | 311 | self.play(FadeOut(Group(*rects))) 312 | self.wait() 313 | 314 | constraints[3].save_state() 315 | self.play( 316 | constraints[3].animate.scale(1.5).to_edge(DOWN, buff=2).shift(1 * LEFT) 317 | ) 318 | self.wait() 319 | 320 | rec = SurroundingRectangle(constraints[3][0], color=RED) 321 | self.play(Create(rec)) 322 | self.wait() 323 | self.play(Transform(rec, SurroundingRectangle(constraints[3][2:6], color=RED))) 324 | self.wait() 325 | self.play(Transform(rec, SurroundingRectangle(constraints[3][7:], color=RED))) 326 | self.wait() 327 | rec_faded = SurroundingRectangle( 328 | constraints[3].saved_state[7:], color=RED 329 | ).fade(1) 330 | self.play(rec.animate.become(rec_faded), constraints[3].animate.restore()) 331 | self.wait() 332 | 333 | # for i, str in enumerate([true_str, false_str, false_str, true_str]): 334 | # variables[i+1][2] = Tex(str).next_to(variables[i+1][1], RIGHT) 335 | self.next_section(skip_animations=False) 336 | 337 | self.play( 338 | AnimationGroup( 339 | *[FadeIn(variable[1:]) for variable in variables[1:]], lag_ratio=0.5 340 | ) 341 | ) 342 | self.wait() 343 | 344 | copies = [] 345 | for i, k, l in [ 346 | (1, 0, 0), 347 | (3, 0, 4), 348 | (2, 1, 1), 349 | (2, 2, 0), 350 | (3, 2, 4), 351 | (4, 2, 9), 352 | ]: 353 | copy = variables[i][2].copy() 354 | copy.generate_target() 355 | copy.target.scale(SIMPLIFIED_SCALE).move_to(plugged_constraints[k][l]) 356 | copies.append(copy) 357 | 358 | self.play( 359 | *[ 360 | Transform(constraint, plugged_constraint) 361 | for constraint, plugged_constraint in zip( 362 | constraints[1:], plugged_constraints 363 | ) 364 | ], 365 | *[MoveToTarget(copy) for copy in copies], 366 | ) 367 | self.wait() 368 | 369 | self.remove(*copies) 370 | self.play( 371 | Transform(constraints[1][2:], simplified_constraints[0][2]), 372 | Transform(constraints[2], simplified_constraints[1]), 373 | Transform(constraints[3][2:6], simplified_constraints[2][2]), 374 | Transform(constraints[3][6], simplified_constraints[2][3]), 375 | Transform(constraints[3][7:], simplified_constraints[2][4]), 376 | ) 377 | self.wait() 378 | 379 | # self.play( 380 | # *[ 381 | # Create(SurroundingRectangle(c, color=RED)) 382 | # for c in [ 383 | # simplified_constraints[0][0], 384 | # simplified_constraints[1][0], 385 | # simplified_constraints[2][2], 386 | # ] 387 | # ], 388 | # ) 389 | # self.wait() 390 | 391 | self.play( 392 | *[ 393 | Transform( 394 | constraint, Tex(r"1").move_to(constraint).align_to(constraint, LEFT) 395 | ) 396 | for constraint in constraints[1:] 397 | ] 398 | ) 399 | self.wait() 400 | 401 | 402 | class SecurityRSA(Scene): 403 | def construct(self): 404 | default() 405 | 406 | product_tex = Tex( 407 | r"{{$308649719645509$ }}{{$\;\;=\;\; 54658423 \;\;\times\;\; 5646883$}}" 408 | ).scale(1.25) 409 | self.play(Write(product_tex[0])) 410 | self.wait() 411 | self.play(Write(product_tex[1])) 412 | self.wait() 413 | 414 | 415 | class BreakRSA(Scene): 416 | def construct(self): 417 | default() 418 | texts = [ 419 | Tex(r"Breaking RSA \\ (factoring numbers of size $10^{500}$)"), 420 | Tex(r"our reduction $\Downarrow$"), 421 | Tex(r"$\Uparrow$ fast SAT solver", color=GREEN), 422 | Tex(r"Solving SAT with $\sim50$M variables"), 423 | # MathTex(r"\Rightarrow"), 424 | ] 425 | ars = Group(texts[1], texts[2]).arrange(RIGHT, buff=0.5) 426 | g = Group(texts[0], ars, texts[3]).arrange(DOWN, buff=1) 427 | 428 | for t in [texts[0], texts[1], texts[3], texts[2]]: 429 | self.play(Write(t)) 430 | self.wait() 431 | 432 | 433 | class TitleGeneral(Scene): 434 | def construct(self): 435 | default() 436 | title = Tex("General Reductions", color=text_color).scale(3) 437 | self.play(Write(title)) 438 | self.wait() 439 | -------------------------------------------------------------------------------- /np_completeness/anims_circuit.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, cast 2 | 3 | from manim import * 4 | 5 | from np_completeness.utils.coloring_circuits import ( 6 | GRAPH_COLORS, 7 | get_example_graph, 8 | make_coloring_circuit, 9 | ) 10 | from np_completeness.utils.manim_circuit import ManimCircuit 11 | from np_completeness.utils.specific_circuits import ( 12 | make_adder_circuit, 13 | make_adder_gate, 14 | make_example_circuit, 15 | make_multiplication_circuit, 16 | make_multiplication_circuit_constraints, 17 | to_binary, 18 | ) 19 | 20 | # Imported for the side effect of changing the default colors 21 | from np_completeness.utils.util_general import * 22 | 23 | FINAL_VIDEO = False 24 | 25 | 26 | def make_multiplication_by_hand( 27 | rows: list[str], color: bool = False 28 | ) -> tuple[VGroup, list[list[VMobject]], list[Line]]: 29 | """Make a visualisation of the "school algorithm" for multiplying by hand.""" 30 | assert all( 31 | len(row) == len(rows[0]) for row in rows 32 | ), "All rows must have the same length" 33 | 34 | n_rows, n_cols = len(rows), len(rows[0]) 35 | 36 | def coloring(c: str) -> str: 37 | if color is False or c not in "01": 38 | return BASE00 39 | return get_wire_color(c == "1") 40 | 41 | # Manim refuses to render a single space to TeX, so use an empty object 42 | numbers: list[list[VMobject]] = [ 43 | [ 44 | Text(c, color=coloring(c)) 45 | if c == "×" 46 | else (Tex(c, color=coloring(c)) if c != " " else VGroup()) 47 | for c in row 48 | ] 49 | for row in rows 50 | ] 51 | 52 | # Fade out the helper zeroes in the intermediate results 53 | for i in range(3, len(rows) - 1): 54 | for j in range(n_cols - i + 2, n_cols): 55 | assert ( 56 | rows[i][j] == "0" 57 | ), f"Expected a zero, got {repr(rows[i][j])} at {i}, {j}" 58 | numbers[i][j].fade(0.6) 59 | 60 | X_SHIFT = RIGHT * 0.45 61 | Y_SHIFT = DOWN * 0.6 62 | 63 | # There is also Group.arrange_in_grid() but it takes into account the sizes of the 64 | # objects and that leads to wonky spacing because numbers have different widths. 65 | for i, row in enumerate(numbers): 66 | for j, number in enumerate(row): 67 | number.move_to( 68 | X_SHIFT * j + Y_SHIFT * i, 69 | ) 70 | 71 | group = VGroup() 72 | for row in numbers: 73 | group.add(*row) 74 | 75 | lines = [ 76 | Line( 77 | -X_SHIFT * 0.5 + Y_SHIFT * 1.5, 78 | +X_SHIFT * (n_cols - 0.5) + Y_SHIFT * 1.5, 79 | color=BASE00, 80 | ), 81 | Line( 82 | -X_SHIFT * 0.5 + Y_SHIFT * (n_rows - 1.5), 83 | +X_SHIFT * (n_cols - 0.5) + Y_SHIFT * (n_rows - 1.5), 84 | color=BASE00, 85 | ), 86 | ] 87 | 88 | group.add(*lines) 89 | 90 | return (group, numbers, lines) 91 | 92 | 93 | def lagged_create( 94 | objects: list[VMobject], lag_ratio: float = 0.1, anim: type[Animation] | None = None 95 | ) -> AnimationGroup: 96 | if anim is None: 97 | anim = Create 98 | return LaggedStart(*[anim(obj) for obj in objects], lag_ratio=lag_ratio) 99 | 100 | 101 | class MultiplicationByHand(Scene): 102 | def construct(self): 103 | disable_rich_logging() 104 | default() 105 | 106 | mult_tex = Tex(r"{{$3$}}{{$\,\times\,$}}{{$5$}}{{$\,=\,???$}}").scale(4) 107 | self.play( 108 | AnimationGroup( 109 | *[Write(cast(VMobject, mult_tex[i])) for i in range(4)], 110 | lag_ratio=0.5, 111 | ) 112 | ) 113 | self.wait(1) 114 | self.play(FadeOut(mult_tex)) 115 | self.wait() 116 | 117 | grid = [ 118 | " 68", 119 | "× 18", 120 | " 544", 121 | " 680", 122 | "1224", 123 | ] 124 | group, _, _ = make_multiplication_by_hand(grid) 125 | group.scale(2).to_edge(UP, buff=0.75) 126 | 127 | self.play(FadeIn(group)) 128 | self.wait(2) 129 | self.play(FadeOut(group)) 130 | 131 | grid = [ 132 | " 11", 133 | "× 101", 134 | " 11", 135 | " 0", 136 | " 1100", 137 | " 1111", 138 | ] 139 | group, numbers, lines = make_multiplication_by_hand(grid, color=True) 140 | group.scale(1.75).center().shift(LEFT * 2).to_edge(UP, buff=0.5) 141 | 142 | base10_rows = [ 143 | Tex("{{=}}{{\\,3}}", color=MAGENTA), 144 | Tex("{{=}}{{\\,5}}", color=MAGENTA), 145 | VGroup(), 146 | VGroup(), 147 | VGroup(), 148 | Tex("=\\,15", color=MAGENTA), 149 | ] 150 | 151 | for i, row in enumerate(base10_rows): 152 | row.scale(2).next_to( 153 | numbers[i][-1], direction=RIGHT, buff=1, aligned_edge=DOWN 154 | ) 155 | 156 | # this is what it is in base10 157 | for j in [1, 0]: 158 | objects_to_animate = [ 159 | cast(VMobject, base10_rows[0][j]), 160 | cast(VMobject, base10_rows[1][j]), 161 | ] 162 | self.play(lagged_create(objects_to_animate, anim=Write)) 163 | self.wait(0.5) 164 | 165 | self.play(lagged_create(numbers[0] + numbers[1], anim=Write)) 166 | self.wait(1) 167 | 168 | # intermediate results 169 | self.play( 170 | lagged_create([lines[0], *numbers[2], *numbers[3], *numbers[4]], anim=Write) 171 | ) 172 | self.wait(1) 173 | # final result, explanation in base10 174 | self.play(lagged_create([lines[1], *numbers[5]], anim=Write)) 175 | self.wait(0.5) 176 | self.play(lagged_create([base10_rows[5]], anim=Write)) 177 | self.wait(2) 178 | 179 | 180 | def make_multiplication_explanation_texts( 181 | manim_circuit: ManimCircuit, a: int, b: int, text_scale: float 182 | ) -> tuple[list[list[VMobject]], list[list[Animation]]]: 183 | """Create texts next to the multiplication inputs explaining the binary.""" 184 | # Add explanations to the inputs 185 | all_explanations, all_anims = [], [] 186 | 187 | for symbol, value in zip("ab", [a, b]): 188 | binary_values = to_binary(value) 189 | explanations = [] 190 | anims = [] 191 | 192 | for i, bit in enumerate(binary_values): 193 | manim_gate = manim_circuit.gates[f"input_{symbol}_{i}"] 194 | 195 | explanation = ( 196 | Tex(str(int(bit)), color=BLUE) 197 | .scale(text_scale) 198 | .move_to( 199 | manim_gate.get_center() 200 | + ( 201 | np.array([0.1, 0.35, 0]) 202 | if symbol == "a" 203 | else np.array([0.1, 0.35, 0]) 204 | ) 205 | ) 206 | ) 207 | explanations.append(explanation) 208 | 209 | anims.append(manim_gate.animate_to_value(bit)) 210 | anims.append(Write(explanation)) 211 | 212 | decimal_explanation = ( 213 | Tex(f"=\\,{value}", color=MAGENTA) 214 | .scale(text_scale) 215 | .move_to( 216 | np.array( 217 | [ 218 | manim_circuit.gates[f"input_a_0"].get_center()[0] + 0.9, 219 | explanations[0].get_center()[1], 220 | 0, 221 | ] 222 | ) 223 | ) 224 | ) 225 | explanations.append(decimal_explanation) 226 | anims.append(Write(decimal_explanation)) 227 | 228 | all_explanations.append(explanations) 229 | all_anims.append(anims) 230 | 231 | return all_explanations, all_anims 232 | 233 | 234 | class MultiplicationCircuitScene(Scene): 235 | def construct(self): 236 | disable_rich_logging() 237 | 238 | a, b = 3, 5 239 | 240 | circuit = make_multiplication_circuit(a=a, b=b) 241 | 242 | # We want to leave a bit of room at the bottom because of subtitles 243 | # TODO: is this enough? 244 | # NOTE: Keep in sync with GreaterThanOneConstraint 245 | circuit.scale(0.8).shift(LEFT * 0.4 + UP * 0.2) 246 | 247 | circuit.add_missing_inputs_and_outputs() 248 | manim_circuit = ManimCircuit(circuit, with_evaluation=True) 249 | 250 | self.play(Create(manim_circuit, lag_ratio=0.002), run_time=3) 251 | self.wait() 252 | 253 | rect = SurroundingRectangle( 254 | Group( 255 | *[manim_circuit.gates[f"input_a_{i}"] for i in range(4)], 256 | *[manim_circuit.gates[f"input_b_{i}"] for i in range(4)], 257 | ), 258 | color=RED, 259 | ) 260 | self.play(Create(rect)) 261 | self.play(FadeOut(rect)) 262 | self.wait() 263 | 264 | TEXT_SCALE = 1.4 265 | _all_explanations, all_anims = make_multiplication_explanation_texts( 266 | manim_circuit, a, b, text_scale=TEXT_SCALE 267 | ) 268 | 269 | for i in range(2): 270 | self.play(LaggedStart(*all_anims[i])) 271 | 272 | self.play(manim_circuit.animate_evaluation(scene=self)) 273 | self.wait() 274 | 275 | # Add explanations to the outputs 276 | anims = [] 277 | explanations = [] 278 | for i, bit in enumerate(to_binary(a * b, n_digits=8)): 279 | manim_gate = manim_circuit.gates[f"output_{i}"] 280 | 281 | explanation = ( 282 | Tex(str(int(bit)), color=BLUE) 283 | .scale(TEXT_SCALE) 284 | .move_to(manim_gate.get_center() + np.array([00, -0.45, 0])) 285 | ) 286 | explanations.append(explanation) 287 | 288 | anims.append(manim_gate.animate_to_value(bit)) 289 | anims.append(Write(explanation)) 290 | 291 | decimal_explanation = ( 292 | Tex(f"=\\,{a * b}", color=MAGENTA) 293 | .scale(TEXT_SCALE) 294 | .move_to(explanations[0].get_center() + RIGHT * 1.5) 295 | ) 296 | anims.append(Write(decimal_explanation)) 297 | 298 | self.play(LaggedStart(*anims)) 299 | self.wait(2) 300 | 301 | 302 | class GreaterThanOneConstraint(Scene): 303 | def construct(self): 304 | a, b = 1, 15 305 | circuit = make_multiplication_circuit_constraints(a, b) 306 | # NOTE: Keep in sync with MultiplicationCircuitScene 307 | circuit.scale(0.8).shift(LEFT * 0.4 + UP * 0.2) 308 | 309 | manim_circuit = ManimCircuit(circuit) 310 | 311 | for manim_gate in manim_circuit.gates.values(): 312 | if manim_gate.gate.visual_type in ["not", "or", "and"]: 313 | manim_gate.scale(2) 314 | 315 | self.add(manim_circuit) 316 | self.wait() 317 | 318 | TEXT_SCALE = 1.4 319 | all_explanations, all_anims = make_multiplication_explanation_texts( 320 | manim_circuit, a, b, text_scale=TEXT_SCALE 321 | ) 322 | 323 | for i in range(2): 324 | self.play(LaggedStart(*all_anims[i])) 325 | 326 | self.wait() 327 | self.play(manim_circuit.animate_evaluation(scene=self)) 328 | self.wait() 329 | self.play( 330 | Create(SurroundingRectangle(all_explanations[0][-1], color=RED)), 331 | Create(SurroundingRectangle(manim_circuit.gates["or_a"], color=RED)), 332 | ) 333 | self.wait() 334 | 335 | 336 | class ExampleCircuitScene(Scene): 337 | def construct(self): 338 | circuit = make_example_circuit() 339 | manim_circuit = ManimCircuit(circuit, scale=2) 340 | self.add(manim_circuit) 341 | self.wait() 342 | 343 | # Create input labels 344 | input_values = [1, 1, 0] # Matching the out_value in make_example_circuit 345 | input_labels = [] 346 | for i, value in enumerate(input_values): 347 | color = get_wire_color(bool(value)) 348 | label = Tex(str(value), color=color).scale(1.5) 349 | 350 | input_gate = manim_circuit.gates[f"input_{i}"] 351 | label.next_to(input_gate, UP, buff=0.2) 352 | 353 | input_labels.append(label) 354 | 355 | internal_gates = [ 356 | gate 357 | for name, gate in manim_circuit.gates.items() 358 | if not name.startswith(("input_", "output_")) 359 | ] 360 | 361 | # highlight the wires 362 | # TODO somehow this does not work 363 | for sc in [10, 1]: 364 | self.play( 365 | *[ 366 | wire.animate.set_stroke_width(WIRE_WIDTH * sc) 367 | for wire in manim_circuit.wires.values() 368 | ], 369 | run_time=0.5, 370 | ) 371 | self.wait(0.5) 372 | 373 | # highlight the gates 374 | for sc in [1.5, 1 / 1.5]: 375 | self.play( 376 | *[ 377 | cast(Animation, gate.animate.scale(sc)) 378 | for gate in internal_gates 379 | if gate.gate.visual_type != "knot" 380 | ], 381 | run_time=0.5, 382 | ) 383 | self.wait(0.5) 384 | 385 | # Animate the appearance of input labels 386 | self.play( 387 | AnimationGroup( 388 | *[Write(label) for label in input_labels], 389 | lag_ratio=0.5, 390 | ) 391 | ) 392 | self.wait() 393 | 394 | # Animate inputs 395 | self.play(manim_circuit.animate_inputs()) 396 | self.wait() 397 | 398 | # Simulate the circuit 399 | self.play(manim_circuit.animate_evaluation(scene=self)) 400 | 401 | # add output labels 402 | output_labels = [ 403 | Tex(str(val), color=get_wire_color(True if val == 1 else False)) 404 | .scale(1.5) 405 | .next_to(manim_circuit.gates["output_" + str(i)], dir) 406 | for val, i, dir in [(1, 0, LEFT), (0, 1, RIGHT)] 407 | ] 408 | self.play( 409 | AnimationGroup( 410 | *[Write(label) for label in output_labels], 411 | lag_ratio=0.5, 412 | ) 413 | ) 414 | self.wait() 415 | 416 | 417 | class AdderCircuitScene(Scene): 418 | def construct(self): 419 | circuit = make_adder_gate(inputs=[True, False, True]) 420 | circuit.add_missing_inputs_and_outputs() 421 | manim_circuit = ManimCircuit(circuit, scale=2, wire_scale=1) 422 | self.add(manim_circuit) 423 | self.wait() 424 | 425 | description_tex = Tex( 426 | r"\hbox{\hbox to 9mm{In:\hss}three bits}\hbox{\hbox to 9mm{Out:\hss}their sum in binary}", 427 | color=text_color, 428 | ) 429 | description = Group(description_tex).to_corner(UR) 430 | 431 | self.play(FadeIn(description)) 432 | self.wait() 433 | 434 | detailed_circuit = make_adder_circuit(inputs=[True, False, True]) 435 | detailed_circuit.add_missing_inputs_and_outputs() 436 | detailed_manim_circuit = ManimCircuit(detailed_circuit) 437 | detailed_manim_circuit.shift(RIGHT * 0.5 + DOWN * 0.5) 438 | 439 | self.play( 440 | animate(manim_circuit).scale(15).set_stroke_width(15).fade(1), 441 | FadeIn(detailed_manim_circuit, scale=0.1), 442 | run_time=2, 443 | ) 444 | self.wait() 445 | 446 | self.play(detailed_manim_circuit.animate_evaluation(scene=self)) 447 | 448 | self.wait(1) 449 | 450 | 451 | class ColoringCircuitScene(Scene): 452 | def construct(self): 453 | graph, coloring = get_example_graph(good_coloring=True, colored=False) 454 | graph.shift(0.2 * UP) 455 | 456 | self.play(Create(graph, lag_ratio=0.1)) 457 | self.wait() 458 | 459 | self.play( 460 | AnimationGroup( 461 | *[ 462 | graph.vertices[vertex].animate.set_fill_color( 463 | GRAPH_COLORS[coloring[vertex]] 464 | ) 465 | for vertex in graph.vertices 466 | ], 467 | lag_ratio=0.75, 468 | ) 469 | ) 470 | self.wait() 471 | 472 | rectangle = SurroundingRectangle(graph.vertices[4], color=RED) 473 | self.play(Create(rectangle)) 474 | self.play(animate(graph.vertices[4]).set_color(GRAPH_COLORS[0])) 475 | coloring[4] = 0 476 | self.play(Uncreate(rectangle)) 477 | self.wait() 478 | self.play( 479 | Indicate(graph.vertices[4], scale_factor=1.5, color=GRAPH_COLORS[0]), 480 | Indicate(graph.vertices[5], scale_factor=1.5, color=GRAPH_COLORS[0]), 481 | ) 482 | self.wait() 483 | 484 | circuit, circuit2 = [ 485 | make_coloring_circuit( 486 | graph, coloring, output_position=np.array([-4, -2.0, 0]) 487 | ) 488 | for _ in range(2) 489 | ] 490 | manim_circuit, manim_circuit2 = ManimCircuit(circuit), ManimCircuit(circuit2) 491 | 492 | self.play( 493 | Create(manim_circuit, lag_ratio=0.002), 494 | LaggedStart( 495 | *[ 496 | cast(Animation, v.animate.fade(0.5).scale(5)) 497 | for v in graph.vertices.values() 498 | ] 499 | ), 500 | *[FadeOut(e) for e in graph.edges.values()], 501 | run_time=3, 502 | ) 503 | self.wait() 504 | 505 | # Fade out the wires that lead to the output gate 506 | anims = [] 507 | for (_wire_start, wire_end), manim_wire in manim_circuit.wires.items(): 508 | if wire_end == "big_and": 509 | anims.append(animate(manim_wire).fade(0.9)) 510 | self.play(*anims) 511 | self.wait() 512 | 513 | # The idea is that you have one variable per vertex and color 514 | self.play(manim_circuit.animate_inputs()) 515 | self.wait() 516 | 517 | def make_highlight_rectangles( 518 | condition: Callable[[str], bool], 519 | ) -> None: 520 | evaluation = circuit.evaluate() 521 | 522 | selected_gates = [ 523 | name for name in evaluation.gate_evaluations if condition(name) 524 | ] 525 | rectangles = VGroup( 526 | *[ 527 | SurroundingRectangle(manim_circuit.gates[gate], color=RED) 528 | for gate in selected_gates 529 | ] 530 | ) 531 | self.play(Create(rectangles, lag_ratio=0.05)) 532 | self.wait() 533 | self.play(Uncreate(rectangles, lag_ratio=0.05)) 534 | self.wait() 535 | 536 | make_highlight_rectangles( 537 | lambda name: name.startswith("vertex_") 538 | and not "value" in name 539 | or name.startswith("edge_") 540 | ) 541 | 542 | make_highlight_rectangles(lambda name: name == "output") 543 | 544 | self.play(manim_circuit.animate_evaluation(scene=self)) 545 | self.wait() 546 | 547 | evaluation = circuit.evaluate() 548 | make_highlight_rectangles( 549 | lambda name: "nand" in name 550 | and not name.startswith("output_") # auto-generated hidden output nodes 551 | and not evaluation.get_gate_outputs(name)[0] 552 | ) 553 | 554 | self.wait() 555 | self.play( 556 | FadeOut(manim_circuit), 557 | FadeIn(manim_circuit2), 558 | ) 559 | self.wait() 560 | 561 | out_tex = ( 562 | Tex(r"$x_{\text{output}} = 1$", color=WIRE_COLOR_TRUE) 563 | .scale(0.8) 564 | .next_to(manim_circuit.gates["output"], LEFT) 565 | ) 566 | self.play(Write(out_tex)) 567 | self.wait() 568 | 569 | 570 | if __name__ == "__main__": 571 | graph, coloring = get_example_graph(good_coloring=False) 572 | circuit = make_coloring_circuit(graph, coloring) 573 | 574 | with_evaluation = False 575 | if with_evaluation: 576 | circuit.add_missing_inputs_and_outputs() 577 | circuit.display_graph(with_evaluation=with_evaluation) 578 | -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_000.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_000.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_001.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_001.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_002.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_003.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_003.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_004.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_004.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_005.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_005.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_006.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_006.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_007.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_007.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_008.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_008.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_009.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_009.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_010.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_010.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_011.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_011.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_012.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_012.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_013.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_013.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_014.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_014.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_015.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_015.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_016.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_016.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_017.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_017.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_018.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_018.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_019.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_019.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_020.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_020.wav -------------------------------------------------------------------------------- /np_completeness/audio/bfs/bfs_021.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/bfs/bfs_021.wav -------------------------------------------------------------------------------- /np_completeness/audio/click/click_0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/click/click_0.wav -------------------------------------------------------------------------------- /np_completeness/audio/click/click_1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/click/click_1.wav -------------------------------------------------------------------------------- /np_completeness/audio/click/click_2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/click/click_2.wav -------------------------------------------------------------------------------- /np_completeness/audio/click/click_3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/click/click_3.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/full_track.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/full_track.mp3 -------------------------------------------------------------------------------- /np_completeness/audio/cube/r1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r1.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r10.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r10.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r11.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r11.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r12.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r12.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r13.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r13.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r14.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r14.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r15.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r15.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r16.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r16.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r17.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r17.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r18.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r18.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r19.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r19.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r2.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r20.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r20.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r3.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r4.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r5.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r6.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r7.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r8.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r8.wav -------------------------------------------------------------------------------- /np_completeness/audio/cube/r9.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/cube/r9.wav -------------------------------------------------------------------------------- /np_completeness/audio/gong.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/gong.wav -------------------------------------------------------------------------------- /np_completeness/audio/polylog_failure.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/polylog_failure.wav -------------------------------------------------------------------------------- /np_completeness/audio/polylog_success.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/polylog_success.wav -------------------------------------------------------------------------------- /np_completeness/audio/pop/pop_0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/pop/pop_0.wav -------------------------------------------------------------------------------- /np_completeness/audio/pop/pop_1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/pop/pop_1.wav -------------------------------------------------------------------------------- /np_completeness/audio/pop/pop_2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/pop/pop_2.wav -------------------------------------------------------------------------------- /np_completeness/audio/pop/pop_3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/pop/pop_3.wav -------------------------------------------------------------------------------- /np_completeness/audio/pop/pop_4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/pop/pop_4.wav -------------------------------------------------------------------------------- /np_completeness/audio/pop/pop_5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/pop/pop_5.wav -------------------------------------------------------------------------------- /np_completeness/audio/pop/pop_6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/pop/pop_6.wav -------------------------------------------------------------------------------- /np_completeness/audio/rising-falling.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/rising-falling.flac -------------------------------------------------------------------------------- /np_completeness/audio/whoops/whoops1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/whoops/whoops1.mp3 -------------------------------------------------------------------------------- /np_completeness/audio/whoosh/whoosh_0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/whoosh/whoosh_0.wav -------------------------------------------------------------------------------- /np_completeness/audio/whoosh/whoosh_1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/whoosh/whoosh_1.wav -------------------------------------------------------------------------------- /np_completeness/audio/whoosh/whoosh_2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/whoosh/whoosh_2.wav -------------------------------------------------------------------------------- /np_completeness/audio/whoosh/whoosh_3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/audio/whoosh/whoosh_3.wav -------------------------------------------------------------------------------- /np_completeness/circ.ipe: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0 0 m 9 | -1 0.333 l 10 | -1 -0.333 l 11 | h 12 | 13 | 14 | 15 | 16 | 0 0 m 17 | -1 0.333 l 18 | -1 -0.333 l 19 | h 20 | 21 | 22 | 23 | 24 | 0 0 m 25 | -1 0.333 l 26 | -0.8 0 l 27 | -1 -0.333 l 28 | h 29 | 30 | 31 | 32 | 33 | 0 0 m 34 | -1 0.333 l 35 | -0.8 0 l 36 | -1 -0.333 l 37 | h 38 | 39 | 40 | 41 | 42 | 0.6 0 0 0.6 0 0 e 43 | 0.4 0 0 0.4 0 0 e 44 | 45 | 46 | 47 | 48 | 0.6 0 0 0.6 0 0 e 49 | 50 | 51 | 52 | 53 | 54 | 0.5 0 0 0.5 0 0 e 55 | 56 | 57 | 0.6 0 0 0.6 0 0 e 58 | 0.4 0 0 0.4 0 0 e 59 | 60 | 61 | 62 | 63 | 64 | -0.6 -0.6 m 65 | 0.6 -0.6 l 66 | 0.6 0.6 l 67 | -0.6 0.6 l 68 | h 69 | -0.4 -0.4 m 70 | 0.4 -0.4 l 71 | 0.4 0.4 l 72 | -0.4 0.4 l 73 | h 74 | 75 | 76 | 77 | 78 | -0.6 -0.6 m 79 | 0.6 -0.6 l 80 | 0.6 0.6 l 81 | -0.6 0.6 l 82 | h 83 | 84 | 85 | 86 | 87 | 88 | -0.5 -0.5 m 89 | 0.5 -0.5 l 90 | 0.5 0.5 l 91 | -0.5 0.5 l 92 | h 93 | 94 | 95 | -0.6 -0.6 m 96 | 0.6 -0.6 l 97 | 0.6 0.6 l 98 | -0.6 0.6 l 99 | h 100 | -0.4 -0.4 m 101 | 0.4 -0.4 l 102 | 0.4 0.4 l 103 | -0.4 0.4 l 104 | h 105 | 106 | 107 | 108 | 109 | 110 | 111 | -0.43 -0.57 m 112 | 0.57 0.43 l 113 | 0.43 0.57 l 114 | -0.57 -0.43 l 115 | h 116 | 117 | 118 | -0.43 0.57 m 119 | 0.57 -0.43 l 120 | 0.43 -0.57 l 121 | -0.57 0.43 l 122 | h 123 | 124 | 125 | 126 | 127 | 128 | 0 0 m 129 | -1 0.333 l 130 | -1 -0.333 l 131 | h 132 | 133 | 134 | 135 | 136 | 0 0 m 137 | -1 0.333 l 138 | -0.8 0 l 139 | -1 -0.333 l 140 | h 141 | 142 | 143 | 144 | 145 | 0 0 m 146 | -1 0.333 l 147 | -0.8 0 l 148 | -1 -0.333 l 149 | h 150 | 151 | 152 | 153 | 154 | -1 0.333 m 155 | 0 0 l 156 | -1 -0.333 l 157 | 158 | 159 | 160 | 161 | 0 0 m 162 | -1 0.333 l 163 | -1 -0.333 l 164 | h 165 | -1 0 m 166 | -2 0.333 l 167 | -2 -0.333 l 168 | h 169 | 170 | 171 | 172 | 173 | 0 0 m 174 | -1 0.333 l 175 | -1 -0.333 l 176 | h 177 | -1 0 m 178 | -2 0.333 l 179 | -2 -0.333 l 180 | h 181 | 182 | 183 | 184 | 185 | 0.5 0 m 186 | -0.5 0.333 l 187 | -0.5 -0.333 l 188 | h 189 | 190 | 191 | 192 | 193 | 0.5 0 m 194 | -0.5 0.333 l 195 | -0.5 -0.333 l 196 | h 197 | 198 | 199 | 200 | 201 | 0.5 0 m 202 | -0.5 0.333 l 203 | -0.3 0 l 204 | -0.5 -0.333 l 205 | h 206 | 207 | 208 | 209 | 210 | 0.5 0 m 211 | -0.5 0.333 l 212 | -0.3 0 l 213 | -0.5 -0.333 l 214 | h 215 | 216 | 217 | 218 | 219 | 1 0 m 220 | 0 0.333 l 221 | 0 -0.333 l 222 | h 223 | 0 0 m 224 | -1 0.333 l 225 | -1 -0.333 l 226 | h 227 | 228 | 229 | 230 | 231 | 1 0 m 232 | 0 0.333 l 233 | 0 -0.333 l 234 | h 235 | 0 0 m 236 | -1 0.333 l 237 | -1 -0.333 l 238 | h 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 256 592 m 317 | 256 576 l 318 | 288 576 l 319 | 288 592 l 320 | h 321 | 322 | 323 | 256 592 m 324 | 256 576 l 325 | 288 576 l 326 | 288 592 l 327 | h 328 | 329 | 330 | 256 592 m 331 | 256 576 l 332 | 288 576 l 333 | 288 592 l 334 | h 335 | 336 | 337 | 256 592 m 338 | 256 576 l 339 | 288 576 l 340 | 288 592 l 341 | h 342 | 343 | 344 | 256 592 m 345 | 256 576 l 346 | 288 576 l 347 | 288 592 l 348 | h 349 | 350 | 351 | 256 592 m 352 | 256 576 l 353 | 288 576 l 354 | 288 592 l 355 | h 356 | 357 | 358 | 256 592 m 359 | 256 576 l 360 | 288 576 l 361 | 288 592 l 362 | h 363 | 364 | 365 | 256 592 m 366 | 256 576 l 367 | 288 576 l 368 | 288 592 l 369 | h 370 | 371 | 372 | 256 592 m 373 | 256 576 l 374 | 288 576 l 375 | 288 592 l 376 | h 377 | 378 | 379 | 256 592 m 380 | 256 576 l 381 | 288 576 l 382 | 288 592 l 383 | h 384 | 385 | 386 | 256 592 m 387 | 256 576 l 388 | 288 576 l 389 | 288 592 l 390 | h 391 | 392 | 393 | 256 592 m 394 | 256 576 l 395 | 288 576 l 396 | 288 592 l 397 | h 398 | 399 | 400 | 256 592 m 401 | 256 576 l 402 | 288 576 l 403 | 288 592 l 404 | h 405 | 406 | 407 | 256 592 m 408 | 256 576 l 409 | 288 576 l 410 | 288 592 l 411 | h 412 | 413 | 414 | 256 592 m 415 | 256 576 l 416 | 288 576 l 417 | 288 592 l 418 | h 419 | 420 | 421 | 256 592 m 422 | 256 576 l 423 | 288 576 l 424 | 288 592 l 425 | h 426 | 427 | 428 | 176 640 m 429 | 176 624 l 430 | 431 | 432 | 192 640 m 433 | 192 624 l 434 | 435 | 436 | 208 640 m 437 | 208 624 l 438 | 439 | 440 | 176 608 m 441 | 176 592 l 442 | 443 | 444 | 144 640 m 445 | 144 608 l 446 | 192 592 l 447 | 448 | 449 | 450 | -------------------------------------------------------------------------------- /np_completeness/ffmpeg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | fn="${@:$#}" 4 | set -- "${@:1:$(($# - 1))}" 5 | exec ffmpeg "$@" -crf 10 "$fn" 6 | -------------------------------------------------------------------------------- /np_completeness/img/8008.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/8008.jpg -------------------------------------------------------------------------------- /np_completeness/img/DEC_F-11_Data_chip_die.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/DEC_F-11_Data_chip_die.JPG -------------------------------------------------------------------------------- /np_completeness/img/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/arrow.png -------------------------------------------------------------------------------- /np_completeness/img/chess.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/chess.jpg -------------------------------------------------------------------------------- /np_completeness/img/cook.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/cook.webp -------------------------------------------------------------------------------- /np_completeness/img/cpu2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/cpu2.JPG -------------------------------------------------------------------------------- /np_completeness/img/cpu3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/cpu3.jpg -------------------------------------------------------------------------------- /np_completeness/img/crown.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.15, written by Peter Selinger 2001-2017 9 | 10 | 12 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /np_completeness/img/emoji-cool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/emoji-cool.png -------------------------------------------------------------------------------- /np_completeness/img/emoji-hot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/emoji-hot.png -------------------------------------------------------------------------------- /np_completeness/img/example_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/example_code.png -------------------------------------------------------------------------------- /np_completeness/img/jagger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/jagger.jpg -------------------------------------------------------------------------------- /np_completeness/img/karp.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/karp.jpeg -------------------------------------------------------------------------------- /np_completeness/img/levin.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/levin.jpeg -------------------------------------------------------------------------------- /np_completeness/img/logo-solarized.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /np_completeness/img/richards.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/richards.jpg -------------------------------------------------------------------------------- /np_completeness/img/sha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/sha.png -------------------------------------------------------------------------------- /np_completeness/img/sus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/sus.png -------------------------------------------------------------------------------- /np_completeness/img/sus2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/sus2.png -------------------------------------------------------------------------------- /np_completeness/img/thumbnail2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/np_completeness/img/thumbnail2.png -------------------------------------------------------------------------------- /np_completeness/utils/bundle_rendered_videos.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import shutil 3 | from pathlib import Path 4 | 5 | QUALITY = "2160p60" 6 | 7 | 8 | def main(output_dir: Path): 9 | videos = list(Path(__file__).parents[1].glob(f"media/videos/*/{QUALITY}/*.mp4")) 10 | 11 | if output_dir.exists(): 12 | if input(f"{output_dir} already exists. Overwrite? (y/n) ").lower() != "y": 13 | shutil.rmtree(output_dir) 14 | exit(1) 15 | 16 | output_dir.mkdir(parents=True, exist_ok=True) 17 | 18 | for video in videos: 19 | shutil.copy(video, output_dir / video.name) 20 | 21 | print(f"Copied {len(videos)} videos to {output_dir}") 22 | 23 | 24 | if __name__ == "__main__": 25 | parser = argparse.ArgumentParser() 26 | parser.add_argument("output_dir", type=Path) 27 | args = parser.parse_args() 28 | 29 | main(args.output_dir) 30 | -------------------------------------------------------------------------------- /np_completeness/utils/circuit.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from queue import PriorityQueue 4 | 5 | import matplotlib.pyplot as plt 6 | import networkx as nx 7 | import numpy as np 8 | from manim.typing import InternalPoint3D 9 | 10 | from np_completeness.utils.gate import Gate, GateEvaluation 11 | from np_completeness.utils.util_general import ( 12 | AnyPoint, 13 | get_wire_color, 14 | normalize_position, 15 | ) 16 | 17 | 18 | class CircuitEvaluation: 19 | def __init__( 20 | self, 21 | circuit: "Circuit", 22 | ): 23 | self.circuit = circuit 24 | self.gate_evaluations: dict[str, GateEvaluation] = {} 25 | 26 | def get_wire_value(self, wire_start: str, wire_end: str) -> bool: 27 | """Get the value of the wire from wire_start to wire_end. 28 | 29 | Args: 30 | wire_start: The name of the gate where the wire starts. 31 | wire_end: The name of the gate where the wire ends. 32 | input_values: The values of the inputs to `wire_start`. 33 | """ 34 | truth_table = self.circuit.gates[wire_start].truth_table 35 | outputs = truth_table[self.gate_evaluations[wire_start].input_values] 36 | output_index = self.circuit.get_successors(wire_start).index(wire_end) 37 | 38 | return outputs[output_index] 39 | 40 | def get_gate_outputs(self, name: str) -> tuple[bool, ...]: 41 | input_values = self.gate_evaluations[name].input_values 42 | return self.circuit.gates[name].truth_table[input_values] 43 | 44 | def get_gate_inputs(self, name: str) -> tuple[bool, ...]: 45 | # Convenience method for symmetry with get_gate_outputs() 46 | return self.gate_evaluations[name].input_values 47 | 48 | def get_simplified_value(self, name: str, reversed: bool = False) -> bool | None: 49 | """Returns a single value representing the gate's output. 50 | 51 | Multi-output gates are simplified to a single value, using None 52 | if there's ambiguity. 53 | """ 54 | gate_inputs = self.get_gate_inputs(name) 55 | gate_outputs = self.get_gate_outputs(name) 56 | 57 | if reversed: 58 | gate_inputs, gate_outputs = gate_outputs, gate_inputs 59 | 60 | def simplify(values: tuple[bool, ...]) -> bool | None: 61 | if not values: 62 | return None 63 | else: 64 | # If any of the values is true, return true. This is not entirely 65 | # accurate if the output is e.g. (True, False) but it's clearer visually 66 | # than using None because then the gate would look unvisited. 67 | return any(values) 68 | 69 | match self.get_gate_outputs(name): 70 | case (): 71 | # No outputs 72 | return simplify(gate_inputs) 73 | case (single_output,): 74 | return single_output 75 | case _: 76 | # Multi-output gate 77 | return simplify(gate_outputs) 78 | 79 | 80 | class Circuit: 81 | def __init__(self): 82 | self.g = nx.DiGraph() 83 | self.gates: dict[str, Gate] = {} 84 | self.wires: list[tuple[str, str]] = [] 85 | 86 | def add_gate(self, name: str, gate: Gate) -> str: 87 | """Add a gate and, for convenience, return its name.""" 88 | if name in self.gates: 89 | raise ValueError(f"Gate with name {repr(name)} already exists") 90 | 91 | self.gates[name] = gate 92 | 93 | return name 94 | 95 | def add_wire( 96 | self, 97 | wire_start: str, 98 | wire_end: str, 99 | knot_positions: list[AnyPoint] | None = None, 100 | ): 101 | """Add a wire from `wire_start` to `wire_end`. 102 | 103 | By default, the wire goes directly between the two gates. By adding 104 | `knot_positions`, the wire will go through the given points. This is 105 | for readability purposes. 106 | """ 107 | if wire_start not in self.gates: 108 | raise ValueError(f"Invalid wire start: {wire_start}") 109 | if wire_end not in self.gates: 110 | raise ValueError(f"Invalid wire end: {wire_end}") 111 | 112 | gates = [wire_start] 113 | 114 | for knot_position in knot_positions or []: 115 | if np.array_equal( 116 | normalize_position(knot_position), self.gates[gates[-1]].position 117 | ): 118 | # The knot is in the same place as the last one, so it's not needed 119 | continue 120 | 121 | knot_name = self.add_knot(knot_position) 122 | gates.append(knot_name) 123 | 124 | gates.append(wire_end) 125 | 126 | for start, end in zip(gates, gates[1:]): 127 | self.wires.append((start, end)) 128 | 129 | def add_knot( 130 | self, 131 | position: AnyPoint, 132 | name: str | None = None, 133 | n_inputs: int = 1, 134 | n_outputs: int = 1, 135 | ) -> str: 136 | if name is None: 137 | name = f"knot_0" 138 | for i in range(len(self.gates) + 1): 139 | if name not in self.gates: 140 | break 141 | name = f"knot_{i}" 142 | 143 | assert name is not None, "Internal error" 144 | 145 | self.add_gate( 146 | name, Gate.make_knot(position, n_inputs=n_inputs, n_outputs=n_outputs) 147 | ) 148 | return name 149 | 150 | def position_of(self, name: str) -> InternalPoint3D: 151 | """Get the position of a gate as a [x, y, 0] NumPy array.""" 152 | return self.gates[name].position 153 | 154 | def x_of(self, name: str) -> float: 155 | """Get the X position of a gate.""" 156 | return self.gates[name].position[0] 157 | 158 | def y_of(self, name: str) -> float: 159 | """Get the Y position of a gate.""" 160 | return self.gates[name].position[1] 161 | 162 | def check(self): 163 | for wire_start, wire_end in self.wires: 164 | for gate_id in [wire_start, wire_end]: 165 | if gate_id not in self.gates: 166 | raise ValueError( 167 | f"Invalid gate id: {gate_id}. Available: {self.gates.keys()}" 168 | ) 169 | 170 | def get_predecessors(self, gate_name: str) -> list[str]: 171 | """Return the gates that are inputs to the given gate, in order. 172 | 173 | The order matters for gates that are not commutative. 174 | """ 175 | relevant_wires = [wire for wire in self.wires if wire[1] == gate_name] 176 | 177 | if len(relevant_wires) != self.gates[gate_name].n_inputs: 178 | raise ValueError( 179 | f"Gate {gate_name} has {self.gates[gate_name].n_inputs} inputs, but got " 180 | f"{len(relevant_wires)} wires" 181 | ) 182 | 183 | return [wire[0] for wire in relevant_wires] 184 | 185 | def get_successors(self, gate_name: str) -> list[str]: 186 | """Return the gates that are outputs to the given gate, in order. 187 | 188 | The order matters for gates with multiple outputs that are not commutative. 189 | """ 190 | relevant_wires = [wire for wire in self.wires if wire[0] == gate_name] 191 | 192 | if len(relevant_wires) != self.gates[gate_name].n_outputs: 193 | raise ValueError( 194 | f"Gate {gate_name} has {self.gates[gate_name].n_outputs} outputs, but got " 195 | f"{len(relevant_wires)} wires" 196 | ) 197 | 198 | return [wire[1] for wire in relevant_wires] 199 | 200 | def to_networkx(self) -> tuple[nx.DiGraph, dict[str, Point2D]]: # type: ignore[reportMissingTypeArgument] 201 | g = nx.DiGraph() 202 | 203 | positions = {} 204 | 205 | for gate in self.gates: 206 | g.add_node(gate) 207 | 208 | positions[gate] = self.gates[gate].position[:2] 209 | 210 | for wire_start, wire_end in self.wires: 211 | g.add_edge( 212 | wire_start, 213 | wire_end, 214 | length=self.get_wire_length(wire_start, wire_end), 215 | ) 216 | 217 | return g, positions 218 | 219 | def evaluate(self) -> CircuitEvaluation: 220 | """Compute the gates' values and reach times.""" 221 | # (reach time, node name) 222 | event_queue: PriorityQueue[tuple[float, str]] = PriorityQueue() 223 | n_inputs_done: dict[str, int] = {name: 0 for name in self.gates} 224 | evaluation = CircuitEvaluation(circuit=self) 225 | 226 | for name, gate in self.gates.items(): 227 | # Start from the nodes that have no inputs 228 | if gate.n_inputs == 0: 229 | event_queue.put((0, name)) 230 | n_inputs_done[name] = -1 231 | 232 | while not event_queue.empty(): 233 | time, name = event_queue.get() 234 | n_inputs_done[name] += 1 235 | 236 | # If we've already visited from all of its inputs 237 | if n_inputs_done[name] == self.gates[name].n_inputs: 238 | for output_name in self.get_successors(name): 239 | event_queue.put( 240 | ( 241 | time 242 | + self.get_wire_length(name, output_name) 243 | # This is when the wire starts filling, 244 | # so we add the gate's length 245 | + self.gates[name].length, 246 | output_name, 247 | ) 248 | ) 249 | 250 | gate_inputs = self.get_predecessors(name) 251 | input_values = [] 252 | for input_name in gate_inputs: 253 | value = evaluation.get_wire_value( 254 | wire_start=input_name, 255 | wire_end=name, 256 | ) 257 | input_values.append(value) 258 | 259 | evaluation.gate_evaluations[name] = GateEvaluation( 260 | input_values=tuple(input_values), reach_time=time 261 | ) 262 | 263 | return evaluation 264 | 265 | def display_graph(self, with_evaluation: bool = True): 266 | g, positions = self.to_networkx() 267 | 268 | if with_evaluation: 269 | evaluation = self.evaluate() 270 | else: 271 | evaluation = None 272 | 273 | node_color = [] 274 | for gate in g.nodes: 275 | simplified_value = ( 276 | evaluation.get_simplified_value(gate) 277 | if evaluation is not None 278 | else None 279 | ) 280 | node_color.append(get_wire_color(simplified_value)) 281 | 282 | edge_colors = [] 283 | for in_gate, out_gate in g.edges: 284 | if evaluation is not None: 285 | wire_value = evaluation.get_wire_value( 286 | wire_start=in_gate, wire_end=out_gate 287 | ) 288 | else: 289 | wire_value = None 290 | 291 | edge_colors.append(get_wire_color(wire_value)) 292 | 293 | plt.figure(figsize=(12, 8)) 294 | 295 | nx.draw( 296 | g, 297 | positions, 298 | with_labels=True, 299 | node_color=node_color, 300 | edge_color=edge_colors, 301 | width=2, 302 | ) 303 | 304 | plt.show() 305 | 306 | def get_wire_length(self, wire_start: str, wire_end: str) -> float: 307 | distance = np.linalg.norm( 308 | self.gates[wire_start].position - self.gates[wire_end].position 309 | ) 310 | # Round for legibility when debugging. 311 | return round(float(distance), 2) 312 | 313 | def reverse(self) -> Circuit: 314 | evaluation = self.evaluate() 315 | 316 | reversed = Circuit() 317 | reversed.gates = { 318 | name: gate.reverse(evaluation.gate_evaluations[name]) 319 | for name, gate in self.gates.items() 320 | } 321 | 322 | reversed.wires = [(wire_end, wire_start) for wire_start, wire_end in self.wires] 323 | 324 | reversed.check() 325 | return reversed 326 | 327 | def add_missing_inputs_and_outputs(self, visible: bool = True): 328 | """Add input/output nodes to any gates that don't lead anywhere. 329 | 330 | Useful for debugging. 331 | """ 332 | # Need to make a copy because we're modifying the dictionary 333 | gates_to_check = list(self.gates.items()) 334 | 335 | for name, gate in gates_to_check: 336 | n_inputs = len([wire for wire in self.wires if wire[1] == name]) 337 | for i in range(gate.n_inputs - n_inputs): 338 | self.add_gate( 339 | f"input_{name}_{i}", 340 | Gate( 341 | truth_table={(): (False,)}, 342 | position=(gate.position + np.array([i * 0.5, 0.5, 0])) 343 | if visible 344 | else gate.position, 345 | visual_type="constant" if visible else "invisible", 346 | ), 347 | ) 348 | self.add_wire(wire_start=f"input_{name}_{i}", wire_end=name) 349 | 350 | n_outputs = len([wire for wire in self.wires if wire[0] == name]) 351 | for i in range(gate.n_outputs - n_outputs): 352 | self.add_gate( 353 | f"output_{name}_{i}", 354 | Gate( 355 | truth_table={(False,): (), (True,): ()}, 356 | position=(gate.position + np.array([i * 0.5, -0.5, 0])) 357 | if visible 358 | else gate.position, 359 | visual_type="constant" if visible else "invisible", 360 | ), 361 | ) 362 | self.add_wire(wire_start=name, wire_end=f"output_{name}_{i}") 363 | 364 | def scale(self, factor: float) -> Circuit: 365 | """Scale the positions of the gates in-place. 366 | 367 | Similar to running .scale() on ManimCircuit(circuit), but the size of the gates 368 | doesn't change - just the positions. 369 | """ 370 | for gate in self.gates.values(): 371 | gate.position *= factor 372 | 373 | return self 374 | 375 | def shift(self, shift: InternalPoint3D) -> Circuit: 376 | """Shift the positions of the gates in-place. 377 | 378 | Should be equivalent to running .shift() on ManimCircuit(circuit). 379 | """ 380 | for gate in self.gates.values(): 381 | gate.position += shift 382 | 383 | return self 384 | -------------------------------------------------------------------------------- /np_completeness/utils/cnf_constraints.py: -------------------------------------------------------------------------------- 1 | CNF_NUM_VARS = 82 2 | CNF_CONSTRAINTS = [ 3 | [-1, -5, 9], 4 | [1, -9], 5 | [5, -9], 6 | [-1, -6, 10], 7 | [1, -10], 8 | [6, -10], 9 | [-1, -7, 11], 10 | [1, -11], 11 | [7, -11], 12 | [-1, -8, 12], 13 | [1, -12], 14 | [8, -12], 15 | [-2, -5, 13], 16 | [2, -13], 17 | [5, -13], 18 | [-2, -6, 14], 19 | [2, -14], 20 | [6, -14], 21 | [-2, -7, 15], 22 | [2, -15], 23 | [7, -15], 24 | [-2, -8, 16], 25 | [2, -16], 26 | [8, -16], 27 | [-3, -5, 17], 28 | [3, -17], 29 | [5, -17], 30 | [-3, -6, 18], 31 | [3, -18], 32 | [6, -18], 33 | [-3, -7, 19], 34 | [3, -19], 35 | [7, -19], 36 | [-3, -8, 20], 37 | [3, -20], 38 | [8, -20], 39 | [-4, -5, 21], 40 | [4, -21], 41 | [5, -21], 42 | [-4, -6, 22], 43 | [4, -22], 44 | [6, -22], 45 | [-4, -7, 23], 46 | [4, -23], 47 | [7, -23], 48 | [-4, -8, 24], 49 | [4, -24], 50 | [8, -24], 51 | [9, 82, 82, -25], 52 | [9, 82, -82, 25], 53 | [9, -82, 82, 25], 54 | [-9, 82, 82, 25], 55 | [9, -82, -82, -25], 56 | [-9, 82, -82, -25], 57 | [-9, -82, 82, -25], 58 | [-9, -82, -82, 25], 59 | [-9, -82, 49], 60 | [-9, -82, 49], 61 | [-82, -82, 49], 62 | [9, 82, -49], 63 | [9, 82, -49], 64 | [82, 82, -49], 65 | [10, 13, 49, -26], 66 | [10, 13, -49, 26], 67 | [10, -13, 49, 26], 68 | [-10, 13, 49, 26], 69 | [10, -13, -49, -26], 70 | [-10, 13, -49, -26], 71 | [-10, -13, 49, -26], 72 | [-10, -13, -49, 26], 73 | [-10, -13, 50], 74 | [-10, -49, 50], 75 | [-13, -49, 50], 76 | [10, 13, -50], 77 | [10, 49, -50], 78 | [13, 49, -50], 79 | [11, 14, 50, -27], 80 | [11, 14, -50, 27], 81 | [11, -14, 50, 27], 82 | [-11, 14, 50, 27], 83 | [11, -14, -50, -27], 84 | [-11, 14, -50, -27], 85 | [-11, -14, 50, -27], 86 | [-11, -14, -50, 27], 87 | [-11, -14, 51], 88 | [-11, -50, 51], 89 | [-14, -50, 51], 90 | [11, 14, -51], 91 | [11, 50, -51], 92 | [14, 50, -51], 93 | [12, 15, 51, -28], 94 | [12, 15, -51, 28], 95 | [12, -15, 51, 28], 96 | [-12, 15, 51, 28], 97 | [12, -15, -51, -28], 98 | [-12, 15, -51, -28], 99 | [-12, -15, 51, -28], 100 | [-12, -15, -51, 28], 101 | [-12, -15, 52], 102 | [-12, -51, 52], 103 | [-15, -51, 52], 104 | [12, 15, -52], 105 | [12, 51, -52], 106 | [15, 51, -52], 107 | [82, 16, 52, -29], 108 | [82, 16, -52, 29], 109 | [82, -16, 52, 29], 110 | [-82, 16, 52, 29], 111 | [82, -16, -52, -29], 112 | [-82, 16, -52, -29], 113 | [-82, -16, 52, -29], 114 | [-82, -16, -52, 29], 115 | [-82, -16, 53], 116 | [-82, -52, 53], 117 | [-16, -52, 53], 118 | [82, 16, -53], 119 | [82, 52, -53], 120 | [16, 52, -53], 121 | [82, 82, 53, -30], 122 | [82, 82, -53, 30], 123 | [82, -82, 53, 30], 124 | [-82, 82, 53, 30], 125 | [82, -82, -53, -30], 126 | [-82, 82, -53, -30], 127 | [-82, -82, 53, -30], 128 | [-82, -82, -53, 30], 129 | [-82, -82, 54], 130 | [-82, -53, 54], 131 | [-82, -53, 54], 132 | [82, 82, -54], 133 | [82, 53, -54], 134 | [82, 53, -54], 135 | [82, 82, 54, -31], 136 | [82, 82, -54, 31], 137 | [82, -82, 54, 31], 138 | [-82, 82, 54, 31], 139 | [82, -82, -54, -31], 140 | [-82, 82, -54, -31], 141 | [-82, -82, 54, -31], 142 | [-82, -82, -54, 31], 143 | [-82, -82, 55], 144 | [-82, -54, 55], 145 | [-82, -54, 55], 146 | [82, 82, -55], 147 | [82, 54, -55], 148 | [82, 54, -55], 149 | [82, 82, 55, -32], 150 | [82, 82, -55, 32], 151 | [82, -82, 55, 32], 152 | [-82, 82, 55, 32], 153 | [82, -82, -55, -32], 154 | [-82, 82, -55, -32], 155 | [-82, -82, 55, -32], 156 | [-82, -82, -55, 32], 157 | [-82, -82, 56], 158 | [-82, -55, 56], 159 | [-82, -55, 56], 160 | [82, 82, -56], 161 | [82, 55, -56], 162 | [82, 55, -56], 163 | [25, 82, 82, -33], 164 | [25, 82, -82, 33], 165 | [25, -82, 82, 33], 166 | [-25, 82, 82, 33], 167 | [25, -82, -82, -33], 168 | [-25, 82, -82, -33], 169 | [-25, -82, 82, -33], 170 | [-25, -82, -82, 33], 171 | [-25, -82, 57], 172 | [-25, -82, 57], 173 | [-82, -82, 57], 174 | [25, 82, -57], 175 | [25, 82, -57], 176 | [82, 82, -57], 177 | [26, 82, 57, -34], 178 | [26, 82, -57, 34], 179 | [26, -82, 57, 34], 180 | [-26, 82, 57, 34], 181 | [26, -82, -57, -34], 182 | [-26, 82, -57, -34], 183 | [-26, -82, 57, -34], 184 | [-26, -82, -57, 34], 185 | [-26, -82, 58], 186 | [-26, -57, 58], 187 | [-82, -57, 58], 188 | [26, 82, -58], 189 | [26, 57, -58], 190 | [82, 57, -58], 191 | [27, 17, 58, -35], 192 | [27, 17, -58, 35], 193 | [27, -17, 58, 35], 194 | [-27, 17, 58, 35], 195 | [27, -17, -58, -35], 196 | [-27, 17, -58, -35], 197 | [-27, -17, 58, -35], 198 | [-27, -17, -58, 35], 199 | [-27, -17, 59], 200 | [-27, -58, 59], 201 | [-17, -58, 59], 202 | [27, 17, -59], 203 | [27, 58, -59], 204 | [17, 58, -59], 205 | [28, 18, 59, -36], 206 | [28, 18, -59, 36], 207 | [28, -18, 59, 36], 208 | [-28, 18, 59, 36], 209 | [28, -18, -59, -36], 210 | [-28, 18, -59, -36], 211 | [-28, -18, 59, -36], 212 | [-28, -18, -59, 36], 213 | [-28, -18, 60], 214 | [-28, -59, 60], 215 | [-18, -59, 60], 216 | [28, 18, -60], 217 | [28, 59, -60], 218 | [18, 59, -60], 219 | [29, 19, 60, -37], 220 | [29, 19, -60, 37], 221 | [29, -19, 60, 37], 222 | [-29, 19, 60, 37], 223 | [29, -19, -60, -37], 224 | [-29, 19, -60, -37], 225 | [-29, -19, 60, -37], 226 | [-29, -19, -60, 37], 227 | [-29, -19, 61], 228 | [-29, -60, 61], 229 | [-19, -60, 61], 230 | [29, 19, -61], 231 | [29, 60, -61], 232 | [19, 60, -61], 233 | [30, 20, 61, -38], 234 | [30, 20, -61, 38], 235 | [30, -20, 61, 38], 236 | [-30, 20, 61, 38], 237 | [30, -20, -61, -38], 238 | [-30, 20, -61, -38], 239 | [-30, -20, 61, -38], 240 | [-30, -20, -61, 38], 241 | [-30, -20, 62], 242 | [-30, -61, 62], 243 | [-20, -61, 62], 244 | [30, 20, -62], 245 | [30, 61, -62], 246 | [20, 61, -62], 247 | [31, 82, 62, -39], 248 | [31, 82, -62, 39], 249 | [31, -82, 62, 39], 250 | [-31, 82, 62, 39], 251 | [31, -82, -62, -39], 252 | [-31, 82, -62, -39], 253 | [-31, -82, 62, -39], 254 | [-31, -82, -62, 39], 255 | [-31, -82, 63], 256 | [-31, -62, 63], 257 | [-82, -62, 63], 258 | [31, 82, -63], 259 | [31, 62, -63], 260 | [82, 62, -63], 261 | [32, 82, 63, -40], 262 | [32, 82, -63, 40], 263 | [32, -82, 63, 40], 264 | [-32, 82, 63, 40], 265 | [32, -82, -63, -40], 266 | [-32, 82, -63, -40], 267 | [-32, -82, 63, -40], 268 | [-32, -82, -63, 40], 269 | [-32, -82, 64], 270 | [-32, -63, 64], 271 | [-82, -63, 64], 272 | [32, 82, -64], 273 | [32, 63, -64], 274 | [82, 63, -64], 275 | [33, 82, 82, -41], 276 | [33, 82, -82, 41], 277 | [33, -82, 82, 41], 278 | [-33, 82, 82, 41], 279 | [33, -82, -82, -41], 280 | [-33, 82, -82, -41], 281 | [-33, -82, 82, -41], 282 | [-33, -82, -82, 41], 283 | [-33, -82, 65], 284 | [-33, -82, 65], 285 | [-82, -82, 65], 286 | [33, 82, -65], 287 | [33, 82, -65], 288 | [82, 82, -65], 289 | [34, 82, 65, -42], 290 | [34, 82, -65, 42], 291 | [34, -82, 65, 42], 292 | [-34, 82, 65, 42], 293 | [34, -82, -65, -42], 294 | [-34, 82, -65, -42], 295 | [-34, -82, 65, -42], 296 | [-34, -82, -65, 42], 297 | [-34, -82, 66], 298 | [-34, -65, 66], 299 | [-82, -65, 66], 300 | [34, 82, -66], 301 | [34, 65, -66], 302 | [82, 65, -66], 303 | [35, 82, 66, -43], 304 | [35, 82, -66, 43], 305 | [35, -82, 66, 43], 306 | [-35, 82, 66, 43], 307 | [35, -82, -66, -43], 308 | [-35, 82, -66, -43], 309 | [-35, -82, 66, -43], 310 | [-35, -82, -66, 43], 311 | [-35, -82, 67], 312 | [-35, -66, 67], 313 | [-82, -66, 67], 314 | [35, 82, -67], 315 | [35, 66, -67], 316 | [82, 66, -67], 317 | [36, 21, 67, -44], 318 | [36, 21, -67, 44], 319 | [36, -21, 67, 44], 320 | [-36, 21, 67, 44], 321 | [36, -21, -67, -44], 322 | [-36, 21, -67, -44], 323 | [-36, -21, 67, -44], 324 | [-36, -21, -67, 44], 325 | [-36, -21, 68], 326 | [-36, -67, 68], 327 | [-21, -67, 68], 328 | [36, 21, -68], 329 | [36, 67, -68], 330 | [21, 67, -68], 331 | [37, 22, 68, -45], 332 | [37, 22, -68, 45], 333 | [37, -22, 68, 45], 334 | [-37, 22, 68, 45], 335 | [37, -22, -68, -45], 336 | [-37, 22, -68, -45], 337 | [-37, -22, 68, -45], 338 | [-37, -22, -68, 45], 339 | [-37, -22, 69], 340 | [-37, -68, 69], 341 | [-22, -68, 69], 342 | [37, 22, -69], 343 | [37, 68, -69], 344 | [22, 68, -69], 345 | [38, 23, 69, -46], 346 | [38, 23, -69, 46], 347 | [38, -23, 69, 46], 348 | [-38, 23, 69, 46], 349 | [38, -23, -69, -46], 350 | [-38, 23, -69, -46], 351 | [-38, -23, 69, -46], 352 | [-38, -23, -69, 46], 353 | [-38, -23, 70], 354 | [-38, -69, 70], 355 | [-23, -69, 70], 356 | [38, 23, -70], 357 | [38, 69, -70], 358 | [23, 69, -70], 359 | [39, 24, 70, -47], 360 | [39, 24, -70, 47], 361 | [39, -24, 70, 47], 362 | [-39, 24, 70, 47], 363 | [39, -24, -70, -47], 364 | [-39, 24, -70, -47], 365 | [-39, -24, 70, -47], 366 | [-39, -24, -70, 47], 367 | [-39, -24, 71], 368 | [-39, -70, 71], 369 | [-24, -70, 71], 370 | [39, 24, -71], 371 | [39, 70, -71], 372 | [24, 70, -71], 373 | [40, 82, 71, -48], 374 | [40, 82, -71, 48], 375 | [40, -82, 71, 48], 376 | [-40, 82, 71, 48], 377 | [40, -82, -71, -48], 378 | [-40, 82, -71, -48], 379 | [-40, -82, 71, -48], 380 | [-40, -82, -71, 48], 381 | [-40, -82, 72], 382 | [-40, -71, 72], 383 | [-82, -71, 72], 384 | [40, 82, -72], 385 | [40, 71, -72], 386 | [82, 71, -72], 387 | [-41, 73], 388 | [41, -73], 389 | [-42, 74], 390 | [42, -74], 391 | [-43, 75], 392 | [43, -75], 393 | [-44, 76], 394 | [44, -76], 395 | [-45, 77], 396 | [45, -77], 397 | [-46, 78], 398 | [46, -78], 399 | [-47, 79], 400 | [47, -79], 401 | [-48, 80], 402 | [48, -80], 403 | [2, 3, 4, -1], 404 | [6, 7, 8, -5], 405 | [81], 406 | [-82], 407 | ] 408 | -------------------------------------------------------------------------------- /np_completeness/utils/coloring_circuits.py: -------------------------------------------------------------------------------- 1 | from typing import Hashable, TypeAlias, cast 2 | 3 | from manim import * 4 | from manim.typing import InternalPoint3D 5 | 6 | from np_completeness.utils.circuit import Circuit 7 | from np_completeness.utils.gate import NAND_TABLE, OR3_TABLE, Gate, LazyTruthTable 8 | from np_completeness.utils.util_general import BASE00, CYAN, MAGENTA 9 | 10 | Coloring: TypeAlias = dict[int, int] 11 | 12 | 13 | def make_coloring_circuit( 14 | graph: Graph, coloring: Coloring, output_position: InternalPoint3D | None = None 15 | ) -> Circuit: 16 | if output_position is None: 17 | output_position = np.array([0, 0, 0]) 18 | 19 | circuit = Circuit() 20 | n_colors = 3 21 | 22 | def get_degree(vertex: Hashable): 23 | edges = graph.edges.keys() 24 | return sum(1 for edge in edges if vertex in edge) 25 | 26 | for vertex_name in graph.vertices: 27 | position = graph.vertices[vertex_name].get_center() 28 | circuit.add_gate( 29 | f"vertex_{vertex_name}_or3", Gate(OR3_TABLE, position, visual_type="or") 30 | ) 31 | try: 32 | color = coloring[cast(int, vertex_name)] 33 | except KeyError as e: 34 | raise KeyError(f"No color found in the coloring for {vertex_name}") from e 35 | 36 | assert color in range(n_colors) 37 | 38 | for i in range(n_colors): 39 | offset = rotate_vector(RIGHT * 0.7, angle=2 * PI / n_colors * i + 0.3) 40 | n_outputs = 3 + get_degree(vertex_name) 41 | gate = Gate( 42 | truth_table={(): tuple([color == i for _ in range(n_outputs)])}, 43 | position=position + offset, 44 | visual_type="constant", 45 | ) 46 | circuit.add_gate(f"vertex_{vertex_name}_value_{i}", gate) 47 | circuit.add_wire( 48 | f"vertex_{vertex_name}_value_{i}", f"vertex_{vertex_name}_or3" 49 | ) 50 | 51 | assert n_colors == 3, "This part will need rewriting if we change n_colors" 52 | for i in range(n_colors): 53 | name_1 = f"vertex_{vertex_name}_value_{i}" 54 | name_2 = f"vertex_{vertex_name}_value_{(i+1)%n_colors}" 55 | 56 | and_gate = circuit.add_gate( 57 | f"vertex_{vertex_name}_nand_{i}", 58 | Gate( 59 | truth_table=NAND_TABLE, 60 | position=(circuit.position_of(name_1) + circuit.position_of(name_2)) 61 | / 2, 62 | visual_type="nand", 63 | ), 64 | ) 65 | circuit.add_wire(name_1, and_gate) 66 | circuit.add_wire(name_2, and_gate) 67 | 68 | for vertex_name_1, vertex_name_2 in graph.edges: 69 | for i in range(n_colors): 70 | name_1 = f"vertex_{vertex_name_1}_value_{i}" 71 | name_2 = f"vertex_{vertex_name_2}_value_{i}" 72 | 73 | nand_gate = circuit.add_gate( 74 | f"edge_{vertex_name_1}_{vertex_name_2}_nand_{i}", 75 | Gate( 76 | truth_table=NAND_TABLE, 77 | position=(circuit.position_of(name_1) + circuit.position_of(name_2)) 78 | / 2, 79 | visual_type="nand", 80 | ), 81 | ) 82 | circuit.add_wire(name_1, nand_gate) 83 | circuit.add_wire(name_2, nand_gate) 84 | 85 | gates_to_connect = [] 86 | for gate in circuit.gates: 87 | n_outputs = len([wire for wire in circuit.wires if wire[0] == gate]) 88 | if n_outputs == 0: 89 | gates_to_connect.append(gate) 90 | 91 | big_and_gate = circuit.add_gate( 92 | "big_and", 93 | Gate( 94 | LazyTruthTable( 95 | n_inputs=len(gates_to_connect), 96 | n_outputs=1, 97 | fn=lambda inputs: (all(inputs),), 98 | ), 99 | output_position, 100 | visual_type="and", 101 | ), 102 | ) 103 | for gate in gates_to_connect: 104 | circuit.add_wire(gate, big_and_gate) 105 | 106 | output_gate = circuit.add_gate( 107 | "output", 108 | Gate.make_knot( 109 | circuit.gates[big_and_gate].position + DOWN * 0.5, n_inputs=1, n_outputs=0 110 | ), 111 | ) 112 | circuit.add_wire(big_and_gate, output_gate) 113 | 114 | return circuit 115 | 116 | 117 | GRAPH_COLORS = [MAGENTA, CYAN, ORANGE] 118 | 119 | 120 | def get_example_graph(*, good_coloring: bool, colored=True) -> tuple[Graph, Coloring]: 121 | """Create an example graph. 122 | 123 | It's a function and not a constant so that we don't modify it accidentally. 124 | """ 125 | graph = Graph( 126 | vertices=[1, 2, 3, 4, 5, 6], 127 | edges=[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1), (2, 5), (2, 6)], 128 | layout="kamada_kawai", 129 | layout_scale=4, 130 | vertex_config={"fill_color": BASE00, "radius": 0.2}, 131 | edge_config={"color": BASE00, "stroke_width": 10}, 132 | ) 133 | 134 | coloring = {1: 0, 2: 1, 3: 2, 4: 0, 5: 0, 6: 2} 135 | 136 | if good_coloring: 137 | coloring[4] = 1 138 | 139 | assert set(coloring.keys()) == set(graph.vertices.keys()) 140 | 141 | if colored: 142 | for vertex in graph.vertices: 143 | graph.vertices[vertex].fill_color = GRAPH_COLORS[coloring[vertex]] # type: ignore 144 | 145 | return graph, coloring 146 | -------------------------------------------------------------------------------- /np_completeness/utils/gate.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import itertools 4 | from typing import Callable, Literal, Mapping, TypeAlias 5 | 6 | from manim.typing import InternalPoint3D 7 | from pydantic import BaseModel 8 | 9 | from np_completeness.utils.util_general import ( 10 | DEFAULT_GATE_LENGTH, 11 | AnyPoint, 12 | normalize_position, 13 | ) 14 | 15 | GateVisualType = Literal[ 16 | "default", "constant", "knot", "invisible", "not", "and", "or", "nand", "+" 17 | ] 18 | TruthTable: TypeAlias = Mapping[tuple[bool, ...], tuple[bool, ...]] 19 | 20 | 21 | class Gate: 22 | def __init__( 23 | self, 24 | truth_table: TruthTable, 25 | position: AnyPoint, 26 | length: float = DEFAULT_GATE_LENGTH, 27 | visual_type: GateVisualType = "default", 28 | ): 29 | """A logical gate, possibly with multiple outputs. 30 | 31 | Args: 32 | truth_table: A dictionary mapping input values to output values. If we try 33 | to evaluate for an input combination that's not present in the keys, 34 | will raise an error. 35 | position: The position of the gate when visualizing. 36 | length: How long signal takes to propagate through this gate. 37 | visual_type: The type of gate to visualize. If "default", will try to infer 38 | the type from the truth table. 39 | """ 40 | self.truth_table = truth_table 41 | check_truth_table(self.truth_table) 42 | 43 | self.position: InternalPoint3D = normalize_position(position) 44 | self.length = length 45 | 46 | if visual_type == "default": 47 | visual_type = infer_gate_visual_type(truth_table) 48 | 49 | self.visual_type: GateVisualType = visual_type 50 | 51 | @property 52 | def n_inputs(self): 53 | return truth_table_n_inputs(self.truth_table) 54 | 55 | @property 56 | def n_outputs(self): 57 | return truth_table_n_outputs(self.truth_table) 58 | 59 | def reverse(self, gate_evaluation: GateEvaluation) -> Gate: 60 | input_values = gate_evaluation.input_values 61 | output_values = self.truth_table[input_values] 62 | 63 | return Gate( 64 | # The only supported input value for the inverted gate 65 | # is the actual output value that we got. This is because 66 | # gates will usually be non-invertible - for example, if 67 | # we have an AND gate that had False as the output, we don't 68 | # know what the inputs were (which is why running circuits 69 | # backwards is hard). 70 | truth_table={output_values: input_values}, 71 | position=self.position.copy(), 72 | length=self.length, 73 | visual_type=self.visual_type, 74 | ) 75 | 76 | @staticmethod 77 | def make_knot( 78 | position: AnyPoint, n_inputs: int = 1, n_outputs: int = 1, length: float = 0.05 79 | ): 80 | if n_inputs == 0: 81 | # For constants, always output False. 82 | truth_table = {(): tuple([False] * n_outputs)} 83 | else: 84 | # A mix of True and False values is not supported. 85 | truth_table = { 86 | tuple([False] * n_inputs): tuple([False] * n_outputs), 87 | tuple([True] * n_inputs): tuple([True] * n_outputs), 88 | } 89 | 90 | # No inputs or no outputs means it's a constant at the beginning or end 91 | # of the graph 92 | is_constant = n_inputs == 0 or n_outputs == 0 93 | 94 | return Gate( 95 | truth_table, 96 | position, 97 | length=length, 98 | visual_type="constant" if is_constant else "knot", 99 | ) 100 | 101 | 102 | def check_truth_table(truth_table: TruthTable): 103 | if isinstance(truth_table, LazyTruthTable): 104 | return 105 | 106 | entries = list(truth_table.items()) 107 | 108 | if not entries: 109 | raise ValueError( 110 | "Truth table is empty. For gates with no inputs or no outputs, " 111 | "add an entry with an empty tuple () as the key (no inputs) " 112 | "or value (no outputs)." 113 | ) 114 | 115 | some_inputs, some_outputs = entries[0] 116 | 117 | for inputs, outputs in entries: 118 | if len(inputs) != len(some_inputs): 119 | raise ValueError( 120 | f"Invalid truth table: {inputs} has {len(inputs)} inputs, " 121 | f"expected {len(some_inputs)}" 122 | ) 123 | 124 | if len(outputs) != len(some_outputs): 125 | raise ValueError( 126 | f"Invalid truth table: {outputs} has {len(outputs)} outputs, " 127 | f"expected {len(some_outputs)}" 128 | ) 129 | 130 | 131 | def truth_table_n_inputs(truth_table: TruthTable) -> int: 132 | check_truth_table(truth_table) 133 | 134 | if isinstance(truth_table, LazyTruthTable): 135 | return truth_table.n_inputs 136 | else: 137 | return len(list(truth_table.keys())[0]) 138 | 139 | 140 | def truth_table_n_outputs(truth_table: TruthTable) -> int: 141 | check_truth_table(truth_table) 142 | 143 | if isinstance(truth_table, LazyTruthTable): 144 | return truth_table.n_outputs 145 | else: 146 | return len(list(truth_table.values())[0]) 147 | 148 | 149 | class GateEvaluation(BaseModel): 150 | input_values: tuple[bool, ...] 151 | reach_time: float 152 | 153 | 154 | NOT_TABLE = { 155 | (False,): (True,), 156 | (True,): (False,), 157 | } 158 | 159 | AND_TABLE = { 160 | (False, False): (False,), 161 | (False, True): (False,), 162 | (True, False): (False,), 163 | (True, True): (True,), 164 | } 165 | 166 | AND_TABLE_3 = { 167 | (False, False, False): (False,), 168 | (False, True, False): (False,), 169 | (True, False, False): (False,), 170 | (True, True, False): (False,), 171 | (False, False, True): (False,), 172 | (False, True, True): (False,), 173 | (True, False, True): (False,), 174 | (True, True, True): (True,), 175 | } 176 | 177 | OR_TABLE = { 178 | (False, False): (False,), 179 | (False, True): (True,), 180 | (True, False): (True,), 181 | (True, True): (True,), 182 | } 183 | 184 | NAND_TABLE = { 185 | (False, False): (True,), 186 | (False, True): (True,), 187 | (True, False): (True,), 188 | (True, True): (False,), 189 | } 190 | 191 | 192 | def all_inputs(n_inputs: int) -> list[tuple[bool, ...]]: 193 | """Return all possible input combinations for `n_inputs`.""" 194 | return list(itertools.product([False, True], repeat=n_inputs)) 195 | 196 | 197 | OR3_TABLE = {inputs: (any(inputs),) for inputs in all_inputs(3)} 198 | 199 | # the output is (lower bit, upper bit = carry) 200 | ADD_TABLE = { 201 | (False, False, False): (False, False), 202 | (False, False, True): (True, False), 203 | (False, True, False): (True, False), 204 | (False, True, True): (False, True), 205 | (True, False, False): (True, False), 206 | (True, False, True): (False, True), 207 | (True, True, False): (False, True), 208 | (True, True, True): (True, True), 209 | } 210 | 211 | 212 | def infer_gate_visual_type(truth_table: TruthTable) -> GateVisualType: 213 | if truth_table == NOT_TABLE: 214 | return "not" 215 | if truth_table == AND_TABLE: 216 | return "and" 217 | if truth_table == OR_TABLE: 218 | return "or" 219 | if truth_table == NAND_TABLE: 220 | return "nand" 221 | if truth_table == ADD_TABLE: 222 | return "+" 223 | 224 | if ( 225 | truth_table_n_inputs(truth_table) == 0 226 | or truth_table_n_outputs(truth_table) == 0 227 | ): 228 | return "constant" 229 | 230 | return "default" # unknown 231 | 232 | 233 | class LazyTruthTable(Mapping[tuple[bool, ...], tuple[bool, ...]]): 234 | """A truth table that doesn't store all the values, but computes them on the fly. 235 | 236 | For cases with many inputs when the truth table is too large to store in memory. 237 | """ 238 | 239 | def __init__( 240 | self, 241 | n_inputs: int, 242 | n_outputs: int, 243 | fn: Callable[[tuple[bool, ...]], tuple[bool, ...]], 244 | ): 245 | self.n_inputs = n_inputs 246 | self.n_outputs = n_outputs 247 | self.fn = fn 248 | 249 | def __getitem__(self, key: tuple[bool, ...]) -> tuple[bool, ...]: 250 | return self.fn(key) 251 | 252 | def __iter__(self): 253 | raise NotImplementedError("LazyTruthTable is not iterable") 254 | 255 | def __len__(self): 256 | return 2**self.n_inputs 257 | -------------------------------------------------------------------------------- /np_completeness/utils/manim_circuit.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Any 3 | 4 | from manim import * 5 | from manim.typing import InternalPoint3D 6 | 7 | from np_completeness.utils.circuit import Circuit 8 | from np_completeness.utils.gate import Gate 9 | from np_completeness.utils.util_general import ( 10 | BASE01, 11 | BASE2, 12 | GATE_HEIGHT, 13 | GATE_TEXT_RATIO, 14 | GATE_WIDTH, 15 | WIRE_WIDTH, 16 | get_wire_color, 17 | ) 18 | 19 | 20 | class ManimGate(VMobject): 21 | def __init__( 22 | self, 23 | gate: Gate, 24 | value: bool | None = None, 25 | scale: float = 1, 26 | wire_scale: float = 1, 27 | ): 28 | super().__init__() 29 | self.gate = gate 30 | 31 | fill_color = get_wire_color(value) 32 | self._scale = scale # don't conflict with the `scale()` method 33 | 34 | if gate.visual_type == "invisible": 35 | pass 36 | elif gate.visual_type == "knot": 37 | self.circle = Dot( 38 | radius=0.026 * wire_scale, 39 | color=fill_color, 40 | ) 41 | self.circle.move_to(gate.position) 42 | 43 | self.add(self.circle) 44 | elif gate.visual_type == "constant": 45 | self.circle = Dot( 46 | radius=GATE_HEIGHT * 0.3 * scale, 47 | color=fill_color, 48 | ) 49 | self.circle.move_to(gate.position) 50 | 51 | self.add(self.circle) 52 | else: 53 | self.rect = Rectangle( 54 | height=GATE_HEIGHT * scale, 55 | width=GATE_WIDTH * scale, 56 | fill_opacity=1.0, 57 | color=BASE01, 58 | stroke_opacity=0, 59 | fill_color=fill_color, 60 | ) 61 | self.rect.move_to(gate.position) 62 | 63 | text = "?" if gate.visual_type == "default" else gate.visual_type.upper() 64 | 65 | self.text = ( 66 | Text(text, color=BASE2) 67 | .move_to(self.rect.get_center()) 68 | .scale_to_fit_height(GATE_HEIGHT * GATE_TEXT_RATIO * scale) 69 | ) 70 | 71 | self.add(self.rect, self.text) 72 | 73 | def set_value(self, value: bool | None): 74 | """Set the value of this gate. Helper for animate_to_value().""" 75 | for mobject in self.submobjects: 76 | if not isinstance(mobject, Text): 77 | mobject.set_fill(get_wire_color(value)) 78 | 79 | def animate_to_value(self, value: bool | None, **kwargs: Any) -> Animation: 80 | """Animate the setting of the value of this gate.""" 81 | # ignore typing because of weird type annotation for self.animate 82 | return self.animate(**kwargs).set_value(value) # type: ignore 83 | 84 | 85 | class ManimWire(VMobject): 86 | def __init__( 87 | self, 88 | start: InternalPoint3D, 89 | end: InternalPoint3D, 90 | value: bool, 91 | progress: float = 0, 92 | scale: float = 1, 93 | ): 94 | super().__init__() 95 | self.start_point: InternalPoint3D = start 96 | self.end_point: InternalPoint3D = end 97 | self.value = value 98 | self.progress = progress 99 | self._scale = scale # don't conflict with the `scale()` method 100 | 101 | self.background_line = Line( 102 | start, end, color=get_wire_color(None), stroke_width=WIRE_WIDTH * scale 103 | ) 104 | progress_end = interpolate(start, end, max(progress, 0.00001)) 105 | self.value_line = Line( 106 | start, 107 | progress_end, 108 | color=get_wire_color(value), 109 | stroke_width=WIRE_WIDTH * scale, 110 | ) 111 | 112 | self.add(self.background_line, self.value_line) 113 | 114 | def set_progress(self, progress: float): 115 | start = self.background_line.get_all_points()[0] 116 | end = self.background_line.get_all_points()[-1] 117 | if not np.array_equal(start, end): 118 | self.value_line.put_start_and_end_on( 119 | start, interpolate(start, end, max(progress, 0.00001)) 120 | ) 121 | 122 | 123 | class FillWire(Animation): 124 | def __init__(self, wire: ManimWire, **kwargs: Any) -> None: # type: ignore[reportInconsistentConstructor] 125 | super().__init__(wire, **kwargs) 126 | 127 | def interpolate_mobject(self, alpha: float) -> None: 128 | assert isinstance(self.mobject, ManimWire) 129 | self.mobject.set_progress(alpha) 130 | 131 | 132 | class ManimCircuit(VGroup): 133 | def __init__( 134 | self, 135 | circuit: Circuit, 136 | scale: float = 1, 137 | with_evaluation: bool = True, 138 | wire_scale: float | None = None, 139 | ): 140 | """A Manim representation of a circuit. 141 | 142 | Args: 143 | circuit: The circuit to visualize. 144 | scale: The scale of the circuit. This doesn't scale the positions, just the 145 | size of the gates and wires. 146 | with_evaluation: Whether to evaluate the circuit to be able to show the 147 | progress of the evaluation. (We could probably do this lazily only when 148 | we need to and not in the constructor.) 149 | """ 150 | super().__init__() 151 | self.circuit = circuit 152 | 153 | if with_evaluation: 154 | evaluation = circuit.evaluate() 155 | else: 156 | evaluation = None 157 | 158 | if wire_scale is None: 159 | wire_scale = scale 160 | self.gates = { 161 | name: ManimGate(gate, scale=scale, wire_scale=wire_scale) 162 | for name, gate in self.circuit.gates.items() 163 | } 164 | self.wires = { 165 | (wire_start, wire_end): ManimWire( 166 | self.circuit.gates[wire_start].position, 167 | self.circuit.gates[wire_end].position, 168 | evaluation.get_wire_value(wire_start, wire_end) 169 | if evaluation 170 | else False, 171 | scale=wire_scale, 172 | ) 173 | for wire_start, wire_end in self.circuit.wires 174 | } 175 | 176 | # TODO(vv): some knots appear before AND gates and this `bg_gates` doesn't 177 | # seem to help. Why? 178 | bg_gates = ["knot", "invisible"] 179 | self.add( 180 | # Add wires first so they are behind the gates 181 | *self.wires.values(), 182 | *[g for g in self.gates.values() if g.gate.visual_type in bg_gates], 183 | *[g for g in self.gates.values() if g.gate.visual_type not in bg_gates], 184 | ) 185 | 186 | def animate_inputs(self) -> AnimationGroup: 187 | """Animate the input nodes filling with their given color.""" 188 | anims = [] 189 | for manim_gate in self.gates.values(): 190 | if manim_gate.gate.n_inputs == 0: 191 | value = manim_gate.gate.truth_table[()][0] 192 | anims.append(manim_gate.animate_to_value(value)) 193 | 194 | return LaggedStart(*anims) 195 | 196 | def animate_evaluation( 197 | self, 198 | scene: Scene, # Hack: we need the scene in order to play sounds. 199 | reversed: bool = False, 200 | speed: float = 1, 201 | ) -> AnimationGroup: 202 | """Animate the color flowing through the wires to evaluate the circuit.""" 203 | evaluation = self.circuit.evaluate() 204 | animations = [] 205 | 206 | speed *= 3 # make it faster while keeping the default speed at 0 207 | MIN_DURATION = 0.01 # Prevent divison by 0 208 | rng = np.random.default_rng(37) # for sounds jitter 209 | 210 | for (wire_start, wire_end), manim_wire in self.wires.items(): 211 | start_time = ( 212 | evaluation.gate_evaluations[wire_start].reach_time 213 | + self.circuit.gates[wire_start].length 214 | ) / speed 215 | duration = ( 216 | max(self.circuit.get_wire_length(wire_start, wire_end), MIN_DURATION) 217 | / speed 218 | ) 219 | 220 | # A complicated Manim construction that says "wait for start_time seconds, 221 | # start filling the wire and end at end_time seconds" 222 | animations.append( 223 | AnimationGroup( 224 | *[ 225 | Wait(start_time), 226 | FillWire(manim_wire, run_time=duration), 227 | ], 228 | lag_ratio=1.0, 229 | run_time=start_time + duration, 230 | ) 231 | ) 232 | 233 | for gate_name, manim_gate in self.gates.items(): 234 | gate = self.circuit.gates[gate_name] 235 | gate_evaluation = evaluation.gate_evaluations[gate_name] 236 | 237 | start_time = gate_evaluation.reach_time 238 | start_time = max(0, start_time + rng.normal(scale=0.1, loc=0.05)) 239 | start_time /= speed 240 | 241 | duration = max(gate.length, MIN_DURATION) / speed 242 | 243 | simplified_value = evaluation.get_simplified_value( 244 | gate_name, reversed=reversed 245 | ) 246 | 247 | # Jitter the start time for cases where there's a lot of gates going 248 | # off at the same time 249 | 250 | animations.append( 251 | AnimationGroup( 252 | *[ 253 | Wait(start_time), 254 | manim_gate.animate_to_value( 255 | simplified_value, 256 | run_time=duration, 257 | ), 258 | ], 259 | lag_ratio=1.0, 260 | run_time=start_time + duration, 261 | ) 262 | ) 263 | if simplified_value is not None: 264 | add_sound_for_gate( 265 | scene, 266 | manim_gate, 267 | simplified_value, 268 | start_time, 269 | ) 270 | 271 | # These all get played "simultaneously" but there are delays internal to the 272 | # individual animations 273 | return AnimationGroup(*animations) 274 | 275 | def set_stroke_width(self, scale: float): 276 | for wire in self.wires.values(): 277 | wire.background_line.set_stroke_width(scale * WIRE_WIDTH) 278 | wire.value_line.set_stroke_width(scale * WIRE_WIDTH) 279 | 280 | 281 | def add_sound_for_gate( 282 | scene: Scene, manim_gate: ManimGate, value: bool, time_offset: float 283 | ): 284 | gate = manim_gate.gate 285 | if gate.visual_type in ["knot", "invisible"] or gate.n_inputs == 0: 286 | return # No sound for "utility gates" and inputs 287 | 288 | if gate.n_outputs == 0: 289 | sound_file = "click_2" if value else "click_1" 290 | else: 291 | sound_file = "click_0" if value else "click_3" 292 | 293 | scene.add_sound( 294 | str(Path(__file__).parents[1] / f"audio/click/{sound_file}.wav"), 295 | time_offset=time_offset, 296 | gain=-12.0, 297 | ) 298 | -------------------------------------------------------------------------------- /np_completeness/utils/specific_circuits.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from manim import DOWN, LEFT, RIGHT, UP, np 3 | 4 | from np_completeness.utils.circuit import ( 5 | Circuit, 6 | ) 7 | from np_completeness.utils.gate import ( 8 | ADD_TABLE, 9 | AND_TABLE, 10 | AND_TABLE_3, 11 | NOT_TABLE, 12 | OR3_TABLE, 13 | OR_TABLE, 14 | Gate, 15 | all_inputs, 16 | ) 17 | from np_completeness.utils.util_general import ( 18 | GATE_X_SPACING, 19 | GATE_Y_SPACING, 20 | WIRE_TIGHT_SPACING, 21 | ) 22 | 23 | 24 | def make_verifier_circuit(xs: float = 1, ys: float = 1) -> Circuit: 25 | """Make a simple example verifier circuit.""" 26 | circuit = Circuit() 27 | 28 | for i, out_value in enumerate([False, True, False, False, True, False]): 29 | circuit.add_gate( 30 | f"input_{i}", 31 | Gate( 32 | truth_table={(): (out_value,)}, 33 | position=np.array( 34 | [ 35 | (i - 2.5) * GATE_X_SPACING * xs * 1.0, 36 | GATE_Y_SPACING * ys * 2, 37 | 0, 38 | ] 39 | ), 40 | ), 41 | ) 42 | 43 | # layer 1 44 | for i in range(2): 45 | print(i) 46 | circuit.add_gate( 47 | "not_gate_" + str(i), 48 | Gate( 49 | truth_table=NOT_TABLE, 50 | position=circuit.position_of("input_" + str(i + 3)) 51 | + DOWN * GATE_Y_SPACING * ys, 52 | ), 53 | ) 54 | print("input_" + str(i + 3), "not_gate_" + str(i)) 55 | add_snake_wire(circuit, "input_" + str(i + 3), "not_gate_" + str(i)) 56 | 57 | if i == 0: 58 | circuit.add_gate( 59 | "xknot_1", 60 | Gate.make_knot( 61 | circuit.position_of("not_gate_0") 62 | + GATE_Y_SPACING * ys * 1.25 * DOWN, 63 | n_inputs=1, 64 | n_outputs=2, 65 | ), 66 | ) 67 | add_snake_wire(circuit, "not_gate_0", "xknot_1") 68 | 69 | circuit.add_gate( 70 | "or_gate_0", 71 | Gate( 72 | truth_table=OR_TABLE, 73 | position=np.array( 74 | (circuit.position_of("input_1") + circuit.position_of("input_2")) / 2.0 75 | ) 76 | + DOWN * GATE_Y_SPACING * ys, 77 | ), 78 | ) 79 | add_snake_wire(circuit, "input_2", "or_gate_0") 80 | add_snake_wire(circuit, "input_1", "or_gate_0") 81 | circuit.add_gate( 82 | "xknot_0", 83 | Gate.make_knot( 84 | circuit.position_of("or_gate_0") + GATE_Y_SPACING * ys * 0.25 * DOWN, 85 | n_inputs=1, 86 | n_outputs=2, 87 | ), 88 | ) 89 | add_snake_wire(circuit, "or_gate_0", "xknot_0") 90 | 91 | # layer 2 92 | circuit.add_gate( 93 | "and_gate_0", 94 | Gate( 95 | truth_table=AND_TABLE, 96 | position=(circuit.position_of("input_0") + circuit.position_of("input_1")) 97 | / 2.0 98 | + 2 * DOWN * GATE_Y_SPACING * ys, 99 | ), 100 | ) 101 | add_snake_wire(circuit, "input_0", "and_gate_0") 102 | add_snake_wire(circuit, "xknot_0", "and_gate_0") 103 | 104 | circuit.add_gate( 105 | "and_gate_1", 106 | Gate( 107 | truth_table=AND_TABLE, 108 | position=(circuit.position_of("input_4") + circuit.position_of("input_5")) 109 | / 2.0 110 | + 2 * DOWN * GATE_Y_SPACING * ys, 111 | ), 112 | ) 113 | add_snake_wire(circuit, "not_gate_1", "and_gate_1") 114 | add_snake_wire(circuit, "input_5", "and_gate_1") 115 | 116 | # layer 3 117 | circuit.add_gate( 118 | "not_gate_2", 119 | Gate( 120 | truth_table=NOT_TABLE, 121 | position=circuit.position_of("and_gate_0") + DOWN * GATE_Y_SPACING * ys, 122 | ), 123 | ) 124 | add_snake_wire(circuit, "and_gate_0", "not_gate_2") 125 | 126 | circuit.add_gate( 127 | "or_gate_1", 128 | Gate( 129 | truth_table=OR_TABLE, 130 | position=(circuit.position_of("input_2") + circuit.position_of("input_3")) 131 | / 2.0 132 | + 3 * DOWN * GATE_Y_SPACING * ys, 133 | ), 134 | ) 135 | add_snake_wire(circuit, "xknot_0", "or_gate_1") 136 | add_snake_wire(circuit, "xknot_1", "or_gate_1") 137 | 138 | circuit.add_gate( 139 | "or_gate_2", 140 | Gate( 141 | truth_table=OR_TABLE, 142 | position=circuit.position_of("input_4") + 3 * DOWN * GATE_Y_SPACING * ys, 143 | ), 144 | ) 145 | add_snake_wire(circuit, "xknot_1", "or_gate_2") 146 | add_snake_wire(circuit, "and_gate_1", "or_gate_2") 147 | 148 | # layer 4 149 | circuit.add_gate( 150 | "and_gate_2", 151 | Gate( 152 | truth_table=AND_TABLE_3, 153 | position=circuit.position_of("or_gate_1") + 1 * DOWN * GATE_Y_SPACING * ys, 154 | visual_type="and", 155 | ), 156 | ) 157 | add_snake_wire(circuit, "not_gate_2", "and_gate_2", y_start_offset=-0.5) 158 | add_snake_wire(circuit, "or_gate_1", "and_gate_2") 159 | add_snake_wire(circuit, "or_gate_2", "and_gate_2", y_start_offset=-0.5) 160 | 161 | circuit.add_gate( 162 | f"output", 163 | Gate.make_knot( 164 | circuit.position_of("and_gate_2") + DOWN * GATE_Y_SPACING * ys, 165 | n_inputs=1, 166 | n_outputs=0, 167 | length=1, 168 | ), 169 | ) 170 | circuit.add_wire("and_gate_2", "output") 171 | 172 | circuit.check() 173 | return circuit 174 | 175 | 176 | def make_example_circuit(sc: float = 1, thumb: bool = False) -> Circuit: 177 | """Make a simple example circuit.""" 178 | circuit = Circuit() 179 | 180 | for i, out_value in enumerate([True, True, False]): 181 | circuit.add_gate( 182 | f"input_{i}", 183 | Gate( 184 | truth_table={(): (out_value,)}, 185 | position=np.array( 186 | [ 187 | (i - 1) * GATE_X_SPACING * sc * 1.0, 188 | GATE_Y_SPACING * sc * 2, 189 | 0, 190 | ] 191 | ), 192 | ), 193 | ) 194 | 195 | circuit.add_gate( 196 | "not_gate", 197 | Gate( 198 | truth_table=NOT_TABLE, 199 | position=circuit.position_of("input_1") + DOWN * GATE_Y_SPACING * sc, 200 | ), 201 | ) 202 | 203 | circuit.add_gate( 204 | "or_gate", 205 | Gate( 206 | truth_table=OR_TABLE, 207 | position=np.array([circuit.x_of("input_1") - GATE_X_SPACING * sc, 0, 0]), 208 | ), 209 | ) 210 | circuit.add_gate( 211 | "and_gate", 212 | Gate( 213 | truth_table=AND_TABLE, 214 | position=np.array( 215 | [circuit.x_of("input_1") + GATE_X_SPACING * sc, -GATE_Y_SPACING * sc, 0] 216 | ), 217 | ), 218 | ) 219 | circuit.add_gate( 220 | "knot", 221 | Gate.make_knot( 222 | np.array([circuit.x_of("or_gate"), -GATE_Y_SPACING * sc / 2, 0]), 223 | n_inputs=1, 224 | n_outputs=2, 225 | ), 226 | ) 227 | 228 | for i in range(2): 229 | circuit.add_gate( 230 | f"output_{i}", 231 | Gate.make_knot( 232 | np.array( 233 | [(i - 0.5) * GATE_X_SPACING * sc * 2, -GATE_Y_SPACING * sc * 2, 0] 234 | ), 235 | n_inputs=1, 236 | n_outputs=0, 237 | length=1, 238 | ), 239 | ) 240 | 241 | add_snake_wire(circuit, "input_2", "and_gate") 242 | circuit.add_wire("input_1", "not_gate") 243 | add_snake_wire(circuit, "input_0", "or_gate") 244 | if thumb: 245 | add_snake_wire(circuit, "not_gate", "or_gate", y_start_offset=-0.75) 246 | else: 247 | add_snake_wire(circuit, "not_gate", "or_gate", y_start_offset=-0.5) 248 | circuit.add_wire("or_gate", "knot") 249 | add_snake_wire(circuit, "knot", "and_gate", y_start_offset=0) 250 | circuit.add_wire("knot", "output_0") 251 | circuit.add_wire("and_gate", "output_1") 252 | 253 | circuit.check() 254 | return circuit 255 | 256 | 257 | if __name__ == "__main__": 258 | circuit = make_example_circuit() 259 | # evaluation = circuit.evaluate() 260 | circuit = circuit.reverse() 261 | evaluation = circuit.evaluate() 262 | 263 | 264 | def add_snake_wire( 265 | circuit: Circuit, 266 | wire_start: str, 267 | wire_end: str, 268 | *, 269 | y_start_offset: float = -0.25, 270 | x_end_offset: float = WIRE_TIGHT_SPACING * 2, 271 | ): 272 | x_start, y_start, _ = circuit.position_of(wire_start) 273 | x_end, y_end, _ = circuit.position_of(wire_end) 274 | 275 | assert ( 276 | x_end_offset >= 0 277 | ), "Expected non-negative offset (will be flipped automatically if needed)" 278 | 279 | x_end_offset = min(x_end_offset, abs(x_start - x_end)) 280 | if x_start < x_end: 281 | x_end_offset = -x_end_offset 282 | 283 | circuit.add_wire( 284 | wire_start, 285 | wire_end, 286 | knot_positions=[ 287 | (x_start, y_start + y_start_offset), 288 | (x_end + x_end_offset, y_start + y_start_offset), 289 | (x_end + x_end_offset, y_end + 0.3), 290 | ], 291 | ) 292 | 293 | 294 | MULTIPLICATION_CIRCUIT_SIZE = 4 295 | 296 | 297 | def to_binary(x: int, n_digits: int = MULTIPLICATION_CIRCUIT_SIZE) -> list[bool]: 298 | """Convert an integer to a binary list of booleans, least significant bit first. 299 | 300 | Example: 301 | >>> to_binary(2, n_digits=4) 302 | [False, True, False, False] 303 | """ 304 | res = [bool(int(digit)) for digit in bin(x)[2:]][::-1] 305 | while len(res) < n_digits: 306 | res.append(False) 307 | 308 | return res 309 | 310 | 311 | def _multiplication_and_gate_position(i: int, j: int) -> tuple[float, float]: 312 | return ( 313 | 2 - 1 * (i + j) * GATE_X_SPACING - 0.5 * i, 314 | 2 - i * GATE_Y_SPACING, 315 | ) 316 | 317 | 318 | def _add_multiplication_inputs(circuit: Circuit, a: list[bool], b: list[bool]) -> None: 319 | assert len(a) == len(b) 320 | n = len(a) 321 | 322 | for t, symbol, values in [(0, "a", a), (1, "b", b)]: 323 | for j in range(n): 324 | input_name = f"input_{symbol}_{j}" 325 | # For visual reasons, the horizontal positioning is different for 326 | # input A and input B: they're placed under different gates. 327 | if symbol == "a": 328 | x = _multiplication_and_gate_position(j, 0)[0] + 0.15 329 | else: 330 | x = _multiplication_and_gate_position(0, j)[0] - 0.2 331 | 332 | input_pos = np.array([x, 3.8 - t * 0.9]) 333 | 334 | circuit.add_gate( 335 | input_name, 336 | Gate( 337 | truth_table={(): (values[j],)}, 338 | position=input_pos, 339 | ), 340 | ) 341 | 342 | 343 | def make_multiplication_circuit(a: list[bool] | int, b: list[bool] | int) -> Circuit: 344 | circuit = Circuit() 345 | n = MULTIPLICATION_CIRCUIT_SIZE 346 | 347 | if isinstance(a, int): 348 | a = to_binary(a, n_digits=n) 349 | if isinstance(b, int): 350 | b = to_binary(b, n_digits=n) 351 | 352 | # Define the AND gates with appropriate positions 353 | for i in range(n): 354 | for j in range(n): 355 | gate_name = f"and_{i}_{j}" 356 | position = _multiplication_and_gate_position(i, j) 357 | circuit.add_gate( 358 | gate_name, 359 | Gate(truth_table=AND_TABLE, position=position), 360 | ) 361 | 362 | _add_multiplication_inputs(circuit, a, b) 363 | 364 | # wires from input A 365 | for i in range(n): 366 | for j in range(n): 367 | knot_name = f"knot_a_{i}_{j}" 368 | gate_name = f"and_{i}_{j}" 369 | knot_position = circuit.position_of(gate_name) + UP * 0.4 + RIGHT * 0.15 370 | circuit.add_gate( 371 | knot_name, 372 | Gate.make_knot( 373 | knot_position, 374 | n_inputs=1, 375 | n_outputs=1 if j == (n - 1) else 2, 376 | ), 377 | ) 378 | 379 | circuit.add_wire(knot_name, gate_name) 380 | if j == 0: 381 | circuit.add_wire(f"input_a_{i}", knot_name) 382 | else: 383 | circuit.add_wire(f"knot_a_{i}_{j-1}", knot_name) 384 | 385 | # wires from input B 386 | for i in range(n): 387 | for j in range(n): 388 | knot_name = f"knot_b_{i}_{j}" 389 | gate_name = f"and_{i}_{j}" 390 | knot_position = circuit.position_of(gate_name) + LEFT * 0.2 + UP * 0.3 391 | circuit.add_gate( 392 | knot_name, 393 | Gate.make_knot( 394 | knot_position, 395 | n_inputs=1, 396 | n_outputs=1 if i == (n - 1) else 2, 397 | ), 398 | ) 399 | 400 | circuit.add_wire(knot_name, gate_name) 401 | if i == 0: 402 | circuit.add_wire(f"input_b_{j}", knot_name) 403 | else: 404 | circuit.add_wire(f"knot_b_{i-1}_{j}", knot_name) 405 | 406 | # adder gates 407 | for i in range(n - 1): 408 | for j in range(n): 409 | gate_name = f"plus_{i}_{j}" 410 | position = ( 411 | (4 - i - j) * GATE_X_SPACING, 412 | (0 - i) * GATE_Y_SPACING, 413 | ) 414 | circuit.add_gate( 415 | gate_name, 416 | Gate(truth_table=ADD_TABLE, position=position), 417 | ) 418 | 419 | if i > 0 and j < n - 1: 420 | circuit.add_wire(f"plus_{i-1}_{j+1}", f"plus_{i}_{j}") 421 | 422 | # outputs 423 | for i in range(n * 2): 424 | gate_name = f"output_{i}" 425 | position = ( 426 | (5 - i) * GATE_X_SPACING, 427 | (-2.8) * GATE_Y_SPACING, 428 | ) 429 | 430 | circuit.add_gate(gate_name, Gate.make_knot(position, n_inputs=1, n_outputs=0)) 431 | from_i = min(i - 1, n - 2) 432 | from_j = i - 1 - from_i 433 | 434 | # The first output gets its input without any adders, the last 435 | # output gets it from the carry of the last adder (wire added later) 436 | if i > 0 and i < n * 2 - 1: 437 | circuit.add_wire(f"plus_{from_i}_{from_j}", gate_name) 438 | 439 | # for n rows, there are n-1 adders, so the first row is special 440 | for j in range(n): 441 | add_snake_wire( 442 | circuit, 443 | f"and_0_{j}", 444 | f"plus_0_{j-1}" if j > 0 else "output_0", 445 | y_start_offset=-WIRE_TIGHT_SPACING * (j + 1), 446 | x_end_offset=0.0, 447 | ) 448 | 449 | for i in range(1, n): 450 | for j in range(n): 451 | add_snake_wire( 452 | circuit, 453 | f"and_{i}_{j}", 454 | f"plus_{i-1}_{j}", 455 | y_start_offset=-0.2 - WIRE_TIGHT_SPACING * j, 456 | ) 457 | 458 | # carry wires - note these must be the second and not the first output of 459 | # the adder, meaning we need to put them here at the end 460 | for i in range(n - 1): 461 | for j in range(n): 462 | if i == n - 2 and j == n - 1: 463 | to = f"output_{n*2-1}" 464 | else: 465 | to = f"plus_{i}_{j+1}" if j < n - 1 else f"plus_{i+1}_{j}" 466 | 467 | circuit.add_wire( 468 | f"plus_{i}_{j}", 469 | to, 470 | knot_positions=[ 471 | circuit.position_of(f"plus_{i}_{j}") + DOWN * 0.2 + LEFT * 0.2, 472 | circuit.position_of(f"plus_{i}_{j}") 473 | + UP * 0.3 474 | + RIGHT * (0.3 - GATE_X_SPACING), 475 | ], 476 | ) 477 | 478 | # Some of the adders don't have 3 inputs, add invisible inputs that go to 0 479 | circuit.add_missing_inputs_and_outputs(visible=False) 480 | 481 | circuit.check() 482 | return circuit 483 | 484 | 485 | def make_multiplication_circuit_constraints( 486 | a: list[bool] | int, b: list[bool] | int 487 | ) -> Circuit: 488 | circuit = Circuit() 489 | n = MULTIPLICATION_CIRCUIT_SIZE 490 | 491 | if isinstance(a, int): 492 | a = to_binary(a, n_digits=n) 493 | if isinstance(b, int): 494 | b = to_binary(b, n_digits=n) 495 | 496 | _add_multiplication_inputs(circuit, a, b) 497 | 498 | for symbol in ["a", "b"]: 499 | for j in range(n): 500 | x = ( 501 | (-GATE_X_SPACING * j - 1) 502 | if symbol == "a" 503 | else (-GATE_X_SPACING * j + 5) 504 | ) 505 | y = 1 if j == 0 else 0 506 | circuit.add_knot((x, y), name=f"knot_{symbol}_{j}") 507 | add_snake_wire( 508 | circuit, 509 | f"input_{symbol}_{j}", 510 | f"knot_{symbol}_{j}", 511 | y_start_offset=-GATE_Y_SPACING 512 | + (j if symbol == "a" else (n - 1 - j)) * WIRE_TIGHT_SPACING, 513 | x_end_offset=0, 514 | ) 515 | 516 | circuit.add_gate( 517 | f"not_{symbol}", Gate(NOT_TABLE, (circuit.x_of(f"knot_{symbol}_0"), 0)) 518 | ) 519 | circuit.add_wire(f"knot_{symbol}_0", f"not_{symbol}") 520 | 521 | circuit.add_gate( 522 | f"or_{symbol}", 523 | Gate( 524 | {inputs: (any(inputs),) for inputs in all_inputs(n)}, 525 | position=( 526 | ( 527 | circuit.x_of(f"not_{symbol}") 528 | + circuit.x_of(f"knot_{symbol}_{n-1}") 529 | ) 530 | / 2, 531 | -1, 532 | ), 533 | visual_type="or", 534 | ), 535 | ) 536 | for j in range(n): 537 | in_name = f"not_{symbol}" if j == 0 else f"knot_{symbol}_{j}" 538 | circuit.add_wire(in_name, f"or_{symbol}") 539 | 540 | circuit.add_gate( 541 | "and", Gate(AND_TABLE, ((circuit.x_of("or_a") + circuit.x_of("or_b")) / 2, -2)) 542 | ) 543 | add_snake_wire(circuit, "or_a", "and") 544 | add_snake_wire(circuit, "or_b", "and") 545 | 546 | circuit.add_gate("output", Gate.make_knot((circuit.x_of("and"), -3), n_outputs=0)) 547 | circuit.add_wire("and", "output") 548 | 549 | return circuit 550 | 551 | 552 | def _make_xor_gadget(circuit: Circuit, prefix: str, input_0: str, input_1: str) -> str: 553 | """Make a XOR gadget out of NOT, AND and OR gates and return the name of the output.""" 554 | input_x_0, input_y_0, _ = circuit.position_of(input_0) 555 | input_x_1, input_y_1, _ = circuit.position_of(input_1) 556 | 557 | # In other places, we use right-to-left indexing, so let's be consistent 558 | assert input_x_0 > input_x_1 559 | # Start from the lower of the two positions 560 | input_y = min(input_y_0, input_y_1) 561 | 562 | for i, (input_x, input_name) in enumerate( 563 | [(input_x_0, input_0), (input_x_1, input_1)] 564 | ): 565 | knot_name = circuit.add_gate( 566 | f"{prefix}_knot_{i}", 567 | Gate.make_knot( 568 | (input_x, input_y - GATE_Y_SPACING * 0.5), 569 | n_inputs=1, 570 | n_outputs=2, 571 | ), 572 | ) 573 | circuit.add_wire(input_name, knot_name) 574 | 575 | not_name = circuit.add_gate( 576 | f"{prefix}_not_{i}", 577 | Gate( 578 | truth_table=NOT_TABLE, 579 | position=circuit.position_of(knot_name) 580 | + np.array( 581 | [ 582 | GATE_X_SPACING * 0.5 * (-1 if i == 0 else 1), 583 | -GATE_Y_SPACING / 2, 584 | 0, 585 | ] 586 | ), 587 | ), 588 | ) 589 | knot2_name = circuit.add_gate( 590 | f"{prefix}_knot_{i}'", 591 | Gate.make_knot( 592 | ( 593 | circuit.x_of(not_name), 594 | circuit.y_of(knot_name), 595 | ), 596 | ), 597 | ) 598 | circuit.add_wire(knot_name, knot2_name) 599 | circuit.add_wire(knot2_name, not_name) 600 | 601 | circuit.add_gate( 602 | f"{prefix}_and_{i}", 603 | Gate( 604 | truth_table=AND_TABLE, 605 | position=circuit.position_of(knot_name) + DOWN * GATE_Y_SPACING, 606 | ), 607 | ) 608 | circuit.add_wire(knot_name, f"{prefix}_and_{i}") 609 | 610 | for i in range(2): 611 | not_name = f"{prefix}_not_{i}" 612 | knot = circuit.add_knot(position=circuit.position_of(not_name) + DOWN * 0.2) 613 | circuit.add_wire(not_name, knot) 614 | circuit.add_wire(knot, f"{prefix}_and_{1-i}") 615 | 616 | or_name = circuit.add_gate( 617 | f"{prefix}_or", 618 | Gate( 619 | OR_TABLE, 620 | ( 621 | (input_x_0 + input_x_1) / 2, 622 | circuit.y_of(f"{prefix}_and_0") - GATE_Y_SPACING * 0.5, 623 | ), 624 | ), 625 | ) 626 | circuit.add_wire(f"{prefix}_and_0", or_name) 627 | circuit.add_wire(f"{prefix}_and_1", or_name) 628 | 629 | return or_name 630 | 631 | 632 | def make_adder_circuit(inputs: list[bool]) -> Circuit: 633 | circuit = Circuit() 634 | 635 | for i in range(3): 636 | circuit.add_gate( 637 | f"input_{i}", 638 | Gate( 639 | truth_table={(): (inputs[i],)}, 640 | position=(-GATE_X_SPACING * i * 1.1, GATE_Y_SPACING * 3.5), 641 | ), 642 | ) 643 | 644 | # Majority AND gates + knots for their inputs 645 | for i in range(3): 646 | name = f"maj_and_{i}" 647 | circuit.add_gate( 648 | name, 649 | Gate( 650 | truth_table=AND_TABLE, 651 | position=( 652 | -GATE_X_SPACING * (i + 2), 653 | GATE_Y_SPACING * 1.5, 654 | ), 655 | ), 656 | ) 657 | for j in range(2): 658 | knot_name = f"maj_and_knot_{i}_{j}" 659 | circuit.add_gate( 660 | knot_name, 661 | Gate.make_knot( 662 | circuit.position_of(name) 663 | + np.array( 664 | [ 665 | 0.2 * (0.5 - j), 666 | 0.3, 667 | 0, 668 | ] 669 | ), 670 | ), 671 | ) 672 | circuit.add_wire(knot_name, name) 673 | 674 | # Wires from the inputs to the AND gates 675 | for i, (output_knot1, output_knot2) in enumerate( 676 | [ 677 | ["maj_and_knot_0_0", "maj_and_knot_1_0"], 678 | ["maj_and_knot_0_1", "maj_and_knot_2_0"], 679 | ["maj_and_knot_1_1", "maj_and_knot_2_1"], 680 | ] 681 | ): 682 | knot_x = [ 683 | circuit.x_of(f"input_{i}"), 684 | circuit.x_of(output_knot1), 685 | circuit.x_of(output_knot2), 686 | circuit.x_of(f"input_{i}") + GATE_X_SPACING * (3 - i * 0.5), 687 | ] 688 | knot_y = circuit.y_of(f"input_{i}") - GATE_Y_SPACING + WIRE_TIGHT_SPACING * i 689 | k1 = circuit.add_knot( 690 | (knot_x[0], knot_y), n_inputs=1, n_outputs=2, name=f"input_{i}_knot" 691 | ) 692 | k2 = circuit.add_knot((knot_x[1], knot_y), n_inputs=1, n_outputs=2) 693 | k3 = circuit.add_knot((knot_x[2], knot_y), n_inputs=1, n_outputs=1) 694 | k4 = circuit.add_knot( 695 | (knot_x[3], knot_y), n_inputs=1, n_outputs=1, name=f"xor3_input_{i}" 696 | ) 697 | 698 | circuit.wires.extend( 699 | [ 700 | (f"input_{i}", k1), 701 | (k1, k2), 702 | (k2, output_knot1), 703 | (k2, k3), 704 | (k3, output_knot2), 705 | (k1, k4), 706 | ] 707 | ) 708 | 709 | # ORing the three together to get the carry 710 | name = "maj_or3" 711 | circuit.add_gate( 712 | name, 713 | Gate( 714 | truth_table=OR3_TABLE, 715 | position=circuit.position_of("maj_and_1") + DOWN * GATE_Y_SPACING, 716 | visual_type="or", 717 | ), 718 | ) 719 | for i in range(3): 720 | add_snake_wire( 721 | circuit, 722 | f"maj_and_{i}", 723 | "maj_or3", 724 | y_start_offset=-0.3, 725 | ) 726 | 727 | # XORing three numbers via two XOR gadgets 728 | xor_output_0 = _make_xor_gadget( 729 | circuit, prefix="xor0", input_0="xor3_input_1", input_1="xor3_input_2" 730 | ) 731 | xor_output_1 = _make_xor_gadget( 732 | circuit, prefix="xor1", input_0="xor3_input_0", input_1=xor_output_0 733 | ) 734 | 735 | lower_output = circuit.add_knot( 736 | position=circuit.position_of(xor_output_1) + DOWN * GATE_Y_SPACING, 737 | name="lower_output", 738 | n_inputs=1, 739 | n_outputs=0, 740 | ) 741 | circuit.add_wire(xor_output_1, lower_output) 742 | 743 | # The upper output bit also happens to be the upper one on the screen hehe 744 | upper_output = circuit.add_knot( 745 | position=circuit.position_of("maj_or3") + DOWN * GATE_Y_SPACING, 746 | name="upper_output", 747 | n_inputs=1, 748 | n_outputs=0, 749 | ) 750 | circuit.add_wire("maj_or3", upper_output) 751 | 752 | return circuit 753 | 754 | 755 | def make_adder_gate(inputs: list[bool]) -> Circuit: 756 | circuit = Circuit() 757 | 758 | # Add the adder gate 759 | circuit.add_gate( 760 | "adder", 761 | Gate( 762 | truth_table=ADD_TABLE, 763 | position=(0, 0, 0), 764 | ), 765 | ) 766 | 767 | # Add input gates 768 | for i, value in enumerate(inputs): 769 | input_name = f"input_{i}" 770 | circuit.add_gate( 771 | input_name, 772 | Gate( 773 | truth_table={(): (value,)}, 774 | position=(-1 + i * 1, 1, 0), 775 | ), 776 | ) 777 | add_snake_wire( 778 | circuit, 779 | input_name, 780 | "adder", 781 | y_start_offset=-0.3, 782 | x_end_offset=3.5 * WIRE_TIGHT_SPACING, 783 | ) 784 | 785 | # Add output gates 786 | for i in range(2): 787 | output_name = f"output_{i}" 788 | circuit.add_gate( 789 | output_name, 790 | Gate.make_knot( 791 | position=(0.5 - i, -1, 0), 792 | n_inputs=1, 793 | n_outputs=0, 794 | ), 795 | ) 796 | add_snake_wire( 797 | circuit, "adder", output_name, x_end_offset=3 * WIRE_TIGHT_SPACING 798 | ) 799 | 800 | return circuit 801 | 802 | 803 | def make_trivial_circuit() -> Circuit: 804 | circuit = Circuit() 805 | circuit.add_gate( 806 | "and_gate", 807 | Gate(truth_table=AND_TABLE, position=(0, 0, 0)), 808 | ) 809 | 810 | circuit.add_missing_inputs_and_outputs() 811 | return circuit 812 | -------------------------------------------------------------------------------- /np_completeness/utils/util_cliparts.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | import numpy as np 4 | from manim import * 5 | 6 | 7 | def clipart_arrow(): 8 | return ImageMobject("img/arrow.png").scale_to_fit_height(0.7) 9 | 10 | 11 | def clipart_yes_no_maybe(which: Literal["yes", "no", "maybe"], height: float): 12 | pnts_yes = [ 13 | np.array([174.042, 364.002, 0]), 14 | np.array([169.653, 359.498, 0]), 15 | np.array([181.318, 347.66, 0]), 16 | np.array([200.663, 367.236, 0]), 17 | np.array([195.928, 371.625, 0]), 18 | np.array([181.376, 356.553, 0]), 19 | ] 20 | pnts_no = [ 21 | np.array([397.791, 350.711, 0]), 22 | np.array([402.185, 346.322, 0]), 23 | np.array([410.393, 354.779, 0]), 24 | np.array([417.863, 347.317, 0]), 25 | np.array([421.913, 351.489, 0]), 26 | np.array([414.443, 358.95, 0]), 27 | np.array([421.999, 366.735, 0]), 28 | np.array([417.606, 371.123, 0]), 29 | np.array([410.049, 363.339, 0]), 30 | np.array([401.857, 371.522, 0]), 31 | np.array([397.807, 367.35, 0]), 32 | np.array([406.359, 359.167, 0]), 33 | ] 34 | pnts_maybe = [ 35 | # np.array([300.242, 355.568, 0]), 36 | np.array([300.329, 356.423, 0]), 37 | np.array([300.478, 357.373, 0]), 38 | np.array([300.915, 358.039, 0]), 39 | np.array([301.621, 358.773, 0]), 40 | np.array([302.28, 359.361, 0]), 41 | np.array([302.983, 359.868, 0]), 42 | np.array([303.927, 360.481, 0]), 43 | np.array([304.549, 360.903, 0]), 44 | np.array([305.347, 361.538, 0]), 45 | np.array([305.847, 362.036, 0]), 46 | np.array([306.411, 362.764, 0]), 47 | np.array([306.822, 363.514, 0]), 48 | np.array([307.069, 364.183, 0]), 49 | np.array([307.247, 364.906, 0]), 50 | np.array([307.382, 365.766, 0]), 51 | np.array([307.454, 366.456, 0]), 52 | np.array([307.5, 367.296, 0]), 53 | np.array([307.483, 368.449, 0]), 54 | np.array([307.368, 369.476, 0]), 55 | np.array([307.122, 370.533, 0]), 56 | np.array([306.738, 371.538, 0]), 57 | np.array([306.243, 372.415, 0]), 58 | np.array([305.63, 373.196, 0]), 59 | np.array([305.216, 373.623, 0]), 60 | np.array([304.639, 374.132, 0]), 61 | np.array([304.202, 374.464, 0]), 62 | np.array([303.471, 374.93, 0]), 63 | np.array([302.656, 375.315, 0]), 64 | np.array([301.972, 375.546, 0]), 65 | np.array([301.166, 375.736, 0]), 66 | np.array([300.224, 375.859, 0]), 67 | np.array([298.285, 375.953, 0]), 68 | np.array([296.657, 375.957, 0]), 69 | np.array([294.859, 375.787, 0]), 70 | np.array([294.403, 375.672, 0]), 71 | np.array([293.672, 375.397, 0]), 72 | np.array([292.749, 374.913, 0]), 73 | np.array([291.972, 374.442, 0]), 74 | np.array([290.817, 373.659, 0]), 75 | np.array([289.949, 372.98, 0]), 76 | np.array([289.316, 372.386, 0]), 77 | np.array([288.951, 371.975, 0]), 78 | np.array([288.621, 371.532, 0]), 79 | np.array([288.237, 370.902, 0]), 80 | np.array([287.855, 370.102, 0]), 81 | np.array([287.6, 369.378, 0]), 82 | np.array([287.436, 368.697, 0]), 83 | np.array([287.307, 367.822, 0]), 84 | np.array([287.235, 366.977, 0]), 85 | np.array([287.282, 366.009, 0]), 86 | np.array([292.414, 366.022, 0]), 87 | np.array([293.403, 366.042, 0]), 88 | np.array([294.352, 366.039, 0]), 89 | np.array([294.433, 366.942, 0]), 90 | np.array([294.533, 367.926, 0]), 91 | np.array([294.593, 368.426, 0]), 92 | np.array([294.835, 368.99, 0]), 93 | np.array([295.18, 369.352, 0]), 94 | np.array([295.838, 369.706, 0]), 95 | np.array([296.789, 369.93, 0]), 96 | np.array([297.278, 369.977, 0]), 97 | np.array([298.182, 369.937, 0]), 98 | np.array([298.87, 369.745, 0]), 99 | np.array([299.466, 369.41, 0]), 100 | np.array([299.913, 369.01, 0]), 101 | np.array([300.142, 368.711, 0]), 102 | np.array([300.326, 368.337, 0]), 103 | np.array([300.399, 368.005, 0]), 104 | np.array([300.392, 367.466, 0]), 105 | np.array([300.315, 366.959, 0]), 106 | np.array([300.217, 366.476, 0]), 107 | np.array([300.052, 365.885, 0]), 108 | np.array([299.736, 365.153, 0]), 109 | np.array([299.328, 364.545, 0]), 110 | np.array([298.823, 363.99, 0]), 111 | np.array([298.173, 363.384, 0]), 112 | np.array([297.472, 362.763, 0]), 113 | np.array([296.921, 362.255, 0]), 114 | np.array([296.5, 361.84, 0]), 115 | np.array([295.955, 361.235, 0]), 116 | np.array([295.516, 360.609, 0]), 117 | np.array([295.169, 359.915, 0]), 118 | np.array([294.877, 358.949, 0]), 119 | np.array([294.851, 358.451, 0]), 120 | np.array([294.803, 357.471, 0]), 121 | np.array([294.769, 356.475, 0]), 122 | np.array([294.771, 355.811, 0]), 123 | np.array([300.261, 355.911, 0]), 124 | ] 125 | 126 | color = "" 127 | small_circle = None 128 | 129 | if which == "yes": 130 | color = GREEN 131 | clipart = ( 132 | Polygon( 133 | *pnts_yes, 134 | color=color, 135 | fill_color=WHITE, 136 | fill_opacity=1, 137 | ) 138 | .move_to(ORIGIN) 139 | .scale_to_fit_height(height / 2) 140 | ) 141 | elif which == "no": 142 | color = RED 143 | clipart = ( 144 | Polygon( 145 | *pnts_no, 146 | color=color, 147 | fill_color=WHITE, 148 | fill_opacity=1, 149 | ) 150 | .move_to(ORIGIN) 151 | .scale_to_fit_height(height / 2) 152 | ) 153 | elif which == "maybe": 154 | color = ORANGE 155 | clipart = ( 156 | Polygon( 157 | *pnts_maybe, 158 | color=color, 159 | fill_color=WHITE, 160 | fill_opacity=1, 161 | ) 162 | .move_to(ORIGIN) 163 | .scale_to_fit_height(height / 2.5) 164 | ) 165 | small_circle = Circle( 166 | radius=height / 12.5, 167 | color=color, 168 | fill_color=WHITE, 169 | fill_opacity=1, 170 | ).next_to(clipart, DOWN, buff=height / 20) 171 | Group(clipart, small_circle).move_to(ORIGIN) 172 | else: 173 | raise ValueError(f"Invalid value for 'which': {which}") 174 | 175 | circle = Circle( 176 | radius=height / 2, 177 | color=color, 178 | fill_color=color, 179 | fill_opacity=1, 180 | ).move_to(ORIGIN) 181 | 182 | if small_circle is not None: 183 | return Group(circle, clipart, small_circle) 184 | else: 185 | return Group(circle, clipart) 186 | 187 | 188 | def clipart_house(color: ManimColor = RED, height: float = 1, z_index: int = 100): 189 | pnts = [ 190 | np.array([232.535, 333.808, 0.0]), 191 | np.array([277.698, 333.811, 0.0]), 192 | np.array([277.387, 373.503, 0.0]), 193 | np.array([318.11, 373.566, 0.0]), 194 | np.array([318.057, 333.881, 0.0]), 195 | np.array([363.215, 333.935, 0.0]), 196 | np.array([362.703, 419.758, 0.0]), 197 | np.array([368.717, 425.367, 0.0]), 198 | np.array([379.969, 415.454, 0.0]), 199 | np.array([390.258, 426.885, 0.0]), 200 | np.array([297.362, 509.816, 0.0]), 201 | np.array([256.582, 472.796, 0.0]), 202 | np.array([256.626, 497.065, 0.0]), 203 | np.array([232.588, 497.017, 0.0]), 204 | np.array([232.899, 451.371, 0.0]), 205 | np.array([204.978, 426.922, 0.0]), 206 | np.array([215.11, 415.777, 0.0]), 207 | np.array([225.569, 425.578, 0.0]), 208 | np.array([232.235, 419.834, 0.0]), 209 | np.array([232.549, 333.833, 0.0]), 210 | ] 211 | 212 | house = ( 213 | Polygon(*pnts, color=color, fill_color=color, fill_opacity=1, z_index=z_index) 214 | .move_to(0 * DOWN) 215 | .scale_to_fit_height(height) 216 | ) 217 | 218 | return house 219 | 220 | 221 | def clipart_icon(color: ManimColor = BLUE, height: float = 1, z_index: int = 100): 222 | pnts = [ 223 | np.array([407.837, 313.233, 0.0]), 224 | np.array([340.843, 431.234, 0.0]), 225 | np.array([297.995, 558.503, 0.0]), 226 | np.array([253.986, 431.689, 0.0]), 227 | np.array([187.414, 311.624, 0.0]), 228 | ] 229 | 230 | icon = ( 231 | ArcPolygon( 232 | *pnts, 233 | color=color, 234 | arc_config=[ 235 | {"radius": 119.256, "color": color}, 236 | {"radius": 70.9444, "color": color}, 237 | {"radius": 70.9444, "color": color}, 238 | {"radius": 119.256, "color": color}, 239 | {"radius": 216.488, "color": color}, 240 | ], 241 | fill_color=color, 242 | fill_opacity=1, 243 | z_index=z_index, 244 | ) 245 | .move_to(0 * DOWN) 246 | .scale_to_fit_height(height) 247 | ) 248 | 249 | return icon 250 | -------------------------------------------------------------------------------- /np_completeness/utils/util_general.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import random 3 | import sys 4 | from typing import Any, TypeAlias 5 | 6 | import manim 7 | import numpy as np 8 | from manim import * 9 | from manim.typing import InternalPoint3D, Point2D, Point3D 10 | from rich.logging import RichHandler 11 | 12 | ############### DEFAULT OPTIONS 13 | 14 | random.seed(0) 15 | 16 | 17 | def default(): 18 | VMobject.set_default(color=GRAY) 19 | Polygon.set_default(color=RED) 20 | Text.set_default(color=GRAY) 21 | Tex.set_default(color=GRAY) 22 | VMobject.set_default(color=GRAY) 23 | # SurroundingRectangle.set_default(color = RED) 24 | # SurroundingRectangle.set_default(fill_color = config.background_color) 25 | # SurroundingRectangle.set_default(fill_opacity = 1) 26 | 27 | 28 | ############### GENERATING SOUNDS 29 | # self.add_sound(file_name) 30 | 31 | 32 | def random_click_file(): 33 | return f"audio/click/click_{random.randint(0, 3)}.wav" 34 | 35 | 36 | def random_pop_file(): 37 | return f"audio/pop/pop_{random.randint(0, 6)}.wav" 38 | 39 | 40 | def random_whoosh_file(): 41 | return f"audio/whoosh/whoosh_{random.randint(0, 3)}.wav" 42 | 43 | 44 | whoosh_gain = -8 45 | 46 | 47 | def random_tick_file(): 48 | return f"audio/tick/tick_{random.randint(0, 7)}.wav" 49 | 50 | 51 | def random_whoops_file(): 52 | return f"audio/whoops/whoops{random.randint(1, 1)}.mp3" 53 | 54 | 55 | def random_rubik_file(): 56 | return f"audio/cube/r{random.randint(1, 20)}.wav" 57 | 58 | 59 | def random_typewriter_file(): 60 | return f"audio/typewriter/t{random.randint(0, 9)}.wav" 61 | 62 | 63 | def step_sound_file(randomize: bool = True): 64 | if randomize: 65 | return random_tick_file() 66 | else: 67 | return "audio/tick/tick_0.wav" 68 | 69 | 70 | ############### ANIMATIONS 71 | 72 | 73 | def arrive_from(obj: Mobject, dir: InternalPoint3D, buff: float = 0.5): 74 | pos = obj.get_center() 75 | obj.align_to(Point().to_edge(dir, buff=0), -dir).shift(buff * dir) 76 | return obj.animate.move_to(pos) 77 | 78 | 79 | ############### SOLARIZED COLORS 80 | 81 | 82 | # background tones (dark theme) 83 | 84 | BASE03 = "#002b36" 85 | BASE02 = "#073642" 86 | BASE01 = "#586e75" 87 | 88 | # content tones 89 | 90 | BASE00 = "#657b83" # dark gray 91 | BASE0 = "#839496" # light gray 92 | BASE1 = "#93a1a1" 93 | 94 | # background tones (light theme) 95 | 96 | BASE2 = "#eee8d5" 97 | BASE3 = "#fdf6e3" 98 | 99 | # accent tones 100 | 101 | YELLOW = "#d0b700" 102 | YELLOW2 = "#b58900" # The original Solarized yellow 103 | ORANGE = "#c1670c" 104 | ORANGE2 = "#cb4b16" # The original Solarized orange - too close to red 105 | RED = "#dc322f" 106 | MAGENTA = "#d33682" 107 | VIOLET = "#6c71c4" 108 | BLUE = "#268bd2" 109 | CYAN = "#2aa198" 110 | CYAN2 = "#008080" 111 | GREEN = "#859900" 112 | HIGHLIGHT = YELLOW2 113 | 114 | # Alias 115 | GRAY = BASE00 116 | GREY = BASE00 117 | 118 | text_color = GRAY 119 | TEXT_COLOR = GRAY 120 | DALLE_ORANGE = r"#%02x%02x%02x" % (254, 145, 4) 121 | 122 | # whenever more colors are needed 123 | rainbow = [RED, MAGENTA, VIOLET, BLUE, CYAN, GREEN] 124 | # [RED, ORANGE, GREEN, TEAL, BLUE, VIOLET, MAGENTA] 125 | # [GREEN, TEAL, BLUE, VIOLET, MAGENTA, RED, ORANGE] 126 | 127 | from manim import config 128 | 129 | config.background_color = BASE2 130 | BACKGROUND_COLOR_LIGHT = BASE2 131 | BACKGROUND_COLOR_DARK = BASE02 132 | BACKGROUND_COLOR = BACKGROUND_COLOR_LIGHT 133 | 134 | config.max_files_cached = 1000 135 | 136 | 137 | def disable_rich_logging(): 138 | """Disable Manim's Rich-based logger because it's annoying. 139 | 140 | Manim uses the Python package Rich to format its logs. 141 | It tries to split lines nicely, but then it also splits file paths and then you can't 142 | command-click them to open them in the terminal. 143 | """ 144 | # It seems that manim only has the rich handler, but let's remove it specifically 145 | # in case any file handlers are added under some circumstances. 146 | for handler in manim.logger.handlers: 147 | if isinstance(handler, RichHandler): 148 | manim.logger.handlers.remove(handler) 149 | 150 | ANSI_DARK_GRAY = "\033[1;30m" 151 | ANSI_END = "\033[0m" 152 | 153 | # Add a new handler with a given format. Note that the removal above is still needed 154 | # because otherwise we get two copies of the same log messages. 155 | logging.basicConfig( 156 | format=f"{ANSI_DARK_GRAY}%(asctime)s %(levelname)s{ANSI_END} %(message)s", 157 | datefmt="%Y-%m-%d %H:%M:%S", 158 | handlers=[logging.StreamHandler(sys.stdout)], 159 | ) 160 | 161 | 162 | disable_rich_logging() 163 | 164 | #### Video-specific 165 | 166 | 167 | GATE_WIDTH = 0.5 168 | GATE_HEIGHT = 0.3 169 | SIGNAL_SPEED = 2.5 # units per second 170 | 171 | WIRE_COLOR_NONE = BASE00 172 | WIRE_COLOR_TRUE = YELLOW 173 | WIRE_COLOR_FALSE = BASE02 174 | WIRE_WIDTH = 5 175 | WIRE_TIGHT_SPACING = 0.1 176 | 177 | GATE_TEXT_RATIO = 0.4 178 | GATE_X_SPACING = 1.2 179 | GATE_Y_SPACING = 1 180 | DEFAULT_GATE_LENGTH = 0.5 181 | 182 | 183 | def get_wire_color(value: bool | None) -> str: 184 | match value: 185 | case True: 186 | return WIRE_COLOR_TRUE 187 | case False: 188 | return WIRE_COLOR_FALSE 189 | case None: 190 | return WIRE_COLOR_NONE 191 | 192 | 193 | AnyPoint: TypeAlias = Point3D | Point2D 194 | 195 | 196 | def normalize_position(position: AnyPoint) -> InternalPoint3D: 197 | if isinstance(position, tuple): 198 | if len(position) == 2: 199 | position = np.array([*position, 0]) 200 | elif len(position) == 3: 201 | position = np.array(position) 202 | else: 203 | raise ValueError(f"Invalid position: {position}") 204 | else: 205 | if position.shape == (2,): 206 | position = np.array([*position, 0]) 207 | elif position.shape == (3,): 208 | pass 209 | else: 210 | raise ValueError(f"Invalid position: {position}") 211 | 212 | return position.astype(np.float64) 213 | 214 | 215 | def animate(mobject: Mobject) -> Any: 216 | """A hack to work around the fact that Manim's typing for .animate sucks.""" 217 | return mobject.animate 218 | 219 | 220 | def center_of(mobject: Mobject) -> InternalPoint3D: 221 | """Another hack to work around Manim's typing. 222 | 223 | It thinks get_center() might return a tuple of floats, but that never happens. 224 | """ 225 | center = mobject.get_center() 226 | assert isinstance(center, np.ndarray) 227 | return center 228 | 229 | 230 | def submobjects_of(mobject: VMobject) -> list[VMobject]: 231 | return mobject.submobjects 232 | 233 | 234 | eq_str = r"{{$\,=\,$}}" 235 | not_str = r"{{NOT\;}}" 236 | and_str = r"{{\;AND\;}}" 237 | or_str = r"{{\;OR\;}}" 238 | left_str = r"{{$($}}" 239 | right_str = r"{{$)$}}" 240 | x1_str = r"{{$a$}}" 241 | x2_str = r"{{$b$}}" 242 | x3_str = r"{{$c$}}" 243 | x4_str = r"{{$d$}}" 244 | x5_str = r"{{$e$}}" 245 | x6_str = r"{{$f$}}" 246 | true_str = r"{{TRUE}}" 247 | false_str = r"{{FALSE}}" 248 | one_str = r"{{1}}" 249 | zero_str = r"{{0}}" 250 | 251 | 252 | def coltex(s: str, color: str = GRAY, **kwargs: Any) -> Tex: 253 | d = { 254 | not_str: RED, 255 | or_str: BLUE, 256 | and_str: ORANGE, 257 | true_str: WIRE_COLOR_TRUE, 258 | false_str: WIRE_COLOR_FALSE, 259 | one_str: WIRE_COLOR_TRUE, 260 | zero_str: WIRE_COLOR_FALSE, 261 | } 262 | tex = Tex(s, **kwargs) 263 | for subtex in tex: 264 | new_color = d.get("{{" + subtex.get_tex_string() + "}}", color) 265 | subtex.set_color(new_color) 266 | return tex 267 | -------------------------------------------------------------------------------- /postproduction/audio/Thannoid.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/postproduction/audio/Thannoid.mp3 -------------------------------------------------------------------------------- /postproduction/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/postproduction/images/.keep -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "np_completeness" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["The Polylog Team"] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10,<3.13" 10 | manim = "^0.18.1" 11 | numpy = "^1.26" 12 | python-sat = {extras = ["aiger", "approxmc"], version = "^1.8.dev13"} 13 | pydantic = "^2.8.2" 14 | matplotlib = "^3.9.0" 15 | 16 | [tool.poetry.group.dev.dependencies] 17 | pyright = "^1.1.372" 18 | pytest = "^8.3.1" 19 | 20 | [build-system] 21 | requires = ["poetry-core"] 22 | build-backend = "poetry.core.masonry.api" 23 | 24 | [tool.ruff] 25 | 26 | 27 | 28 | ignore = [ 29 | # `from X import *` used; unable to detect undefined names. 30 | # It's an unfortunate manim convention to use `from manim import *`. 31 | "F403", 32 | # X may be undefined, or defined from star imports. 33 | # Same reason. 34 | "F405", 35 | # Ambiguous variable name: `l` 36 | # This one is kind of pedantic. 37 | "E741", 38 | ] 39 | 40 | select = [ 41 | "I", # Sort imports 42 | "F401", # Remove unused imports 43 | ] 44 | 45 | [tool.pyright] 46 | 47 | include = [ 48 | "np_completeness/", 49 | "tests/", 50 | # "code/", # We might want to include this in the future 51 | ] 52 | 53 | typeCheckingMode = "strict" 54 | 55 | # `from manim import *` is an unfortunate Manim convention. 56 | reportWildcardImportFromLibrary = false 57 | 58 | # In strict mode, Pyright wants to know the types of all variables. 59 | # This is too strict. 60 | reportUnknownArgumentType = false 61 | reportUnknownLambdaType = false 62 | reportUnknownMemberType = false 63 | reportUnknownParameterType = false 64 | reportUnknownVariableType = false 65 | 66 | reportMissingTypeStubs = false 67 | 68 | # We redefine some Manim colors - it's hacky because then the import order matters, 69 | # but it also makes some things easier. 70 | reportConstantRedefinition = false 71 | -------------------------------------------------------------------------------- /render_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # exit on error 4 | set -eo pipefail 5 | 6 | # cd to script dir 7 | cd "$(dirname "$0")" 8 | cd manim/ 9 | 10 | filenames="anims.py prng.py statistical_test.py" 11 | 12 | for filename in $filenames; do 13 | manim -qk $filename --write_all 14 | done 15 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polylog-cs/np-completeness/e0d50c6b735c32fcbdb62d331b423d232e15e3d7/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_circuit.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from np_completeness.utils.circuit import ( 4 | Circuit, 5 | CircuitEvaluation, 6 | ) 7 | from np_completeness.utils.gate import AND_TABLE, OR_TABLE, Gate 8 | 9 | 10 | def make_circuit_fixture() -> Circuit: 11 | """Make a simple example circuit with 3 inputs and 2 outputs.""" 12 | circuit = Circuit() 13 | 14 | for i, out_value in enumerate([False, True, True]): 15 | circuit.add_gate( 16 | f"input_{i}", 17 | Gate( 18 | truth_table={(): (out_value,)}, 19 | position=np.array([i, 3, 0]), 20 | visual_type="constant", 21 | ), 22 | ) 23 | 24 | circuit.add_gate( 25 | "and_gate", 26 | Gate(truth_table=AND_TABLE, position=np.array([1.5, 1, 0]), visual_type="and"), 27 | ) 28 | circuit.add_gate( 29 | "or_gate", 30 | Gate(truth_table=OR_TABLE, position=np.array([0.5, 0, 0]), visual_type="or"), 31 | ) 32 | circuit.add_gate( 33 | "knot", Gate.make_knot(np.array([1, -1, 0]), n_inputs=1, n_outputs=2, length=0) 34 | ) 35 | 36 | for i in range(2): 37 | circuit.add_gate( 38 | f"output_{i}", 39 | Gate.make_knot(np.array([i, -3, 0]), n_inputs=1, n_outputs=0, length=0), 40 | ) 41 | 42 | circuit.wires = [ 43 | ("input_0", "or_gate"), 44 | ("input_1", "and_gate"), 45 | ("input_2", "and_gate"), 46 | ("and_gate", "or_gate"), 47 | ("or_gate", "knot"), 48 | ("knot", "output_0"), 49 | ("knot", "output_1"), 50 | ] 51 | 52 | circuit.check() 53 | return circuit 54 | 55 | 56 | def test_evaluate(): 57 | circuit = make_circuit_fixture() 58 | evaluation: CircuitEvaluation = circuit.evaluate() 59 | 60 | gate_evaluations = evaluation.gate_evaluations 61 | 62 | assert gate_evaluations["or_gate"].input_values == (False, True) 63 | assert gate_evaluations["and_gate"].input_values == (True, True) 64 | assert gate_evaluations["knot"].input_values == (True,) 65 | 66 | assert ( 67 | 0 68 | == gate_evaluations["input_0"].reach_time 69 | == gate_evaluations["input_1"].reach_time 70 | == gate_evaluations["input_2"].reach_time 71 | < gate_evaluations["and_gate"].reach_time 72 | < gate_evaluations["or_gate"].reach_time 73 | < gate_evaluations["knot"].reach_time 74 | # Outputs have different reach times because of differing wire lengths 75 | < gate_evaluations["output_1"].reach_time 76 | < gate_evaluations["output_0"].reach_time 77 | ) 78 | 79 | 80 | def test_reverse(): 81 | circuit = make_circuit_fixture() 82 | circuit = circuit.reverse() 83 | evaluation: CircuitEvaluation = circuit.evaluate() 84 | 85 | expected = [ 86 | ("input_0", (False,)), 87 | ("input_1", (True,)), 88 | ("input_2", (True,)), 89 | ("and_gate", (True,)), 90 | ("or_gate", (True,)), 91 | ("knot", (True, True)), 92 | ("output_0", ()), 93 | ("output_1", ()), 94 | ] 95 | 96 | for gate_name, expected_value in expected: 97 | assert ( 98 | evaluation.get_gate_inputs(gate_name) == expected_value 99 | ), f"Gate {gate_name} failed" 100 | 101 | assert evaluation.gate_evaluations["knot"].reach_time == max( 102 | circuit.get_wire_length("knot", "output_0"), 103 | circuit.get_wire_length("knot", "output_1"), 104 | ) 105 | 106 | # Reverse back to the original - the gate truth tables are lost 107 | # because they're not invertible, but the evaluation should still work 108 | circuit = circuit.reverse() 109 | evaluation: CircuitEvaluation = circuit.evaluate() 110 | 111 | for gate_name, expected_value in expected: 112 | # Notice we're checking outputs and not inputs now 113 | assert ( 114 | evaluation.get_gate_outputs(gate_name) == expected_value 115 | ), f"Gate {gate_name} failed" 116 | -------------------------------------------------------------------------------- /tests/test_manim_circuit.py: -------------------------------------------------------------------------------- 1 | from typing import cast 2 | 3 | import numpy as np 4 | from manim import RIGHT, Animation, Scene 5 | 6 | from np_completeness.utils.manim_circuit import ManimCircuit 7 | from tests.test_circuit import make_circuit_fixture 8 | 9 | 10 | def test_animation_after_shift(): 11 | circuit = make_circuit_fixture() 12 | manim_circuit = ManimCircuit(circuit, scale=2) 13 | 14 | scene = Scene() 15 | 16 | scene.add(manim_circuit) 17 | scene.play(cast(Animation, manim_circuit.animate.shift(RIGHT).scale(0.8))) 18 | 19 | positions_1 = [m.get_center() for m in manim_circuit.submobjects] 20 | scene.wait() 21 | scene.play(manim_circuit.animate_evaluation(scene=scene, speed=5)) 22 | scene.wait() 23 | positions_2 = [m.get_center() for m in manim_circuit.submobjects] 24 | 25 | # There was a bug where .animate_evaluation() would move mobjects to the positions 26 | # they were at before the shift. This test checks that this doesn't happen any more. 27 | for p1, p2 in zip(positions_1, positions_2): 28 | np.testing.assert_almost_equal(p1, p2) 29 | -------------------------------------------------------------------------------- /tests/test_specific_circuits.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from np_completeness.utils.gate import ADD_TABLE 4 | from np_completeness.utils.specific_circuits import ( 5 | MULTIPLICATION_CIRCUIT_SIZE, 6 | make_adder_circuit, 7 | make_multiplication_circuit, 8 | to_binary, 9 | ) 10 | 11 | 12 | @pytest.mark.parametrize( 13 | "a,b", 14 | [ 15 | (0, 0), 16 | (0, 1), 17 | (1, 1), 18 | (3, 1), 19 | (3, 5), 20 | (2**MULTIPLICATION_CIRCUIT_SIZE - 1, 1), 21 | (2**MULTIPLICATION_CIRCUIT_SIZE - 1, 3), 22 | (2**MULTIPLICATION_CIRCUIT_SIZE - 1, 2**MULTIPLICATION_CIRCUIT_SIZE - 1), 23 | ], 24 | ) 25 | def test_multiplication_circuit(a: int, b: int): 26 | for cur_a, cur_b in [(a, b), (b, a)]: # Test commutativity 27 | circuit = make_multiplication_circuit(to_binary(a), to_binary(b)) 28 | evaluation = circuit.evaluate() 29 | 30 | expected = to_binary(cur_a * cur_b, n_digits=MULTIPLICATION_CIRCUIT_SIZE * 2) 31 | actual = [ 32 | evaluation.get_gate_inputs(f"output_{i}")[0] 33 | for i in range(MULTIPLICATION_CIRCUIT_SIZE * 2) 34 | ] 35 | 36 | assert expected == actual, f"Expected {expected}, got {actual}" 37 | 38 | 39 | def test_adder_circuit(): 40 | for inputs, (lower_bit, upper_bit) in ADD_TABLE.items(): 41 | circuit = make_adder_circuit(list(inputs)) 42 | evaluation = circuit.evaluate() 43 | assert ( 44 | evaluation.get_simplified_value("lower_output") == lower_bit 45 | ), f"Wrong lower bit for {inputs}" 46 | assert ( 47 | evaluation.get_simplified_value("upper_output") == upper_bit 48 | ), f"Wrong upper bit for {inputs}" 49 | --------------------------------------------------------------------------------