JMSLab / Template

Template for research repository using scons.
9 stars 1 forks source link

Naming issue with Python Autofill function #50

Closed jinfu93 closed 2 years ago

jinfu93 commented 2 years ago

In IntQuantEc, we added a simple Autofill function in Python under source/lib/JMSLab/general.py to facilitate generating autofill macros from data and calculations:

def Autofill(commandname, content, mode = "text"):
    if mode == "text":
        newcommand = "\\newcommand{{\\{}}}{{\\textnormal{{{}}}}}\n".format(commandname, content)
    else:
        newcommand = "\\newcommand{{\\{}}}{{{}}}\n".format(commandname, content)
    return newcommand

However, float objects from calculations do not have unique names, only pointers, thus feeding them into Autofill() becomes a little awkward where an object name is redeclared:

MarginalCost = (1 + 1 / Epsilon) * df.loc[1994, 'P']

file.write(Autofill("MarginalCost", MarginalCost, mode = "math"))
jinfu93 commented 2 years ago

@veli-m-andirin I think you mentioned you were working with a Python autofill elsewhere, would love to hear your quick thoughts on this. Thanks a lot!

jinfu93 commented 2 years ago

@jmshapir are we considering adding this to Template upon resolution of this issue? If so, perhaps I can work on this on a branch if you don't mind elevating my access for the repo. Thanks!

jmshapir commented 2 years ago

@jinfu93 thanks! Your permissions are elevated. And yep, I think we should at least consider adding something to Template. Will ask about it now...

jmshapir commented 2 years ago

@veli-m-andirin @santiagohermo do you think it's worth adding an autofill.py module to the JMSLab library, maybe along the lines in https://github.com/JMSLab/Template/issues/50#issue-1166605375?

And if so does either of you have thoughts on the problem posed in https://github.com/JMSLab/Template/issues/50#issue-1166605375?

Thanks!

santiagohermo commented 2 years ago

Cool idea @jmshapir @jinfu93! I think it would be useful. In Skills we usually have to define a function that does something like this every time we want to autofill values. Having a pre-written program would be helpful to prevent this repetition.

If I understand correctly, the proposed function would simply take a value and a name and return the string to write in the tex file. I think that for this problem it might be better to have the function take a dictionary as input and create the entire file. Then, you would use the function as follows:


    GenerateAutofillValues({'Elasticity':Elasticity,
                            'MarginalCost':(1 + 1 / Epsilon) * df.loc[1994, 'P']}
                           outfile = os.path.join(outstub, "autofill_values.tex")
mcaceresb commented 2 years ago

@jinfu93 Is the issue that you have to pass the name twice to the Autofill function?

veli-m-andirin commented 2 years ago

Thanks all!

@jmshapir, I also think it is useful to have autofill functions. As evident from the content of the function @jinfu93 posted, the type of stuff one needs to enter each time in the lack of such functions is slightly annoying, so reducing the amount we need to repeat them sounds nice to me. If you'd like, we can bring in STATA and R autofill functions we wrote in Protests, too.

@jinfu93, the problem in the function you posted is not immediately clear to me. If useful, here is the function I used in my project, alongside an example:

def autofill_number(outfile, command, number, digitnumber):
    number_formatted = round(number, digitnumber)
    output_string = r'\newcommand{\%s}{\textnormal{%s}}' % (command, number_formatted)
    autofill_file = open(outfile, 'w')
    autofill_file.write(output_string)
    autofill_file.close()

autofill_number("output/autofill/alphaLb.tex", "alphaLb", alpha_lb, 3)
jmshapir commented 2 years ago

@jinfu93 Is the issue that you have to pass the name twice to the Autofill function?

@mcaceresb exactly! I'd ideally like to set up the function so that the default name of the TeX macro is the name of the pointer to the scalar mathematical object. Do you know how to do that?

mcaceresb commented 2 years ago

@jmshapir @jinfu93 The easiest solution would be to pass the current environment's locals explicitly. If you don't want to do that you can always try to find them using inspect. It's a bit clunky (mainly to a account for @santiagohermo's idea of passing multiple variables). If you want to put the loop inside Aufofill then there'd be no need for the recursion, but then the input would generally have to be a list.

import inspect

def Autofill(var, format = "{}", namespace = None):
    newcommand = f"\\newcommand{{{{\\{{}}}}}}{{{{{format}}}}}\n"

    # Look for var in parent frames
    if namespace is None:
        parent = inspect.currentframe().f_back
        while var not in parent.f_locals.keys() and parent.f_back:
            parent = parent.f_back

        if var not in parent.f_locals.keys() or parent is None:
            raise Warning(f"Autofill: Variable '{var}' not found")

        namespace = parent.f_locals

    commandname, content = var, namespace[var]
    return newcommand.format(commandname, content)

var1 = 1729
var2 = 3.1415
var3 = var1 * var2 ** 2 / 2.718
var4 = "string"
var5 = "other string"
print(Autofill("var1"))
print(''.join(Autofill(var) for var in ["var1", "var2", "var3"]))
print(''.join(Autofill(var, "{:.1f}") for var in ["var1", "var2", "var3"]))
print(Autofill("var4", "\\textnormal{{{}}}"))
print(''.join(Autofill(s, "\\textnormal{{{}}}") for s in ["var4", "var5"]))
jinfu93 commented 2 years ago

Thanks for pitching in @santiagohermo @veli-m-andirin and thanks for the awesome tip with inspect @mcaceresb! All your inputs have been extremely helpful, in fbb091d I integrated the ideas here into @mcaceresb's primary solution.

Per @veli-m-andirin's point:

If you'd like, we can bring in STATA and R autofill functions we wrote in Protests, too.

@jmshapir do we want to incorporate that in this issue as well? Otherwise, perhaps I can go into a quick review? Many thanks!

jmshapir commented 2 years ago

Per @veli-m-andirin's point:

If you'd like, we can bring in STATA and R autofill functions we wrote in Protests, too.

@jmshapir do we want to incorporate that in this issue as well? Otherwise, perhaps I can go into a quick review? Many thanks!

If we have well-functioning STATA and R tools for autofill I'd be happy to incorporate those into gslab-stata and gslab_r, respectively. @veli-m-andirin do you want to open issues for those when you have bandwidth?

Meantime @jinfu93 I think we can limit our scope here to the python module. Thanks!

jinfu93 commented 2 years ago

This thread continues in its PR #51.

jinfu93 commented 2 years ago

Summary:

In this issue we added an autofill function and its wrapper that does not require explicitly passing environment locals. We also initiated work on unit testing for the function.

Final state of issue branch can be seen here with issue folder here.

Changes were merged into main in 92ffd01.