10.34: Numerical Methods Applied To Chemical Engineering Prof. K. Beers
10.34: Numerical Methods Applied To Chemical Engineering Prof. K. Beers
10.34: Numerical Methods Applied To Chemical Engineering Prof. K. Beers
ΔWt is drawn from a normal distribution with a mean of zero and a variance of σ2 = Δt.
Therefore, to agree with this limiting case, we write the SDE for the particle motion in a
moving fluid and an external field as
⎡ dU ⎤ 1
dx = ⎢V f − ζ −1 ⎥ dt + ( 2 D ) 2 dWt
⎣⎢ dx x( t ) ⎦⎥
Which yields the simulation algorithm
⎡ dU ⎤ 1
x ( t + Δt ) − x ( t ) = ⎢V f − ζ −1 ⎥ ( ) ( ) 2 ΔWt
Δt + 2 D
⎢⎣ dx x( t ) ⎥⎦
As shown in class, the drag constant and the diffusivity are related by Einstein’s relation
kT
D= b
ζ
Part 1.A.
Considering the SDE above, we see that if we had no random force, we would have a
dU
deterministic velocity of the particle equal to v p = V f − ζ −1 . So, the deterministic
dx x( t )
(non-random) part of the SDE appears to describe convective motion, and the random
part (as we have seen) describes diffusive motion. This appears to suggest that the
probability distribution p(t,x) follows a convection/diffusion equation
∂p ∂ ∂2 p
= − ⎣⎡v p p ( t , x ) ⎦⎤ + D 2
∂t ∂x ∂x
In fact, it is shown in the text that for a system described by the SDE
dx = a ( t , x ) dt + b ( t , x ) dWt
The probability distribution is governed by a corresponding Fokker-Planck equation
∂p ∂
= ⎡⎣ a ( t , x ) p ( t , x ) ⎤⎦ +
∂t ∂x
1 ∂2
2 ∂x
{2 ⎣ ( }
⎡b t , x ) ⎤⎦ p ( t , x )
2
p (t, x ) =
1
e
( 2σ ) 2
μ = Vf t σ 2 = 2Dt
σ 2π
Well, note that the question just asks you to “show” that the solution is such. Thus, I’ll
only be presenting here a demonstration that this is, indeed, the solution; this will be
sufficient for full credit for this part. If you were feeling saucy and decided to actually
give a proper derivation, then more power to you.
Looking at the equation
∂p ∂ ∂2 p dU
= − ⎣⎡v p p ( t , x ) ⎦⎤ + D 2 where v p = V f − ζ −1 = Vf
∂t ∂x ∂x dx x( t )
We see that all we need to do is take the individual derivatives and make sure that
everything cancels. We’ll use some intuition in our grouping of terms: derivatives of p
are likely to equal p times some factor, since there is an exponential term in p. So…
∂ − ( x −Vf t )
2
∂p 1
= p (t, x ) − p (t, x )
∂t ∂t 4 Dt 2t
∂ − ( x − 2V f tx + V f t )
2 2 2
1
= p (t, x ) − p ( t, x )
∂t 4 Dt 2t
⎛ x2 Vf 2
1⎞
= p (t, x ) ⎜ − −
⎜ 4 Dt 2 4 D 2t ⎟⎟
⎝ ⎠
∂ − ( x − 2V f tx + V f t )
2 2 2
∂p
= p (t, x )
∂x ∂x 4 Dt
− p (t, x ) ∂ 2
=
4 Dt ∂x
( x − 2V f tx + V f2t 2 )
− p (t, x ) ⎛ 2V t − 2 x ⎞
=
4 Dt
( 2 x − 2V f t ) = p ( t , x ) ⎜ f ⎟
⎝ 4 Dt ⎠
∂ 2 p ∂ ∂p ∂ ⎡ ⎛ 2V f t − 2 x ⎞ ⎤
= = ⎢ p ( t , x ) ⎜ ⎟⎥
∂x 2 ∂x ∂x ∂x ⎣ ⎝ 4 Dt ⎠ ⎦
2
⎛ −1 ⎞ ⎛ 2V f t − 2 x ⎞
= p (t, x ) ⎜ ⎟ + p (t, x ) ⎜ ⎟
⎝ 2 Dt ⎠ ⎝ 4 Dt ⎠
Substituting those into the convection/diffusion equation, we get
⎛ x2 V f2 1 ⎞ ⎡ ⎛ 2V f t − 2 x ⎞ ⎤ ⎡ ⎛ −1 ⎞ ⎛ 2V f t − 2 x ⎞ ⎤
2
p (t, x ) ⎜ − − = −V f ⎢ p ( t , x ) ⎜ ⎟⎥ + D ⎢ p ( t, x ) ⎜ ⎟ + p (t, x ) ⎜ ⎟ ⎥
⎜ 4 Dt 2 4 D 2t ⎟⎟
⎝ ⎠ ⎣ ⎝ 4 Dt ⎠ ⎦ ⎢⎣ ⎝ 2 Dt ⎠ ⎝ 4 Dt ⎠ ⎥⎦
We can divide everything through by p(t,x) and simplify the last term to get
V f2 1 2V f x − 2V f t 1 4V f t − 8V f xt + 4 x
2 2 2 2
x2
− − = − +
4 Dt 2 4 D 2t 4 Dt 2t 16 Dt 2
Then we clear out the denominators by multiplying through by 4Dt2
x 2 − V f2t 2 − 2 Dt = 2V f xt − 2V f2t 2 − 2 Dt + V f2t 2 − 2V f xt + x 2
Which can be rearranged to yield
x 2 − 2V f2t 2 − 2 Dt + 2V f xt = x 2 − 2V f2t 2 − 2 Dt + 2V f xt
Which is a true equation, since all terms cancel out to yield 0 = 0.
And yes, it was OK if you used Mathematica or some symbolic calculus program to do
all of the manipulations for you, as long as you included that code or screenshots of that
program after it did the calculations for you so that we know you didn’t just pull it out of
thin air.
OK, so here we’re just doing a very straightforward problem. You have already been
given the simulation equations and the values of all of the appropriate variables. You can
either remove the (dU/dx)/ζ term from your equations in your code, or just set ζ to some
arbitrary non-zero number since the zero value of dU/dx will then remove its effect.
For Monte Carlo simulations, it is ideal to do a lot of trials. To get a fairly smooth curve,
you need to damp out the random fluctuations caused by your sampling. To do that, you
should run many trials. For instance, if you are using 50 bins for your histogram and you
only run 1000 trials, that the average bin will have 20 occurrences. With such a low
frequency, it is easy for random fluctuations to cause slight deviations from the expected
distribution. At the same time, though, we would like our programs to run as quickly as
possible, so we don’t want to run too many trials.
One thing to keep in mind for this task is that since we are running the commands in the
same loop tens of thousands (or more) times, it behooves us to make only the most
necessary commands be in the loop. For instance, if we needlessly re-assign a variable
every time through the loop, that will take up extra time. If, on the other hand, we assign
the variable outside of the loop, then we can cut down on the computational expense.
Similarly, if you use a separate function to calculate your update, it is easiest if you don’t
bother to unpack your variables. What I mean by this is that if you pass in a bunch of
parameters in a structure called Param, and each time you are in the function you say that
x = Param.x, y = Param.y, theta = Param.theta, etc., then you are executing each of those
thousands of times when they don’t really need to be executed. Instead, you should just
put Param.x, Param.y, Param.theta, etc., into your function. Though this makes your
code harder to read, it makes the program run faster.
In fact, this is a consistent pattern throughout computational work: code that is easy to
read is frequently suboptimal in execution time. Advanced computer languages and
compilers attempt to circumvent these problems for you by intelligently optimizing your
code when they assemble your program; in a case like this, though, it pays to note small
ways to optimize your code and turn a 90-second execution time into a 10-second
execution time. In my code, I have tried for a balance… by not outsourcing functions
and including them “inline” in the loops, things may be a little faster than they otherwise
might be, though perhaps with a little redundancy in my code. At the same time, though,
I’m striving for legibility, even at the cost of storing extra variables.
Oh, one final thing to note: you may see that I start off by defining my “storage” array
beforehand. This isn’t strictly necessary, as Matlab can automatically extend the array
for you. However, each time Matlab tries to extend an array, behind the scenes it is
reallocating the array, a rather time-intensive process. This can produce a many-fold,
even order-of-magnitude speedup for array that are particularly large.
That’s it. The actual coding for this part isn’t too tricky. Here’s my sample code, and a
graph that yours should look something like. The runtime for this program is about 30
seconds. The second figure shows how fewer trials will give the same trend, but with
more noise.
% Mark Styczynski
% 10.34
% HW 8
% Problem 1.A.2.
storage(1,j) = x;
for i=1:.5/deltaT,
deltaWt = randn()*sqrt(deltaT);
x = x + (Vf-dUdx/zeta)*deltaT + (2*D)^.5*deltaWt;
end
storage(2,j) = x;
for i=1:1/deltaT,
deltaWt = randn()*sqrt(deltaT);
x = x + (Vf-dUdx/zeta)*deltaT + (2*D)^.5*deltaWt;
end
storage(3,j) = x;
for i=1:1/deltaT,
deltaWt = randn()*sqrt(deltaT);
x = x + (Vf-dUdx/zeta)*deltaT + (2*D)^.5*deltaWt;
end
storage(4,j) = x;
end
toc
firstArray = ['b:';'g:';'r:';'k:'];
secondArray = ['b-';'g-';'r-';'k-'];
figure(2);
for i=1:4,
plot(x,probPlot(i,:),firstArray(i,:));
hold on;
% Don't forget to normalize...
plot(bins(i,:),freq(i,:)/area(i),secondArray(i,:));
end
xlabel('x')
ylabel('p(x)')
title('Particles in fluid flow with no external force, 10,000 trials')
legend('Theoretical, t = 0.5','Simulation, t = 0.5',...
'Theoretical, t = 1','Simulation, t = 1',...
'Theoretical, t = 2','Simulation, t = 2',...
'Theoretical, t = 3','Simulation, t = 3');
Part 1.B.
Now, set vf = 0 and let us introduce a spatially-periodic potential
U ( x ) = Ea ⎡⎣sin ( xπ ) ⎤⎦
2
∫e
kbT
dx
0
In your Brownian dynamics simulation, you should not start sampling the distribution of
x until you have run the simulation for a while to “equilibrate” the system. Simulate the
motion of a particle at kbT = 1, Ea = 1, D = 1 and demonstrate that the Brownian
dynamics simulation samples properly from the equilibrium distribution. That is, the
probability distribution measured from the trajectory x(t) agrees with the Boltzmann
distribution. Store your program as username_HW8_1B.m.
kb T
Well, the first thing to note is that using equation 9, D = , we know that ζ = 1.
ζ
Everything else is fairly straightforward; the parameters have been given to you. You do
need to realize that dU/dx must be recalculated at each step because it varies with x. You
also need to play with your equilibration time a little bit, as well as your time steps. For
each of these, you should decide on their values similarly to how you decided on the
number of grid points in a finite difference approximation: gradually change them until
you see no difference in your solution. For instance, if your time step is too big (say even
0.01), then the same number of equilibration steps as a smaller time step (say 0.001) will
not give you the correct result… instead, the curve will be not quite equilibrated. So,
these must be adjusted appropriately until the expected behavior is observed. There are
many correct values for these parameters that will achieve this, you just needed to find
one set. Finding these appropriate values is the key part of this problem.
dx
= 2 Eaπ sin ( xπ ) cos ( xπ )
When you integrate your curves, note that you must supply the x vectors to trapz; if you
only supply the frequency or probability vector, you’ll be some (significant) constant
factor off in the scale of your probabilities, and the area would not integrate to 1.
I did multiple trials just to get a smoother graph; one trial would have given a little more
jagged of a graph. Another alternative to smooth out would be to just sample for a lot
longer; this would produce the same result.
Finally, for your graph… if your graph consistently follows the trend of the theoretical
graph but is offset slightly above, this is acceptable (if not expected). This is because we
are not able to properly normalize because there are no bins centered at 0 or 1; this means
that when using trapz, we are essentially integrating from “almost 0” to “almost 1”, and
so we are missing some area. The area calculated by trapz is then smaller than the real
area, and our curve will be shifted slightly up. This effect can be decreased by using
more bins (so that we integrate closer to 0 and 1, capturing more of the area) or by
extrapolating a point at 0 and 1 from the curve assuming a derivative equal to zero. But
the bins are likely to be unevenly spaced by default, making that extrapolation a little
more difficult and thus entirely optional. Brownie points for anyone who did that,
though.
That’s about it. Here’s a figure and the code that generated it.
% Mark Styczynski
% 10.34
% HW 8
% Problem 1.B.
x = 0;
tic
figure(1);
plot(x,probPlot,'k-');
hold on;
plot(bins,freq/area,'r-');
xlabel('x')
ylabel('p(x)')
title('A particle in periodic potential and boundary conditions')
legend('Boltzmann distribution','Brownian simulation')
Part 1.C.
Consider again the same external potential, but now do not use the periodic boundary
conditions. Instead, generate trajectories x(t) that are not shifted in space to remain in
[0,1]. In the limit Ea << kbT, the energy barriers are negligibly small and the particles
essentially undergo “regular” diffusion. But, when Ea becomes comparatively large
relative to kbT, we expect the barriers to be difficult to overcome such that the particle
trajectories become “trapped” between barriers for a long time until they are finally able
to “jump” to the next energy well. If we then continue the simulation over very long
periods of time such that each trajectory has experienced many jumps, and we measure
at various times the mean squared displacement, <x2(t)>, we can estimate the effective
diffusivity, Deff, in the presence of the barriers from the relation <x2(t)> = 2 Defft as tÆ
∞.
Perform this calculation to measure Deff when D = 1 for Ea = 0.1, 0.5, 1, 2, 3, 4, 5 when
kbT = 1 and plot ln Deff vs. Ea. A reasonable prediction of how the effective diffusivity
should be affected by the barrier height is
− Ea
Deff = De kbT
Compare the results of your calculation to this functional form to see if it is an accurate
description of the effect of energy barriers on long-time diffusive motion. Store your
program as username_HW8_1C.m.
OK, so first we need to remove the periodic boundary condition from our code. We also
need to know how to calculate <x2(t)>; this is rather simple, actually. Though you could
have created a probability distribution function using histogram routines, this was
unnecessary. All you need to do is calculate the average of x2, which in this case is the
sum of all x2 values divided by the number of samples taken. This is equivalent to taking
∑ xi2 P ( xi ) , because in this case the probability of each xi is just one divided by the
i
number of samples. This is actually more accurate than the histogram method since it
does not put items into bins before calculating an average.
− Ea
We see that the plots do not agree; that is, the predicted effect of barrier height on
effective diffusivity is not exactly the equation that Dr. Beers gave you. There is a
different slope at the very least, and probably even a different functionality. As was
mentioned in class, this may have to do with differences between activation
energy/transition state theory and our methods of simulation.
Note that there are different methods to calculate the effective diffusivity given a set of
<x2(t)> values; Dr. Beers mentioned in class that you can do a simple polynomial or least
squares fit; I found it easier to divide the <x2(t)> values by the time at which they were
taken and to then take the simple arithmetic mean of the resulting Deff values. Either way
is valid, and they should yield approximately equal results given equal sampling.
However, it is a little bit harder to get good sampling for the line-fitting method, as the
best fits will occur over a wider range of time. I ran the code for the same number of
iterations for both methods, but that gives significantly fewer samples for the line-fitting
method, so the curve looks more jagged. The main point is there, though: either method
will produce approximately the same curve, which does not agree with the equation that
Dr. Beers proposed.
% Mark Styczynski
% 10.34
% HW 8
% Problem 1.C.
for k=1:length(EaList),
for i=1:6,
% PDL> Calculate <x^2> for the slope-fitting method.
DeffT(k,i) = mean(storage(k,i,:).^2)/2;
% Alternative way of calculating average x^2 using
histograms...
% numBins = 30;
% [freq(k,i,:), bins(k,i,:)] = hist(storage(k,i,:),numBins);
% area(k,i) = trapz(bins(k,i,:),freq(k,i,:));
% Deff2(i) =
trapz(bins(i,:),freq(i,:)/area(i).*(bins(i,:).^2))/2/(i+equilTime-1);
end
% PDL> Find Deff as slope of Deff*t vs. t
timeArray = linspace(equilTime,equilTime+50,6);
fit = polyfit(timeArray,DeffT(k,:),1);
DeffFit(k) = fit(1);
end
Consider again the 1-D system with the periodic potential energy
U ( x ) = Ea ⎡⎣sin ( xπ ) ⎤⎦
2
Write a program to sample the NVT equilibrium distribution of x using Metropolis Monte
carlo for the case Ea = 1, kbT = 1, and show that the results agree with the Boltzmann
distribution
−U ( x )
kbT
e
P ( x) = −U ( x )
1
∫e
kbT
dx
0
Where again you use periodic BC to maintain the particle within 0 ≤ x ≤ 1.
Here we implement a Metropolis Monte Carlo method to sample from the NVT
distribution. To make an iterative move from our current point, we need to add some
random number to our value of x. In class Dr. Beers suggested that we sample from a
normal distribution; in the book and in some sample code he used a uniform distribution.
Either of those should suffice for this case. Of course, be sure that if you use the uniform
distribution with rand() that you recenter it around zero by subtracting 0.5 from whatever
value it gives you.
As we know from the book, the update steps for Metropolis Monte Carlo are accepted
dependent upon the ratio of the probability of the proposed state to the probability of the
current state. Since these two probabilities have the same normalization factor, this can
be directly calculated as
−U ( xnew )
− ⎡⎣U ( xnew ) −U ( xcurr )⎤⎦
kbT
e
α= −U ( xcurr )
=e kbT
e kbT
We then select a number, u, from a uniform distribution over [0,1]. If u ≤ min{1, α},
then we accept the proposed step and sample from it. Otherwise, we stay at the current
step and sample from it.
Note that the same issues with integration for the purposes of normalizing the probability
distribution function still apply, so it’s OK if our graph is offset from the Boltzmann
distribution slightly upward.
Here is the resulting figure and the code to produce it. The program takes about 20
seconds to run.
% Mark Styczynski
% 10.34
% HW8
% Problem 2. Metropolis Monte Carlo simulation
Clearly, the cost function has many local minima, and it would be very difficult to find the
global minimum using the deterministic techniques that we developed in chapter 5.
Unfortunately, such irregular cost functions are not uncommon, especially when
attempting to compute the minimum energy geometry of a molecule or a crystal.
Write a program that uses simulated annealing to identify the global minimum from a
random initial guess. Store your program as username_HW8_P3.m and provide
directions for its use.
Yes, if you used Dr. Beers’s sample code from the website, you will get credit for this
part. However, if something went wrong somewhere in your program, you are much
more likely to be given partial credit for work that you did versus work that you
borrowed (with appropriate citations) from the website.
As for the specific problem… as detailed in the book, simulated annealing is essentially a
sequential set of MCMC problems designed to find the minimum of your function. (See
above for a brief explanation of the MCMC method.) Slowly, the “temperature”
parameter is lowered; as this happens, the routine is less likely to accept moves that result
in higher function values, but still leaves some non-zero chance. This method helps to
avoid (though does not eliminate) getting stuck in local minima.
It is important to note that simulated annealing still can get stuck in local minima. In
addition, it may be somewhat dependent on your initial guess (particularly if your
maximum displacement/move/jump size is relatively small). As such, it is best to run it
multiple times and see what the result is. Whatever single lowest function value you find
in any run is then the answer that you should return.
And as you can mostly see from the graph you were initially given, the correct minimum
for this system is x = 0.8566, F(x) = -1.4120. Here’s that figure again, and some fresh
code to find the solution.
% Mark Styczynski
% 10.34
% HW8
% Problem 3. Simulated annealing
tic
bestX
bestF
Problem 4. 4.A.2
Compute the value of the following definite integral using both dblquad and Monte Carlo
integration.
2 x
ID = ∫ ∫ ⎡⎣( x − 1) + y 2 ⎤ dydx
2
1 0 ⎦
Monte Carlo integration is explained in your book, particularly on pages 241-243 and
504. There are two different methods by which we can do this: one using an iterative,
Markov chain approach, and one using a simple uniform sampling approach. Most of
you likely used the latter, but I’ll present both here for completeness.
The alternative approach, briefly explained on page 504 (beware typos, though), is to use
an iterative method. Here, as before in MCMC methods, we propose a new step each
time. Now, our probability function is defined as
P( x) =
H Ω x[ ( ])
j
VS
Where VS is the space being integrated (not necessary right now, as it will cancel in our
calculations), and HΩ is an indicator function with a value of 1 if we are in the space to be
integrated and 0 otherwise. Thus, assuming we start in the space to be integrated, then
the value of the ratio of P(xproposed)/P(xcurrent) is always either 1 or 0… that is, we
either definitely accept the sample if it is in the space, or definitely reject it if it is
not. If we reject it, we just sample the current point again. (See problem 2 for a
quick run-through of MCMC methods.) This time, the average function value that
we get will only be for the space to be integrated; in this case, we multiply this
average by the volume of the space to be integrated, not the volume of some
hypercube. (Of course, this does mean that we have to know that volume, which
may in some cases be non-trivial to calculate.)
And of course, the answer hasn’t changed from the previous assignment… you
should be getting 1.0612 for the area (or at least approximately that, depending on
your sample size). Here’s the code to do all of that… and now there’s only one
more assignment left.
function marksty_HW8_P4()
end
end