## Calculating a bubble point pressure

| categories: nonlinear algebra | View Comments

Calculating a bubble point pressure

# Calculating a bubble point pressure

In Post 1060 we learned to read a datafile containing lots of Antoine coefficients into a database, and use the coefficients to estimate vapor pressure. Today, we use those coefficents to compute a bubble point pressure. You will need download antoine_database.mat if you haven't already run the previous example.

The bubble point is the temperature at which the sum of the component vapor pressures is equal to the the total pressure. This is where a bubble of vapor will first start forming, and the mixture starts to boil.

Consider an equimolar mixture of benzene, toluene, chloroform, acetone and methanol. Compute the bubble point at 760 mmHg, and the gas phase composition.

## Contents

load antoine_database.mat


## list the compounds

compounds = {'benzene' 'toluene' 'chloroform' 'acetone' 'methanol'}
n = numel(compounds); % we use this as a counter later.

compounds =

Columns 1 through 4

'benzene'    'toluene'    'chloroform'    'acetone'

Column 5

'methanol'



## preallocate the vectors

for large problems it is more efficient to preallocate the vectors.

A = zeros(n,1); B = zeros(n,1); C = zeros(n,1);
Tmin = zeros(n,1); Tmax = zeros(n,1);


## Now get the parameters for each compound

for i = 1:n
c = database(compounds{i}); % c is a cell array
% assign the parameters to the variables
[A(i) B(i) C(i) Tmin(i) Tmax(i)] = c{:};
end


this is the equimolar, i.e. equal mole fractions

x = [0.2; 0.2; 0.2; 0.2; 0.2];


Given a T, we can compute the pressure of each species like this:

T = 67; % degC
P = 10.^(A-B./(T + C))

P =

1.0e+003 *

0.4984
0.1822
0.8983
1.0815
0.8379



note the pressure is a vector, one element for each species. To get the total pressure of the mixture, we use the sum command, and multiply each pure component vapor pressure by the mole fraction of that species.

sum(x.*P)
% This is less than 760 mmHg, so the Temperature is too low.

ans =

699.6546



## Solve for the bubble point

We will use fsolve to find the bubble point. We define a function that is equal to zero at teh bubble point. That function is the difference between the pressure at T, and 760 mmHg, the surrounding pressure.

Tguess = 67;
Ptotal = 760;
func = @(T) Ptotal - sum(x.*10.^(A-B./(T + C)));

T_bubble = fsolve(func,Tguess)

sprintf('The bubble point is %1.2f degC', T_bubble)

Equation solved.

fsolve completed because the vector of function values is near zero
as measured by the default value of the function tolerance, and
the problem appears regular as measured by the gradient.

T_bubble =

69.4612

ans =

The bubble point is 69.46 degC



Let's double check the T_bubble is within the valid temperature ranges

if any(T_bubble < Tmin) | any(T_bubble > Tmax) % | is the "or" operator
error('T_bubble out of range')
else
sprintf('T_bubble is in range')
end

ans =

T_bubble is in range



## Vapor composition

we use the formula y_i = x_i*P_i/P_T.

y = x.*10.^(A-B./(T_bubble + C))/Ptotal;

for i=1:n
sprintf('y_%s = %1.3f',compounds{i},y(i))
end

ans =

y_benzene = 0.142

ans =

y_toluene = 0.053

ans =

y_chloroform = 0.255

ans =

y_acetone = 0.308

ans =

y_methanol = 0.242



## Summary

we saved some typing effort by using the parameters from the database we read in before. We also used the vector math capability of matlab to do element by element processing, e.g. in line 44 and 58. This let us avoid doing some loop programming, or writing equations with 5 compounds in them!

% categories: nonlinear algebra
% tags: thermodynamics


## The equal area method for the van der Waals equation

| categories: nonlinear algebra | View Comments

The equal area method for the van der Waals equation

# The equal area method for the van der Waals equation

John Kitchin

## Introduction

