Ejemplo n.º 1
0
    def __init__(self,
                 workingFluid: Fluid,
                 massFlowRate: float = float('nan'),
                 massFlowFraction: float = float('nan'),
                 constant_c: bool = False):

        # Not considering flows with multiple fluids, one flow can contain only one fluid
        self.workingFluid = workingFluid
        self.massFR = massFlowRate
        self.massFF = massFlowFraction

        # Flow analysis setting
        self.constant_c = constant_c
        if self.constant_c:
            assert isNumeric(self.workingFluid.cp) and isNumeric(
                self.workingFluid.k)

        self.items = []
        # Items is a list of devices and states making up the flow.
        # If flow is cyclic, items list should start with a state and end with the same state.

        self._equations = []
        self._updatedUnknowns = set()

        self._initialSolutionComplete = False
Ejemplo n.º 2
0
 def hasDefined(self, propertyName: Union[str, List]) -> bool:
     """Returns true if a value for the given property is defined."""
     if isinstance(propertyName, str):
         return isNumeric(getattr(self, propertyName))
     elif isinstance(propertyName, list):
         return all(
             isNumeric(getattr(self, property))
             for property in propertyName)
Ejemplo n.º 3
0
def get_saturationProperties(materialPropertyDF: DataFrame, P: Union[float, int] = float('nan'), T: Union[float, int] = float('nan')) -> Tuple[StatePure, StatePure]:
    """Returns saturated liquid and vapor states at the provided pressure or temperature for the material whose materialPropertyDF is provided."""
    # at least the T or P must be provided

    if isNumeric(P):

        satLiq_atP = materialPropertyDF.query('P == {0} and x == 0'.format(P))
        if satLiq_atP.empty:
            # exact state (saturated liquid at P - state denoted "_f") not found
            satLiq_atP = interpolate_onSaturationCurve(materialPropertyDF, interpolate_by='P', interpolate_at=P, endpoint='f')
            materialPropertyDF.append(satLiq_atP.get_asDict_allProperties(), ignore_index=True)  # append the calculated (interpolated) state to materialProperty table for future use in runtime if needed - won't have to interpolate again
        else:
            # if query found direct match (saturated liquid state at pressure), convert DFRow to a StatePure object
            satLiq_atP = StatePure().init_fromDFRow(satLiq_atP)

        satVap_atP = materialPropertyDF.query('P == {0} and x == 1'.format(P))
        if satVap_atP.empty:
            # exact state (saturated vapor at P - state denoted "_g") not found
            satVap_atP = interpolate_onSaturationCurve(materialPropertyDF, interpolate_by='P', interpolate_at=P, endpoint='g')
            materialPropertyDF.append(satVap_atP.get_asDict_allProperties(), ignore_index=True)  # append the calculated (interpolated) state to materialProperty table for future use in runtime if needed - won't have to interpolate again
        else:
            # if query found direct match (saturated vapor state at pressure), convert DFRow to a StatePure object
            satVap_atP = StatePure().init_fromDFRow(satVap_atP)

        assert satLiq_atP.isFullyDefined() and satVap_atP.isFullyDefined()
        assert satLiq_atP.x == 0 and satVap_atP.x == 1
        assert satLiq_atP.T == satVap_atP.T

        if isNumeric(T):
            assert isWithin(satLiq_atP.T, 3, '%', T), 'InputError: Provided saturation temperature and pressure do not match.'

        return satLiq_atP, satVap_atP

    elif isNumeric(T):

        satLiq_atT = materialPropertyDF.query('T == {0} and x == 0'.format(T))
        if satLiq_atT.empty:
            # exact state (saturated liquid at T - state denoted "_f") not found
            satLiq_atT = interpolate_onSaturationCurve(materialPropertyDF, interpolate_by='T', interpolate_at=T, endpoint='f')
            materialPropertyDF.append(satLiq_atT.get_asDict_allProperties(), ignore_index=True)  # append the calculated (interpolated) state to materialProperty table for future use in runtime if needed - won't have to interpolate again
        else:
            # if query found direct match (saturated liquid state at pressure), convert DFRow to a StatePure object
            satLiq_atT = StatePure().init_fromDFRow(satLiq_atT)

        satVap_atT = materialPropertyDF.query('T == {0} and x == 1'.format(T))
        if satVap_atT.empty:
            # exact state (saturated vapor at T - state denoted "_g") not found
            satVap_atT = interpolate_onSaturationCurve(materialPropertyDF, interpolate_by='T', interpolate_at=T, endpoint='g')
            materialPropertyDF.append(satVap_atT.get_asDict_allProperties(), ignore_index=True)  # append the calculated (interpolated) state to materialProperty table for future use in runtime if needed - won't have to interpolate again
        else:
            # if query found direct match (saturated vapor state at pressure), convert DFRow to a StatePure object
            satVap_atT = StatePure().init_fromDFRow(satVap_atT)

        assert satLiq_atT.isFullyDefined() and satVap_atT.isFullyDefined()
        assert satLiq_atT.x == 0 and satVap_atT.x == 1

        return satLiq_atT, satVap_atT
