Handcalcs.jl - For more readable calculations

Hello Julia community!

I am excited to announce my first registered package Handcalcs.jl! This package is inspired by the handcalcs.py package in python.

It is designed to be used in either jupyter or pluto. It will render your algebraic equations in latex. It will also evaluate them! See example below:

Here are the docs for it: Handcalc.jl docs

I am new to Julia so the doc(stable) button isn’t working. If someone could help with that, I would greatly appreciate it.

98 Likes

Oh wow:

Future plans are to integrate the package with Unitful.jl, be able to render the algebraic expressions within a function, and many other things. This package is an extension of Latexify.jl.

This is fantastic! Great work @co1emi11er! I’ll give it a whirl one of these days for some of my literate documents I write with Weave.jl and Quarto!

3 Likes

This already looks amazing.


Regarding your future plans:

A macro that will generate LaTeX for a function that was called. The generated
LaTeX would be the algebraic equations within the function.

My ultimate wish for this type of functionality is to be able to pull simple equation sets from different sources and use them together showing my work at the destination:

using Handcalcs, Smith1993, Jefferson2020
a=3
b=4
@handcalcs y = Smith1993.y(a)     # show LaTeX for y equation defined in package
@handcalcs z = Jefferson2020.z(b) # show LaTeX for z equation defined in package
@handcalcs c = y + z              # just show simple LaTeX addition

I structure my code like this now because:

  1. I can keep a master copy of my reference equations separately from all the reports that will use them. This avoids a lot of boilerplate and copy-paste errors.
  2. I can easily update or swap reference equations for all future reports with a dependency change.
  3. It makes clear in the report where I am coming up with my equations.

I use @latexrun when defining the equations in my dependency packages and then compare a weaved PDF in the dependency package to the reference text for verification that I transcribed them correctly. However, it would be wonderful if I could show the reference equation substitutions in the report itself!

1 Like

Thanks!

Yes this is exactly the goal of the function functionality! I do believe it is possible. The CodeTracking.jl package allows me to pull the function head and body info from a function.

The code is there to do the latex substitution. I just need to figure out how I will parse the args and kwargs of the function!

2 Likes

This looks very cool. My main use of Pluto is for exactly this sort of calculation with Unitful, so I’ll try it out sometime.

Incredible! It looks like handcalcs.py integrates nicely with SymPy. Does Handcalcs.jl have plans to similarly integrate with Symbolics.jl in the future? And what might that look like if so?

This is an amazing package btw!!

1 Like

Cool! Yeah that one exponent issue is the only thing I know of currently for unitful stuff. The back end is Latexify.jl so you can provide kwargs at the end of the handcalc macro to pass those through to latexify.

You can type for example:

a = 0.25u"ft"
b = 4u"inch"
@handcalcs c = sqrt(a^2+b^2) post = u"inch"

with Pluto you want to use begin and end statements though to get rid of the assignment callout in the top left of the output cell. Like so:

@handcalcs begin
  c = sqrt(a^2+b^2) 
  end post = u"inch"

CleanShot 2024-02-07 at 23.06.07@2x

You can see the exponent not rendering properly in the numeric substitution part. This is probably at the top of my list to get fixed. It actually breaks when you have a unit that has an exponent that is then under an exponent. But it should hopefully work smoothly after this fix. I want to get it fixed on the latexify side though, since it is broken on that end too.

Thanks!

I would say top priorities are getting unitful bug fixed, and functions integrated with the package. After that I can look at Symbolics.jl.

I haven’t ever used the Symbolics.jl package so it may take some time, but I see the benefit it could bring!

1 Like

Oh man this is amazing. I would have killed for this in school.

My natural reading of this is that a^2 is 0.25 ft^2 rather than Edit: (0.25 ft)^2. The MathCad 14 equivalent follows.

image

Also note the use of the assignment operator (:=)

5 Likes

Correct. This is what I want to get fixed in Latexify. See my pull request here. This pull request fixes the issue on the latexify side. I haven’t received any responses though.

2 Likes

Out of curiosity, and tangential to your awesome package announcement, would it be possible to have the latex formatting on the “grey boxes”?

