class Controller(NumericalModel): initialState = F.Choices( OrderedDict(( (TC.FUELING, 'fueling'), (TC.EXTRACTION, 'extraction'), )), label='start with', description='start the simulation with fueling or extraction') tWaitBeforeExtraction = F.Quantity( 'Time', default=(0., 's'), minValue=(0., 's'), maxValue=(1.e6, 's'), label='τ (extraction)', description='waiting time before each extraction') tWaitBeforeFueling = F.Quantity( 'Time', default=(0., 's'), minValue=(0., 's'), maxValue=(1.e6, 's'), label='τ (fueling)', description='waiting time before each fueling') pMin = F.Quantity( 'Pressure', default=(0., 'bar'), label='minimum pressure</sub>', description='minimum pressure in the gas storage for starting fueling') pMax = F.Quantity( 'Pressure', default=(0., 'bar'), label='maximum pressure', description='maximum pressure in the gas storage for stopping fueling') mDotExtr = F.Quantity('MassFlowRate', default=(0., 'kg/h'), label='extraction mass flow rate', description='extraction mass flow rate') nCompressor = F.Quantity('AngularVelocity', default=(0., 'rev/s'), minValue=(0, 'rev/s'), maxValue=(1e4, 'rev/s'), label='compressor speed', description='compressor speed') FG = F.FieldGroup([ initialState, pMin, pMax, mDotExtr, nCompressor, tWaitBeforeExtraction, tWaitBeforeFueling ], label='Parameters') SG = F.SuperGroup([FG], label="Parameters") modelBlocks = []
class FluidFlowInput(NumericalModel): fluidName = F.Choices(Fluids, default='ParaHydrogen', label='fluid') flowRateChoice = F.Choices(OrderedDict(( ('V', 'volume'), ('m', 'mass'), )), label='flow rate based on') mDot = F.Quantity('MassFlowRate', minValue=(0, 'kg/h'), default=(1., 'kg/h'), label='mass flow', description='mass flow rate', show='self.flowRateChoice == "m"') VDot = F.Quantity('VolumetricFlowRate', minValue=(0., 'm**3/h'), default=(1., 'm**3/h'), label='volume flow', description='volume flow rate', show='self.flowRateChoice == "V"') T = F.Quantity('Temperature', default=(300., 'K'), label='temperature') p = F.Quantity('Pressure', default=(1., 'bar'), label='pressure') FG = F.FieldGroup([fluidName, flowRateChoice, mDot, VDot, T, p], label='Parameters') modelBlocks = [] def createFluidState(self): return CP.FluidState(self.fluidName) def compute(self): self.fState = self.createFluidState() self.fState.update_Tp(self.T, self.p) if (self.flowRateChoice == 'm'): self.VDot = self.mDot / self.fState.rho else: self.mDot = self.VDot * self.fState.rho
class LiquefactionCycle(ThermodynamicalCycle): abstract = True #================ Inputs ================# fluidName = F.Choices(Fluids, default='R134a', label='liquefied fluid') mDot = F.Quantity('MassFlowRate', default=(1, 'kg/min'), label='inlet flow rate') pIn = F.Quantity('Pressure', default=(1, 'bar'), label='inlet gas pressure') TIn = F.Quantity('Temperature', default=(15, 'degC'), label='inlet gas temperature') pHigh = F.Quantity('Pressure', default=(40, 'bar'), label='compressor high pressure') pLiquid = F.Quantity('Pressure', default=(2, 'bar'), label='liquid pressure') TAmbient = F.Quantity( 'Temperature', default=(15, 'degC'), label='ambient temperature', description='used as reference temperature to calculate exergy') workingFluidGroup = F.FieldGroup( ['fluidName', 'mDot', pIn, TIn, pHigh, pLiquid, TAmbient], label='Cycle parameters') #================ Results ================# liqEnergy = F.Quantity('SpecificEnergy', default=(1, 'kJ/kg'), label='liquefaction energy') minLiqEnergy = F.Quantity( 'SpecificEnergy', default=(1, 'kJ/kg'), label='min. liquefaction energy', description= 'minimum energy required for liquefaction in an ideal carnot cycle; \ equal to the difference in exergies between initial and final state') etaSecondLaw = F.Quantity( 'Efficiency', label='figure of merit (FOM)', description= 'minimum energy required for liquefaction to the actual energy required \ in the cycle; equivalent to second law efficiency') efficiencyFieldGroup = F.FieldGroup( [liqEnergy, minLiqEnergy, etaSecondLaw], label='Efficiency')
class IncompressibleSolutionFlowInput(FluidFlowInput): solName = F.Choices(IncompressibleSolutions, default='MEG', label='fluid (name)', description='fluid (incompressible solutions)') solMassFraction = F.Quantity( 'Fraction', default=(0, '%'), label='fluid (mass fraction)', description='mass fraction of the substance other than water') incomSolFG = F.FieldGroup( [solName, solMassFraction, 'flowRateChoice', 'mDot', 'VDot', 'T', 'p'], label='Parameters') def createFluidState(self): return CP5.FluidStateFactory.createIncompressibleSolution( self.solName, self.solMassFraction)
class FluidStateSource(NumericalModel): sourceTypeTxt = F.Choices(OrderedDict(( ('TP', 'temperature, pressure'), ('PQ', 'pressure, vapour quality'), ('TQ', 'temperature, vapour quality'), )), label='state variables', description='choose the initial state variables') @property def sourceType(self): if self.sourceTypeTxt == 'TP': return DM.FluidStateSource.TP if self.sourceTypeTxt == 'PQ': return DM.FluidStateSource.PQ if self.sourceTypeTxt == 'TQ': return DM.FluidStateSource.TQ else: raise ValueError('Unsupported source type of FluidStateSource.') T = F.Quantity( 'Temperature', default=(0., 'degC'), label='temperature', description='temperature', show="self.sourceTypeTxt == 'TP' || self.sourceTypeTxt == 'TQ'") p = F.Quantity( 'Pressure', default=(0., 'bar'), label='pressure', description='pressure', show="self.sourceTypeTxt == 'TP' || self.sourceTypeTxt == 'PQ'") q = F.Quantity( 'VaporQuality', default=(0., '-'), minValue=0, maxValue=1, label='vapour quality', description='vapour quality', show="self.sourceTypeTxt == 'TQ' || self.sourceTypeTxt == 'PQ'") FG = F.FieldGroup([sourceTypeTxt, T, p, q], label='Parameters') modelBlocks = []
class Compressor(CycleComponent2FlowPorts): modelType = F.Choices(OrderedDict(( ('S', 'isentropic'), ('T', 'isothermal'), )), label='compressor model') etaS = F.Quantity('Efficiency', label='isentropic efficiency', show="self.modelType == 'S'") fQ = F.Quantity('Fraction', default=0., label='heat loss factor', show="self.modelType == 'S'") etaT = F.Quantity('Efficiency', label='isosthermal efficiency', show="self.modelType == 'T'") dT = F.Quantity('TemperatureDifference', default=0, label='temperature increase', show="self.modelType == 'T'") FG = F.FieldGroup([modelType, etaS, fQ, etaT, dT], label='Compressor') modelBlocks = [] #================== Methods =================# def compute(self, pOut): CycleComponent2FlowPorts.compute(self) if (pOut < self.inlet.state.p): raise ValueError( 'Outlet pressure must be higher than inlet pressure') if (self.modelType == 'S'): self.outlet.state.update_ps(pOut, self.inlet.state.s) wIdeal = self.outlet.state.h - self.inlet.state.h self.w = wIdeal / self.etaS self.qIn = -self.fQ * self.w self.delta_h = self.w + self.qIn self.outlet.state.update_ph(pOut, self.inlet.state.h + self.delta_h) else: self.outlet.state.update_Tp(self.inlet.state.T + self.dT, pOut) self.qIn = (self.outlet.state.s - self.inlet.state.s) * self.inlet.state.T wIdeal = self.outlet.state.h - self.inlet.state.h - self.qIn self.w = wIdeal / self.etaT self.qIn -= (self.w - wIdeal)
class Condenser(IsobaricHeatExchanger): computeMethod = F.Choices(OrderedDict(( ('dT', 'sub-cooling'), ('Q', 'vapor quality'), ('eta', 'thermal efficiency'), ('T', 'temperature'), ('H', 'enthalpy'), )), label='compute outlet by') dTOutlet = F.Quantity('TemperatureDifference', default=(-10, 'degC'), minValue=-1e10, maxValue=-1e-3, label='outlet subcooling', show='self.computeMethod == "dT"') FG = F.FieldGroup([ 'computeMethod', 'etaThermal', 'TExt', 'dTOutlet', 'TOutlet', 'qOutlet', 'hOutlet' ], label='Condenser') modelBlocks = []
class Cooler(NumericalModel): workingState = F.Choices(OrderedDict(( (0, 'no'), (1, 'yes'), )), label='enable cooler', description='enable cooler') epsilon = F.Quantity('Efficiency', default=(0., '-'), label='effectiveness', description='effectiveness', show="self.workingState") TCooler = F.Quantity('Temperature', default=(0., 'degC'), label='coolant temperature', description='coolant temperature', show="self.workingState") FG = F.FieldGroup([workingState, epsilon, TCooler], label='Parameters') modelBlocks = []
class Expansion(ThermodynamicalProcess): label = "Expansion" description = F.ModelDescription( "Parameteric model for expansion process: isentropic and isenthalpic", show=True) figure = F.ModelFigure(src="ThermoFluids/img/ModuleImages/Expansion.svg") turbine = F.SubModelGroup(TC.Turbine, 'FG', label='Turbine', show="self.processType == 'S'") throttleValve = F.SubModelGroup(TC.ThrottleValve, 'FG', show="false") processType = F.Choices(options=OrderedDict(( ('S', 'isentropic'), ('H', 'isenthalpic'), )), default='S', label="process type") processTypeFG = F.FieldGroup([processType], label="Process type") inputs = F.SuperGroup( ['fluidSource', 'fluidSink', processTypeFG, turbine, throttleValve]) def __init__(self): self.fluidSource.p1 = (10, 'bar') self.fluidSource.p2 = (10, 'bar') self.fluidSink.p = (1, 'bar') def compute(self): if (self.processType == 'S'): component = self.turbine elif (self.processType == 'H'): component = self.throttleValve self.initCompute(self.fluidSource.fluidName) # Connect components self.connectPorts(self.fluidSource.outlet, component.inlet) self.connectPorts(component.outlet, self.fluidSink.inlet) self.fluidSource.compute() component.compute(self.fluidSink.p) self.postProcess(component)
class HeatEngineCycle(ThermodynamicalCycle): abstract = True #================ Inputs ================# fluidName = F.Choices(Fluids, default='R134a', label='refrigerant') mDot = F.Quantity('MassFlowRate', default=(1, 'kg/min'), label=' refrigerant flow rate') pHighMethod = F.Choices(OrderedDict(( ('P', 'pressure'), ('T', 'temperature'), )), label='high side defined by') TEvaporation = F.Quantity('Temperature', default=(-10, 'degC'), label='evaporation temperature', show="self.pHighMethod == 'T'") pHigh = F.Quantity('Pressure', default=(40, 'bar'), label='high pressure', show="self.pHighMethod == 'P'") pLowMethod = F.Choices(OrderedDict(( ('P', 'pressure'), ('T', 'temperature'), )), label='low side defined by') pLow = F.Quantity('Pressure', default=(1, 'bar'), label='low pressure', show="self.pLowMethod == 'P'") TCondensation = F.Quantity('Temperature', default=(40, 'degC'), label='condensation temperature', show="self.pLowMethod == 'T'") TAmbient = F.Quantity( 'Temperature', default=(15, 'degC'), label='ambient temperature', description='used as reference temperature to calculate exergy') workingFluidGroup = F.FieldGroup([ 'fluidName', 'mDot', pHighMethod, TEvaporation, pHigh, pLowMethod, TCondensation, pLow, TAmbient ], label='Cycle parameters') #================ Results ================# eta = F.Quantity('Efficiency', label='cycle efficiency') etaCarnot = F.Quantity( 'Efficiency', label='Carnot efficiency', description= 'efficiency of Carnot cycle between the high (boiler out) temperature and the low (condenser out) tempreature' ) etaSecondLaw = F.Quantity( 'Efficiency', label='second law efficiency', description='ratio or real cycle efficiency over Carnot efficiency') efficiencyFieldGroup = F.FieldGroup([eta, etaCarnot, etaSecondLaw], label='Efficiency') def setTCondensation(self, T): if (self.fluid.tripple['T'] < T < self.fluid.critical['T']): self.TCondensation = T sat = self.fluid.saturation_T(T) self.pLow = sat['psatL'] else: raise ValueError( 'Condensation temperature ({} K) must be between {} K and {} K' .format(T, self.fluid.tripple['T'], self.fluid.critical['T'])) def setTEvaporation(self, T): if (self.fluid.tripple['T'] < T < self.fluid.critical['T']): self.TEvaporation = T sat = self.fluid.saturation_T(T) self.pHigh = sat['psatL'] else: raise ValueError( 'Evaporation temperature ({} K) must be between {} K and {} K'. format(T, self.fluid.tripple['T'], self.fluid.critical['T'])) def initCompute(self, fluid): ThermodynamicalCycle.initCompute(self, fluid) # Set high and low pressures if (self.pLowMethod == 'P'): self.setPLow(self.pLow) else: self.setTCondensation(self.TCondensation) if (self.pHighMethod == 'P'): self.setPHigh(self.pHigh) else: self.setTEvaporation(self.TEvaporation) if (self.pLow >= self.pHigh): raise ValueError( 'The low cycle pressure must be less than the high cycle pressure' )
class HeatExchangerTwoStreams(CycleComponent): #================== Inputs =================# computeMethod = F.Choices(OrderedDict(( ('EG', 'effectiveness (given)'), ('EN', 'effectiveness (NTU)'), )), label='compute method') type = F.Choices(options=OrderedDict(( ('CF', 'counter flow'), ('PF', 'parallel flow'), ('EC', 'evaporation/condensation'), )), default='CF', label="flow configuration", show='self.computeMethod == "EN"') epsGiven = F.Quantity('Efficiency', default=1, label='effectiveness', show='self.computeMethod == "EG"') UA = F.Quantity('ThermalConductance', default=(1, 'kW/K'), label='UA', show='self.computeMethod == "EN"') QDot = F.Quantity('HeatFlowRate', default=(0, 'kW'), label='heat flow rate in') FG = F.FieldGroup([computeMethod, epsGiven, UA, type], label='Heat exchanger') #================== Results =================# NTU = F.Quantity(label='NTU') Cr = F.Quantity(label='capacity ratio') epsilon = F.Quantity('ThermalConductance', label='effectiveness') modelBlocks = [] #================== Ports =================# inlet1 = F.Port(P.ThermodynamicPort) outlet1 = F.Port(P.ThermodynamicPort) inlet2 = F.Port(P.ThermodynamicPort) outlet2 = F.Port(P.ThermodynamicPort) #================== Methods =================# def compute(self): self.outlet1.state.update_Tp(self.inlet2.state.T, self.inlet1.state.p) self.outlet2.state.update_Tp(self.inlet1.state.T, self.inlet2.state.p) dH1DotMax = self.inlet1.flow.mDot * (self.inlet1.state.h - self.outlet1.state.h) dH2DotMax = self.inlet2.flow.mDot * (self.inlet2.state.h - self.outlet2.state.h) if (abs(dH1DotMax) > abs(dH2DotMax)): if (self.computeMethod == 'EG'): self.epsilon = self.epsGiven else: self.computeNTU(dH2DotMax, dH1DotMax) self.QDot = dH2DotMax * self.epsilon else: if (self.computeMethod == 'EG'): self.epsilon = self.epsGiven else: self.computeNTU(dH1DotMax, dH2DotMax) self.QDot = -dH1DotMax * self.epsilon def computeStream1(self): m1Dot = self.outlet1.flow.mDot = self.inlet1.flow.mDot self.outlet1.state.update_ph(self.inlet1.state.p, self.inlet1.state.h + self.QDot / m1Dot) def computeStream2(self): m2Dot = self.outlet2.flow.mDot = self.inlet2.flow.mDot self.outlet2.state.update_ph(self.inlet2.state.p, self.inlet2.state.h - self.QDot / m2Dot) def computeNTU(self, dHDotMin, dHDotMax): deltaTInlet = self.inlet1.state.T - self.outlet1.state.T CMin = dHDotMin / deltaTInlet CMax = dHDotMax / deltaTInlet self.NTU = self.UA / abs(CMin) self.Cr = abs(CMin / CMax) if self.type == 'CF': self.NTU_counterFlow() elif self.type == 'PF': self.NTU_parallelFlow() elif self.type == 'EC': self.NTU_evaporation_condensation() def NTU_counterFlow(self): self.epsilon = (1 - m.exp(-self.NTU * (1 - self.Cr))) / ( 1 - self.Cr * m.exp(-self.NTU * (1 - self.Cr))) def NTU_parallelFlow(self): self.epsilon = (1 - m.exp(-self.NTU * (1 + self.Cr))) / (1 + self.Cr) def NTU_evaporation_condensation(self): self.epsilon = 1 - m.exp(-self.NTU) def __str__(self): return """ Inlet 1: T = {self.inlet1.state.T}, p = {self.inlet1.state.p}, q = {self.inlet1.state.q}, h = {self.inlet1.state.h} Outlet 1: T = {self.outlet1.state.T}, p = {self.outlet1.state.p}, q = {self.outlet1.state.q}, h = {self.outlet1.state.h} Inlet 2: T = {self.inlet2.state.T}, p = {self.inlet2.state.p}, q = {self.inlet2.state.q}, h = {self.inlet2.state.h} Outlet2: T = {self.outlet2.state.T}, p = {self.outlet2.state.p}, q = {self.outlet2.state.q}, h = {self.outlet2.state.h} """.format(self=self) @staticmethod def test(): fp = [FluidState('Water') for _ in range(4)] he = HeatExchangerTwoStreams() he.inlet1.state = fp[0] he.outlet1.state = fp[1] he.inlet2.state = fp[2] he.outlet2.state = fp[3] he.inlet1.state.update_Tp(80 + 273.15, 1e5) he.inlet2.state.update_Tp(20 + 273.15, 1e5) m1Dot = 1. m2Dot = 1. he.compute(m1Dot, m2Dot) print he
class CycleDiagram(NumericalModel): #================ Inputs ================# enable = F.Boolean(label='create process diagram', default=True) isotherms = F.Boolean(label='isotherms') temperatureUnit = F.Choices(OrderedDict((('K', 'K'), ('degC', 'degC'))), default='K', label="temperature unit", show="self.isotherms == true") isochores = F.Boolean(label='isochores') isentrops = F.Boolean(label='isentrops') qIsolines = F.Boolean(label='vapor quality isolines') diagramInputs = F.FieldGroup( [enable, isotherms, temperatureUnit, isochores, isentrops, qIsolines], label='Diagram') defaultMaxP = F.Boolean(label='default max pressure') defaultMaxT = F.Boolean(label='default max temperature') maxPressure = F.Quantity('Pressure', default=(1, 'bar'), label='max pressure', show="self.defaultMaxP == false") maxTemperature = F.Quantity('Temperature', default=(300, 'K'), label='max temperature', show="self.defaultMaxT == false") boundaryInputs = F.FieldGroup( [defaultMaxP, defaultMaxT, maxPressure, maxTemperature], label='Value Limits') inputs = F.SuperGroup([diagramInputs, boundaryInputs], label='Diagram settings') modelBlocks = [] def draw(self, fluid, fluidPoints, cycleLines): # Create diagram object diagram = PHDiagram(fluid.name, temperatureUnit=self.temperatureUnit) # Set limits pMax, TMax = None, None if not self.defaultMaxP: pMax = self.maxPressure if not self.defaultMaxT: TMax = self.maxTemperature diagram.setLimits(pMax=pMax, TMax=TMax) fig = diagram.draw(isotherms=self.isotherms, isochores=self.isochores, isentrops=self.isentrops, qIsolines=self.qIsolines) ax = fig.get_axes()[0] # Draw points i = 1 for fp in fluidPoints: ax.semilogy(fp.h / 1e3, fp.p / 1e5, 'ko') ax.annotate('{}'.format(i), xy=(fp.h / 1e3, fp.p / 1e5), xytext=(3, 2), textcoords='offset points', size='x-large') i += 1 # Draw lines for (fp1, fp2) in cycleLines: ax.semilogy([fp1.h / 1e3, fp2.h / 1e3], [fp1.p / 1e5, fp2.p / 1e5], 'k', linewidth=2) # Export diagram to file fHandle, resourcePath = diagram.export(fig) os.close(fHandle) return resourcePath
class FluidSource(CycleComponent): fluidName = F.Choices(options=Fluids, default='Water', label='fluid') stateVariable1 = F.Choices(options=StateVariableOptions, default='P', label='first state variable') p1 = F.Quantity('Pressure', default=(1, 'bar'), label='pressure', show="self.stateVariable1 == 'P'") T1 = F.Quantity('Temperature', default=(300, 'K'), label='temperature', show="self.stateVariable1 == 'T'") rho1 = F.Quantity('Density', default=(1, 'kg/m**3'), label='density', show="self.stateVariable1 == 'D'") h1 = F.Quantity('SpecificEnthalpy', default=(1000, 'kJ/kg'), label='specific enthalpy', show="self.stateVariable1 == 'H'") s1 = F.Quantity('SpecificEntropy', default=(100, 'kJ/kg-K'), label='specific entropy', show="self.stateVariable1 == 'S'") q1 = F.Quantity('VaporQuality', default=(1, '-'), minValue=0, maxValue=1, label='vapour quality', show="self.stateVariable1 == 'Q'") stateVariable2 = F.Choices(options=StateVariableOptions, default='T', label='second state variable') p2 = F.Quantity('Pressure', default=(1, 'bar'), label='pressure', show="self.stateVariable2 == 'P'") T2 = F.Quantity('Temperature', default=(300, 'K'), label='temperature', show="self.stateVariable2 == 'T'") rho2 = F.Quantity('Density', default=(1, 'kg/m**3'), label='density', show="self.stateVariable2 == 'D'") h2 = F.Quantity('SpecificEnthalpy', default=(1000, 'kJ/kg'), label='specific enthalpy', show="self.stateVariable2 == 'H'") s2 = F.Quantity('SpecificEntropy', default=(100, 'kJ/kg-K'), label='specific entropy', show="self.stateVariable2 == 'S'") q2 = F.Quantity('VaporQuality', default=(1, '-'), minValue=0, maxValue=1, label='vapour quality', show="self.stateVariable2 == 'Q'") mDot = F.Quantity('MassFlowRate', default=(1, 'kg/h'), label='mass flow') FG = F.FieldGroup([ fluidName, stateVariable1, p1, T1, rho1, h1, s1, q1, stateVariable2, p2, T2, rho2, h2, s2, q2, mDot ], label='Compressor') modelBlocks = [] #================== Ports ===================# outlet = F.Port(P.ThermodynamicPort) #================== Methods =================# def getStateValue(self, sVar, prefix="", suffix=""): sVarDict = { 'P': 'p', 'T': 'T', 'D': 'rho', 'H': 'h', 'S': 's', 'Q': 'q' } return self.__dict__[prefix + sVarDict[sVar] + suffix] def compute(self): self.outlet.flow.mDot = self.mDot self.outlet.state.update( self.stateVariable1, self.getStateValue(self.stateVariable1, suffix="1"), self.stateVariable2, self.getStateValue(self.stateVariable2, suffix="2"))