Ejemplo n.º 4
0
 def infer_constant_pressure(self):
     """Sets or verifies pressures of end states to be equal in all lines."""
     number_ofNumericPvals = sum(1 for state in self.endStates
                                 if isNumeric(getattr(state, 'P')))
     endStates = self.endStates
     if number_ofNumericPvals == 2:
         assert isWithin(endStates[0].P, 3, '%', endStates[1].P)
     elif number_ofNumericPvals == 1:
         state_withNonNumericPval = endStates.itemSatisfying(
             lambda state: not isNumeric(getattr(state, 'P')))
         state_withNumericPval = endStates.other(state_withNonNumericPval)
         state_withNonNumericPval.P = state_withNumericPval.P
Ejemplo n.º 5
0
    def isFullyDefinable(self):
        definable = False
        saturated = (0 <= self.x <= 1)

        if saturated:
            if sum(1 for property_regular in self._properties_regular
                   if isNumeric(getattr(self, property_regular))) >= 1:
                # if saturated mixture, values of quality & 1 other intensive property are enough to fully define state
                definable = True
        else:
            if sum(1 for property_regular in self._properties_regular
                   if isNumeric(getattr(self, property_regular))) >= 2:
                # if not a saturated mixture, values of 2 intensive properties (other than quality) are necessary to define state
                definable = True

        return definable
Ejemplo n.º 6
0
    def solve_workDevice(self, device: WorkDevice):
        """Determines outlet state based on available inlet state using isentropic efficiency."""
        # Find the state_out out of the device IN THIS FLOW - work devices may have multiple states_out (e.g. turbines with many extractions for reheat, regeneration).

        occurrences_ofDevice = [index for index, item in enumerate(self.items) if item is device]
        states_afterDevice: List[StatePure] = [self.items[index + 1] for index in occurrences_ofDevice if index + 1 < len(self.items)]  # state_afterDevice is a StatePure for sure after the check in _check_itemsConsistency

        # If isentropic efficiency is 100%, set entropy of all endStates of the device is the same. This will help determine some states.
        if device.eta_isentropic == 1:
            numeric_s_values = [state.s for state in device.endStates if isNumeric(state.s)]
            if len(numeric_s_values) > 0:
                for endState in device.endStates:
                    # use the first numeric s value as reference to validate others against / or set them
                    if not endState.hasDefined('s'):
                        endState.set({'s': numeric_s_values[0]})  # TODO - ERROR PRONE - added for cengel p10-34, 35

        self._defineStates_ifDefinable(device.endStates)  # if any states became definable with the above process

        for state_out in states_afterDevice:

            if device.state_in.hasDefined('s') and device.state_in.hasDefined('h') and state_out.hasDefined('P'):

                # going to overwrite state_out - TODO: Need to copy in the first time, then verify in subseqs
                state_out.copy_fromState(apply_isentropicEfficiency(state_in=device.state_in,
                                                                    state_out_ideal=state_out,
                                                                    eta_isentropic=device.eta_isentropic,
                                                                    fluid=self.workingFluid))
Ejemplo n.º 7
0
 def set_or_verify(self, setDict: Dict):
     for parameterName in setDict:
         if hasattr(self, parameterName):
             if not isNumeric(getattr(self, parameterName)):
                 setattr(self, parameterName, setDict[parameterName])
             else:
                 assert isWithin(getattr(self, parameterName), 3, '%', setDict[parameterName])
Ejemplo n.º 8
0
    def update(self):
        """Iterates over the unknown items in each term, checks if they have become numeric, i.e. have a value now whereas they previously didn't. If so, updates the constant factor
        by multiplying it with the newly determined value and removes it from the unknowns."""

        terms_toRemove = []

        for termIndex, [term_constantFactor, term_unknowns_attributeAddresses] in enumerate(self.LHS):

            # Check if coefficient is 0 - then no need to process any of the unknowns since term will be 0 anyways
            if term_constantFactor == 0:
                terms_toRemove.append(termIndex)
                continue  # continue to next term, no need to resolve the unknowns of this term since the product will be 0 anyways

            # Check if any unknowns became known
            unknowns_toRemove = []
            for unknown_attributeAddress in term_unknowns_attributeAddresses:
                attribute = getattr_fromAddress(*unknown_attributeAddress)
                if isNumeric(attribute):
                    # object.attribute which had previously been identified as unknown now has a value, add it to the constant factor product and remove from the unknowns
                    self.LHS[termIndex][0] *= attribute  # multiply it with the constant factor product
                    unknowns_toRemove.append([termIndex, unknown_attributeAddress])
            for termIndex, unknown_attributeAddress in unknowns_toRemove:  # remove unknowns which have become known in the end
                # removing in the end not to tamper with the iteration of the above loop
                self.LHS[termIndex][1].remove(unknown_attributeAddress)

            # Move constants to RHS
            if self.LHS[termIndex][1] == []:
                # if term has no unknowns, it is a constant, move to RHS
                self.RHS -= self.LHS[termIndex][0]
                self.LHS.pop(termIndex)

        for termIndex in reversed(terms_toRemove):  # reversed - otherwise would tamper with indices of items identified for removal
            self.LHS.pop(termIndex)

        self._gatherUnknowns()
