# Solver.frink

``` use derivatives.frink use integrals.frink use solvingTransformations.frink use powerTransformations.frink // This class solves a system of equations.  It contains a graph of // EquationNodes.  Each EquationNode represents an equation in the system. // These nodes are connected by Edges which indicate that various // equations are interrelated by containing the same variables. class Solver {    // This is an array of EquationNodes that make up the graph.    var equationNodes    // This is a set of strings indicating the variables that we are not going    // to solve for.    var ignoreSet    // Boolean flag indicating if we have done phase 1 simplifications    var initialized    // The cached final solutions for each variable, keyed by variable name    var finalSolutions    // A (currently-unused) set of aliases that will simplify equations.    var aliases    // Create a new Solver.    new[eqs, ignoreList=[]] :=    {       ignoreSet = new set       equationNodes = new array       finalSolutions = new dict              for i = ignoreList          ignoreSet.put[i]       for eq = eqs          addEquation[eq]       initialized = false    }        // Add an equation to the system.  This creates a new EquationNode and    // automatically connects it to all of the other EquationNodes in the    // system that share its equations.    //    // If index is undef. this will push the node onto the end of the list.    // if index is a number, this will replace the specified node.    //    // (public)    addEquation[eq, index=undef] :=    {       // THINK ABOUT:  Call transformExpression to canonicalize first?       // Check for duplicate equations.       for n = equationNodes          if structureEquals[eq, n.getOriginalEquation[]]          {             println["Eliminating duplicate equation \$eq"]             return          }                    reducedUnknowns = setDifference[getSymbols[eq], ignoreSet]       if length[reducedUnknowns] == 0          println["WARNING:  Equation \$eq has no unknowns!"]       node = new EquationNode[eq, reducedUnknowns]       // If replacing, disconnect the other node       if index != undef          remove[index]       for other = equationNodes       {          // THINK ABOUT:  Should we check to see if one equation is a proper          // subset of the variables of the other and push the simpler into          // the more complex right now?                    // This is a set of shared variables between the two equations.          sharedVars = intersection[reducedUnknowns, other.getUnknowns[]]          for varName = sharedVars             connect[node, other, varName]       }       if index == undef          equationNodes.push[node]       else          equationNodes.insert[index, node]       // Discard any previously-found solutions       finalSolutions = new dict       initialized = false    }    // Method to initialize and simplify the system.    initialize[] :=    {       if ! initialized       {          pushSimpler[] //         draw[]          changed = solveSimultaneous[] //         draw[] //         pushAliases[] //         draw[]          if changed             pushSimpler[]          initialized = true       }    }    // Removes the specified node from the graph.  This removes all connections    // to the specified node.    // (public)    remove[index] :=    {       node = equationNodes.remove[index]       disconnect[node]       initialized = false       finalSolutions = new dict // Discard any previously-found solutions.    }    // Disconnect the specified node from the graph.    // (private)    disconnect[node] :=    {       node.disconnectAllEdges[]    }    // Connect the two specified equations by the specified variable.    // (private)    connect[n1 is EquationNode, n2 is EquationNode, varName is string] :=    {       e = new Edge[n1, n2, varName]       n1.addEdge[e]       n2.addEdge[e]    }    // Return a count of the EquationNodes in the system    // (public)    getEquationCount[] := length[equationNodes]    // Returns the EquationNode with the specified index    // (public)    getEquationNode[index] := equationNodes@index    // Returns an array with each element in the array being an array    // [ unknowns, index ]    // of the equations in the system, ordered with the simplest equations    // (those with the fewest unknowns) first.  Unknowns is a set.    getEquationsSortedByComplexity[] :=    {       list = new array       i = 0       last = length[equationNodes] - 1       for i = 0 to last          list.push[[equationNodes@i.getUnknowns[], i]]       sort[list, {|a,b| length[a@0] <=> length[b@0]}]       return list    }    // Returns the unknowns for the specified index.    getUnknowns[index] := equationNodes@index.getUnknowns[]    // Prints out the state of the solver for debugging.    // (public)    dump[] :=    {       last = getEquationCount[]-1       for i = 0 to last       {          node = getEquationNode[i]          print["\$i\t" + node.getOriginalEquation[] + "\t"]          for e = node.getEdges[]          {             other = e.getOtherNode[node]             print["[" + e.getVariableName[] +  "," + getNodeIndex[other] +"] "]          }          println[]       }       println[]    }    // Draw a representation of the system.    draw[g is graphics, left=0, top=0, right=1, bottom=2] :=    {       last = getEquationCount[]-1       width = right-left       height = bottom-top       g.font["Serif", "italic", height/30]       g.color[0,0,0]       cy = top + height/2       w = 0.7 width/20;       for i = 0 to last       {          node = getEquationNode[i]          [x,y] = getPosition[i, left, top, right, bottom]          for e = node.getEdges[]          {             oi = getNodeIndex[e.getOtherNode[node]]             if (i < oi)             {                [ox, oy] = getPosition[oi, left, top,right,bottom]                g.color[0,0,0]                g.line[x+randomFloat[-w,w],y+randomFloat[-w,w],ox+randomFloat[-w, w],oy+randomFloat[-w,w]]             }          }                    g.color[.9,.9,.9]          g.fillEllipseCenter[x,y,width/10, height/2/10]          g.color[0,0,0]          g.drawEllipseCenter[x,y,width/10, height/2/10]          g.text[i,x,y,"center","center"]          g.text["\$i: " + node.getOriginalEquation[], left, i * height/20 + cy, "left", "top"]       }    }    // Calculates the center of the specified node in the graph.    getPosition[index, left, top, right, bottom] :=    {       phase = circle / getEquationCount[]       width = right-left       height = bottom-top       cx = left + width/2       cy = top + height/4       radius = 1/2 (.9) width       x = radius sin[index phase] + cx       y = radius * -cos[index phase] + cy       return [x,y]    }    draw[] :=    {       g = new graphics       draw[g]       g.show[]    }    // Gets the node index given the node.    getNodeIndex[node is EquationNode] :=    {       last = length[equationNodes]       for i=0 to last          if equationNodes@i == node             return i                 return "Node not found!"    }    // Keep replacing simpler variables until we're done.    pushSimpler[] :=    { //      dump[]       while pushSimplerOnce[]       { //         dump[]       }    }    // Pushes the simpler equations into the more complex    // equations.  This returns true if changes were made to the system,    // false otherwise.    pushSimplerOnce[] :=    {       sortedEqs = getEquationsSortedByComplexity[]       last = length[sortedEqs]-1       for i=0 to last-1       {          [unknownsI, nodeI] = sortedEqs@i          JLOOP:          for j=i+1 to last          {             [unknownsJ, nodeJ] = sortedEqs@j             if isProperSubset[unknownsI, unknownsJ]             { //               println["Node \$nodeI is a proper subset of \$nodeJ"]                simpleVarList = pickSimplestVariable[nodeI, nodeJ]                for [simpleVar, count] = simpleVarList                {                   sols = equationNodes@nodeI.getSolutions[simpleVar]                   if length[sols] < 1                   {                      println["Ugh.  Has " + length[sols] + " solutions in pushSimplerOnce.  Maybe improve pickSimplestVariable?  Solutions are: \$sols, equations are " + equationNodes@nodeI.getOriginalEquation[] + ", " + equationNodes@nodeJ.getOriginalEquation[] ]                      next                   } else                   {                      origEq = equationNodes@nodeJ.getOriginalEquation[] //                     println["Removing node \$nodeJ"]                      remove[nodeJ]                      if (length[sols] > 1)                         println["Warning:  pushSimplerOnce split into " + length[sols] + " solutions: \$sols"]                      for sol = sols                      { //                        println["Replacing \$simpleVar in \$origEq with " + child[sol,1]]                         newEq = replaceVar[origEq, simpleVar, child[sol,1]]                         [newEq, fullyReduced] = prettify[newEq]                         if !fullyReduced                            newEq = transformExpression[newEq]                         addEquation[newEq]                      }                      return true                   }                }             }          }       }       return false              // We made no changes.    }    // Pushes aliases like a == b or a == 2 b around the system so that    // the system is as simple and disconnected as possible.    pushAliases[] :=    {       size = length[equationNodes]       i = 0       while i<size       {          node = equationNodes@i          eq = equationNodes@i.getOriginalEquation[]          left =  child[eq, 0]          lu = getSymbols[left]          lu = setDifference[lu, ignoreSet]          if length[lu] == 1          {             right = child[eq, 1]             ru = getSymbols[right]             ru = setDifference[ru, ignoreSet]             if length[ru] == 1             {                ls = array[lu]@0                rs = array[ru]@0                first = ls > rs ? ls : rs                sols = node.getSolutions[first]                if length[sols] != 1                {                   println["Ugh.  Has " + length[sols] + " solutions in pushAliases.  Solutions are:\n" + sols]                } else                {                   println["Replacing " + sols@0]                   replaceAll[sols@0, i]                   remove[i]                   i = i - 1     // Don't increment i                   size = size - 1                }             }          }          i = i + 1       }    }        // Simplifies an equation to the form a === 10 if only one variable    // remains.    // returns [equation, remainingSymbol, reduced]    //   where reduced is a boolean flag indicating if the equation has been    //   simplified to the form above.    prettify[eq] :=    {       reduced = false       solvedUnknowns = getSymbols[eq]       solvedUnknowns = setDifference[solvedUnknowns, ignoreSet]       remainingSymbol = undef       if length[solvedUnknowns] == 1       {          remainingSymbol = array[solvedUnknowns]@0          prettified = array[solveSingle[eq, remainingSymbol]]          if length[prettified] == 1          {             reduced = true             eq = prettified@0          } else             eq = transformExpression[eq]       }       return [eq, reduced]    }    // Picks the simplest variable shared by nodes 1 and 2.  Node 1 should be    // the simpler node.  Returns a string.    pickSimplestVariable[index1, index2] :=    {       node1 = equationNodes@index1       node2 = equationNodes@index2       u1 = node1.getUnknowns[]       u2 = node2.getUnknowns[]       intersection = intersection[u1, u2]       results = new array       sortedUnknowns = getSymbolsByComplexity[node1.getOriginalEquation[]]       //println["Sorted unknowns is \$sortedUnknowns"]       for [unknown, count] = sortedUnknowns          if intersection.contains[unknown]             results.push[[unknown, count]]       sortedUnknowns = getSymbolsByComplexity[node2.getOriginalEquation[]]       for [unknown, count] = sortedUnknowns          if intersection.contains[unknown]             for i = 0 to length[results]-1                if results@i@0 == unknown                   results@i@1 = results@i@1 + count       sort[results, {|a,b| a@1 <=> b@1}]       return results           }    // Returns true if the node at index 1 contains unknowns which are a    // proper subset of the unknowns in index 2.  This means that node 1    // is a "simpler" version of equation 2, and its values should be    // substituted into equation 2.    isSimpler[index1, index2] :=    {       return isProperSubset[equationNodes@index1.getUnknowns[],                             equationNodes@index2.getUnknowns[]]    }    // Eliminate simultaneous equations in the system.    // returns true if the system has been changed.    solveSimultaneous[] :=    {       changed = false       // TODO:  Sort by simplest equations?       size = length[equationNodes]       for i=0 to size-2       {          JLOOP:          for j = i+1 to size-1          {             nodeI = equationNodes@i             nodeJ = equationNodes@j             ui = nodeI.getUnknowns[]             uj = nodeJ.getUnknowns[]             sharedUnknowns = intersection[ui, uj] //            println[nodeI.getOriginalEquation[]] //            println[nodeJ.getOriginalEquation[]] //            println["\$i: \$ui\t\$j: \$uj"] //            println["\$i \$j Shared unknowns are \$sharedUnknowns"]             if length[sharedUnknowns] >= 2             {                varsToReplace = pickSimplestVariable[i, j] //               println["varsToReplace is \$varsToReplace"]                for [varToReplace, count] = varsToReplace                {                   skipNode = i                   solution = nodeI.getSolutions[varToReplace]                   if length[solution] != 1                   {                      // Didn't find single solution, try solving                      // and replacing from the other node.                      solution = nodeJ.getSolutions[varToReplace]                      skipNode = j                   }                                      if length[solution] == 1                   {                      replaceAll[solution@0, skipNode]                      //                  dump[]                      changed = true                      break JLOOP                   }                }                println["Ugh.  SolveSimultaneous fell through without replacing.  Equations were " + nodeI.getOriginalEquation[] + " and " + nodeJ.getOriginalEquation[]]             }          }       }       return changed    }    // Replace the specified symbol, recursively, in all equations except    // the index specified.    // (private)    replaceAll[solution, skipIndex] :=    {       size = length[equationNodes]       sym = child[solution,0]       rep = child[solution,1]   // Right-hand-side of solution       // Substitute result into other equations.       for k = 0 to size-1          if k != skipIndex          {             orig = equationNodes@k.getOriginalEquation[]             subst = substituteExpression[orig, sym, rep] //            println["orig is \$orig, sym is \$sym, solution is \$solution, rep is \$rep, subst is \$subst"]             if orig != subst // and length[getSymbols[subst]] <= length[getSymbols[orig]]             {                [subst, eqSolved]  = prettify[subst]                subst2 = transformExpression[subst]   // THINK ABOUT:  Do this?                if structureEquals[_a === _b, subst2] //  and structureEquals[child[subst2,0],sym] and ! expressionContains[child[subst2,1], sym]                   subst = subst2                else                   println["Warning:  In replaceAll, did not get solution.  Input was \$solution, output was \$subst2"] //               println["Substituted \$sym to " + rep + " in \$orig, result is \$subst"]                addEquation[subst, k]  // Replace equation.                if eqSolved                { //                  println["Going to recursively replace \$sym"]                   replaceAll[subst, k]  // Recursively replace others                }             }          }    }    // Return a set of all unknowns in the system.    getAllUnknowns[] :=    {       allUnknowns = new set       for node = equationNodes          allUnknowns = union[allUnknowns, node.getUnknowns[]]       return allUnknowns    }        // Solves for all variables in the system.    solveAll[] :=    {       allUnknowns = getAllUnknowns[]       results = new array       for u = allUnknowns       {          res = solveFor[u]          for eq = res             results.push[eq]       }       return results    }    // Solves the system for the specified variable name.    // (public)    solveFor[varName] :=    {       if !initialized          initialize[]       cached = finalSolutions@varName       if cached          return cached       results = new array       size = getEquationCount[]       for i=0 to size-1       {          if getUnknowns[i].contains[varName]          {             partialResults = solveNodeForVariable[i, varName]             for r = partialResults                results.push[r]          }       }       // Cache results.       finalSolutions@varName = results       return results    }    // Solve for the specified variable name, substituting the list of    // arguments.  Args is an array of ["varname", value] pairs.    // The answer will be returned symbolically as an equation in the form    // varName === solution    // with constants and units still intact.    solveForSymbolic[varName, args] :=    {       results = new array       sols = solveFor[varName]       for sol = sols       {          for [arg, val] = args          {             sym = constructExpression["Symbol", arg]             sol = substituteExpression[sol, sym, val]          }          // THINK ABOUT:  Transform expression here to simplify?          // res = transformExpression[res]          results.push[eval[sol]]       }       return eliminateOverconstrained[results, false, false]    }    // Solve for the specified variable name, substituting the list of    // arguments.  The result is a list of evaluated solutions.    solveFor[varName, args] :=    {       sols = solveForSymbolic[varName, args]       results = new array       for sol = sols       {          right = child[sol,1]          final = eval[right]          exists = false          CHECKDUPLICATES:          for r = results             if (final conforms r) and (final == r)             {                exists = true                break CHECKDUPLICATES             }          if ! exists                     results.push[final]       }       return results    }    // Recursive method to find the solutions for the specified variable    // starting from the specified node.  This recursively enumerates all    // of the permutations of substitutions in the system.    // This method just sets up parameters for the recursive call.    // (Private method)    solveNodeForVariable[index, varName, cachedSolutions = undef] :=    {       if cachedSolutions == undef          cachedSolutions = new dict              node = getEquationNode[index]       sols = node.getSolutions[varName] //      println["Solutions for \$varName are \$sols"]       results = solveNodeForVariable[node, varName, sols, new set, cachedSolutions]       results = transformExpression[results] //      return results       return eliminateOverconstrained[results, true, false]    }        // The recursive (private) call to solve for the particular variable.    solveNodeForVariable[node, varName, inEqs, usedEdges, cachedSolutions] :=    { //      print["Solving for \$varName in " + getNodeIndex[node] + ", {"] //      for e = usedEdges //         print[e.getVariableName[] + " "] //      println["}"]              // Return partial solution from cache if possible.       if cachedSolutions.containsKey[node]       {          varDict = cachedSolutions@node          if varDict.containsKey[varName]          {             edgeDict = varDict@varName             if edgeDict.containsKey[usedEdges]                return edgeDict@usedEdges          }       }       results = inEqs.shallowCopy[]       edges = setDifference[node.getEdges[], usedEdges]       for e = edges          if e.getVariableName[] == varName             edges.remove[e]                    len = length[edges]       if (len == 0)             // No more replacements to do.       {          putCache[cachedSolutions, node, varName, usedEdges, results]          return results       }       // Set up states array to enumerate through permutations.       states = new array        for i=0 to len-2          states@i = false              states@(len-1) = true     // Skip all-false state (no replacements)       i = len-1       edgeArray = array[edges]              //newUsedEdges = union[node.getEdges[], usedEdges]              while i >= 0       {          newUsedEdges = usedEdges.shallowCopy[]          for j = 0 to len-1             if states@j                newUsedEdges.put[edgeArray@j]                       // Perform replacements on each edge          EDGELOOP:          for j = 0 to len-1          {             edge = edgeArray@j //            newUsedEdges.put[edge]  // Mark this edge as used.             replacingVar = edge.getVariableName[]             if states@j             {                replacingSymbol = edge.getSymbol[]                otherNode = edge.getOtherNode[node]                //                  newGlobalUsedNodes.put[newUsedNodesHere]                // Recursively solve the other node for the variable                // represented by this edge.                repList = solveNodeForVariable[otherNode,                                               replacingVar,                                               otherNode.getSolutions[replacingVar],                                               newUsedEdges,                                               cachedSolutions ]                //               println["repList is \$repList"]                for repWithFull = repList                {                   repWith = child[repWithFull, 1]  // Get right-hand-side                   for eq = inEqs                   {                      res = substituteExpression[eq, replacingSymbol, repWith]                      //                     println["Replacing \$replacingVar with \$repWith in \$eq, result is \$res"]                      // Check to see if the variable we're solving for occurs on the right                      rightSyms = getSymbols[child[res,1]]                      if rightSyms.contains[varName]                      {                         //println["WARNING:  Right side contains \$varName in \$res"]                         res2 = solveSingle[res, varName]                         //println["Re-solving:  \$res2"]                                                  // TODO:  This may return a whole lot of solutions.                         // We need to evaluate each one and push them all                         // onto the solutions list.                         varSymbol = constructExpression["Symbol", varName]                         for subR = array[res2]                            if structureEquals[_a === _b, subR] and structureEquals[child[subR,0],varSymbol] and ! expressionContains[child[subR,1], varSymbol]                            {                               //println["Re-solving successful."]                               results.push[subR]                            } else                            { //                              println["WARNING:  Right side contains \$varName in \$res"]                               println["Re-solving FAILED:  \$res2"] //                              println["Re-solving FAILED."]                            }                         } else                         {                            //                      res = transformExpression[res]                            results.push[res]                         }                      }                   }                }          }                    // Advance to next binary state          flipped = false          i = len-1          while i>=0 and !flipped          {             // Enter next state             if states@i == false             {                states@i = true                flipped = true             } else             {  // Carry                states@i = false                i = i - 1             }          }          // i now contains the last index flipped.  If i < 0, we're done       }       results = eliminateOverconstrained[results, true, false]       putCache[cachedSolutions, node, varName, usedEdges, results]       return results    }    // This function eliminates overconstrained equations.  For example, a    // system containing the solutions a===1/2 c r  and  a===c d^-1 r^2  is    // overconstrained because a value can always be obtained with the first    // equation.  The second is not necessary, and could lead to    // inconsistent results.   This method ignores any symbols listed in the    // ignoreSymbols list, (these are probably units,) eliminating them from    // the equations.    eliminateOverconstrained[eqArray, dupsOnly, debug=false] :=    {       size = length[eqArray]       unknowns = new array       lefts = new array       for i = 0 to size-1       {          lefts@i = child[eqArray@i, 0]          unknowns@i = setDifference[getSymbols[child[eqArray@i,1]], ignoreSet]       }       res = new array       // Check for duplicates.       for i=0 to size-1       {          remove = false          j = 0          do          {             if i != j and structureEquals[lefts@i, lefts@j]             {                remove = (i<j and structureEquals[eqArray@i, eqArray@j]) or ((! dupsOnly) and isProperSubset[unknowns@j, unknowns@i])                if remove                   if debug                      println[eqArray@j + " is a proper subset or match of " + eqArray@i]             }             j=j+1          } while (j < size) and ! remove          if (! remove)             res.push[eqArray@i]  // If we got here, no j is a proper subset of i.       }       return res    }    // Puts the specified values into the cache.    class putCache[cachedSolutions, node, varName, usedEdges, vals] :=    {       if ! cachedSolutions.containsKey[node]       {          nodeDict = new dict          cachedSolutions@node = nodeDict       } else          nodeDict = cachedSolutions@node                 if ! nodeDict.containsKey[varName]       {          varDict = new dict          nodeDict@varName = varDict       } else         varDict = nodeDict@varName        varDict@usedEdges = vals    }    // This is an experimental function that uses Frink's multi-input capability    // to interactively plug numbers into a solution.    interact[] :=    {       allUnknowns = sort[array[getAllUnknowns[]]]       opts = new array       for u = allUnknowns       {          sols = solveFor[u]          println["\$u:\t\$sols"]          if length[sols] != 1             opts.push[u]       }       vals = input["Enter values: ", opts]    } } // This is a node in the graph that represents an equation.  It stores various // information about the variables stored in the equation and its solutions // for those variables.  Users will not create these directly, but rather // call methods on the System class to create these nodes and connect them // properly. class EquationNode {    // The original equation    var origEq    // A set of unknowns in the equation.    var unknowns    // An set of edges that connect this node to other nodes.    var edges    // This is a dictionary whose key is the variable name (as a string)    // and the value is an object of type SolutionPart.    // If this is undef, it means that the equation has not     var solvedDict    // Create a new equation.  The equation should contain a === expression.    // The set reducedUnknowns is the unknowns with the ignoreSet removed.    new[eq, reducedUnknowns is set] :=    {       origEq = eq       unknowns = reducedUnknowns       edges = new set       solvedDict = new dict    }    // Add a new edge that connects to another node    addEdge[e is Edge] :=    {       edges.put[e]    }    // Returns the primary equation for this node.    getOriginalEquation[] := origEq    // Return the set of unknowns in this equation.    getUnknowns[] := unknowns    // Disconnect any edges that connect this node to other nodes.    disconnectAllEdges[] :=    {       for e = edges       {          other = e.getOtherNode[this]          other.removeEdgesTo[this]       }       edges = new set    }    // Remove any edges that connect to the specified EquationNode.    // This NOT recursive and should only be called from disconnectAllEdges    // (private method)    removeEdgesTo[node is EquationNode] :=    {       for e = edges          if e.connectsTo[node]             edges.remove[e]    }    // Returns a set of all Edge objects.    getEdges[] := edges    // Gets the solutions to this equation for the specified variable.    // This will fetch the value from the cache if it exists, otherwise it    // will solve for it.    getSolutions[varName] :=    {       if ! solvedDict.containsKey[varName]       {          solution = solveSingle[origEq, varName]          addSolutions[varName, solution]       }              return solvedDict@varName.solutions    }    // Add one or more solutions for the specified variable to a node.    // The equation may be a single equation or a list of equations.    // This checks to ensure that the equations are properly solved for the    // variable.    addSolutions[varName, equation] :=    {       if solvedDict.containsKey[varName]          sp = solvedDict@varName       else       {          sp = new SolutionPart[varName]          solvedDict@varName = sp       }       sym = sp.symbol       for eq = flatten[array[equation]]       {          // Make sure that the solution was properly solved for the variable.          // this ensures that the equation is of the form var == solution          // where solution does not contain the variable.          if structureEquals[_a === _b, eq] and structureEquals[child[eq,0],sym] and ! expressionContains[child[eq,1], sym]             sp.addSolution[eq]          else             println["Could not solve \$origEq for \$varName!  Solution was \$eq"]       }    } } // This represents an edge between two EquationNodes in a graph.  It // contains a variable name which defines the variable that connects the // two nodes.  Users will not create these directly, but rather // call methods on the System class to create these nodes and connect them // properly. class Edge {    // A string containing the name of the variable that connects the two    // EquationNodes    var varName    // A symbol representing the symbol.    var varSymbol    // One of the EquationNodes that this connects.    var node1    // The other one of the EquationNodes that this connects.    var node2    // Create a new Edge that connects the specified EquationNodes.    new[n1 is EquationNode, n2 is EquationNode, name is string] :=    {       node1 = n1       node2 = n2       varName = name       varSymbol = constructExpression["Symbol", name]    }    // Returns a string containing the variable name which indicates the    // variable name that connects the two Nodes by this edge    getVariableName[] := varName    // Returns a symbol representing the variable.    getSymbol[] := varSymbol        // Returns the other node of this edge.    getOtherNode[oneNode is EquationNode] :=    {       if oneNode == node1          return node2       else          if oneNode == node2             return node1          else          {             println["getOtherNode:  Matching node not found!"]             return undef          }    }    // Returns true if this Edge connects to the specified node.    connectsTo[node is EquationNode] :=    {       return node == node1 or node == node2    } } // This is a helper class that contains the solutions for a particular // variable. class SolutionPart {    var symbol                   // The variable stored as a symbol.    var solutions                // An array of solutions in the form                                 //  a === b + c    // Construct a new SolutionPart given the string that represents the    // variable.    new[symbolString] :=    {       symbol = constructExpression["Symbol", symbolString]       solutions = new array    }    // Adds a solution to the list.  This does not do any checking; that is    // performed by EquationNode.addSolution.    addSolution[eq] :=    {       solutions.push[eq]    } } // Solve a single equation for the specified symbol. // TODO:  Verify that the equation was solved appropriately? solveSingle[eq, symbol] := {    xSymbol = constructExpression["Symbol", symbol]    // We could use this in symbolic mode, otherwise it warns.    // solveEq = solve[eq, xSymbol]    solveEq = constructExpression["FunctionCall", ["solve", eq, xSymbol]]        return transformExpression[solveEq] } // Replace a variable in the specified equation with the specified value. // You should probably wrap the "eq" and "value" in a noEval[] block if // you're not passing them in from a variable. replaceVar[eq, varName, value] := {    sym = constructExpression["Symbol", varName]    res = substituteExpression[eq, sym, value]    // THINK ABOUT:  Transform expression here to simplify?    // res = transformExpression[res]    return res } ```

This is a program written in the programming language Frink.
For more information, view the Frink Documentation or see More Sample Frink Programs.

Alan Eliasen was born 18492 days, 20 hours, 14 minutes ago.