Generate PDF document showing the equations in a package

I have a small package with equations and tables copied from a book. I want to figure out a way to generate a PDF file rendering all the equations in mathy form. This will serve as a quality control that the equations being used by the package look like the ones in the book.

I had success writing a Pluto notebook for these equations which took the form below and rendered very nicely. However, I could not figure out a way to then make other packages depend on these equations, so I went back to a standard Julia file/package.

using Markdown, DataFrames, Latexify
md"Equation KM-620.5"
@latexrun A_1(σ_ys, ϵ_ys, m_1) = σ_ys * (1 + ϵ_ys) / (log(1 + ϵ_ys))^m_1

md"Equation KM-620.6"
@latexrun m_1(R, ϵ_p, ϵ_ys) = (log(R) + (ϵ_p - ϵ_ys)) / log(log(1+ϵ_p)/log(1+ϵ_ys))

md"Table KM-620"
const tableKM620 = DataFrame(...)

I want to avoid writing the equations twice (once for execution and once for documentation) in case they would get out of sync or be mistyped. Is there a good way to sequentially render the function definitions into a document?

Summary

I was able to achieve my goal. The resultant package is here. The functions and constants defined in the package are available for use by typing using KM620. The PDF outputs are generated by typing ]test KM620. (You must have XeLaTeX installed.)

Approach

Function Definitions

Getting one-liner function definitions rendered to PDF was actually straightforward with Weave.jl and Latexify.jl. Just write @latexrun in front of the function definition and mark a new weave chunk on the line above it with #'. Then weave(inputfile, doctype="md2pdf", out_path=outputfile) will do all the work for you. I put this weave command in my test/runtests.jl file, so I have an easy way to update the PDFs whenever I update the function definitions. (I still may look for a better way to force the PDFs to update periodically.)

File Structure

You cannot run weave on the main package file because the module and end #module keywords will not work with Weave’s idea of code chunks. Instead I structured the main file as below and called weave on the included files.

module KM620
include("KM620_equations.jl")
include("KM620_tables.jl")
end # module KM620

You will also need to repeat the using Latexify, LaTeXStrings line at the top of each of these included files because the weaved files must be self-contained.

Contant Definitions

If your constants have simple types, then you can use @latexdefine in the same manner as @latexrun was used for functions definitions above. Unfortunately, DataFrames cannot be rendered to PDF this way.

The issue with @latexdefine is that it expects things to have a :raw representation (because it does something akin to "\$x = $(latexify(x; env=:raw))\$" ). DataFrames don’t have this, but only “latexify” as latexify(df; env=:mdtable).

I also ran into an error when trying to use const df = DataFrame(<stuff>) and latexify(df) in the same file. The file would weave the PDF fine, but it would break on the latexify line when trying to using the package. To get around this error, I moved the latexify lines to their own file in the test folder and weaved that file instead of the package file. This test file will using KM620 so the constants are defined before latexifying them.

Note that this method of latexifying after defining will not work for function definitions because the alphanumeric representation of the function is not saved with the function. It is only available for latexification at the time when the function definition is run. I worked around this limitation somewhat to display equations inside my DataFrame by defining expressions which I later evaluated into functions.

Markdown Notes

Markdown notes can be written and rendered to PDF anywhere using markdown syntax on the #' commented lines. I rendered a separate Nomenclature PDF alongside the others using this method.

Weave vs. Literate

I originally tried using Literate.jl, but this was a dead end. The LaTeX strings were just displayed in markdown code blocks rather than actually being rendered. This issue led me to Weave. I also came across LiterateWeave.jl that could potentially be useful, but I did not dive into it.

Final Thoughts

My package is in a working state now, but probably not an optimized state. I am open to suggestions on how to do this better. I just wanted to leave some lessons learned for anyone else who happens across this post and to link my package for others to use as a template for this sort of thing.

4 Likes