Difference between revisions of "Physics 212, 2019: Lecture 6"

From Ilya Nemenman: Theoretical Biophysics @ Emory
Jump to: navigation, search
Line 8: Line 8:
 
This is not very precise -- and, indeed, verifying a computational model is often as much an art as it is science. But some simple rules of thumb apply. First, ''do not verify your model all at once!'' Instead, just like we build models hierarchically, we need also to verify them hierarchically. For this, you verify and seal every leaf on the tree that is your model. Then you verify blocks that consist of many leafs, then the blocks of blocks, and so on. Do not attempt to write (and certainly not to verify) a larger piece of a model when its constituent pieces are not yet verified to your satisfaction.
 
This is not very precise -- and, indeed, verifying a computational model is often as much an art as it is science. But some simple rules of thumb apply. First, ''do not verify your model all at once!'' Instead, just like we build models hierarchically, we need also to verify them hierarchically. For this, you verify and seal every leaf on the tree that is your model. Then you verify blocks that consist of many leafs, then the blocks of blocks, and so on. Do not attempt to write (and certainly not to verify) a larger piece of a model when its constituent pieces are not yet verified to your satisfaction.
  
Second
+
Second, when verifying an piece of the code, every free parameter, every variable in the code must be verified. For this, you need to set every one of the parameters (and some parameter combinations) in turn to values that make the problem easier. These could be value that make your problem analytically solvable, or reduce your problem to something that you already solved before. And then you must ensure that the output of your code with such ''special cases'' parameter values is exactly what you expect it to be from the previous solutions. There's something called the thirteenth strike rule -- if a clock strikes 13 times, then all of them (not just the thirteenth) are wrong. If you see that the problem does something unreasonable for these special cases, it's more likely than not that it is also wrong in general. If you found a mistake -- fix it.
First, every free parameter, every variable in the code must be verified.  
 
  
 
+
Third, spend time on verification and try all sorts of initializations, even if you do not expect that the initialization simplifies the problem, allowing you to compare to the prior knowledge. Instead, what is likely to happen is that, for some of the initial conditions, your model will produce obviously wrong results. For example, it may crash, or may produce NaN outputs, or will enter an infinite loop, or produce negative numbers for physical quantities that can only be positive, or something similar. And once you see a problem, do not just step over it -- solve it! Figure out what's going on, and fix things by either introducing more exception checks, or by fixing the logic of the solution. The more things you try, the likely you are to detect more errors. But also remember -- small changes in parameters are almost useless. Go for large changes, explore the edges of the parameter spaces. You are more likely to identify problems this way.
Our main goals here are:
 
main goals for this lecture are.
 
  
  

Revision as of 16:00, 3 February 2019

Emory Logo

Back to the main Teaching page.

Back to Physics 212, 2019: Computational Modeling.

Last Lecture of Module 1: Introduction to Python and Computational Modeling

Introduction to verification of models

The most important thing to remember here is that a computer does exactly what you ask it to do. And we are pretty bad at following our own instruction in an unbiased manner. You will be reading the code and thinking that it does what you want it to do, but you will be wrong. Your biases and expectations will have the best of you. Thus it is almost useless to verify the code line by line for correctness (unless we are dealing with a simple syntactic error). Instead one needs to run the code many times, to look at what it outputs, and to ask if the output makes sense.

This is not very precise -- and, indeed, verifying a computational model is often as much an art as it is science. But some simple rules of thumb apply. First, do not verify your model all at once! Instead, just like we build models hierarchically, we need also to verify them hierarchically. For this, you verify and seal every leaf on the tree that is your model. Then you verify blocks that consist of many leafs, then the blocks of blocks, and so on. Do not attempt to write (and certainly not to verify) a larger piece of a model when its constituent pieces are not yet verified to your satisfaction.

Second, when verifying an piece of the code, every free parameter, every variable in the code must be verified. For this, you need to set every one of the parameters (and some parameter combinations) in turn to values that make the problem easier. These could be value that make your problem analytically solvable, or reduce your problem to something that you already solved before. And then you must ensure that the output of your code with such special cases parameter values is exactly what you expect it to be from the previous solutions. There's something called the thirteenth strike rule -- if a clock strikes 13 times, then all of them (not just the thirteenth) are wrong. If you see that the problem does something unreasonable for these special cases, it's more likely than not that it is also wrong in general. If you found a mistake -- fix it.

Third, spend time on verification and try all sorts of initializations, even if you do not expect that the initialization simplifies the problem, allowing you to compare to the prior knowledge. Instead, what is likely to happen is that, for some of the initial conditions, your model will produce obviously wrong results. For example, it may crash, or may produce NaN outputs, or will enter an infinite loop, or produce negative numbers for physical quantities that can only be positive, or something similar. And once you see a problem, do not just step over it -- solve it! Figure out what's going on, and fix things by either introducing more exception checks, or by fixing the logic of the solution. The more things you try, the likely you are to detect more errors. But also remember -- small changes in parameters are almost useless. Go for large changes, explore the edges of the parameter spaces. You are more likely to identify problems this way.


https://github.com/jupyter/jupyter/wiki/A-gallery-of-interesting-Jupyter-Notebooks#introductory-tutorials

An example of a model: Solving for an equilibrium position, harder version

Problem formulation

