コード例 #1
0
ファイル: Fluids.py プロジェクト: M-R-FARHADI/Thermobrig
 def defineState_ifDefinable(self, state: StatePure):
     if not state.isFullyDefined() and state.isFullyDefinable():
         try:
             state.copy_fromState(self.define(state))
         except NeedsExtrapolationError:
             print(
                 'Fluid.defineState_ifDefinable: Leaving state @ {0} not fully defined'
                 .format(state))
     return state
コード例 #2
0
ファイル: ThprTests.py プロジェクト: canunlusoy/Thermobrig
    def test_satMix_02(self):
        # From MECH2201 - A9 Q2 - state 4

        statePropt = {'P': 5, 's': 7.7622, 'x': 0.92}
        testState = StatePure(**statePropt)
        self.assertTrue(testState.isFullyDefinable())
        testState = fullyDefine_StatePure(testState, water_mpDF)
        expected = 2367.8
        self.assertTrue(isWithin(testState.h, 3, '%', expected))
        print('Expected: {0}'.format(expected))
        print('Received: {0}'.format(testState.h))
コード例 #3
0
ファイル: ThprTests.py プロジェクト: canunlusoy/Thermobrig
    def test_satMix_01(self):
        # From MECH2201 - A9 Q3

        statePropt = {'P': 6, 's': 6.4855, 'x': 0.7638}
        testState = StatePure(**statePropt)
        self.assertTrue(testState.isFullyDefinable())
        testState = fullyDefine_StatePure(testState, water_mpDF)
        expected = 1996.7
        self.assertTrue(isWithin(testState.h, 3, '%', expected))
        print('Expected: {0}'.format(expected))
        print('Received: {0}'.format(testState.h))
コード例 #4
0
ファイル: ThprOps.py プロジェクト: canunlusoy/Thermobrig
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))