# Spacerat/circuits.ipynb Last active Aug 29, 2015
 { "metadata": { "name": "Circuit Tree" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "code", "collapsed": false, "input": "%pylab inline", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "Populating the interactive namespace from numpy and matplotlib\n" } ], "prompt_number": 13 }, { "cell_type": "markdown", "metadata": {}, "source": "# Circuit tree calculator\n\n## Basics\n\nCircuits are created from Impedence Havers. An Impedence Haver is anything which returns a value of **get_impedence(*frequency*)**.\n\nResistors, Capacitors and Inductors are simple Impedence Havers which return a value based on their value.\n\n## Blocks\n\nBlocks are a special kind of impedence haver. Blocks have a *branches* variable, which is a list of branches. Each branch is a list of Impedence Havers.\n\nA Block's **get_impedence()** function performs calculates:\n\n$$\n\\frac{1}{\\sum_{b=1}^{branches} (\\frac{1}{\\sum_{c=1}^{components} Z_c})}\n$$\n\nFor each component $c$ of each branch $b$ of the block. In the case that there is only one branch, the block is a serial circuit instead of parallel. The impedence calculation formula does *not* need to change, since it simplifies to\n\n$$\n\\frac{1}{\\frac{1}{\\sum_{c=1}^{components} Z}} = \\sum_{c=1}^{components} Z\n$$\n\n## Possible improvements:\n\n- A faster string parser which does not use recursion.\n- Graphical representation (graphviz would be good for this - you can generat a *dot* file and feed it to Graphviz.\n- Better treatment of infinite/zero impedences" }, { "cell_type": "code", "collapsed": false, "input": "\n\nclass Impedence_Haver(object):\n \"\"\"Anything which can have an impedence.\"\"\"\n def get_impedence(frequency):\n raise \"Not Implimented\"\n \n def __str__(self):\n \"\"\"The default function for representing a component, looks like:\n Kind[Value]\n \"\"\"\n return \"{}{}\".format(self.__class__.__name__, self.value)\n \n def __repr__(self):\n return \"{}({})\".format(self.__class__.__name__, self.value)\n\nclass Block(Impedence_Haver):\n \"\"\"A block is an impedence haver.\n It contains a list of branches, and each branch is a list of impedence-havers\"\"\"\n \n \n def __init__(self, contents, debug=False):\n \"\"\"Define the contents of this block\"\"\"\n # If the block is passed a string, use init_from_string()\n if isinstance(contents, basestring):\n self.init_from_string(contents, debug=debug)\n\n #Otherwise, asssume that the conents variable is a list of lists (a list of branches)\n elif contents is not None:\n self.branches = contents\n else:\n raise \"Error\"\n \n \n def add_component_from_string(self, branch, text):\n \"\"\"Create a new component based on some text, and add it to a list\"\"\"\n\n # Do nothing for empty text\n if len(text) == 0:\n return\n \n # Otherwise, create LRC objects. \n # This just uses the text after L/R/C as the component parameter, but\n # it could be replaced with a lookup table, map, or some other scheme.\n if text == \"R\":\n branch.append(R(float(text[1:])))\n elif text == \"C\":\n branch.append(C(float(text[1:])))\n elif text == \"L\":\n branch.append(L(float(text[1:])))\n else:\n branch.append(R(1))\n \n def init_from_string(self, s, debug=False):\n \"\"\"Recursively parse a string to create a block\n \n Note: This is not the fastest way of doing this, but it is fairly easy to undersand. \n With this recursive strategy, for a string such as {{{{{{{}}}}}}}, each character would\n be processed one additonal time for each level down it goes. \n \n It would be possible (and not too hard) to write a function which only needs one pass\n over the string. It would need to keep track of a *stack* of Block() objects. Each time\n you enter a curly-brace, you would make a new block and push it on to the stack. \n Each time you exit a curly brace, you would pop the current block from the stack, and\n then add it as a component to its parent.\n \n See: https://github.com/Spacerat/JoeBot2/blob/master/modules/dynamic_core.py#L213\n for an example of doing it without recursion. Instead of using a stack, each 'node'\n knows who its parent is:\n \n At line 225, when an opening tag \"<...>\" is seen, the current_node is given a new child\n and that child is set as the current_node.\n At line 221, When a closing tag \"\" is seen, the current_node's parent is set as\n the current_node.\n \"\"\"\n \n self.branches = [] # Initialise the internal list of branches\n text = \"\" # All of the text seen so far for the current component\n bracket_count = 0 # The current 'level' of curly brackets\n current_branch = [] # The current branch being built\n \n if debug:\n print \"Block text > \",s\n \n # Iterate through each character of the string\n for i, char in enumerate(s):\n # Ignore whitespace (this must come first)\n if char == \" \" or char == \"\\t\":\n continue\n \n # At the lowest level, add components to this block's branches\n if bracket_count == 0:\n # When we see one of \",{-\", the previous component is finished\n # which means it needs to be added to the current branch *before\n # doing anything else*. \n # This is done with self.add_component_from_string()\n \n # Go down a level if we see {\n if char == \"{\":\n self.add_component_from_string(current_branch, text)\n text = \"\"\n bracket_count +=1\n \n # } at the bottom level means that the string is invalid\n elif char == \"}\":\n raise Exception(\"Error processing string \\\"{}\\\" at character {} - mismatched bracket\".format(s, i))\n \n # , means a new branch. Push the current branch to the list of branches.\n elif char == \",\":\n self.add_component_from_string(current_branch, text)\n text = \"\"\n self.branches.append(current_branch)\n current_branch = []\n \n # - means a new component in serial, so just push the current component\n # to the current branch.\n elif char == \"-\":\n self.add_component_from_string(current_branch, text)\n text = \"\"\n \n #Anything other text is part of the current component\n else:\n text+=char\n \n # Inside curly brackets, just wait for closing curly brackets ...\n elif bracket_count > 0:\n # (Counting brackets)\n if char == \"{\":\n bracket_count +=1\n text+=char\n elif char == \"}\":\n bracket_count -= 1\n # Until getting back to the top level.\n if bracket_count == 0:\n # Then *RECURSE*. Make a new block with the 'text' string,\n # which contains the entirety of the {curly {bracket} text}\n current_branch.append(Block(text, debug=debug))\n text = \"\"\n else:\n text+=char\n else:\n text+=char\n\n # At the end, make sure we're back to 0 brackets.\n if bracket_count != 0:\n raise Exception(\"Error processing string \\\"{}\\\" at character {} - mismatched bracket\".format(s, i))\n \n # Then add the last component seen to the current branch\n self.add_component_from_string(current_branch, text)\n # and add the current branch to the list of branches\n self.branches.append(current_branch)\n\n def get_impedence(self, frequency = 1.0):\n \"\"\"Get the impedence of this entire block\"\"\"\n # We could check if there is only a single branch and then do something different here,\n # But there isn't any point.\n \n # This iterates over every branch, getting the reciprocal of the sum of the impedence of each object in the branch \n def branch_imp():\n for b in self.branches:\n yield 1.0/sum((x.get_impedence(frequency) for x in b))\n \n # This sums the branches and takes the reciprocal\n return 1.0 / sum(branch_imp())\n \n def __str__(self):\n \"\"\"Get a string representation of the block,\n which can be be used by init_from_string() to create\n an identical block\"\"\"\n innerstr = \",\".join((\"-\".join((str(x) for x in b))) for b in self.branches)\n \n # Onlt use {...} if the block has multiple branches\n if len(innerstr) == 0:\n return \"{ }\"\n elif len(self.branches) == 1:\n return innerstr\n else:\n return \"{\"+innerstr+\"}\"\n \n def __repr__(self):\n \"\"\"Get a Python-code representation of the block,\n which can be executed to create an identical block\"\"\"\n innerstr = \",\".join(\"[\"+(\",\".join((repr(x) for x in b)))+\"]\" for b in self.branches)\n return \"Block([\"+innerstr+\"])\"\n\n\n \n# These are the component classes, defining resistors, capacitors and inductors.\n\n# They derive their __str__ function from Impedence_Haver\n\n# They convert their inputs to numpy classes float64 and complex_, so that division by 0\n# doesn't cause crashes.\n\nclass R(Impedence_Haver):\n \"\"\"Resistor\"\"\"\n def __init__(self, resistance):\n self.value = float64(resistance)\n \n def get_impedence(self, frequency):\n \"\"\"impedence of R + 0j\"\"\"\n return complex_(complex(self.value, 0))\n \nclass C(Impedence_Haver):\n \"\"\"Capacitor\"\"\"\n def __init__(self, capacitance):\n self.value = float64(capacitance)\n \n def get_impedence(self, frequency):\n \"\"\"impedence of 0 + -1/(f*C)j\"\"\"\n return complex_(complex(0, -1.0/(self.value * frequency)))\n\nclass L(Impedence_Haver):\n \"\"\"Inductor\"\"\"\n def __init__(self, inductance):\n self.value = float64(inductance)\n def get_impedence(self, frequency):\n \"\"\"impedence of 0 + (f*L)j\"\"\"\n return complex_(complex(0, frequency * self.value))\n ", "language": "python", "metadata": {}, "outputs": [], "prompt_number": 303 }, { "cell_type": "markdown", "metadata": {}, "source": "## Tests\n\n### " }, { "cell_type": "code", "collapsed": false, "input": "serial = Block([[R(1), R(2)]])\nprint serial\nprint serial.get_impedence()\n", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "R1.0-R2.0\n(3+0j)\n" } ], "prompt_number": 304 }, { "cell_type": "code", "collapsed": false, "input": "", "language": "python", "metadata": {}, "outputs": [], "prompt_number": 304 }, { "cell_type": "code", "collapsed": false, "input": "parallel = Block([[serial], [serial]])\n\nprint parallel\nprint parallel.get_impedence()", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "{R1.0-R2.0,R1.0-R2.0}\n(1.5+0j)\n" } ], "prompt_number": 305 }, { "cell_type": "code", "collapsed": false, "input": "complicated = Block([[C(2), R(1)], [parallel], [serial]])\nprint complicated\nprint complicated.get_impedence(1)\nprint complicated.get_impedence(5)\nprint complicated.get_impedence(10)\nprint\nprint repr(complicated)", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "{C2.0-R1.0,{R1.0-R2.0,R1.0-R2.0},R1.0-R2.0}\n(0.529411764706-0.117647058824j)\n(0.501246882793-0.0249376558603j)\n(0.500312304809-0.0124921923798j)\n\nBlock([[C(2.0),R(1.0)],[Block([[Block([[R(1.0),R(2.0)]])],[Block([[R(1.0),R(2.0)]])]])],[Block([[R(1.0),R(2.0)]])]])\n" } ], "prompt_number": 306 }, { "cell_type": "code", "collapsed": false, "input": "Block([[C(2.0),R(1.0)],[Block([[Block([[R(1.0),R(2.0)]])],[Block([[R(1.0),R(2.0)]])]])],[Block([[R(1.0),R(2.0)]])]])", "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 307, "text": "Block([[C(2.0),R(1.0)],[Block([[Block([[R(1.0),R(2.0)]])],[Block([[R(1.0),R(2.0)]])]])],[Block([[R(1.0),R(2.0)]])]])" } ], "prompt_number": 307 }, { "cell_type": "code", "collapsed": false, "input": "# Test some examples of string inputs\ntest_strings = [\n \"R1 - R2\",\n \"{{{R1 - C2}}}\",\n \"R1 - {R2 - R2, C2 - C2} - R1\",\n \"{R1, L1 {R1, L1 { R1, L1 {R1}} R2} R2}\",\n \"- R5 - -\"\n]\n\nfor s in test_strings:\n block = Block(s, debug=True)\n print \"Generated string:\",block\n print \"Z =\", block.get_impedence()\n print\n", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "Block text > R1 - R2\nGenerated string: R1.0-R2.0\nZ = (3+0j)\n\nBlock text > {{{R1 - C2}}}\nBlock text > {{R1-C2}}\nBlock text > {R1-C2}\nBlock text > R1-C2\nGenerated string: R1.0-C2.0\nZ = (1-0.5j)\n\nBlock text > R1 - {R2 - R2, C2 - C2} - R1\nBlock text > R2-R2,C2-C2\nGenerated string: R1.0-{R2.0-R2.0,C2.0-C2.0}-R1.0\nZ = (2.23529411765-0.941176470588j)\n\nBlock text > {R1, L1 {R1, L1 { R1, L1 {R1}} R2} R2}\nBlock text > R1,L1{R1,L1{R1,L1{R1}}R2}R2\nBlock text > R1,L1{R1,L1{R1}}R2\nBlock text > R1,L1{R1}\nBlock text > R1\nGenerated string: {R1.0,L1.0-{R1.0,L1.0-{R1.0,L1.0-R1.0}-R2.0}-R2.0}\nZ = (0.75387420237+0.0711030082042j)\n\nBlock text > - R5 - -\nGenerated string: R5.0\nZ = (5+0j)\n\n" } ], "prompt_number": 315 }, { "cell_type": "code", "collapsed": false, "input": "# Test that error checking works\ntry:\n Block(\"{R1}}\")\nexcept Exception as e:\n print e\n\ntry:\n Block(\"{R1{}\")\nexcept Exception as e:\n print e ", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "Error processing string \"{R1}}\" at character 4 - mismatched bracket\nError processing string \"{R1{}\" at character 4 - mismatched bracket\n" } ], "prompt_number": 309 }, { "cell_type": "code", "collapsed": false, "input": "# Show the conversion between an original string, generated string output, and generated code output\noriginal_string = \"{R1,R2{{R3,C1-C2,{L1-L2,L3-L4}},R4{R5,R6-R7}}}\"\nblock = Block(original_string)\nblock_to_string = str(block)\nstring_to_block = Block(block_to_string)\n\nprint \"Original string:\"\nprint original_string\nprint\nprint \"str(Block(original_string)):\"\nprint block_to_string\nprint\nprint \"str(Block(str(Block(original_string)))):\"\nprint string_to_block.__str__()\nprint\nprint \"Generated Python-code representation\"\nprint repr(string_to_block)\nprint\nprint \"Testing the generated code\"\nprint eval(repr(string_to_block))", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "Original string:\n{R1,R2{{R3,C1-C2,{L1-L2,L3-L4}},R4{R5,R6-R7}}}\n\nstr(Block(original_string)):\n{R1.0,R2.0-{{R3.0,C1.0-C2.0,{L1.0-L2.0,L3.0-L4.0}},R4.0-{R5.0,R6.0-R7.0}}}\n\nstr(Block(str(Block(original_string)))):\n{R1.0,R2.0-{{R3.0,C1.0-C2.0,{L1.0-L2.0,L3.0-L4.0}},R4.0-{R5.0,R6.0-R7.0}}}\n\nGenerated Python-code representation\nBlock([[Block([[R(1.0)],[R(2.0),Block([[Block([[R(3.0)],[C(1.0),C(2.0)],[Block([[L(1.0),L(2.0)],[L(3.0),L(4.0)]])]])],[R(4.0),Block([[R(5.0)],[R(6.0),R(7.0)]])]])]])]])\n\nTesting the generated code\n{R1.0,R2.0-{{R3.0,C1.0-C2.0,{L1.0-L2.0,L3.0-L4.0}},R4.0-{R5.0,R6.0-R7.0}}}\n" } ], "prompt_number": 319 }, { "cell_type": "code", "collapsed": false, "input": "# Demonstrate the effect of a large number of levels\nb = Block(\"{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}\", debug=True)\ns = str(b)\nr = repr(b)\nprint \nprint s\nprint\nprint r", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "Block text > {{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}\nBlock text > {{{{{{{{{{{{{{{}}}}}}}}}}}}}}}\nBlock text > {{{{{{{{{{{{{{}}}}}}}}}}}}}}\nBlock text > {{{{{{{{{{{{{}}}}}}}}}}}}}\nBlock text > {{{{{{{{{{{{}}}}}}}}}}}}\nBlock text > {{{{{{{{{{{}}}}}}}}}}}\nBlock text > {{{{{{{{{{}}}}}}}}}}\nBlock text > {{{{{{{{{}}}}}}}}}\nBlock text > {{{{{{{{}}}}}}}}\nBlock text > {{{{{{{}}}}}}}\nBlock text > {{{{{{}}}}}}\nBlock text > {{{{{}}}}}\nBlock text > {{{{}}}}\nBlock text > {{{}}}\nBlock text > {{}}\nBlock text > {}\nBlock text > \n\n{ }\n\nBlock([[Block([[Block([[Block([[Block([[Block([[Block([[Block([[Block([[Block([[Block([[Block([[Block([[Block([[Block([[Block([[Block([[]])]])]])]])]])]])]])]])]])]])]])]])]])]])]])]])]])\n" } ], "prompt_number": 327 }, { "cell_type": "code", "collapsed": false, "input": "", "language": "python", "metadata": {}, "outputs": [] } ], "metadata": {} } ] }