When a gas is below its Tc the van der Waal equation oscillates. In the portion of the isotherm where , the isotherm fails to describe real materials, which phase separate into a liquid and gas in this region.

Maxwell proposed to replace this region by a flat line, where the area above and below the curves are equal. Today, we examine how to identify where that line should be.

function maxwell_equal_area_2

clc; clear all; close all

Tr = 0.9; % A Tr below Tc:  Tr = T/Tc
% analytical equation for Pr. This is the reduced form of the van der Waal
% equation.
Prfh = @(Vr) 8/3*Tr./(Vr - 1/3) - 3./(Vr.^2);

Vr = linspace(0.5,4,100);   % vector of reduced volume
Pr = Prfh(Vr);              % vector of reduced pressure


## Plot the vdW EOS

figure; hold all
plot(Vr,Pr)
ylim([0 2])
xlabel('V_R')
ylabel('P_R')


## show unstable region

dPdV = cmu.der.derc(Vr, Pr);
plot(Vr(dPdV > 0),Pr(dPdV > 0),'r-','linewidth',2)


## Idea behind Maxwell equal area principle

The idea is to pick a Pr and draw a line through the EOS. We want the areas between the line and EOS to be equal on each side of the middle intersection.

y = 0.65;  % Pr that defines the horizontal line
plot([0.5 4],[y y],'k--') % horizontal line


## find three roots

To find the areas, we need to know where the intersection of the vdW eqn with the horizontal line. This is the same as asking what are the roots of the vdW equation at that Pr. We need all three intersections so we can integrate from the first root to the middle root, and then the middle root to the third root. We take advantage of the polynomial nature of the vdW equation, which allows us to use the roots command to get all the roots at once. The polynomial is . We use the coefficients t0 get the rooys like this.

vdWp = [1 -1/3*(1+8*Tr/y) 3/y -1/y];
v = sort(roots(vdWp))

v =

0.6029
1.0974
2.3253



## plot the roots

we want to make sure the roots are really the intersections with the line.

plot(v(1),y,'bo',v(2),y,'bo',v(3),y,'bo')


## compute areas

for A1, we need the area under the line minus the area under the vdW curve. That is the area between the curves. For A2, we want the area under the vdW curve minus the area under the line. The area under the line between root 2 and root 1 is just the width (root2 - root1)*y

A1 = (v(2)-v(1))*y - quad(Prfh,v(1),v(2))
% these areas are pretty close, and we could iterate to get them very
% close. or, we can use a solver to do this. To do that, we need to define
% a function that is equal to zero when the two areas are equal. See the
% equalArea function below.

Pr_equal_area = fzero(@equalArea, 0.65)

A1 =

0.0632

A2 =

0.0580

Pr_equal_area =

0.6470



confirm the two areas are equal

y = Pr_equal_area;
equalArea(y) % this should be close to zero

ans =

1.1102e-016



and we can compute the areas too

vdWp = [1 -1/3*(1+8*Tr/y) 3/y -1/y]
v = sort(roots(vdWp))
% these areas are very close to each other.

vdWp =

1.0000   -4.0428    4.6368   -1.5456

v =

0.6034
1.0905
2.3488

A1 =

0.0618

A2 =

0.0618



## plot the areas that are equal

we want to show the areas that are equal by shading them.

xx = [v(1) Vr(Vr >= v(1) & Vr <= v(2)) v(2)];
yy = [Prfh(v(1)) Pr(Vr >= v(1) & Vr <= v(2)) Prfh(v(2))];
lightgray = [0.9 0.9 0.9];
fill(xx,yy,lightgray)

xx = [v(2) Vr(Vr >= v(2) & Vr <= v(3)) v(3)];
yy = [Prfh(v(2)) Pr(Vr >= v(2) & Vr <= v(3)) Prfh(v(3))];
fill(xx,yy,lightgray)
% and finally lets add text labels to the figure for what the areas are.
text(v(1),1,sprintf('A1 = %f',A1))
text(v(3),1,sprintf('A2 = %f',A2))


