예제 #1
0
    def solve_heatExchanger(self, device: HeatExchanger):
        """Constructs the heat balance equation over the flows entering and exiting the heat exchanger. If equation is solvable as is (i.e. has 1 unknown), calculates the missing property
        and sets its value in the relevant object."""

        # m1h11 + m2h21 + m3h31 = m1h12 + m2h22 + m3h32
        # m1(h1i - h1o) + m2(h2i - h2o) + m3(h3i - h3o) = 0
        # heatBalance = LinearEquation([ [ ( (state_in, 'flow.massFF'), state_in.h - state_out.h) for state_in, state_out in device.lines], 0 ])

        heatBalance_LHS = []
        for state_in, state_out in device.lines:
            heatBalance_LHS.append(
                ((state_in.flow, 'massFF'), (state_in, 'h')))
            heatBalance_LHS.append(
                ((-1), (state_out.flow, 'massFF'), (state_out, 'h')))
        heatBalance = LinearEquation(LHS=heatBalance_LHS, RHS=0)

        if heatBalance.isSolvable(
        ):  # if solvable by itself, there is only one unknown
            solution = heatBalance.solve()
            unknownAddress = list(solution.keys())[0]
            setattr_fromAddress(object=unknownAddress[0],
                                attributeName=unknownAddress[1],
                                value=solution[unknownAddress])
            self._updatedUnknowns.add(unknownAddress)
        else:
            self._equations.append(heatBalance)
            heatBalance.source = device
예제 #2
0
    def _add_pressureRatioRelation(self, device: WorkDevice):
        """Adds a linear equation describing the relation between end state pressures and the pressure ratio parameter of the work device.
        Works only with work devices that have one state_in and one states_out."""
        state_out = device.states_out[0]

        if isinstance(device, (Compressor, Pump)):  # Compression
            pressureRatioRelation_LHS = [((device, 'pressureRatio'),
                                          (device.state_in, 'P')),
                                         (-1, (state_out, 'P'))]
        else:
            assert isinstance(device, Turbine)  # Expansion
            pressureRatioRelation_LHS = [((device, 'pressureRatio'),
                                          (state_out, 'P')),
                                         (-1, (device.state_in, 'P'))]

        pressureRatioRelation = LinearEquation(LHS=pressureRatioRelation_LHS,
                                               RHS=0)
        device._pressureRatioRelationSetup = True

        if pressureRatioRelation.isSolvable():
            solution = pressureRatioRelation.solve()
            unknownAddress = list(solution.keys())[0]
            setattr_fromAddress(object=unknownAddress[0],
                                attributeName=unknownAddress[1],
                                value=solution[unknownAddress])
            self._updatedUnknowns.add(unknownAddress)
        else:
            pressureRatioRelation.source = device
            self._equations.append(pressureRatioRelation)
예제 #3
0
 def _add_Q_in_relation(self):
     """Constructs the equation of total heat inputs. If Q_in is a given, can help find other unknowns such as mass flow rates or state
     enthalpies; otherwise, can help determine Q_in."""
     Q_in_relation_LHS = [((self, 'Q_in'), )]
     mainFlow = self._get_mainFlow()
     for flow in self.flows:
         Q_in_relation_LHS.append(
             (-1, (flow, 'massFF'), (mainFlow, 'massFR'),
              flow.sHeatSupplied))
     Q_in_relation = LinearEquation(LHS=Q_in_relation_LHS, RHS=0)
     Q_in_relation.source = 'Cycles._add_Q_in_relation'
     self._equations.append(Q_in_relation)
예제 #4
0
 def _add_sHeat_relation(self):
     """Constructs the equation of specific heat input per unit flow (per kg/s) on the mainline."""
     sHeat_relation_LHS = [(
         -1,
         (self, 'sHeat'),
     )]
     for flow in self.flows:
         sHeat_relation_LHS.append(((flow, 'massFF'), flow.sHeatSupplied))
     net_sPower_relation = LinearEquation(LHS=sHeat_relation_LHS, RHS=0)
     net_sPower_relation.source = 'Cycles._add_sHeat_relation'
     self._equations.append(net_sPower_relation)
     self._sHeat_relation = net_sPower_relation
