Problem Set 2
Due Thursday Feb. 13, 12:30 pm
Problems
This problem explores declaring types for function arguments and thinking about subtypes.
- Write a function that sums the elements of a dictionary, but only if the values are all integer or floating point scalar numbers, handled by setting the type of the argument to your function (i.e., it should be Dictionary with types specified for the keys and values). Hint: you will need to use the
<:syntax. The keys can be any type. - Add another method for the function that, for a dictionary whose elements are all either strings or character literals, concatenates the strings or character literals.
- Write a function that sums the elements of a dictionary, but only if the values are all integer or floating point scalar numbers, handled by setting the type of the argument to your function (i.e., it should be Dictionary with types specified for the keys and values). Hint: you will need to use the
Let’s enhance your Newton function from PS1.
- Allow the objective function to take arbitrary additional arguments, passed along from your function into the objective function.
- Robustify your Newton function. Add error trapping using Julia’s exception system. This should catch divergence as well as convergence to a local maximum (the goal of the function is to find a local minimum). Add useful messaging with
@info,@warn,@error. - Add handling for the following situations:
- When the next point has a objective value than the current point. In that case you can backtrack toward the current point, perhaps first halfway back, then halfway on the remaining half, etc., until you find a new point whose value lower than that of the current point.
- When the next value is outside the range of the previous values. In that case you can use the technique of bisection to find a new point within the valid interval and then proceed with Newton iterations from there.
- Set up a few tests for your Newton function using Julia’s testing framework (to be discussed in class Thursday Feb. 6).
Tips: Here are some thoughts about functions you can use as you develop your code and for formal tests. One function you can test on is the sine or cosine function. If the starting point is near enough to a local minimum it should converge to that minimum, but if it is further away it should converge to the nearby local maximum instead. And here’s a function that diverges in some cases: \[ x \mbox{tan}^{-1}(x)-0.5*\log(\left|1+x^2\right|), \] because the first derivative is \(\mbox{tan}^{-1}(x)\) (
atan()in Julia), which flattens out as \(\left|x\right|\) goes to infinity. However, there are lots of others you could come up, so feel free to work with others that may be better than these.Write a function constructor that creates a version of a function that reports how many times it has been called. Ideally this would work regardless of how many (if any) positional or keyword arguments are used by the function. Additionally, try to manipulate
kwargsso that the wrapper only reports the number of times run when the wrapped function is called asmyfun(<regular_arguments>, _report = true).If you want a real challenge, try to do this using a macro. I was able to do this using some meta programming (code manipulation) tools in Julia but it took some Googling and experimentation, and it was helpful that I have experience with meta programming in R.
Consider the overdispersed binomial likelihood calculation below.
The following is the probability mass function for an overdispersed binomial random variable: \[ P(Y =y;n,p,\phi) = \frac{f(y;n,p,\phi)}{\sum_{k=0}^{n}f(k;n,p,\phi)} \]
\[ f(k;n,p,\phi) = {n \choose k}\frac{k^{k}(n-k)^{n-k}}{n^{n}}\left(\frac{n^{n}}{k^{k}(n-k)^{n-k}}\right)^{\phi}p^{k\phi}(1-p)^{(n-k)\phi} \]
where the denominator of \(P(Y =y;n,p,\phi)\) serves as a normalizing constant to ensure this is a valid probability mass function.
Write two functions, (a) one using looping and (b) one using vectorization, to implement the calculation of the denominator above and compare speed, taking care that the time for JIT compilation is not included. (Some reasonable values to use are \(n=10000\), \(p=0.3\) and \(\phi=0.5\).) In your implementations, avoid unnecessary calculations (e.g., simplify/combine terms where possible). Also assess the time for the JIT compilation.
Tips: Remember that \(0^0 = 1\) and that for numerical stability the sorts of calculations done in calculating \(f()\) should be done on the log scale before exponentiating at the end.
What happens in terms of speed if you do the calculation outside of a function? In this case the JIT compilation is presumably not possible.
Comments