In the sense that the output you show here, which is awesome, could that be how “the code looked” when one wrote it?

It might seem like a bit of a crazy idea, but for me being able to exactly recognize the equation and have it look like the code, is amazing :slight_smile:

Congratulations on the package!

Kind regards

Hi Nathan,

I just wanted to provide an update on the progress here, because I will be unable to work on this for a couple weeks. I have a rough working macro that does this in my handfunc branch! See example below!

Some function defined in another package

function calc_Ix(b, h) 
    d = if b > 4
        b * 5
    else
        b + 5
    end; "test if statement";
    c = h + 6; "test equation";
    Ix = b*h^3/12
    
end

using @handfunc (you don’t need to use begin end here but can)

@handfunc begin Ix = calc_Ix(5, 15) end

or can be a non exported function

@handfunc Ix = Handcalcs.calc_Ix(5, 15) 

And yes Ix is evaluated!! Ix being the variable assigned in the @handfunc part (variables within function are not defined in the global name space). If you assign it to a different variable then that will be the variable defined (although you will still see it as Ix in the latex portion)

The current limitations are:

  1. You can’t have return statements in the function (I plan to filter these out before passing to be latexified.)
  2. The names of the variables you pass to the function as parameters can not be the same name as the parameters in the actual function or any variable names defined in the function. (There is some macro hygiene I need to do).
  3. you must pass numbers or symbols (not fields of objects). This is also a current limitation of the @handcalcs macro.
  4. For whatever reason things work in jupyter and the repl but not pluto (I am unsure what to do here).
  5. I believe the function needs to be defined in another package. The @code_expr macro from CodeTracking.jl does not see functions in Main for some reason.
8 Likes

There is also an issue with negative exponents in units:

julia> using Unitful, Handcalcs

julia> v = 10u"m/s"
10 m s^-1

julia> A = 2u"m^2"
2 m^2

julia> ρ = 1.2u"kg/m^3"
1.2 kg m^-3

julia> @handcalc mdot = ρ * v * A
L"$\begin{align}
mdot &= \rho \cdot v \cdot A = 1.2 kg m^-3 \cdot 10 m s^-1 \cdot 2 m^2 = 24.0 kg s^-1
\end{align}$

I assume that need a fix on the handcalcs side since the whole negative exponent would need to go into {}

Sorry. What is the issue here? I am getting:
image

I am not super familiar with LaTeX. I have just been going off visual and I would be fine with this.

Interesting. that’s not what I get on my system. You are right, that’s how it should look like.

If you look at my Latex output that has m^-3 in it while yours looks like m^{-3}

What’s the Latex output you get? Am I missing a dependency/setting for Latexify?

oh yes. See what happens when you use UnitfulLatexify.jl.

So do:

julia> using Unitful, Handcalcs, UnitfulLatexify

julia> v = 10u"m/s"
10 m s^-1

julia> A = 2u"m^2"
2 m^2

julia> ρ = 1.2u"kg/m^3"
1.2 kg m^-3

julia> @handcalcs mdot = ρ * v * A
L"$\begin{align}
mdot &= \rho \cdot v \cdot A = 1.2\;\mathrm{kg}\,\mathrm{m}^{-3} \cdot 10\;\mathrm{m}\,\mathrm{s}^{-1} \cdot 2\;\mathrm{m}^{2} = 24\;\mathrm{kg}\,\mathrm{s}^{-1}
\end{align}$"

Thanks. That’s what I was missing. Awesome.

1 Like

New release: Handcalcs.jl v0.1.6

Hello all,

I just wanted to announce that the @handfunc macro is now available in the latest release v0.1.6. See below for the general use case.

Previous Post

Other current limitations:

  • If the function has other function calls within it’s body that are not available in Main, then the macro will error.

Future Plans

  • Get recursion working for @handfunc macro, so functions called within the function that was called also get latexified.
  • A parameters macro similar to what the python package does for inputs. This will make better use of space.
  • A way to break down a \LaTeX equation that is too long to multiple lines
  • I have also thought about adding a setting that you could change if you were within the REPL and instead of latex (since it is not very readable) to instead output a simple string instead. For example: I_x = b*h^3/12 = 5*15^3/12 = 1406.25.
