Download or view Solver.frink in plain text format
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
}
Download or view Solver.frink in plain text format
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 20217 days, 16 hours, 1 minutes ago.