示例#1
0
    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)
示例#2
0
    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())
示例#3
0
 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()
示例#4
0
    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()
示例#5
0
    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()))
示例#6
0
 def SyntaxError(self):
     raise Error.SimError('EqnSyntax', (self.currentEqn, self.GetPath()))
示例#7
0
    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)
示例#8
0
    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