def Solve(self): """ determine variable to solve for and call appropriate method (supplied by derived class) """ p0 = self.GetPort(IN_PORT_0) p1 = self.GetPort(IN_PORT_1) pOut = self.GetPort(OUT_PORT) value0 = p0.GetValue() value1 = p1.GetValue() result = pOut.GetValue() if value0 is not None: if value1 is not None: try: result = self.CalcResult(value0, value1) except ValueError: return pOut.SetValue(result, CALCULATED_V) elif result is not None: try: value1 = self.CalcValue1(value0, result) except ValueError: return p1.SetValue(value1, CALCULATED_V) elif value1 is not None and result is not None: try: value0 = self.CalcValue0(value1, result) except ValueError: raise Error.SimError('EqnCalcError', self.GetPath()) p0.SetValue(value0, CALCULATED_V)
def CalcJacobian(self): """ Use crude numerical differences to approximate Jacobian return inverse """ jacobian = np.zeros((self.numberActive, self.numberActive), dtype=float) for cont in self.activeControllers: cont.SaveBase() self.errors = self.GetErrors() delta = 0.1 for i in range(self.numberActive): self.flowsheet.InfoMessage('ContDerivCalc', (self.flowsheet.GetPath(), i)) cont = self.activeControllers[i] cont.SetOutput(delta) self.flowsheet.InnerSolve() jacobian[:, i] = (self.GetErrors() - self.errors) / delta cont.SetOutput(0.0) self.flowsheet.InnerSolve() try: self.jacobian = np.linalg.inv(jacobian) except: raise Error.SimError('CouldNotInvertJacobian', self.flowsheet.GetPath())
def ProcessParen(self): """ work back up operator stack until matching '(' is found """ while 1: if len(self.operatorStack) == 0: raise Error.SimError('EqnParenMismatch', (self.currentEqn, self.GetPath())) op = self.operatorStack[-1] if op == '(': self.operatorStack.pop() # just throw away matching paren return else: self.ProcessTopOperator()
def ParseEquation(self, tokens): """ tokens is list of tokens """ if len(tokens) == 0: return self.operatorStack = ['('] # start with paren to make end of input easy self.operandStack = [] for token in tokens: # find first operand if token == '(': self.operatorStack.append(token) elif token == ')': self.ProcessParen() elif token in _operators: opClass = _operators[token] op = opClass() self.AddObject(op, '%s_%d' % (opClass.__name__, self.opCount)) self.installedOps.append(op) self.opCount += 1 prevOp = self.operatorStack[-1] while prevOp != '(' and prevOp.precedence >= op.precedence: self.ProcessTopOperator() prevOp = self.operatorStack[-1] self.operatorStack.append(op) elif token in self.GetPortNames(SIG): signal = self.GetChildUO(MakeSignalName(token)) self.operandStack.append(signal) else: # should be a number try: value = float(token) except ValueError: raise Error.SimError('EqnUnknownToken', (token, self.currentEqn, self.GetPath())) self.operandStack.append(value) # process everything back to that initial paren self.ProcessParen() if len(self.operandStack) > 1 or len(self.operatorStack): self.SyntaxError()
def ParseFormula(self): eqnStr = self.parameters[RXNFFORMULA_PAR] eqn = eqnStr # keep a copy of the original equation if eqnStr is None or eqnStr == '': return # reset all coeffs to zero cmpNames = self.GetCompoundNames() self.cmpNames = cmpNames self.stoichCoeffs = [] for i in range(len(cmpNames)): self.stoichCoeffs.append(0) # replace compounds within quotes by the index # for compounds with '-' cmps = re.findall(r'"[^"]+"|\'[^\']+\'', eqnStr) for token in cmps: # strip out the quote cmpx = token[1:-1] # underscore represent space cmpx = re.sub('_', ' ', cmpx) try: idx = cmpNames.index(cmpx) eqnStr = re.sub(token, str(idx), eqnStr) except: pass try: # extract the reaction name tokens = re.split(r':', eqnStr, 1) if len(tokens) == 2: eqnStr = tokens[1].strip() self.rxnName = tokens[0].strip() # replace all - by +- so that when i split the tokens, # the signs of the coeff are preserved eqnStr = re.sub('-', '+-', eqnStr) tokens = re.split('\+', eqnStr) for token in tokens: if token.strip() == '': continue x = re.split('\*', token.strip()) # if coeff is missing, assume 1 or -1 if len(x) == 1: x0 = x[0] if x0[0] == '-': x.append(x0[1:]) x[0] = '-1' else: x.append(x0) x[0] = '1' # let underscores stand for spaces cmpx = re.sub('_', ' ', x[1].strip()) # base compound indicator baseCmp = 0 if cmpx[0] == '!': cmpx = cmpx[1:] baseCmp = 1 # if the input compound name is numberic, it is the compound index try: idx = int(cmpx) except: idx = cmpNames.index(cmpx) coef = float(x[0].strip()) self.stoichCoeffs[idx] = coef if baseCmp: self.baseCompIdx = idx except: # self.SetParameterValue(RXNFFORMULA_PAR, '') self.stoichCoeffs = [] raise Error.SimError('EqnSyntax', (eqn, self.GetPath())) # base compound must be a reactant # check for equation mass balance later (need MW of selected compounds) if self.baseCompIdx < 0: raise Error.SimError('EqnSyntax', (eqn, self.GetPath())) elif self.stoichCoeffs[self.baseCompIdx] >= 0: raise Error.SimError('EqnSyntax', (eqn, self.GetPath()))
def SyntaxError(self): raise Error.SimError('EqnSyntax', (self.currentEqn, self.GetPath()))
def SetParameterValue(self, name, value): """ do the main work of parsing the equation and setting up the solution network """ super(Equation, self).SetParameterValue(name, value) if name == EQUATION_PAR: # eliminate the old operators for op in self.installedOps: self.DeleteObject(op) self.installedOps = [] self.opCount = 0 # remove any clone ports from installed signal streams for name in self.GetPortNames(SIG): sig = self.GetChildUO(MakeSignalName(name)) if sig: nTimesUsed = sig.GetParameterValue(USEDCOUNT_PAR) for i in range(1, nTimesUsed): sig.DeletePortNamed('Clone_%d' % i) sig.SetParameterValue(USEDCOUNT_PAR, 0) lines = re.split(r'\n', value) signals = [] eqns = [] for line in lines: line = line.strip() if _reSignal.match(line): signals.append(line) else: eqns.append(line) newNames = [] for sigDcl in signals: # step over word signal sigDcl = sigDcl[6:].lstrip() dclTypes = _reTypeDcl.findall(sigDcl) for dclType in dclTypes: (sigType, sigNames, junk) = _reEitherParen.split(dclType) sigNames = _reSpaceComma.split(sigNames) for name in sigNames: if not name: continue if name in newNames: raise Error.SimError('EqnDuplicateSigName', (name, self.GetPath())) newNames.append(name) if name not in self.GetPortNames(SIG): stream = Stream.Stream_Signal() stream.SetParameterValue(SIGTYPE_PAR, sigType) stream.SetParameterValue(USEDCOUNT_PAR, 0) self.AddObject(stream, MakeSignalName(name)) self.BorrowChildPort(stream.GetPort(IN_PORT), name) # any current ports not in new list need to be removed missingNames = [] for name in self.GetPortNames(SIG): if name not in newNames: missingNames.append(name) for name in missingNames: stream = self.GetObject(MakeSignalName(name)) port = self.GetPort(name) self.DelUnitOperation(MakeSignalName(port.GetName())) self.DeleteObject(port) # now parse the equations for eqn in eqns: # transform string into list of tokens if not eqn: continue tokens = _reTokenizeEqn.findall(eqn) self.currentEqn = eqn # for error reporting self.ParseEquation(tokens)
def InnerSolve(self): """Solve this flowsheet - inside controller loops""" path = self.GetPath() tolerance = self.GetParameterValue(MAXERROR_PAR) maxIter = self.GetParameterValue(MAXITER_PAR) maxStep = self.GetParameterValue(MAXRECYCLESTEP_VAR) recycDetails = self.GetParameterValue(RECYCLE_DETAILS_VAR) uncRecyclesDict = self.lastUnconvRecycles.GetDictionary() consErrorDict = self.lastConsistErrrors.GetDictionary() # clear any consistency errors left over PopConsistencyError = self.PopConsistencyError SolverForget = self.SolverForget PopSolveOp = self.PopSolveOp PopResetCalcPort = self.PopResetCalcPort PopIterationProperty = self.PopIterationProperty InfoMessage = self.InfoMessage while PopConsistencyError(): pass iter = 0 jacobian = None if not maxStep: maxStep = .05 while iter < maxIter: iter += 1 # print iter SolverForget() if self.hold: return 1 try: op = PopSolveOp() while op: if not op is self and op.GetParameterValue( IGNORED_PAR) is None: op.BlockPush(1) try: InfoMessage('SolvingOp', op.GetPath()) op.unitOpMessage = ('', ) # print('Solving...', op.GetName()) op.Solve() for port in op.GetPorts(): port.UpdateConnection() for obj in op.associatedObjs: obj.NotifySolved(op) for obj in list(op.designObjects.values()): obj.NotifyUnitOpSolved() finally: op.BlockPush(0) # Remove the unit op if it got attempted to solve if uncRecyclesDict and op in uncRecyclesDict: del uncRecyclesDict[op] if consErrorDict and op in consErrorDict: del consErrorDict[op] op = PopSolveOp() finally: port = PopResetCalcPort() while port: port.ResetNewCalc() port = PopResetCalcPort() nIterationValues = len(self._iterationStack) if nIterationValues <= 0: break maxError = 0.0 maxErrProp = '' for i in range(nIterationValues): prop = self._iterationStack[i] if prop._myPort and recycDetails: InfoMessage('RecycleErrorDetail', (prop.GetParent().GetParent().GetPath(), prop.GetType().name, prop._newIterationValue, prop._value)) err = prop.CalculateError(prop._newIterationValue) if maxError < err: maxError = err maxErrProp = prop.GetPath() for prop in self._consistencyErrorStack: if prop._myPort and recycDetails: InfoMessage('RecycleConsistency', (prop.GetParent().GetParent().GetPath(), prop.GetType().name, prop._consistencyError, prop._value)) err = prop.CalculateError(prop._consistencyError) if maxError < err: maxError = err maxErrProp = prop.GetPath() InfoMessage('RecycleIter', (iter, maxError, maxErrProp)) if maxError < tolerance and len(self._consistencyErrorStack) == 0: while PopIterationProperty(): pass break if iter + 1 < maxIter: # broyden acceleration for successive substitution # Solve f(x) = 0 where f(x) = g(x) - x # g(x) is the new value of x calculated by the flowsheet given x # thus g(x) will be values - the new iteration values # lastValues will be x # errors will be g(x) - x -> lastvalues - values # for UpdateJacobian B will be jacobian - initially identity # dx is newValues - lastValues # dF is errors - lastErrors if jacobian is None or jacobian.shape[0] != nIterationValues: # use identity matrix as initial jacobian values = np.zeros(nIterationValues, dtype=float) lastValues = np.zeros(nIterationValues, dtype=float) indexDict = {} for i in range(nIterationValues): prop = self._iterationStack[i] indexDict[prop] = i lastValues[i] = prop._value / prop.GetType( ).scaleFactor values[i] = prop._newIterationValue / prop.GetType( ).scaleFactor jacobian = np.identity(nIterationValues, dtype=float) newValues = values errors = lastValues - values else: for prop in self._iterationStack: values[indexDict[ prop]] = prop._newIterationValue / prop.GetType( ).scaleFactor errors = lastValues - values adjustment = np.dot(jacobian, errors) largestChange = max(abs(adjustment)) if largestChange > maxStep: adjustment *= (maxStep / largestChange) newValues = lastValues - adjustment jacobian = self.UpdateJacobian(jacobian, dx, errors - lastErrors) for prop in self._iterationStack: propType = prop.GetType() val = newValues[indexDict[prop]] if propType.name == FRAC_VAR: val = np.clip(val, 0.0, 1.0) prop.SetValue(val * propType.scaleFactor, FIXED_V | ESTIMATED_V) while PopIterationProperty(): pass dx = newValues - lastValues lastErrors = np.array(errors) lastValues = np.array(newValues) else: # this will fail on iteration overflow so just clear stack # without updating port so the differences can be examined # prop = self.PopIterationProperty() prop = PopIterationProperty() while prop: # Don't let this new code stop things try: if prop[0].CalculateError(prop[1]) > tolerance: port = prop[0].GetParent() uo = port.GetParent() uncRecyclesDict[uo] = (port, prop) port.AddToBorrowedIn(self.lastUnconvRecycles) except: pass prop = PopIterationProperty() if len(self._solveStack) > 0: # just clear the consistency stack for next iteration # but don't if there is nothing left to solve # while self.PopConsistencyError(): pass while PopConsistencyError(): pass # Recycles that haven't been resolved. Both, new and old ones if uncRecyclesDict: # Do some last checking to make sure recycles are indeed still there uoLst = list(uncRecyclesDict.keys()) for uo in uoLst: port_props = uncRecyclesDict[uo] if not port_props[0].GetConnection(): del uncRecyclesDict[uo] if uncRecyclesDict: self.unitOpMessage = ('UnresolvedRecycles', (str(self.lastUnconvRecycles), )) self.InfoMessage('UnresolvedRecycles', (path, str(self.lastUnconvRecycles))) if iter >= maxIter: self._isSolving = 0 raise Error.SimError("MaxSolverIterExceeded", (maxIter, path)) if len(self._consistencyErrorStack): # just raise first error self._isSolving = 0 try: for prop in self._consistencyErrorStack: port = prop.GetParent() uo = port.GetParent() consErrorDict[uo] = (port, (prop, prop._consistencyError)) port.AddToBorrowedIn(self.lastConsistErrrors) except: pass self.unitOpMessage = ('UnresolvedConsistencyErrors', (str(self.lastConsistErrrors), )) self.InfoMessage('UnresolvedConsistencyErrors', (path, str(self.lastConsistErrrors))) raise Error.ConsistencyError(self._consistencyErrorStack[0]) elif consErrorDict: self.unitOpMessage = ('UnresolvedConsistencyErrors', (str(self.lastConsistErrrors), )) self.InfoMessage('UnresolvedConsistencyErrors', (path, str(self.lastConsistErrrors))) return 1