def temperature_loop(temperature): """ Uses fixed point iteration with Aitkens sequence acceleration to find the vapor phase composition """ assert temperature > 0, "Temperature must be greater than zero" mixture.temperature = temperature lnphiL = model.logfug(mixture, 'liquid') y0 = mixture.composition['liquid'] * psatguess(mixture.critical['acentric'], mixture.reduced['temperature']) / mixture.reduced['pressure'] for count in range(MAX_ITER): mixture.composition['vapor'] = y0 / y0.sum() lnphiV = model.logfug(mixture, 'vapor') y1 = mixture.composition['liquid'] * np.exp(lnphiL - lnphiV) mixture.composition['vapor'] = y1 / y1.sum() lnphiV = model.logfug(mixture, 'vapor') y2 = mixture.composition['liquid'] * np.exp(lnphiL - lnphiV) d = y2 - 2.0 * y1 + y0 y = np.where(np.absolute(d) < 1e-16, y2, y2 - (y2 - y1)**2 / d) if np.allclose(y, y2, atol=ERROR_TOL): mixture.composition['vapor'] = y2 / y2.sum() return 1 - y.sum() y0 = y raise SearchError("Temperature loop ({} K) failed to converge after {} iterations".format(temperature, MAX_ITER))
def raoult_bubble_temperature(model, mixture, pressure, psat=None): """ Calculates the bubble temperature at a given pressure using Raoult's law and a saturation pressure routine. Defaults to an approximate saturation pressure calculated using the Clausius-Clapeyron equation. Requires that the mixture have an overall composition, this is the assumed composition of the liquid at the bubble point. Returns a mixture with the vapor and liquid compositions at the bubble point and all state values and departure functions completed. This algorithm is unstable in the region close to the mixture critial point. model: model that implements the Model interface mixture: instance of Mixture pressure: float in Pascals psat: function, mixture -> saturation pressure in Pascals """ mixture.pressure = pressure mixture.quality = 1.0 mixture.composition['liquid'] = mixture.composition['overall'] mixture.normalize() if psat is None: mixture.temperature = _rt_bt(mixture) Ps = psatguess(mixture.critical['acentric'], mixture.reduced['temperature']) * mixture.critical['pressure'] else: T0 = np.dot(tsatguess(mixture.critical['acentric'], mixture.reduced['pressure']) * mixture.critical['temperature'], mixture.composition['liquid']) def temperature_loop(temperature): mixture.temperature = temperature Ps = psat(mixture) return pressure - np.dot(Ps, mixture.composition['liquid']) mixture.temperature = opt.newton(temperature_loop, T0) Ps = psat(mixture) mixture.composition['vapor'] = mixture.composition['liquid'] * Ps / mixture.pressure return model(mixture)
def raoult_isothermalflash(mixture, q0=0.5): """ Performs isothermal flash using Raoult's Law """ K = psatguess(mixture.critical['acentric'], mixture.reduced['temperature']) / mixture.reduced['pressure'] try: quality = opt.newton(rachford_rice, q0, args=(mixture.composition['overall'], K)) except RuntimeError: raise SearchError("Isothermal flash failed to converge") return quality
def loop(q0): z = mix.composition['overall'] K = psatguess(mix.critical['acentric'], mix.reduced['temperature']) / mix.reduced['pressure'] for count in range(MAX_ITER): mix.composition['liquid'] = z / (K + q0 * (1 - K)) lnphiL = model.logfug(mix, 'liquid') mix.composition['vapor'] = mix.composition['liquid'] * K lnphiV = model.logfug(mix, 'vapor') K = np.exp(lnphiL - lnphiV) q1 = opt.newton(rachford_rice, q0, args=(z, K)) if np.allclose(q1, q0, atol=ERROR_TOL): return q1 q0 = q1 raise SearchError("Isothermal flash (q= {}) failed to converge after {} iterations".format(q0, MAX_ITER))
def _rt_bp(mixture): """ Calculate the bubble pressure using Raoult's Law""" return np.dot(psatguess(mixture.critical['acentric'], mixture.reduced['temperature']) * mixture.critical['pressure'], mixture.composition['liquid'])
def two_loop_bubble_pressure(model, mixture, temperature, bounds=None): """ Calculates the bubble pressure at a given temperature, uses Raoult's law for an initial guess of the bubble pressure, unless otherwise specified Requires that the mixture have an overall composition, this is the assumed composition of the liquid at the bubble point. Returns a mixture with the vapor and liquid compositions at the bubble point and all state values and departure functions completed. This algorithm is unstable in the region close to the mixture critial point. model: model that implements the Model interface mixture: instance of Mixture temperature: float in Kelvin bounds: tuple, boundary of pressure region to search, in Pascals """ mixture.temperature = temperature mixture.quality = 1.0 mixture.composition['liquid'] = mixture.composition['overall'] mixture.normalize() psat0 = psatguess(mixture.critical['acentric'], mixture.reduced['temperature']) def pressure_loop(pressure): """ Uses fixed point iteration with Aitkens sequence acceleration to find the vapor phase composition """ assert pressure > 0, "Pressure must be greater than zero" mixture.pressure = pressure lnphiL = model.logfug(mixture, 'liquid') y0 = mixture.composition['liquid'] * psat0 / mixture.reduced['pressure'] for count in range(MAX_ITER): mixture.composition['vapor'] = y0 / y0.sum() lnphiV = model.logfug(mixture, 'vapor') y1 = mixture.composition['liquid'] * np.exp(lnphiL - lnphiV) mixture.composition['vapor'] = y1 / y1.sum() lnphiV = model.logfug(mixture, 'vapor') y2 = mixture.composition['liquid'] * np.exp(lnphiL - lnphiV) d = y2 - 2.0 * y1 + y0 y = np.where(np.absolute(d) < 1e-16, y2, y2 - (y2 - y1)**2 / d) if np.allclose(y, y2, atol=ERROR_TOL): mixture.composition['vapor'] = y2 / y2.sum() return 1 - y.sum() y0 = y raise SearchError("Pressure loop ({} Pa) failed to converge after {} iterations".format(pressure, MAX_ITER)) # Find bracket for pressure loop zero and solve for bubble pressure if bounds is None: # Use Raoult's Law to get an initial guess of the bubble pressure p0 = _rt_bp(mixture) y0 = pressure_loop(p0) dp = -0.5 * p0 if y0 > 0 else +0.5 * p0 bounds = bounds_search(pressure_loop, p0, dp) mixture.pressure = bounds[0] if np.all(mixture.reduced['pressure'] > Prcrit): msg = "Pressure {} is too high, bubble point routine is unstable in this region".format(p0) warnings.warn(msg, RuntimeWarning) mixture.pressure = opt.brentq(pressure_loop, *bounds) return model(mixture)