Ejemplo n.º 9
0
 def infer_common_exitTemperatures(self):
     """Sets the exit state temperature of all lines to be equal."""
     line_exitStates = [line_endStates[1] for line_endStates in self.lines]
     numeric_exitStateTvals = [
         line_exitState.T for line_exitState in line_exitStates
         if isNumeric(line_exitState.T)
     ]
     if len(numeric_exitStateTvals) > 0:
         for line_exitState in line_exitStates:
             line_exitState.set_or_verify({'T': numeric_exitStateTvals[0]})
Ejemplo n.º 10
0
 def _set_endStateEntropiesEqual(self, device: WorkDevice):
     """If isentropic efficiency is 100%, sets entropy of all endStates of the device to be the same. This will help determine some states."""
     numeric_s_values = [
         state.s for state in device.endStates if isNumeric(state.s)
     ]
     if len(numeric_s_values) > 0:
         for endState in device.endStates:
             # use the first numeric s value as reference to validate others against / or set them
             if not endState.hasDefined('s'):
                 endState.set({
                     's': numeric_s_values[0]
                 })  # TODO - ERROR PRONE - added for cengel p10-34, 35
Ejemplo n.º 11
0
    def cQuery(self, conditions: Dict) -> DataFrame:
        """Custom query method - wrapper around the regular DataFrame.query for convenience."""
        queryString_components = []

        for columnName, columnValue in conditions.items():
            if isinstance(columnValue, tuple):
                if all(isNumeric(value) for value in columnValue):
                    queryString_components.append('{0} <= {1} <= {2}'.format(
                        columnValue[0], columnName, columnValue[1]))
                elif isinstance(sign := columnValue[0], str) and isNumeric(
                        columnValue[1]):
                    if any(sign == ltSign
                           for ltSign in ['<', '<=', '>', '>=']):
                        queryString_components.append('{0} {1} {2}'.format(
                            columnName, sign, columnValue[0]))
                    else:
                        raise AssertionError(
                            'Query string could not be resolved.')
            elif any(isinstance(columnValue, _type) for _type in [float, int]):
                queryString_components.append('{0} == {1}'.format(
                    columnName, columnValue))
Ejemplo n.º 12
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
Ejemplo n.º 13
0
def define_StateIGas(state: StateIGas, fluid: 'IdealGas'):
    """Tries to fill in the properties of an ideal gas state by applying the ideal gas law and looking up state on the provided mpDF."""

    if len(available_TDependentProperties := [propertyName for propertyName in fluid.mpDF.mp.availableProperties if propertyName in state._properties_all and isNumeric(getattr(state, propertyName))]) >= 1:
        refPropt_name = available_TDependentProperties[0]

        # Try finding exact state on mpDF
        state_at_refPropt = fluid.mpDF.cq.cQuery({refPropt_name: getattr(state, refPropt_name)})
        try:
            if state_at_refPropt.empty:
                # Interpolate in mpDF
                refPropt_valueBelow, refPropt_valueAbove = get_surroundingValues(fluid.mpDF[refPropt_name], value=getattr(state, refPropt_name))
                state_at_refPropt_valueBelow = StateIGas().init_fromDFRow(fluid.mpDF.cq.cQuery({refPropt_name: refPropt_valueBelow}))
                state_at_refPropt_valueAbove = StateIGas().init_fromDFRow(fluid.mpDF.cq.cQuery({refPropt_name: refPropt_valueAbove}))
                state_at_refPropt = interpolate_betweenPureStates(state_at_refPropt_valueBelow, state_at_refPropt_valueAbove, interpolate_at={refPropt_name: getattr(state, refPropt_name)})

            state.copy_fromState(state_at_refPropt)
        except NeedsExtrapolationError:
            pass
Ejemplo n.º 14
0
def apply_IGasLaw(state: StateIGas, R: float):
    """Uses Ideal Gas Law to find missing properties, if possible. If all variables in the Ideal Gas Law are already defined, checks consistency"""
    # P mu = R T
    IGasLaw_allProperties = ['P', 'mu', 'T']
    IGasLaw_availableProperties = [propertyName for propertyName in IGasLaw_allProperties if isNumeric(getattr(state, propertyName))]
    IGasLaw_missingProperties = [propertyName for propertyName in IGasLaw_allProperties if propertyName not in IGasLaw_availableProperties]

    if (number_ofMissingProperties := len(IGasLaw_missingProperties)) == 1:
        assert state.isFullyDefinable()
        missingProperty = IGasLaw_missingProperties[0]
        if missingProperty == 'P':
            state.P = (R * to_Kelvin(state.T) / state.mu)
        elif missingProperty == 'mu':
            state.mu = (R * to_Kelvin(state.T) / state.P)
        elif missingProperty == 'T':
            state.T = to_deg_C(state.P * state.mu / R)