예제 #5
0
 def _add_netPowerBalance(self):
     """Constructs the power balance equation, i.e. netPower = sum(flow.massFR * workDevice.net_sWorkExtracted for workDevice in flow) for flow in self.flows.
     If netPower is known, this equation can help finding other unknowns such as mass flow rates or state enthalpies. Otherwise, netPower can be obtained vice versa.
     Adds the equation the _equations pool."""
     powerBalance_LHS = [((self, 'netPower'), )]
     mainFlow = self._get_mainFlow()
     for flow in self.flows:
         powerBalance_LHS.append(
             (-1, (flow, 'massFF'), (mainFlow, 'massFR'),
              flow.net_sWorkExtracted))
     powerBalance = LinearEquation(LHS=powerBalance_LHS, RHS=0)
     powerBalance.source = 'Cycles._add_netPowerBalance'
     self._equations.append(powerBalance)
예제 #6
0
 def _add_net_sPower_relation(self):
     """Constructs the equation of net work per unit flow, i.e. per kg/s of flow on the mainline."""
     net_sPower_relation_LHS = [(
         -1,
         (self, 'net_sPower'),
     )]
     for flow in self.flows:
         net_sPower_relation_LHS.append(
             ((flow, 'massFF'), flow.net_sWorkExtracted))
     net_sPower_relation = LinearEquation(LHS=net_sPower_relation_LHS,
                                          RHS=0)
     net_sPower_relation.source = 'Cycles._add_net_sPower_relation'
     self._equations.append(net_sPower_relation)
     self._net_sPower_relation = net_sPower_relation
예제 #7
0
    def solve_mixingChamber(self, device: MixingChamber):
        """Sets or verifies common mixing pressure on all end states. Does mass & heat balances on flows."""

        # Infer constant mixing pressure
        sampleState_withPressure = None
        for endState in device.endStates:
            if isNumeric(endState.P):
                sampleState_withPressure = endState
                break
        if sampleState_withPressure is not None:
            for endState in [
                    state for state in device.endStates
                    if state is not sampleState_withPressure
            ]:
                endState.set_or_verify({'P': sampleState_withPressure.P})

        # Construct the equations

        # m1 + m2 + m3 - m4 = 0
        massBalance_LHS = []
        for state_in in device.states_in:
            massBalance_LHS.append((1, (state_in.flow, 'massFF')))
        massBalance_LHS.append((-1, (device.state_out.flow, 'massFF')))
        massBalance = LinearEquation(LHS=massBalance_LHS, RHS=0)

        # m1h1 + m2h2 + m3h3 - m4h4 = 0
        heatBalance_LHS = []
        for state_in in device.states_in:
            heatBalance_LHS.append(
                ((state_in.flow, 'massFF'), (state_in, 'h')))
        heatBalance_LHS.append(
            (-1, (device.state_out.flow, 'massFF'), (device.state_out, 'h')))
        heatBalance = LinearEquation(LHS=heatBalance_LHS, RHS=0)

        for equation in [massBalance, heatBalance]:
            if equation.isSolvable():
                solution = equation.solve()
                unknownAddress = list(solution.keys())[0]
                setattr_fromAddress(object=unknownAddress[0],
                                    attributeName=unknownAddress[1],
                                    value=solution[unknownAddress])
                self._updatedUnknowns.add(unknownAddress)
            else:
                self._equations.append(equation)
                equation.source = device
예제 #8
0
    def get_sHeatSupplied(self, returnExpression: bool = False):
        """Returns the value of the total specific heat supplied by the HeatDevices of the flow. If returnExpression, returns the expression that gives the value when added in a LinearEquation."""
        self._sHeatSupplied = float('nan')
        expression_LHS = [(-1, (self, '_sHeatSupplied'))]

        for device in set(
                device for device in self.heatDevices
                if isinstance(device, (
                    Boiler, ReheatBoiler, Combustor,
                    GasReheater))):  # if not isinstance(device, HeatExchanger)
            expression_LHS += device.get_sHeatSuppliedExpression(
                forFlow=self, constant_c=self.constant_c)

        expression = LinearEquation(LHS=expression_LHS, RHS=0)
        expression.source = 'Flows.get_sHeatSupplied'

        if expression.isSolvable():
            assert expression.get_unknowns()[0][0] == (self, '_sHeatSupplied')
            # Linear equation is solvable if only there is only one unknown. If there is only one unknown, it must be the self._net_sWorkExtracted since we know it is unknown for sure
            result = list(expression.solve().values())[0]
            return result
        else:
            if returnExpression:
                return expression.isolate([(self, '_sHeatSupplied')])
            else:
                return float('nan')