A charged particle free to move on a rod is put in the between the first and the second of three immovable particles with an electric charge of the same sign affixed to the rod at various points. Where will the charge come to rest?

Analysis
The same analysis as in the simpler version applies here: it's a static, deterministic, continuous space model. We again need the friction for the solution to exist, and the moving particle with come to rest, and the solution will exist.
Model building
As before, the equilibrium for the system will be given by . There are now three Coulomb forces acting on the particle from the fixed charges. Let's say the charges are at positions , with charges , , and , respectively. Let's suppose again that the position of the movable particle is . Then the solution of the problem will be given by . We notice again that the three charges and fixed charge positions are the only things that are needed for the solution. This is equivalent to . This is not a quartic equation in , and solving this problem would be equivalent to finding the root of this quartic equation between and . This is going to be more interesting!
Model implementation
The analysis above suggests that the entire model consists of solving for roots of a fourth order polynomial. How do we do this? For this we will introduce the Newton-Raphson method for finding a root of an equation . This is the first of many computational algorithms that we will introduce in this class, and it allows to find roots (zero crossings) of more-or-less arbitrary functions, not just of polynomials.
Let's suppose that we have a guess for a root of a function . We evaluate the function, , and it turns out that the guess is not quite right, . We can then write the Taylor expansion for the function for the values of around . Namely, , where is the value of the derivative of evaluated at , . But then we can get a better estimate of where the root is by solving the equation . This would give for the next estimate. Iterating this, one will get . This way we will approach the solution progressively better and better, as we iterate more. At some point we will need to stop the iteration. Usually, this is yet another parameter that one adds to the initialization in the experiment: the tolerance for finding the solution. That is, one interrupts the recursion when . Note that one can also choose to interrupt the solution choice when the function itself is approximately zero, and both approaches are valid. They are, in fact, equivalent for typical functions, but may give different results for various special cases -- and the discussion of this in "Numerical recipes" is illuminating.
A few notes about the algorithm are in order. First, the algorithm will only find a root if it is initialized with the initial guess in the root's vicinity. So if one wants to find a root, one needs to have good guesses. And to find all roots, one needs to have initial guesses near all of the roots, and then run the algorithm for each of the guesses. Even then the solution may not exist -- there is no guarantee that the the iterations converge; they can enter cycles or diverge instead. Most of such problems can be tracked to the fact that the iteration step involves dividing by , derivative of the function . When this derivative becomes small, changes to will be large, and all sorts of problems will emerge. We will explore this in the your work section.
Your work: with the provided skeletal code implementing the Newton-Raphson method, try to find all roots of the following functions and . Do you need to modify the code to deal with convergence of the iterations for some conditions? If so, do the modification. Can you understand why some of your attempts converged to a solution, and some did not? Don't forget to submit your code.
Once the Newton solver for roots has been implemented, the rest of the model design is simple. We start with initializing variables, verifying various possible exceptions, finding the root of the force equal to zero equation, and then outputting the root. A script for this is available for download,

as a Jupyter notebook.

Model verification
To verify the model, we need to check that it produces results that we know to be correct for various special cases. As a general rule, every term, every function in the model must be verified. For this, you set all of the parameters in turn to values that make the problem easy, and compare the output to what you should have expected. In this particular case, the following verifications would be reasonable:
  • Set the initial condition outside of the leftmost or the rightmost charge. Then there should be no equilibrium, and the charge will move to infinity -- which will mean that the Newton solver won't converge. Do we observe this?
  • If we have three fixed charges, but one of them is zero, then the problem is equivalent to having two fixed charges only. And that problem we already solved previously. So we can set, in turn, the values of each of the three fixed charges to zero, and compare that the output of the program is the same as that of the two-charges quadratic solver program. An even simpler first step could be setting one of the charges to zero, and the two other equal to each other. Then the movable charge should settle exactly in the middle between the two non-zero charges. Does this?
Notice that with these checks, we verify the effects of the initial condition and the value and the position of the fixed charges to be what we expect them to be. Thus the verification is more or less complete. In addition, none of the verification steps is useless -- if you only checked the initial condition, then the effect of the fixed charges would not be explored, and so on.
Discussion
Here we implemented a solution for the equilibrium position of the charge in the background of three fixed charges. The program, based on the Newton-Raphson algorithm, found the solution. Exceptions for position of the charges and the charge values were encoded. The program output has been verified against solution of a simpler problem with only two fixed charges, and the results (hopefully) matched. We assumed here that only the equilibrium position mattered. In principle, we could have written the problem as a dynamical model, and used the Newton's laws to find the position of the charge as a function of time. The value of the position at large time would be the equilibrium position, provides there is some friction in the problem that would force the charge to stop eventually. Another simplification we assumed was that the charges are positioned in just one dimension (on a rod). For a 2-d or a 3-d configuration of the charges, we would need to extend the Newton-Raphson algorithm to be useable for multi-dimensional root finding, which goes slightly outside of the scope of this class (would require knowing multivariate calculus).

New Python Concepts

  • Loops of different types (for and while).
  • We started talking about scopes: what happens to the variable i after the for loops finishes executing?
  • Variables vs objects, yet again.
  • Vector math is always faster than element-by-element math -- we showed this in a simple script in class.

Old Math Concepts

  • Make sure you review Taylor series. These will be useful in the rest of the course.