connorferster / handcalcs

Python library for converting Python calculations into rendered latex.
Apache License 2.0
5.59k stars 427 forks source link

Suggestion and question: best practice for using handcalcs to verify equations written in imported python modules #173

Open sebanalysis opened 1 year ago

sebanalysis commented 1 year ago

For numerical computing, handcalcs is a great way to render mathematical functions so that you can verify you got everything correct and follow through the logic. Such functions need to be in a python module and hence use decorator @handcalc(). This is so that you can build automated tests and import them wherever else they are required.

You can then call the function in the ipynb cell to check it. The issue is that you often want to change the default parameters from within the ipynb, not the original module. I assumed you could use the %%render to turn @handcalc() into @handcalcs.decorator.handcalc(jupyter_display=True). Or likewise %%render params to add the overwrite="params" argument. But you just get an error.

I don't use the decorator in the module because:

  1. jupyter_display=True is too heavy and slow to leave in the decorator when you are calling the same function many times. I know you can lambify, but is an annoying additional step that you have to perform when developing and testing that slows you down.
  2. If you want to turn it off, it changes the return value into a tuple.
  3. It changes the metadata of the function which makes it more complicated to extracted varnames or default parameters

Current method

My workaround is to leave the decorator completely out of the module function and apply a decorator factory every time I call it in the ipynb. I.e.

# .py module, undecorated function
def test():
    a =1
    return a

# .ipynb notebook
# factory
def hc(func):
    return handcalc(jupyter_display=True, overwrite='params')(func)

# rendered equation call
hc(test)()

Desired behavior

# .py module, decorated function
@render
def test():
    a =1
    return a
# .ipynb notebook

test.render(overwrite='params')()

I think it is better because you mark the original function out in the module. Which means that people are more likely to maintain it according the handcalc's requirements. But also, it is clearer what you are doing in the ipynb end.

Perhaps we could create a @render which adds a hook to the function:

Anyway, I expect the answer is a no, as this might be specific to my workflow, but I am generally interested to hear whether people think what I am doing is good practice or not.