├── .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 |
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 |
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 |
--------------------------------------------------------------------------------