예제 #9
0
    def _add_turbineMassBalance(self, device: Turbine):
        """Creates a mass balance equation for flows entering/exiting a turbine."""
        massBalance_LHS = []
        massBalance_LHS.append((1, (device.state_in.flow, 'massFF')))
        for state_out in device.states_out:
            massBalance_LHS.append((-1, (state_out.flow, 'massFF')))
        massBalance = LinearEquation(LHS=massBalance_LHS, RHS=0)
        massBalance.source = device

        if massBalance.isSolvable():
            solution = massBalance.solve()
            unknownAddress = list(solution.keys())[0]
            setattr_fromAddress(object=unknownAddress[0],
                                attributeName=unknownAddress[1],
                                value=solution[unknownAddress])
            self._updatedUnknowns.add(unknownAddress)
        else:
            self._equations.append(massBalance)
예제 #10
0
 def _add_massFlowRelations(self):
     """Expects main flow (flow with mass flow fraction = 1) to be identified already. Constructs the equation **mainFlow.massFR * flow.massFF = flow.massFR** for each flow. Adds the equation for each flow
     to the _equations pool."""
     mainFlow = self._get_mainFlow()
     for flow in self.flows:
         self._equations.append(
             LinearEquation(LHS=[(1, (flow, 'massFR')),
                                 (-1, (mainFlow, 'massFR'), (flow,
                                                             'massFF'))],
                            RHS=0))
예제 #11
0
    def _add_sHeatSupplied_relation(
            self, device: Union[Combustor, GasReheater]):  # For combustors
        # sHeatSupplied = state_out.h - state_in.h
        # sHeatSupplied + state_in.h - state_out.h = 0
        sHeatSupplied_relation_LHS = [(1, (device, 'sHeatSupplied'))]
        if not device.state_in.flow.constant_c:
            sHeatSupplied_relation_LHS += [(1, (device.state_in, 'h')),
                                           (-1, (device.state_out, 'h'))]
        else:
            sHeatSupplied_relation_LHS += [
                (1, (device.state_in.flow.workingFluid.cp), (device.state_in,
                                                             'T')),
                (-1, (device.state_out.flow.workingFluid.cp),
                 (device.state_out, 'T'))
            ]

        net_sHeatSupplied_relation = LinearEquation(
            LHS=sHeatSupplied_relation_LHS, RHS=0)
        net_sHeatSupplied_relation.source = 'Cycles._add_sHeatSupplied_relation'
        self._equations.append(net_sHeatSupplied_relation)
예제 #12
0
 def _add_COP_relation(self):
     """Constructs the equation of the coefficient of performance (COP) of the complete cycle."""
     # COP = Q_in/W_in
     COP_relation_LHS = [((self, 'COP'),
                          (self._net_sPower_relation.isolate([
                              (self, 'net_sPower'),
                          ]))),
                         (-1, -1, (self._sHeat_relation.isolate([
                             (self, 'sHeat'),
                         ])))]
     COP_relation = LinearEquation(LHS=COP_relation_LHS, RHS=0)
     self._equations.append(COP_relation)
예제 #13
0
def apply_incompressibleWorkRelation(state_in: StatePure, state_out: StatePure):
    """Applies the steady flow **reversible** work relation for incompressible states. (h2 - h1 = mu * (P2 - P1))"""
    endStates = [state_in, state_out]
    # h2 - h1 = mu * (P2 - P1)
    # mu * P2 - mu * P1 - h2 + h1 = 0

    # Check if both end states are incompressible
    assert all(state.x <= 0 for state in endStates)
    # Incompressible -> mu constant
    states_with_mu = [state for state in endStates if state.hasDefined('mu')]
    if len(states_with_mu) > 0:
        sampleState_with_mu = states_with_mu[0]
        for state in [state for state in endStates if state is not sampleState_with_mu]:
            state.set_or_verify({'mu': sampleState_with_mu.mu})

    workRelation = LinearEquation(LHS=[ ( (state_out, 'mu'), (state_out, 'P') ), (-1, (state_in, 'mu'), (state_in, 'P')), (-1, (state_out, 'h')), (1, (state_in, 'h')) ], RHS=0)
    if workRelation.isSolvable():
        workRelation.solve_and_set()
        return True
    else:
        return workRelation