14 Likes

New Release: Handcalcs.jl v0.3.0

Hello all,

There have been a few updates to Handcalcs.jl.

Quarto Integration

In light of the recent quarto advancements within Julia, I wanted to announce that recent updates allow handcalcs to be rendered using Quarto or weave! Here is a simple example.

Edit defaults

  • cols - change the number of columns the expression returns (default = 1).
  • spa - change the vertical line spacing between expressions (default = 10).
  • h_env - change the environment (default = “aligned”); “align” and “align*” are other options.

Note: @handcalcs macro can also take symbols of defined variables. See below.

a, b, c = 1, 2, 3
@handcalcs begin
    a # see note above
    b
    c
    x = 4
    y = 5
    z = 6
end cols=3 spa=0

image

Recursive functions

The @handcalcs macro will now automatically try to “unroll” the expressions within a function when the expression has the following pattern: variable = function_name(args...; kwargs...). Note that this is recursive, so if you have a function that calls other functions where the expressions that call the function are of the format mentioned, it will continue to step into each function to “unroll” all expressions.

One issue that can arise are for the functions that you do not want to unroll. Consider the expression: y = sin(x) or y = x + 5. Both of these expressions match the format: variable = function_name(args...; kwargs...) and would be unrolled. This would result in an error since these functions don’t have generic math expressions that can be latexified defining the function. You will need to use the not_funcs keyword to manually tell the @handcalcs macro to pass over these functions. Some of the common math functions that you will not want to unroll are automatically passed over. See examples below.

function calc_Ix(b, h) # function defined in TestHandcalcFunctions
    Ix = b*h^3/12
    return Ix
end;

function calc_Is(b, h) # function defined in TestHandcalcFunctions
    Ix = calc_Ix(b, h)
    Iy = calc_Iy(h, b)
    return Ix, Iy
end;
using TestHandcalcFunctions
x = 0
@handcalcs begin
y = sin(x)
z = cos(x)
I_x, I_y = TestHandcalcFunctions.calc_Is(5, 15)
end not_funcs = [sin cos]

image

In the above example sin and cos were passed over and calc_Is was not. As you can see, the calc_Is function was a function that called other functions, and the @handcalcs macro continued to step into each function to unroll all expressions. Please see below for a list of the current functions that are passed over automatically. Please submit a pull request if you would like to add more generic math functions that I have left out.

const math_syms = [
    :*, :/, :^, :+, :-, :%,
    :.*, :./, :.^, :.+, :.-, :.%,
    :<, :>, Symbol(==), :<=, :>=,
    :.<, :.>, :.==, :.<=, :.>=,
    :sqrt, :sin, :cos, :tan]

Note: If the function has other function calls within it’s body that are not available in Main, then the macro will error.

  • The problem is if I escape the macro to the calling scope and run the expressions with functions that were within the called function, it won’t have the function namespace. If I escape it within the function being called, it won’t have access to the @handcalc macro that does the latexifying. I am not sure of a way around this, but I think it isn’t too much of an inconvienience.

Handcalcs can now receive fields of structs

struct MyRec
       b
       h
end

rec = MyRec(5, 15)

@handcalcs Ix = calc_Ix(rec.b, rec.h)

image

Now you can take advantage of multiple dispatch more effectively!

Future Plans

  • A way to break down a \LaTeX equation that is too long to multiple lines
  • Maybe a symbolic mode that would essentially be like @latexdefine but you get function unrolling and multiline support.
  • A way to disable @handcalcs macro if you are wanting to run script for multiple iterations and not worried about rendering all iterations. That way you get speed for those situations.
  • I have also thought about adding a setting that you could change if you were within the REPL and instead of latex (since it is not very readable) to instead output a simple string instead. For example: I_x = b*h^3/12 = 5*15^3/12 = 1406.25.

Provide Feedback

I do plan to hopefully use this as a professional tool and want to provide a way for more documented calculation packages. I hope to get this to 1.0.0 and I welcome any ideas or reports of any bugs that you may find. Feel free to contribute as well!

14 Likes