|
64 | 64 | },
|
65 | 65 | {
|
66 | 66 | "cell_type": "code",
|
67 |
| - "execution_count": 28, |
| 67 | + "execution_count": 41, |
68 | 68 | "metadata": {
|
69 | 69 | "id": "p5Ki_HvOJUWk",
|
70 | 70 | "tags": []
|
|
258 | 258 | },
|
259 | 259 | {
|
260 | 260 | "cell_type": "code",
|
261 |
| - "execution_count": 31, |
| 261 | + "execution_count": 44, |
262 | 262 | "metadata": {
|
263 | 263 | "id": "lwP0r3BAaxjt",
|
264 | 264 | "tags": []
|
|
331 | 331 | },
|
332 | 332 | {
|
333 | 333 | "cell_type": "code",
|
334 |
| - "execution_count": 32, |
| 334 | + "execution_count": 45, |
335 | 335 | "metadata": {
|
336 | 336 | "id": "Y6nbd6WMryWi",
|
337 | 337 | "tags": []
|
|
359 | 359 | },
|
360 | 360 | {
|
361 | 361 | "cell_type": "code",
|
362 |
| - "execution_count": 33, |
| 362 | + "execution_count": 46, |
363 | 363 | "metadata": {
|
364 | 364 | "id": "A8sU4Ez_bBKl",
|
365 | 365 | "tags": []
|
|
529 | 529 | },
|
530 | 530 | {
|
531 | 531 | "cell_type": "code",
|
532 |
| - "execution_count": 34, |
| 532 | + "execution_count": 47, |
533 | 533 | "metadata": {
|
534 | 534 | "id": "DT5FSYliC9wp",
|
535 | 535 | "tags": []
|
|
8963 | 8963 | },
|
8964 | 8964 | {
|
8965 | 8965 | "cell_type": "code",
|
8966 |
| - "execution_count": 37, |
| 8966 | + "execution_count": 50, |
8967 | 8967 | "metadata": {},
|
8968 | 8968 | "outputs": [],
|
8969 | 8969 | "source": [
|
|
9147 | 9147 | "\n",
|
9148 | 9148 | "E.g. we might need to do swaps like this:\n",
|
9149 | 9149 | "\n",
|
9150 |
| - "<img src=\"https://aoc.just2good.co.uk/assets/images/gate-pairs.png\" width=\"480\" alt=\"Gate swaps\" />\n", |
| 9150 | + "<img src=\"https://aoc.just2good.co.uk/assets/images/gate-pairs.png\" width=\"420\" alt=\"Gate swaps\" />\n", |
9151 | 9151 | "\n",
|
9152 | 9152 | "E.g. from:\n",
|
9153 | 9153 | "\n",
|
|
9229 | 9229 | "\n",
|
9230 | 9230 | "A single adder can be depicted like this:\n",
|
9231 | 9231 | "\n",
|
9232 |
| - "<img src=\"https://aoc.just2good.co.uk/assets/images/1-bit-fa.png\" width=\"420\" alt=\"Full Adder\" />\n", |
| 9232 | + "<img src=\"https://aoc.just2good.co.uk/assets/images/1-bit-fa.png\" width=\"280px\" alt=\"Full Adder\" />\n", |
9233 | 9233 | "\n",
|
9234 | 9234 | "So we can implement a multi-bit ripple adder like this:\n",
|
9235 | 9235 | "\n",
|
9236 |
| - "<img src=\"https://aoc.just2good.co.uk/assets/images/ripple-adder.png\" width=\"420\" alt=\"Ripple Adder\" />\n", |
| 9236 | + "<img src=\"https://aoc.just2good.co.uk/assets/images/ripple-adder.png\" width=\"480px\" alt=\"Ripple Adder\" />\n", |
9237 | 9237 | "\n",
|
9238 | 9238 | "And this is how we can generate a single 5-bit number from the addition of two 4-bit numbers.\n",
|
9239 | 9239 | "\n",
|
|
9250 | 9250 | "source": [
|
9251 | 9251 | "op_by_ouput = {}\n",
|
9252 | 9252 | "\n",
|
9253 |
| - "def get_code_for_output(wire: str) -> str:\n", |
| 9253 | + "def get_code_for_wire(wire: str) -> str:\n", |
9254 | 9254 | " \"\"\" Generate a string representation of the logical operations needed \n",
|
9255 | 9255 | " to compute the value of a given wire. Uses recursion to handle dependencies between operations. \"\"\"\n",
|
9256 | 9256 | " \n",
|
9257 | 9257 | " # Base case - exit once we have a wire that requires no further op to calculate\n",
|
9258 |
| - " if wire not in op_by_ouput:\n", |
| 9258 | + " if wire not in op_by_ouput: # I.e. this wire is not the output of an operation\n", |
9259 | 9259 | " return wire\n",
|
9260 | 9260 | " \n",
|
| 9261 | + " # Otherwise, get the operation required to output to this wire\n", |
9261 | 9262 | " left, right, gate = op_by_ouput[wire]\n",
|
9262 | 9263 | " \n",
|
9263 |
| - " # Recursive calls\n", |
9264 | 9264 | " # If the left or right inputs are themselves the result of other operations, \n",
|
9265 | 9265 | " # these recursive calls will generate the code for those operations first.\n",
|
9266 |
| - " left_code = get_code_for_output(left)\n", |
9267 |
| - " right_code = get_code_for_output(right)\n", |
9268 |
| - " \n", |
9269 |
| - " # Prevents (a & b) and (b & a) from being different strings.\n", |
9270 |
| - " left_code, right_code = sorted((left_code, right_code))\n", |
| 9266 | + " # Sort so that (a & b) and (b & a) are the same.\n", |
| 9267 | + " left_code, right_code = sorted((get_code_for_wire(left), get_code_for_wire(right)))\n", |
9271 | 9268 | " \n",
|
9272 | 9269 | " match gate:\n",
|
9273 |
| - " case \"AND\":\n", |
9274 |
| - " return f\"({left_code} & {right_code})\"\n", |
9275 |
| - " case \"OR\":\n", |
9276 |
| - " return f\"({left_code} | {right_code})\"\n", |
9277 |
| - " case \"XOR\":\n", |
9278 |
| - " return f\"({left_code} ^ {right_code})\"\n", |
| 9270 | + " case \"AND\": gate_code = \"&\"\n", |
| 9271 | + " case \"OR\": gate_code = \"|\"\n", |
| 9272 | + " case \"XOR\": gate_code = \"^\"\n", |
| 9273 | + "\n", |
| 9274 | + " return f\"({left_code} {gate_code} {right_code})\"\n", |
9279 | 9275 | " \n",
|
9280 | 9276 | "def create_vis(wires: dict):\n",
|
| 9277 | + " \"\"\" Creates a visual representation of a circuit using Graphviz. \"\"\"\n", |
| 9278 | + " \n", |
9281 | 9279 | " dot = graphviz.Digraph(format='png', engine='dot')\n",
|
9282 | 9280 | " \n",
|
9283 |
| - " # Define gate shapes, colors, and output styles\n", |
| 9281 | + " # Define gate shapes, colors, and output styles for different types of gate\n", |
9284 | 9282 | " gate_styles = {\n",
|
9285 | 9283 | " \"AND\": {\"shape\": \"invtrapezium\", \"fillcolor\": \"lightblue\"},\n",
|
9286 | 9284 | " \"OR\": {\"shape\": \"invtriangle\", \"fillcolor\": \"lightgreen\"},\n",
|
9287 | 9285 | " \"XOR\": {\"shape\": \"box\", \"fillcolor\": \"lightyellow\"},\n",
|
9288 | 9286 | " }\n",
|
| 9287 | + " \n", |
| 9288 | + " # Define styles for output nodes based on their initial character\n", |
9289 | 9289 | " output_styles = {\n",
|
9290 | 9290 | " \"z\": {\"fillcolor\": \"black\", \"fontcolor\": \"white\"},\n",
|
9291 | 9291 | " \"x\": {\"fillcolor\": \"blue\", \"fontcolor\": \"white\"},\n",
|
|
9301 | 9301 | " for output in op_by_ouput:\n",
|
9302 | 9302 | " left, right, gate = op_by_ouput[output]\n",
|
9303 | 9303 | "\n",
|
9304 |
| - " # Create gate node in a box\n", |
| 9304 | + " # Create gate node\n", |
9305 | 9305 | " gate_label = f'{gate}'\n",
|
9306 | 9306 | " gate_name = f'{left}_{right}_{gate}' # Unique gate name\n",
|
9307 | 9307 | " gate_style = gate_styles.get(gate, {\"shape\": \"box\"})\n",
|
|
9317 | 9317 | "\n",
|
9318 | 9318 | " # Connect gate to output\n",
|
9319 | 9319 | " output_style = output_styles.get(output[0], {}) \n",
|
9320 |
| - " dot.node(output, output, shape='circle', style='filled', **output_style) # Output node as circle\n", |
| 9320 | + " dot.node(output, output, shape='circle', style='filled', **output_style)\n", |
9321 | 9321 | " dot.edge(gate_name, output)\n",
|
9322 | 9322 | "\n",
|
9323 | 9323 | " # Render the graph to a file\n",
|
9324 | 9324 | " output_path = 'adder_operations'\n",
|
9325 |
| - " dot.render(f\"{locations.output_dir}/{output_path}\") # Saves the output to 'circuit_graph.png'\n", |
| 9325 | + " dot.render(f\"{locations.output_dir}/{output_path}\") # Saves the output as a png\n", |
9326 | 9326 | " display(Image(f\"{locations.output_dir}/{output_path}.png\"))\n",
|
9327 | 9327 | "\n",
|
9328 | 9328 | "def visualise_operations(data: str):\n",
|
|
9331 | 9331 | " for output in outputs:\n",
|
9332 | 9332 | " op_by_ouput[output] = (left, right, gate)\n",
|
9333 | 9333 | " \n",
|
| 9334 | + " # View text-based representation of all wires\n", |
9334 | 9335 | " for output in sorted(op_by_ouput.keys()):\n",
|
9335 |
| - " logger.info(f\"{output} = {get_code_for_output(output)}\")\n", |
| 9336 | + " print(f\"{output} = {get_code_for_wire(output)}\")\n", |
9336 | 9337 | " \n",
|
9337 |
| - " create_vis(wires)\n", |
| 9338 | + " create_vis(wires) # Create a graphical representation\n", |
9338 | 9339 | " \n",
|
9339 | 9340 | "soln = visualise_operations(input_data)"
|
9340 | 9341 | ]
|
|
0 commit comments