예제 #14
0
 def _add_efficiency_relation(self):
     """Constructs the equation of thermal efficiency of the complete cycle."""
     # eta = wnet_o / q_in -> eta * q_in = wnet_o
     eta_relation_LHS = [((self, 'efficiency'),
                          (self._sHeat_relation.isolate([
                              (self, 'sHeat'),
                          ]))),
                         (-1, (self._net_sPower_relation.isolate([
                             (self, 'net_sPower'),
                         ])))]
     eta_relation = LinearEquation(LHS=eta_relation_LHS, RHS=0)
     self._equations.append(eta_relation)
예제 #15
0
 def get_sHeatSupplied(self, returnExpression: bool = False):
     self._sHeatSupplied = float('nan')
     expression_LHS = [ (-1, (self, '_sHeatSupplied')) ]
     for device in set(device for device in self.heatDevices if isinstance(device, Boiler) or isinstance(device, ReheatBoiler)):  # if not isinstance(device, HeatExchanger)
         expression_LHS += device.get_sHeatSuppliedExpression(forFlow=self)
         # expression_LHS += [ (1, (device.state_out, 'h')), (-1, (device.state_in, 'h')) ]
     expression = LinearEquation(LHS=expression_LHS, RHS=0)
     expression.source = 'Flows.get_sHeatSupplied'
     if expression.isSolvable():
         assert expression.get_unknowns()[0][0] == (self, '_sHeatSupplied')
         # Linear equation is solvable if only there is only one unknown. If there is only one unknown, it must be the self._net_sWorkExtracted since we know it is unknown for sure
         result = list(expression.solve().values())[0]
         return result
     else:
         if returnExpression:
             return expression.isolate( [(self, '_sHeatSupplied')] )
         else:
             return float('nan')
예제 #16
0
    def get_net_sWorkExtracted(self, returnExpression: bool = False):
        """Returns the value of the total specific work extracted by the WorkDevices of the flow. If returnExpression, returns the expression that gives the value when added in a LinearEquation."""
        self._net_sWorkExtracted = float('nan')
        expression_LHS = [(-1, (self, '_net_sWorkExtracted'))]

        for device in self.workDevices:
            stateBefore, stateAfter = self.get_surroundingItems(
                device, includeNone=True
            )  # returned list will have None values if there is no item in the spot before / after
            if stateBefore is None and isinstance(device, Turbine):
                stateBefore = device.state_in  # A turbine (commonly in steam cycles) may have multiple flows coming out of it. The state_in may not belong to the same flow as the state_out.
            if stateBefore is not None and stateAfter is not None:
                if not self.constant_c:
                    expression_LHS += [
                        (1, (stateBefore, 'h')), (-1, (stateAfter, 'h'))
                    ]  # effectively adds (h_in - h_out) to the equation
                else:  # constant c analysis
                    expression_LHS += [
                        (1, (self.workingFluid.cp), (stateBefore, 'T')),
                        (-1, (self.workingFluid.cp), (stateAfter, 'T'))
                    ]
            else:
                continue
        expression = LinearEquation(
            LHS=expression_LHS, RHS=0
        )  # -1 * self._net_sWorkExtracted + state_in.h - state_out.h = 0
        expression.source = 'Flows.get_net_sWorkExtracted'

        if expression.isSolvable():
            assert expression.get_unknowns()[0][0] == (self,
                                                       '_net_sWorkExtracted')
            # Linear equation is solvable if only there is only one unknown. If there is only one unknown, it must be the self._net_sWorkExtracted since we know it is unknown for sure
            result = list(expression.solve().values())[0]
            return result
        else:
            if returnExpression:
                return expression.isolate([(self, '_net_sWorkExtracted')])
            else:
                return float('nan')