## Finally, plot the isotherm

we want to replace the unstable region with the flat line that defines the equal areas. The roots are stored in the variable . We use logical indexing to select the parts of the data that we want.

figure
Vrf = [Vr(Vr <= v(1)) v(1)       v(3)       Vr(Vr>= v(3))];
Prf = [Pr(Vr <= v(1)) Prfh(v(1)) Prfh(v(3)) Pr(Vr>= v(3))];
plot(Vrf,Prf)
ylim([0 2])
xlabel('V_R')
ylabel('P_R')
title('T_R = 0.9')


The flat line in this isotherm corresponds to a vapor-liquid equilibrium. This is the result for . You could also compute the isotherm for other values.

'done'

ans =

done


function Z = equalArea(y)
% compute A1 - A2 for Tr = 0.9.
Tr = 0.9;
vdWp = [1 -1/3*(1+8*Tr/y) 3/y -1/y];
v = sort(roots(vdWp));

Prfh = @(Vr) 8/3*Tr./(Vr - 1/3) - 3./(Vr.^2);

Z = A1 - A2;

% categories: Nonlinear algebra
% tags: thermodynamics


Counting roots

# Counting roots

John Kitchin

## Problem statement

The goal here is to determine how many roots there are in a nonlinear function we are interested in solving. For this example, we use a cubic polynomial because we know there are three roots.

function main

clc; clear all; close all;


## Use roots for this polynomial

This ony works for a polynomial, it does not work for any other nonlinear function.

roots([1 6 -4 -24])

ans =

-6.0000
2.0000
-2.0000



## Plot the function to see where the roots are

x = linspace(-8,4);
fx = x.^3 + 6*x.^2 - 4*x - 24;
plot(x,fx)


## method 1

count the number of times the sign changes in the interval. We determine the sign of each element (-1, 0 or +1) and then use a a loop to go through each element. A sign change is detected when an element is not equal to the previous element. Note this is not foolproof; we may over count roots if we get a sequence -1 0 1. We check for that by making sure neither of the two elements being compared are zero.

sign_fx = sign(fx);

count = 0;
for i=2:length(sign_fx)
if sign_fx(i) ~= sign_fx(i-1) && sign_fx(i) ~= 0 && sign_fx(i-1) ~= 0
count = count + 1;
disp(sprintf('A root is between x = %f and %f',x(i-1),x(i)))
end
end

disp(sprintf('The number of sign changes is %i.',count))

A root is between x = -6.060606 and -5.939394
A root is between x = -2.060606 and -1.939394
A root is between x = 1.939394 and 2.060606
The number of sign changes is 3.


## method 1a

this one line code also counts sign changes. the diff command makes all adjacent, identical elements equal zero, and sign changes = 2 (1 - (-1)) or (-1 - 1). Then we just sum up the diff vector, divide by two, and that is the number of roots. Note that no check for 0 is needed; you will get 1/2 a sign change from -1 to 0. and another half from 0 to 1. They add up to a whole sign change.

sum(abs(diff(sign_fx))/2)

ans =

3



## Discussion

Method 1 is functional but requires some programming. Method 1a is concise but probably tricky to remember.

## Method 2

using events in an ODE solver Matlab can identify events in the solution to an ODE, for example, when a function has a certain value, e.g. f(x) = 0. We can take advantage of this to find the roots and number of roots in this case.

with f(-8) = -120

xspan = [-8 4];
fp0 = -120;

options = odeset('Events',@event_function);
[X,Y,XE,VE,IE] = ode45(@myode,xspan,fp0,options);
XE % these are the x-values where the function equals zero
nroots = length(XE);
disp(sprintf('Found %i roots in the x interval',nroots))

% lets compare the solution to the  original function to make sure they are
% the same.
figure
plot(X,Y,x,fx)
legend 'numerical' 'analytical'