Ejemplo n.º 15
0
def fullyDefine_StatePure(state: StatePure, mpDF: DataFrame):
    """Fully defines StatePure objects by looking them up / interpolating on the material property table."""

    assert state.isFullyDefinable(), 'State not fully definable: need at least 2 (independent) intensive properties to be known.'
    availableProperties = state.get_asDict_definedProperties()
    availablePropertiesNames = list(availableProperties.keys())
    non_referencePropertiesNames = [propertyName for propertyName in availablePropertiesNames if propertyName not in ['P', 'T']]
    P_available, T_available = ('P' in availablePropertiesNames), ('T' in availablePropertiesNames)

    # DETERMINE PHASE OF SUBSTANCE

    isSaturatedMixture = None

    # If quality is provided, phase is inferred
    if isNumeric(state.x):
        isSaturatedMixture = (0 <= state.x <= 1)

    # If quality is not provided, determine phase, then assign / calculate quality
    else:
        # Determine phase: Compare provided T to the saturation T at provided P
        if P_available and T_available:
            # If both P & T are provided, check if T is above saturation temperature at that P
            # Using the fact that P & T are not independent for saturated states - substance is saturated at known temperature and pressure

            saturationTemperature_atP = get_saturationTemperature_atP(mpDF, P=state.P)  # This can handle pressures at which no distinct saturation process exists

            if state.T == saturationTemperature_atP:
                # state.x needs to be calculated
                isSaturatedMixture = True
            elif state.T > saturationTemperature_atP:
                state.x = 2
                isSaturatedMixture = False
            elif state.T < saturationTemperature_atP:
                state.x = -1
                isSaturatedMixture = False

        # Determine phase
        elif P_available or T_available:

            # Compare provided P / T to critical P / T of substance - Check if there are even saturation states at the P/T to begin with. If there are, couldBe_saturatedMixture.
            couldBe_saturatedMixture = False

            if P_available:
                if state.P > mpDF.mp.criticalPoint.P:
                    isSaturatedMixture = False
                    state.x = 2
                else:
                    couldBe_saturatedMixture = True
            elif T_available:
                if state.T > mpDF.mp.criticalPoint.T:
                    isSaturatedMixture = False
                    state.x = 2
                else:
                    couldBe_saturatedMixture = True

            # Determine phase: Saturated states do exist at the provided P / T. Check if saturated mixture with P / T and 1 other property
            if couldBe_saturatedMixture:
                # Is provided u/h/s/mu between saturation limits at the provided T/P?

                satLiq_atRef, satVap_atRef = get_saturationProperties(mpDF, P=state.P, T=state.T)

                # Define lambda function to check if value of a non-reference property (i.e. property other than P/T) is within the saturated mixture limits
                isWithinSaturationZone = lambda propertyName, propertyValue: getattr(satLiq_atRef, propertyName) <= propertyValue <= getattr(satVap_atRef, propertyName)
                isSuperheated = lambda propertyName, propertyValue: getattr(satVap_atRef, propertyName) < propertyValue
                isSubcooled = lambda propertyName, propertyValue: propertyValue < getattr(satLiq_atRef, propertyName)

                # Check if the first available non-reference property has value within saturation limits
                isSaturatedMixture = isWithinSaturationZone(non_referencePropertiesNames[0], getattr(state, non_referencePropertiesNames[0]))

                # All non-reference properties should give the same result - if the first one is found to be within saturation limits, all should be so.
                assert all(isSaturatedMixture == isWithinSaturationZone(propertyName, getattr(state, propertyName)) for propertyName in non_referencePropertiesNames), 'ThDataError: While defining state {0}, property {1} suggests saturated state (value within saturation limits), but other properties do not.'.format(state, non_referencePropertiesNames[0])
                if isSaturatedMixture:
                    # Calculate state.x using the first available non-reference property
                    calcProptName, calcProptValue = non_referencePropertiesNames[0], availableProperties[non_referencePropertiesNames[0]]
                    state.x = (calcProptValue - getattr(satLiq_atRef, calcProptName))/(getattr(satVap_atRef, calcProptName) - getattr(satLiq_atRef, calcProptName))

                else:
                    superheated = isSuperheated(non_referencePropertiesNames[0], getattr(state, non_referencePropertiesNames[0]))
                    # Check if first non-ref propt suggests suph, then assert all other non-ref propts to suggest the same
                    assert all(superheated == isSuperheated(propertyName, getattr(state, propertyName)) for propertyName in non_referencePropertiesNames), 'ThDataError: While defining state {0}, property {1} suggests superheated state (value above saturation limits), but other properties do not.'.format(state, non_referencePropertiesNames[0])

                    if superheated:
                        state.x = 2
                    else:
                        subcooled = isSubcooled(non_referencePropertiesNames[0], getattr(state, non_referencePropertiesNames[0]))
                        # Check if first non-ref propt suggests subc, then assert all other non-ref propts to suggest the same
                        assert all(subcooled == isSubcooled(propertyName, getattr(state, propertyName)) for propertyName in non_referencePropertiesNames), 'ThDataError: While defining state {0}, property {1} suggests subcooled state (value below saturation limits), but other properties do not.'.format(state, non_referencePropertiesNames[0])

                        if subcooled:
                            state.x = -1
                        else:
                            raise AssertionError('Error: While checking if state is saturated using P or T as reference, could not determine if state is subcooled / saturated / superheated.')

        # Determine phase: Neither P / T of the state provided
        else:
            # Determine if saturated or not using properties other than P/T - P/T not available
            # TODO
            isSaturatedMixture = False
            raise FeatureNotAvailableError('State definition with variables other than at least P / T')

        # By now, it should have been determined whether state is a saturated (mixture) and state.x should have been defined.
        assert isSaturatedMixture is not None and isNumeric(state.x)

    # Fully define state: State is saturated (mixture)
    if isSaturatedMixture:
        if P_available or T_available:
            satLiq_atP, satVap_atP = get_saturationProperties(mpDF, P=state.P, T=state.T)  # either state.P or state.T has to be known - pass both, it is ok if one is NaN
            if state.x == 0:
                return satLiq_atP
            elif state.x == 1:
                return satVap_atP
            else:  # saturated mixture with unique quality (not 0 or 1)
                return interpolate_betweenPureStates(satLiq_atP, satVap_atP, interpolate_at={'x': state.x})
        else:
            # Define saturated state with properties other than P/T
            pass

    # Fully define state: State is not saturated (mixture)
    else:
        # Set phase_mpDF: section of main mpDF with states of only the same phase
        if state.x == 2:
            phase_mpDF = mpDF.cq.suphVaps
            phase = 'superheated'
        elif state.x == -1:
            phase_mpDF = mpDF.cq.subcLiqs
            phase = 'subcooled'
        else:
            # This would be a coding error - can be removed once confidence is established
            raise AssertionError('Error: Phase of state could not be determined - x value not -1, 0-1 or 2')

        refPropt1_name, refPropt2_name = availablePropertiesNames[:2]  # first 2 available properties used as reference  # TODO: If cannot be interpolated with these 2, can try others if provided
        refPropt1_queryValue, refPropt2_queryValue = [availableProperties[property] for property in [refPropt1_name, refPropt2_name]]
        refPropts = [(refPropt1_name, refPropt1_queryValue), (refPropt2_name, refPropt2_queryValue)]

        # Check if exact state available
        exactState = mpDF.cq.cQuery({refPropt1_name: refPropt1_queryValue, refPropt2_name: refPropt2_queryValue})

        if not exactState.empty:
            if len(exactState.index) == 1:
                return StatePure().init_fromDFRow(exactState)
            else:
                # Found multiple states with same P & T - need to pick one
                # TODO - Pick one
                raise AssertionError('NotImplementedError: Multiple states satisfying same conditions found.')

        # Exact state not available
        else:
            # Check if 1D interpolation possible
            _1d_interpolationCheck = {}

            # Check if either refPropt1_queryValue or refPropt2_queryValue has data available
            for refProptCurrent_index, (refProptCurrent_name, refProptCurrent_queryValue) in enumerate(refPropts):

                states_at_refProptCurrent = phase_mpDF.cq.cQuery({refProptCurrent_name: refProptCurrent_queryValue})

                if not states_at_refProptCurrent.empty and len(states_at_refProptCurrent.index) > 1:  # there should be more than one state at refProptCurrent to interpolate between
                    # If so, get refProptOther and its interpolation gap (gap between available values)

                    refProptOther_name, refProptOther_queryValue = refPropts[refProptCurrent_index - 1]
                    values_of_refProptOther = states_at_refProptCurrent[refProptOther_name].to_list()

                    try:
                        refProptOther_valueBelow, refProptOther_valueAbove = get_surroundingValues(values_of_refProptOther, refProptOther_queryValue)
                    except NeedsExtrapolationError:
                        _1d_interpolationCheck.update({refProptCurrent_name: {'1D_interpolatable': False, 'gap': None}})
                        continue

                    gap_betweenValues = abs(refProptOther_valueAbove - refProptOther_valueBelow)

                    _1d_interpolationCheck.update({refProptCurrent_name: {'1D_interpolatable': True, 'gap': gap_betweenValues,
                                                                          'refProptOther': {'name': refProptOther_name, 'surroundingValues': (refProptOther_valueBelow, refProptOther_valueAbove)}}})

                else:
                    _1d_interpolationCheck.update({refProptCurrent_name: {'1D_interpolatable': False, 'gap': None}})

            # Pick reference property to hold constant: pick the one where the interpolation interval for the other refPropt is minimum
            # Future: consider picking one where the gap between query value and an endpoint is the minimum

            minimumGap = 10**5  # arbitrary large value
            refPropt_for_minimumGap_in1Dinterpolation = None

            for refProptCurrent_name in _1d_interpolationCheck:
                if _1d_interpolationCheck[refProptCurrent_name]['1D_interpolatable']:
                    if (gap_of_refProptCurrent := _1d_interpolationCheck[refProptCurrent_name]['gap']) < minimumGap:
                        minimumGap = gap_of_refProptCurrent
                        refPropt_for_minimumGap_in1Dinterpolation = refProptCurrent_name

            if refPropt_for_minimumGap_in1Dinterpolation is not None:
                # 1D INTERPOLATION
                # At least one refPropt allows 1D interpolation. If multiple does, the one where the other has the minimum interpolation gap has been picked

                refPropt_name = refPropt_for_minimumGap_in1Dinterpolation
                refPropt_value = availableProperties[refPropt_name]

                refProptOther_name = _1d_interpolationCheck[refPropt_name]['refProptOther']['name']
                refProptOther_queryValue = availableProperties[refProptOther_name]
                refProptOther_valueBelow, refProptOther_valueAbove = _1d_interpolationCheck[refPropt_name]['refProptOther']['surroundingValues']

                state_with_refProptOther_valueBelow = StatePure().init_fromDFRow(phase_mpDF.cq.cQuery({refPropt_name: refPropt_value,
                                                                                                       refProptOther_name: refProptOther_valueBelow}))

                state_with_refProptOther_valueAbove = StatePure().init_fromDFRow(phase_mpDF.cq.cQuery({refPropt_name: refPropt_value,
                                                                                                       refProptOther_name: refProptOther_valueAbove}))

                return interpolate_betweenPureStates(state_with_refProptOther_valueBelow, state_with_refProptOther_valueAbove, interpolate_at={refProptOther_name: refProptOther_queryValue})

            else:
                # DOUBLE INTERPOLATION
                available_refProptPairs = list(phase_mpDF[[refPropt1_name, refPropt2_name]].itertuples(index=False, name=None))

                # refPropt1 -> x, refPropt2 -> y

                xVals = sorted(set(pair[0] for pair in available_refProptPairs))
                xVals_available_yVals = {xVal: set(pair[1] for pair in available_refProptPairs if pair[0] == xVal) for xVal in xVals}

                xVals_less = xVals[: (index := bisect_left(xVals, refPropt1_queryValue))]  # list of available xValues less than query value available in states
                xVals_more = xVals[index:]  # same for available xValues more than query value

                # Iterate over values of x surrounding the queryValue of x (= refPropt1_queryValue)
                t1 = time()

                # Strategy: First find 2 states:
                # one with x value less than refPropt1_queryValue but with y value = refPropt2_queryValue
                # one with x value more than refPropt1_queryValue but with y value = refPropt2_queryValue
                # i.e. two states surround the requested state in terms of x.
                # To find these 2 states, iterate over available xValues more than and less than the x query value. At each iteration, TRY to get the 2 surrounding values of y available for that x.
                # There may not be 2 values of y available at each x value surrounding the queried y value. In such case, try/except clause continues iteration with a new x value.
                # Once at an x value, 2 values of y surrounding the y query value are found, interpolate between the states with (xVal, yVal_below) and (xVal, yVal_above)
                # The outmost for loop does this for both state with x value less than x query value and state with x value more than x query value.

                states_at_y_queryValue = []  # list of states at which y = refPropt2 value is the query value, but x = refPropt1 value is not the query value.

                for available_xValList in [reversed(xVals_less), xVals_more]:

                    for xVal in available_xValList:
                        xVal_available_yVals = xVals_available_yVals[xVal]

                        try:
                            yVal_below, yVal_above = get_surroundingValues(xVal_available_yVals, refPropt2_queryValue)
                        except NeedsExtrapolationError:
                            continue

                        state_at_xVal_less_yVal_below = StatePure().init_fromDFRow(phase_mpDF.cq.cQuery({refPropt1_name: xVal, refPropt2_name: yVal_below}))
                        state_at_xVal_less_yVal_above = StatePure().init_fromDFRow(phase_mpDF.cq.cQuery({refPropt1_name: xVal, refPropt2_name: yVal_above}))

                        states_at_y_queryValue.append(interpolate_betweenPureStates(state_at_xVal_less_yVal_below, state_at_xVal_less_yVal_above, interpolate_at={refPropt2_name: refPropt2_queryValue}))
                        break

                if len(states_at_y_queryValue) == 2:
                    t2 = time()
                    print('TimeNotification: 2DInterpolation - Time to iteratively find smallest suitable interpolation interval: {0} seconds'.format((t2 - t1) / 1000))
                    return interpolate_betweenPureStates(states_at_y_queryValue[0], states_at_y_queryValue[1], interpolate_at={refPropt1_name: refPropt1_queryValue})
                else:
                    # 2 states to interpolate between could not be found
                    print('ThPrNotification: 2DInterpolation not successful.')
                    if state.x == -1 and T_available and P_available:
                        # SATURATED LIQUID APPROXIMATION AT SAME TEMPERATURE FOR SUBCOOLED LIQUIDS
                        print('ThPrNotification: Applying saturated liquid approximation for subcooled liquid state.')
                        satLiq_atT = get_saturationProperties(mpDF, T=state.T)[0]  # returns [satLiq, satVap], pick first
                        # should provide full mpDF and not phase_mpDF - if phase is subcooled, won't find saturated states (x=0) in its phase_mpDF

                        toReturn = satLiq_atT
                        toReturn.P = state.P  # Equation 3-8
                        toReturn.h = toReturn.h + toReturn.mu * (state.P - satLiq_atT.P)  # Equation 3-9
                        return toReturn
                    else:
                        raise NeedsExtrapolationError('DataError InputError: No {0} states with values of {1} lower or higher than the query value of {2} are available in the data table.'.format(phase.upper(), refPropt1_name, refPropt1_queryValue))