예제 #17
0
    def get_net_sWorkExtracted(self, returnExpression: bool = False):
        self._net_sWorkExtracted = float('nan')
        expression_LHS = [ (-1, (self, '_net_sWorkExtracted')) ]
        for device in self.workDevices:
            stateBefore, stateAfter = self.get_surroundingItems(device, includeNone=True)  # returned list will have None values if there is no item in the spot before / after
            if stateBefore is None and isinstance(device, Turbine):
                stateBefore = device.state_in
            if stateBefore is not None and stateAfter is not None:
                expression_LHS += [ (1, (stateBefore, 'h')) , (-1, (stateAfter, 'h')) ]  # effectively adds (h_in - h_out) to the equation
            else:
                continue
        expression = LinearEquation(LHS=expression_LHS, RHS=0)  # -1 * self._net_sWorkExtracted + state_in.h - state_out.h = 0
        expression.source = 'Flows.get_net_sWorkExtracted'

        if expression.isSolvable():
            assert expression.get_unknowns()[0][0] == (self, '_net_sWorkExtracted')
            # Linear equation is solvable if only there is only one unknown. If there is only one unknown, it must be the self._net_sWorkExtracted since we know it is unknown for sure
            result = list(expression.solve().values())[0]
            return result
        else:
            if returnExpression:
                return expression.isolate( [(self, '_net_sWorkExtracted')] )
            else:
                return float('nan')
예제 #18
0
    def solve_regenerator(self, device: Regenerator):

        if all(
                isNumeric(line[0].T) for line in device.lines
        ):  # Need inlet temperatures of both lines to determine in which direction heat will flow

            warmLine, coldLine = device.lines
            if device.lines[1][0].T > device.lines[0][
                    0].T:  # state_in of device.lines[1]
                coldLine, warmLine = device.lines
            warm_in, warm_out = warmLine
            cold_in, cold_out = coldLine

            assert warm_in.flow.constant_c == cold_in.flow.constant_c, 'solve_regenerator: Flows of the warm and cold lines have different constant_c settings! Not allowed.'
            constant_c = warm_in.flow.constant_c

            if device.counterFlow_commonColdTemperature:
                warm_out.set_or_verify({'T': cold_in.T})

            heatBalance_LHS = []
            # warm_mFF*(warm_in.h - warm_out.h)*effectiveness = cold_mFF*(cold_out.h - cold_in.h)
            # warm_mFF*(warm_in.h - warm_out.h)*effectiveness + cold_mFF*(cold_in.h - cold_out.h) = 0

            if constant_c:
                assert isNumeric(warm_in.flow.workingFluid.cp)
                heatBalance_LHS.append(
                    ((device.effectiveness), (warm_in.flow, 'massFF'),
                     (warm_in.flow.workingFluid.cp), (warm_in, 'T')))
                heatBalance_LHS.append(
                    ((device.effectiveness), (-1), (warm_out.flow, 'massFF'),
                     (warm_out.flow.workingFluid.cp), (warm_out, 'T')))

                heatBalance_LHS.append(
                    ((cold_in.flow, 'massFF'), (cold_in.flow.workingFluid.cp),
                     (cold_in, 'T')))
                heatBalance_LHS.append(
                    ((-1), (cold_out.flow, 'massFF'),
                     (cold_out.flow.workingFluid.cp), (cold_out, 'T')))

            else:
                heatBalance_LHS.append(
                    ((device.effectiveness), (warm_in.flow, 'massFF'),
                     (warm_in, 'h')))
                heatBalance_LHS.append(
                    ((device.effectiveness), (-1), (warm_out.flow, 'massFF'),
                     (warm_out, 'h')))

                heatBalance_LHS.append(
                    ((cold_in.flow, 'massFF'), (cold_in, 'h')))
                heatBalance_LHS.append(
                    ((-1), (cold_out.flow, 'massFF'), (cold_out, 'h')))

            heatBalance = LinearEquation(LHS=heatBalance_LHS, RHS=0)

            if heatBalance.isSolvable(
            ):  # if solvable by itself, there is only one unknown
                solution = heatBalance.solve()
                unknownAddress = list(solution.keys())[0]
                setattr_fromAddress(object=unknownAddress[0],
                                    attributeName=unknownAddress[1],
                                    value=solution[unknownAddress])
                self._updatedUnknowns.add(unknownAddress)
            else:
                self._equations.append(heatBalance)
                heatBalance.source = device

            return True

        else:
            return False