function dfdx = myode(x,f)
dfdx = 3*x^2 + 12*x - 4;

function [value,isterminal,direction] = event_function(t,y)
value = y - 0;  % then value = 0, an event is triggered
isterminal = 0; % do not terminate after the first event
direction = 0;  % get all the zeros

% categories: ODEs, nonlinear algebra

XE =

-6.0000
-2.0000
2.0000

Found 3 roots in the x interval


| categories: nonlinear algebra | View Comments

Given:

## Contents

with the information given below, solve for the exit concentration. This should be simple. We use the units package to make sure we don't miss any units.

clear all; close all; clc
u = cmu.units;

Cao = 2*u.mol/u.L;
V = 10*u.L;
nu = 0.5*u.L/u.s;
k = 0.23 * u.L/u.mol/u.s;


function we need to solve

f = @(Ca) V - nu*(Cao - Ca)./(k*Ca.^2);


## plot the function to estimate the solution

c = linspace(0,2)*u.mol/u.L;
plot(c/(u.mol/u.L),f(c))
ylim([-0.1 0.1]) % limit the y-range to see the zero easier
xlabel('C_A')
ylabel('f(C_A)')
% we want the value of C_A that makes f(C_A)=0. this happens around C_A=0.5

cguess = 0.5*u.mol/u.L;
c = fsolve(f,cguess)
% it is a little weird that the solution is the same as our initial guess.
% Usually the answer is at least a little different!

Equation solved at initial point.

fsolve completed because the vector of function values at the initial point
is near zero as measured by the default value of the function tolerance, and
the problem appears regular as measured by the gradient.

ans =

500.0000

Equation solved at initial point.

fsolve completed because the vector of function values at the initial point
is near zero as measured by the default value of the function tolerance, and
the problem appears regular as measured by the gradient.

500/m^3*mol


z = f(c)
z.as(u.L)
% this is not really that close to zero, especially when you look at the
% units in L!

-0.00304348*m^3

ans =

-3.043*L



## closer look at fsolve

options = optimset('Display','iter');
[X,FVAL,EXITFLAG,OUTPUT] = fsolve(f,cguess,options)
% It should be a warning that zero iterations were taken! Our guess was
% probably not that good! The EXITFLAG=1 means the function ended ok as far
% as fsolve is concerned.

                                         Norm of      First-order   Trust-region
Iteration  Func-count     f(x)          step         optimality    radius
0          2    9.26276e-006                     1.85e-007               1

Equation solved at initial point.

fsolve completed because the vector of function values at the initial point
is near zero as measured by the default value of the function tolerance, and
the problem appears regular as measured by the gradient.

ans =

500.0000

Norm of      First-order   Trust-region
Iteration  Func-count     f(x)          step         optimality    radius
0          2    9.26276e-006                     1.85e-007               1

Equation solved at initial point.

fsolve completed because the vector of function values at the initial point
is near zero as measured by the default value of the function tolerance, and
the problem appears regular as measured by the gradient.

500/m^3*mol
-0.00304348*m^3

EXITFLAG =

1

OUTPUT =

iterations: 0
funcCount: 2
algorithm: 'trust-region dogleg'
firstorderopt: 1.8526e-007
message: [1x756 char]



The problem is bad scaling because the underlying units are stored in m^3, which results in very small numbers that are close to zero. So, fsolve sees a number it thinks is close to zero, and stops. One way to fix this is to decrease the tolerance on what constitutes zero.

options = optimset('TolFun',1e-9);
cguess = 0.5*u.mol/u.L;
[c2,FVAL,EXITFLAG,OUTPUT] = fsolve(f,cguess,options)
z2 = f(c2)
z2.as(u.L) % much closer to zero.

sprintf('The exit concentration is %1.2f mol/L', c2/(u.mol/u.L))

Equation solved.

fsolve completed because the vector of function values is near zero
as measured by the selected value of the function tolerance, and
the problem appears regular as measured by the gradient.