Ejemplo n.º 16
0
    def solve(self):

        # Steps for initialization
        if not self._initialSolutionComplete:
            self._convertStates_toFlowPoints(
            )  # At the cycle level, states become FlowPoints, i.e. states that are aware of the flows they are in.

            self._deviceDict = self.get_deviceDict()
            self._regerator_solutionSetups = {}

            if Regenerator in self._deviceDict:
                for regenerator in self._deviceDict[Regenerator]:
                    self._regerator_solutionSetups[regenerator] = False

            # 2 separate loops - device endStates may be from different flows. First set endState references, then work on each device for each flow they are in.
            for flow in self.flows:
                flow._set_devices_endStateReferences()

            for flow in self.flows:
                flow.solve()

            # Get updated unknowns (i.e. defined states) from flow solution

            # TODO #######################################################################

            # Identify areas where flows interact, e.g. heat exchangers or flow connections
            self.intersections = self._get_intersections()

            if not all(flow.isFullyDefined() for flow in self.flows):
                for device in self.intersections:
                    self._solveIntersection(
                        device
                    )  # constructs equations for intersections & adds to the pool

            # Review intersections, construct equations & attempt to solve - if there are undefined states around them
            # This process needs to be done only once since equations need to be constructed once. Then they are added to the _equations pool.

            # intersections_attempted_toSolve = []  # list of intersection devices on which the _solveIntersection() has been run - keeping track not to run the _solveIntersection twice on the same device
            # for state in self.get_undefinedStates():
            #     surroundingDevices = state.flow.get_surroundingItems(state)
            #     for surroundingDevice in surroundingDevices:
            #         if surroundingDevice in self.intersections and surroundingDevice not in intersections_attempted_toSolve:
            #             self._solveIntersection(surroundingDevice)
            #             intersections_attempted_toSolve.append(surroundingDevice)

            self._add_net_sPower_relation()
            self._add_sHeat_relation()
            self._add_netPowerBalance()
            self._add_Q_in_relation()

            if self.type == 'power':
                self._add_efficiency_relation()
            elif self.type == 'refrigeration':
                self._add_COP_relation()

            self._add_massFlowRelations()

            updateEquations(
                self._equations, self._updatedUnknowns
            )  # updating all equations in case _solveIntersection() above found any of their unknowns
            self._initialSolutionComplete = True
        # Initialization steps completed.

        # Quick and dirty regenerator solution - need to form heat balance equation only once, check each time if equation formed for device.
        if Regenerator in self._deviceDict:
            for regenerator in self._deviceDict[Regenerator]:
                if self._regerator_solutionSetups[regenerator] == False:
                    solution_setup = self.solve_regenerator(regenerator)
                    self._regerator_solutionSetups[
                        regenerator] = solution_setup

        for deviceType in [Combustor, GasReheater]:
            if deviceType in self._deviceDict:
                for device in self._deviceDict[Combustor]:
                    if isNumeric(device.sHeatSupplied):
                        self._add_sHeatSupplied_relation(device)

        # Review flow devices again in case some properties of some of their endStates became known above
        for flow in self.flows:
            flow.solve()

        updateEquations(self._equations, self._updatedUnknowns)
        updatedUnknowns = solve_solvableEquations(self._equations)
        self._updatedUnknowns.union(updatedUnknowns)

        updateEquations(self._equations, self._updatedUnknowns)
        updatedUnknowns = solve_combination_ofEquations(self._equations,
                                                        number_ofEquations=2)
        self._updatedUnknowns.union(updatedUnknowns)

        updateEquations(self._equations, self._updatedUnknowns)
        updatedUnknowns = solve_combination_ofEquations(self._equations,
                                                        number_ofEquations=3)
        self._updatedUnknowns.union(updatedUnknowns)

        updateEquations(self._equations, self._updatedUnknowns)