예제 #19
0
    def _solveDevice(self, device: Device):
        endStates = device.endStates

        if isinstance(device, WorkDevice):
            # Apply isentropic efficiency relations to determine outlet state
            self.solve_workDevice(device)

        # if not self._initialSolutionComplete:  # the below processes do not need to be done in each flow solution iteration, but only for the initial one

        if isinstance(device, HeatDevice):
            # Setting end state pressures to be the same
            if device._infer_constant_pressure:
                device.infer_constant_pressure()

            if isinstance(
                    device,
                    ReheatBoiler):  # reheat boilers can have multiple lines.
                # Setting up fixed exit temperature if inferring exit temperature from one exit state
                if device._infer_fixed_exitT:
                    device.infer_fixed_exitT()

            elif isinstance(device, Intercooler):
                if device.coolTo == 'ideal':  # Cool to the temperature of the compressor inlet state
                    assert isinstance(
                        (compressorBefore := self.get_itemRelative(
                            device, -2)), Compressor
                    )  # before intercooler, there should be compressor exit state, and then a compressor
                    device.state_out.set_or_verify(
                        {'T': compressorBefore.state_in.T})
                else:  # Cool to specified temperature
                    assert isNumeric(device.coolTo)
                    device.state_out.set_or_verify({'T': device.coolTo})

            elif isinstance(device, GasReheater):
                if device.heatTo == 'ideal':  # Heat to the temperature of the turbine inlet state
                    assert isinstance(
                        (turbineBefore := self.get_itemRelative(device, -2)),
                        Turbine)
                    device.state_out.set_or_verify(
                        {'T': turbineBefore.state_in.T})

                elif device.heatTo == 'heatSupplied':
                    if not self._initialSolutionComplete:
                        assert isNumeric(device.sHeatSupplied)
                        if not self.constant_c:
                            sHeatSuppliedRelation = LinearEquation(
                                LHS=[(1, (device.state_out, 'h')),
                                     (-1, (device.state_in, 'h'))],
                                RHS=device.sHeatSupplied)
                        else:
                            sHeatSuppliedRelation = LinearEquation(
                                LHS=[(1, self.workingFluid.cp,
                                      (device.state_out, 'T')),
                                     (-1, self.workingFluid.cp,
                                      (device.state_in, 'T'))],
                                RHS=device.sHeatSupplied)
                        self._equations.append(sHeatSuppliedRelation)

                        if sHeatSuppliedRelation.isSolvable():
                            solution = sHeatSuppliedRelation.solve()
                            unknownAddress = list(solution.keys())[0]
                            setattr_fromAddress(
                                object=unknownAddress[0],
                                attributeName=unknownAddress[1],
                                value=solution[unknownAddress])
                            self._updatedUnknowns.add(unknownAddress)
                        else:
                            sHeatSuppliedRelation.source = device
                            self._equations.append(sHeatSuppliedRelation)
                else:  # Heat to specified temperature
                    assert isNumeric(device.heatTo)
                    device.state_out.set_or_verify({'T': device.heatTo})

        elif isinstance(device, HeatExchanger):
            # Setting end state pressures along the same line if pressures is assumed constant along each line
            if device._infer_constant_linePressures:
                device.infer_constant_linePressures()

            # Setting temperature of exit states equal for all lines # TODO - not the ideal place - inter-flow operation should ideally be in cycle scope
            if device._infer_common_exitTemperatures:
                device.infer_common_exitTemperatures()

        elif isinstance(device, MixingChamber):
            # Setting pressures of all in / out flows to the same value
            if device._infer_common_mixingPressure:
                device.infer_common_mixingPressure()

        elif isinstance(device, Trap):
            if device._infer_constant_enthalpy:
                device.infer_constant_enthalpy()

        self._defineStates_ifDefinable(endStates)