ans =

559.5545

Equation solved.

fsolve completed because the vector of function values is near zero
as measured by the selected value of the function tolerance, and
the problem appears regular as measured by the gradient.

559.554/m^3*mol
-1.24874e-006*m^3

EXITFLAG =

1

OUTPUT =

iterations: 6
funcCount: 14
algorithm: 'trust-region dogleg'
firstorderopt: 5.3309e-011
message: [1x698 char]

-1.24874e-006*m^3

ans =

-0.001*L

ans =

The exit concentration is 0.56 mol/L



## Rescaling the units

We can make the default length unit be dm, then the default volume unit will be 1L (dm^3). This will effectively rescale the function so that the default tolerance will be effective. Note you have to specify the unit for each unit in the order (length, time, mass, temperature, charge, mol).

u = cmu.unit.units('dm','s','kg','K','coul','mol');
Cao = 2*u.mol/u.L;
V = 10*u.L;
nu = 0.5*u.L/u.s;
k = 0.23 * u.L/u.mol/u.s;

% function we need to solve
f = @(Ca) V - nu*(Cao - Ca)./(k*Ca.^2);
cguess = 0.5*u.mol/u.L;
[c3,FVAL,EXITFLAG,OUTPUT] = fsolve(f,cguess)
sprintf('The exit concentration is %1.2f', c3)
% we were able to solve this more reasonably scaled problem without
% changing any default tolerances.

Equation solved.

fsolve completed because the vector of function values is near zero
as measured by the default value of the function tolerance, and
the problem appears regular as measured by the gradient.

ans =

0.5596

Equation solved.

fsolve completed because the vector of function values is near zero
as measured by the default value of the function tolerance, and
the problem appears regular as measured by the gradient.

0.559584/dm^3*mol
-3.9293e-012*dm^3

EXITFLAG =

1

OUTPUT =

iterations: 4
funcCount: 10
algorithm: 'trust-region dogleg'
firstorderopt: 1.6772e-010
message: [1x695 char]

ans =

The exit concentration is 0.56 (dm^3*mol)^-1


'done'
% categories: nonlinear algebra
% tags: reaction engineering, units

ans =

done



## Solving integral equations

| categories: nonlinear algebra | View Comments

Solving integral equations

# Solving integral equations

John Kitchin

## Contents

Occasionally we have integral equations we need to solve in engineering problems, for example, the volume of plug flow reactor can be defined by this equation: where is the rate law. Suppose we know the reactor volume is 100 L, the inlet molar flow of A is 1 mol/L, the volumetric flow is 10 L/min, and , with 1/min. What is the exit molar flow rate? We need to solve the following equation:

k = 0.23;
nu = 10;
Fao = 1;


We start by creating a function handle that describes the integrand. We can use this function in the quad command to evaluate the integral.

f1 = @(Fa) -1./(k*Fa/nu);


Now we create a second function handle that defines the equation to solve. We use the integrand function handle above in the quad command to evaluate the integral

f2 = @(Fa) 100 - quad(f1,Fao,Fa);


## plot f2 to get an idea of where the solution is.

the maximum that Fa can be is Fao, and the minimum is zero. From the graph you can see the solution is at approximately Fa = 0.1.

ezplot(f2,0,Fao)

Warning: Function failed to evaluate on array inputs; vectorizing the function may
speed up its evaluation and avoid the need to loop over array elements.


## Get the solution

Fa_guess = 0.1;
Fa_exit = fsolve(f2, Fa_guess);

Ca_exit = Fa_exit/nu;
sprintf('The exit concentration is %1.2f mol/L',Ca_exit)

Equation solved.

fsolve completed because the vector of function values is near zero
as measured by the default value of the function tolerance, and
the problem appears regular as measured by the gradient.

ans =

The exit concentration is 0.01 mol/L


% categories: nonlinear algebra
% tags: reaction engineering
% post_id = 954; %delete this line to force new post;