Ejemplo n.º 17
0
 def get_mu_R(self, state: StateIGas):
     assert isNumeric(state.T)
     return state.T / self.get_P_r(state)
Ejemplo n.º 18
0
 def get_P_r(self, state: StateIGas):
     assert isNumeric(state.s0)
     return exp(state.s0 / self.workingFluid.R)
Ejemplo n.º 19
0
    def copy_fromState(self, referenceState: 'StatePure'):
        for propertyName in self._properties_all:
            if isNumeric(
                    referenceValue := getattr(referenceState, propertyName)):
                setattr(self, propertyName, referenceValue)

    def copy_or_verify_fromState(self,
                                 referenceState: 'StatePure',
                                 pTolerance: float = 3):
        """Copies property values from the provided reference state. If property already has a value defined, compares it to the one desired to be assigned, raises error if values do not match.
        If the values match, still copies the value from the referenceState - decimals might change."""
        for propertyName in self._properties_all:
            if isNumeric(
                    referenceValue := getattr(referenceState, propertyName)):
                if not isNumeric(getattr(self, propertyName)):
                    setattr(self, propertyName, referenceValue)
                else:
                    # property has a value defined
                    if not isWithin(getattr(self, propertyName), 3, '%',
                                    referenceValue):
                        raise AssertionError

    def set(self, setDict: Dict):
        """Sets values of the properties to the values provided in the dictionary."""
        for parameterName in setDict:
            if parameterName in self._properties_all:
                setattr(self, parameterName, setDict[parameterName])

    def set_or_verify(self, setDict: Dict, percentDifference: float = 3):
        for parameterName in setDict:
