Let’s look at an example of what we can do with our newly liberated JavaDocs. Because we can make use of the JavaDoc information elsewhere, we can combine this information with other sources. This means that we no longer have to use JavaDoc comments for everything, we can just use them for what they are good at and place other types of information elsewhere. A great candidate for this is mathematical descriptions of formulas that are being implemented with code. I have been looking into this as part of my work for OpenGamma, who have a lot of math to document.
The best tool available for typesetting mathematical equations is LaTeX. It’s the only tool I would even consider using, and I don’t know of any other candidates. There is a LaTeX taglet for JavaDoc called LaTeXlet. This works within JavaDoc comments, but to use it you need to type double-escaped LaTeX into the comments. This is difficult to type, it clutters up the comments with strange-looking LaTeX, and it means this LaTeX is hard to use elsewhere.
The new Dexy-based option calls for typing pure, unadulterated LaTeX into separate documents with a .tex extension (so these could easily be rendered as LaTeX for testing or for use elsewhere). JavaDoc information is stored in JSON files using the JSON doclet, and then these can be brought together to write any type of documentation that is needed, such as this blog post.
We want to be able to create HTML documentation, and for this we will be using the excellent open source MathJax JavaScript library, which directly renders LaTeX code in the browser.
I’ve picked the cern.jet.random.Gamma and related classes from the open source Colt library to play with. You can see the original JavaDoc documentation here and here. There is a little inline math, and links to some other pages which have formulas rendered as images. Of course in fairness, this is code from 1999 and at least there is extensive documentation, even if it’s not pretty. A more relevant criticism is that the formulas referenced are not exactly what is implemented, they are background information rather than direct mathematical documentation of the actual code, and it is an ‘exercise for the reader’ to work out how to arrive at the actual Java implementation. In this particular case, the formula given of:
p(x) = k * x^(alpha-1) * e^(-x/beta) with k = 1/(g(alpha) * b^a)) and g(a) being the gamma function.
is inconsistent, the final b and a should be beta and alpha, and in the implementation ‘beta’ is actually called ‘lambda’. Of course, you either need to be intimately familiar with the Gamma distribution or you need to do some research to figure this out. This is a consequence of the fact that the description is so divorced from the implementation, which hopefully would be prevented by a documentation approach like the one taken here.
Let’s write a separate snippet of LaTeX which describes the Gamma distribution. We will be able to include this snippet directly in both PDF (made from LaTeX) and HTML (thanks to MathJax) forms of our new documentation. Please do not use this as reference material for the Gamma distribution! I have taken these formulae from the Colt documentation and the various pages which are referenced, and I have taken care to be accurate, but this is only intended as an illustration of this LaTeX technique.
Here is the raw LaTeX:
The Gamma distribution function is given by
$$ P(x,a) = \frac{1}{\Gamma(a)} \int_0^x e^{-t} t^{a-1} \,dt $$
for real arguments $x \geq 0$ and $a > 0$.
Where Euler's gamma function is defined by the integral
$$ \Gamma(z) = \int_0^\infty t^{z-1} e^{-t} \,dt, \hspace{10mm} Re(z) > 0 $$
And here is the LaTeX rendered as an equation by MathJax
The Gamma distribution function is given by
$$ P(x,a) = \frac{1}{\Gamma(a)} \int_0^x e^{-t} t^{a-1} \,dt $$
for real arguments $x \geq 0$ and $a > 0$.
Where Euler’s gamma function is defined by the integral
$$ \Gamma(z) = \int_0^\infty t^{z-1} e^{-t} \,dt, \hspace{10mm} Re(z) > 0 $$
(If you don’t see pretty equations then check out MathJax’s browser compatibility page)
That describes the Gamma distribution generally, let’s look now at the various functions which implement aspects of this.
For background, here’s the definition of a probability density function.
This information about the method is taken from the JavaDocs:
cern.jet.random.Gamma.pdf Returns the probability distribution function. Returns value of type: double Accepts parameters of type: (double)
Here is our LaTeX description of what is being implemented:
We will implement the probability density function as
$$ p(x) = \frac{1}{\Gamma(\alpha) \lambda^\alpha} x^{\alpha-1} e^{-\frac{x}{\lambda}} $$
which is equivalent to
$$ p(x) = \frac{ e^{\left( (\alpha – 1) \log \left( \frac{x}{\lambda} \right) – \frac{x}{\lambda} – \log \left( \Gamma(\alpha) \right) \right) }}{\lambda} $$
And here is the method’s source code, again taken from the JavaDoc data:
public double pdf(double x) {
if (x < 0) throw new IllegalArgumentException();
if (x == 0) {
if (alpha == 1.0) return 1.0/lambda;
else return 0.0;
}
if (alpha == 1.0) return Math.exp(-x/lambda)/lambda;
return Math.exp((alpha-1.0) * Math.log(x/lambda) - x/lambda - Fun.logGamma(alpha)) / lambda;
}
Thanks to the work we did in the LaTeX, we can see how the formula which was actually implemented relates to the traditional specification of the formula.
Here’s the definition of a cumulative distribution function.
cern.jet.random.Gamma.cdf Returns the cumulative distribution function. Returns value of type: double Accepts parameters of type: (double)
The cdf is the integral of the pdf (the cumulative distribution is the sum/integral of all the individual probabilities).
$$ y(x) = \frac{\alpha^\lambda}{\Gamma(\lambda)} \int_0^x t^{\lambda-1} e^{-\alpha t} \,dt $$
where
$$ \int_0^x t^{\lambda-1} e^{-\alpha t} \,dt $$ is the lower incomplete gamma function.
Here is this method’s source code:
public double cdf(double x) {
return Probability.gamma(alpha,lambda,x);
}
We see that this just calls a method in the cern.jet.stat package:
cern.jet.stat.Probability.gamma Returns value of type: double Accepts parameters of type: (double, double, double)
Here is the original JavaDoc comment for this method:
x b - a | | b-1 -at y = ----- | t e dt - | | | (b) - 0
The incomplete gamma integral is used, according to the
relation
y = Gamma.incompleteGamma( b, a*x ).
And here is its source:
static public double gamma(double a, double b, double x ) {
if( x < 0.0 ) return 0.0;
return Gamma.incompleteGamma(b, a*x);
}
static public double gamma(double a, double b, double x ) {
if( x < 0.0 ) return 0.0;
return Gamma.incompleteGamma(b, a*x);
}
which in turn calls the incompleteGamma method:
cern.jet.stat.Gamma.incompleteGamma
Returns the Incomplete Gamma function; formerly named <tt>igamma</tt>.
Returns value of type: double
Accepts parameters of type: (double, double)
Whose source finally contains an actual implementation:
static public double incompleteGamma(double a, double x)
throws ArithmeticException {
double ans, ax, c, r;
if( x <= 0 || a <= 0 ) return 0.0;
if( x > 1.0 && x > a ) return 1.0 - incompleteGammaComplement(a,x);
/* Compute x**a * exp(-x) / gamma(a) */
ax = a * Math.log(x) - x - logGamma(a);
if( ax < -MAXLOG ) return( 0.0 );
ax = Math.exp(ax);
/* power series */
r = a;
c = 1.0;
ans = 1.0;
do {
r += 1.0;
c *= x/r;
ans += c;
}
while( c/ans > MACHEP );
return( ans * ax/a );
}
Here are a few simple examples of using this, in Java and JRuby.
import cern.jet.random.Gamma;
import cern.jet.random.engine.MersenneTwister;
public class CallGamma {
public static void main(String args[]) {
MersenneTwister mt = new MersenneTwister();
Gamma gamma = new Gamma(0.5, 0.5, mt);
System.out.println(gamma.pdf(0));
System.out.println(gamma.pdf(1));
System.out.println(gamma.pdf(2));
System.out.println(gamma.cdf(0));
System.out.println(gamma.cdf(1));
System.out.println(gamma.cdf(2));
}
}
0.0
0.10798193302620228
0.010333492677029392
0.0
0.682689492137086
0.842700792949715
>> require 'java'
=> true
>>
?> include_class 'cern.jet.random.Gamma'
=> Java::CernJetRandom::Gamma
>> include_class 'cern.jet.random.engine.MersenneTwister'
=> Java::CernJetRandomEngine::MersenneTwister
>>
?> mt = MersenneTwister.new()
=> #<Java::CernJetRandomEngine::MersenneTwister:0×329572>
>> gamma = Gamma.new(0.5, 0.5, mt)
=> #<Java::CernJetRandom::Gamma:0xf800db>
>> gamma.pdf(0)
=> 0.0
>> gamma.pdf(1)
=> 0.107981933026202
>> gamma.pdf(2)
=> 0.0103334926770294
>>
?> gamma.cdf(0)
=> 0.0
>> gamma.cdf(1)
=> 0.682689492137086
>> gamma.cdf(2)
=> 0.842700792949715
>>
Apart from some math porn, what exactly is the point of all this? When mathematics are implemented using code, there is tremendous benefit to preserving the link between theory (math) and practice (code). By using tools which allow the full expression of mathematics, we can see the derivation from the original formula to the form in which it can be actually implemented. These expressions can be checked to make sure they are correct, practitioners can easily understand the implications of the particular implementation, and students can see how the theories they learn are translated into actionable code. Where the full range of mathematical expression isn’t available, documentation tends to ignore or just loosely reference the underlying theory. As we saw in this case, these referenced formulas sometimes use different notation and don’t necessarily map directly to the computations which are implemented.
With the decoupled use of JavaDoc made possible by our custom doclet, we can make use of document formats which allow us to take advantage of tools such as MathJax for rendering LaTeX in HTML, while not losing the benefit of existing documentation and the data automatically extracted by JavaDoc.
if i understand this correctly, it should be straightforward to plug in to doxygen. could be useful to consider adding Qt style as well. then you could pick up the entire doxygen user base.
March 7, 2011
C# and it’s lambda expressions/expression trees make this quite easy to implement without needing a documentation tool. I am going to be examining the structure of a function at runtime, then outputting MathML dynamically in the help system and rendering it using MathJax. These are quite powerful ideas for mostly numerate systems!