Ejemplo n.º 20
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
Ejemplo n.º 21
0
 def hasDefined(self, propertyName: str) -> bool:
     """Returns true if a value for the given property is defined."""
     return isNumeric(getattr(self, propertyName))
Ejemplo n.º 22
0
    def organizeTerms_fromOriginal(self):
        """Iterates over the LHS terms provided in the **original equation description**, replaces variables with their values if they are known, moves constants to the RHS."""
        self.LHS = []

        # LHS: [  ]
        # LHS: [ term1, term2, term3 ]
        # LHS: [ (   ), (   ), (   ) ]
        # LHS: [ ( item1, item2, item3 ), term2, term3 ]
        #          _______________  _______________  __
        # LHS: [ ( (obj1, 'attr1'), (obj2, 'attr2'), 20 ), term2, term3 ]


        # Expand brackets
        def someTermsHaveListItems():
            for term in self._LHS_original:
                if any(isinstance(item, list) for item in term):
                    return True
            return False

        while someTermsHaveListItems():
            for termIndex, Term in enumerate(self._LHS_original):
                newTerms_toAdd = []
                listItems_inTerm = [item for item in Term if isinstance(item, list)]
                hasListItems = len(listItems_inTerm) > 0

                for listItem_inTerm in listItems_inTerm:
                    otherItems_inTerm = tuple(item for item in Term if item is not listItem_inTerm)  # otherItems_inTerm are all multiplied items
                    for term in listItem_inTerm:
                        assert isinstance(term, tuple)  # isolated term in format (const, [unknowns])
                        #                                            coeff         unknown addresses unpacked from unknowns list
                        newTerms_toAdd.append( otherItems_inTerm + (term[0],) + tuple(unknownAddress for unknownAddress in term[1]) )
                    break  # process one list item in the term at once

                if hasListItems:
                    for newTerm_toAdd in reversed(newTerms_toAdd):
                        self._LHS_original.insert(termIndex, newTerm_toAdd)
                    self._LHS_original.remove(Term)
                    break  # process one term and break
        # Brackets expanded.

        for term in self._LHS_original:  # each term is a tuple as all brackets expanded above
            constantFactors = []
            unknownFactors = []

            for item in term:  # items within the term are to be multiplied

                if isinstance(item, tuple):
                    # item is an object.attribute address, in form (object, 'attribute')
                    assert isinstance(item[1], str)

                    attribute = getattr_fromAddress(*item)
                    if isNumeric(attribute):
                        # If the object.attribute has a value, add it to constant factors
                        constantFactors.append(attribute)
                    else:
                        # If the object.attribute does not have a value, it is an unknownFactor
                        # TODO - the following check may be accommodated to have equation solved if the high-power term is found in another equation
                        assert item not in unknownFactors, 'LinearEquationError: Same unknown appears twice in one term, i.e. a higher power of the unknown encountered - not a linear equation!'
                        unknownFactors.append(item)

                elif any(isinstance(item, _type) for _type in [float, int]):
                    # item is a number, i.e. a coefficient
                    assert isNumeric(item)
                    constantFactors.append(item)

            constantFactor = 1
            for factor in constantFactors:
                constantFactor *= factor

            if len(unknownFactors) != 0:
                # term has an unknown, e.g. term is in form of "6*x"
                self.LHS.append([constantFactor, unknownFactors])
            else:
                # term does not have an unknown, e.g. term is in form "6"
                self.RHS -= constantFactor  # move constant term to the RHS

        self._gatherUnknowns()
Ejemplo n.º 23
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)