Using python to process a LaTeX file and create random questions - python

I am using python to pre-process a LaTeX file before running it through the LaTeX compiler. I have a python script which reads a .def file. The .def file contains some python code at the top which helps to initialize problems with randomization. Below the python code I have LaTeX code for the problem. For each variable in the LaTeX code, I use the symbol # to signify that it should be randomized and replaced before compiling. For example, I may write #a to be a random integer between 1 and 10.
There may be larger issues with what I'm trying to do, but so far it's working mostly as I need it to. Here is a sample .def file:
a = random.choice(range(-3,2))
b = random.choice(range(-6,1))
x1 = random.choice(range(a-3,a))
x2 = x1+3
m = 2*x1 - 2*a + 3
y1 = (x1-a)**2+b
y2 = (x2-a)**2+b
xmin = a - 5
xmax = a + 5
ymin = b-1
ymax = b+10
varNames = [["#a", str(a)],["#b", str(b)], ["#x1",str(x1)], ["#x2",str(x2)], ["#m", str(m)], ["#y1",str(y1)], ["#y2",str(y2)], ["#xmin", str(xmin)], ["#xmax", str(xmax)], ["#ymin", str(ymin)], ["#ymax", str(ymax)]]
#####
\question The graph of $f(x) = (x-#a)^2+#b$ is shown below along with a secant line between the points $(#x1,#y1)$ and $(#x2,#y2)$.
\begin{center}
\begin{wc_graph}[xmin=#xmin, xmax=#xmax, ymin=#ymin, ymax=#ymax, scale=.75]
\draw[domain=#a-3.16:#a + 3.16, smooth] plot ({\x}, {(\x-#a)^2+#b});
\draw (#x1,#y1) to (#x2,#y2);
\pic at (#x1,#y1) {closed};
\pic at (#x2,#y2) {closed};
\end{wc_graph}
\end{center}
\begin{parts}
\part What does the slope of the secant line represent?
\vfill
\part Compute the slope of the secant line.
\vfill
\end{parts}
\newpage
As you can see, removing the #a and replacing it with the actual value of a is starting to become tedious. In my python script, I just replace all of the #ed things in my latexCode string.
for x in varNames:
latexCode = latexCode.replace(x[0],x[1])
which seems to work okay. However, it seems obnoxious and ugly.
My Question: Is there a better way of working between the variable identifier and the string version of the identifier? It would be great if I could simply make a python list of variable names I'm using in the .def file, and then run a simple command to update the LaTeX code. What I've done is cumbersome and painful. Thanks in advance!

Yes either eval (name) or getattr (obj, name) or globals () [name]. In this case I'd say globals [name].
Also vars would work:
https://docs.python.org/3/library/functions.html#vars
In the following fragment it's e.g used to make objects know their own name:
def _setPublicElementNames (self):
for var in vars (self):
if not var.startswith ('_'):
getattr (self, var) ._name = var
Yet another, unnecessary complicated, solution would be to generate a .py file with the right replace statements from your .def file.

Related

Modify or add functions to an existing package

I want to modify functions from a package in Python. To be precise, I'll take the example of the fastkde package. I have two questions.
Looking at the source code of the function pdf_at_points, the object returned is pdf. Instead, I'd like to return the _pdfobj object. How can I do so without modifying the source code ? Ideally, I'd like to do it in a random script, so that my code is "transferable".
The last two lines of the pdf_at_points function call the applyBernacchiaFilter and the __transformphiSC_points__ functions, that cannot be called in a script. How can I make them accessible in a random script ?
Here's an idea of the ideal output:
from fastkde import fastKDE
import numpy as np
N = 2e5
var1 = 50*np.random.normal(size=N) + 0.1
var2 = 0.01*np.random.normal(size=N) - 300
var3 = 50*np.random.normal(size=N) + 0.1
var4 = 0.01*np.random.normal(size=N) - 300
test_points = list(zip(var3, var4))
# Some lines of code here that I'm looking for,
# that would create the pdf_at_points_modified function
# and that would make applyBernacchiaFilter and __transformphiSC_points__
# functions usable
myPDF = fastKDE.pdf_at_points_modified(var1,var2)
myPDF.applyBernacchiaFilterModified()
pred = myPDF.__transformphiSC_points__(test_points)
pdf_at_points_modified corresponds to the modified pdf_at_points function.
Thanks in advance.

I want to define (create) and use different variable (using suffix) through loop (specially for loop)

I want to create multiple variable through for loop to further use and compare in the program.
Here is the code -
for i in range(0,len(Header_list)):
(f'len_{i} = {len(Header_list[i]) + 2 }')
print(len_0);print(f'len{i}')
for company in Donar_list:
print(company[i],f"len_{i}")
if len(str(company[i])) > len((f"len_{i}")) :
(f'len_{i}') = len(str(company[i]))
print(f"len_{i}")
But what is happening, though I managed to create variable len_0,len_1,len_2... in line-2, in line - 3 I also can print len_0..etc variable by only using print(len_0), but I can't print it the values of these by - print(f'len_{i}')
In line 5 I also can't compare with the cofition with my intension. I want it do create variable and compare it further when as it necessary, in this case under for loop.What should I do now? I am a beginner and I can do it using if statement but that wouldn't be efficient, also my intention is not to create any **data structure ** for this.
I don't know whether I could manage to deliver you what I am trying to say. Whatever I just wanna create different variable using suffix and also comprare them through for loop in THIS scenario.
Instead of dynamically creating variables, I would HIGHLY recommend checking out dictionaries.
Dictionaries allow you to store variables with an associated key, as so:
variable_dict = dict()
for i in range(0,len(Header_list)):
variable_dict[f'len_{i}'] = {len(Header_list[i]) + 2 }
print(len_0)
print(f'len{i}')
for company in Donar_list:
print(company[i],f"len_{i}")
if len(str(company[i])) > len(variable_dict[f"len_{i}"]) :
variable_dict[f'len_{i}'] = len(str(company[i]))
print(f"len_{i}")
This allows you to access the values using the same key:
len_of_4 = variable_dict['len_4']
If you REALLY REALLY need to dynamically create variables, you could use the exec function to run strings as python code. It's important to note that the exec function is not safe in python, and could be used to run any potentially malicious code:
for i in range(0,len(Header_list)):
exec(f'len_{i} = {len(Header_list[i]) + 2 }')
print(len_0);print(f'len{i}')
for company in Donar_list:
print(company[i],f"len_{i}")
if exec(f"len(str(company[i])) > len(len_{i})"):
exec(f'len_{i} = len(str(company[i]))')
print(f"len_{i}")
In python everything is object so use current module as object and use it like this
import sys
module = sys.modules[__name__]
Header_list=[0,1,2,3,4]
len_ = len(Header_list)
for i in range(len_):
setattr(module, f"len_{i}", Header_list[i]+2)
print(len_0)
print(len_1)
Header_list=[0,1,2,3,4]
for i in range(0,5):
exec(f'len_{i} = {Header_list[i] + 2 }')
print(f'len{i}')
output:
len0
len1
len2
len3
len4

Using exec() to make and assign variables from a text file

This question is a follow-up to the question I asked here, which in summary was:
"In python how do I read in parameters from the text file params.txt, creating the variables and assigning them the values that are in the file? The contents of the file are (please ignore the auto syntax highlighting, params.txt is actually a plain text file):
Lx = 512 Ly = 512
g = 400
================ Dissipation =====================
nupower = 8 nu = 0
...[etc]
and I want my python script to read the file so that I have Lx, Ly, g, nupower, nu etc available as variables (not keys in a dictionary) with the appropriate values given in params.txt. By the way I'm a python novice."
With help, I have come up with the following solution that uses exec():
with open('params.txt', 'r') as infile:
for line in infile:
splitline = line.strip().split(' ')
for i, word in enumerate(splitline):
if word == '=':
exec(splitline[i-1] + splitline[i] + splitline[i+1])
This works, e.g. print(Lx) returns 512 as expected.
My questions are:
(1) Is this approach safe? Most questions mentioning the exec() function have answers that contain dire warnings about its use, and imply that you shouldn't use it unless you really know what you're doing. As mentioned, I'm a novice so I really don't know what I'm doing, so I want to check that I won't be making problems for myself with this solution. The rest of the script does some basic analysis and plotting using the variables read in from this file, and data from other files.
(2) If I want to wrap up the code above in a function, e.g. read_params(), is it just a matter of changing the last line to exec(splitline[i-1] + splitline[i] + splitline[i+1], globals())? I understand that this causes exec() to make the assignments in the global namespace. What I don't understand is whether this is safe, and if not why not. (See above about being a novice!)
(1) Is this approach safe?
No, it is not safe. If someone can edit/control/replace params.txt, they can craft it in such a way to allow arbitrary code execution on the machine running the script.
It really depends where and who will run your Python script, and whether they can modify params.txt. If it's just a script run directly on a normal computer by a user, then there's not much to worry about, because they already have access to the machine and can do whatever malicious things they want, without having to do it using your Python script.
(2) If I want to wrap up the code above in a function, e.g. read_params(), is it just a matter of changing the last line to exec(splitline[i-1] + splitline[i] + splitline[i+1], globals())?
Correct. It doesn't change the fact you can execute arbitrary code.
Suppose this is params.txt:
Lx = 512 Ly = 512
g = 400
_ = print("""Holy\u0020calamity,\u0020scream\u0020insanity\nAll\u0020you\u0020ever\u0020gonna\u0020be's\nAnother\u0020great\u0020fan\u0020of\u0020me,\u0020break\n""")
_ = exec(f"import\u0020ctypes")
_ = ctypes.windll.user32.MessageBoxW(None,"Releasing\u0020your\u0020uranium\u0020hexaflouride\u0020in\u00203...\u00202...\u00201...","Warning!",0)
================ Dissipation =====================
nupower = 8 nu = 0
And this is your script:
def read_params():
with open('params.txt', 'r') as infile:
for line in infile:
splitline = line.strip().split(' ')
for i, word in enumerate(splitline):
if word == '=':
exec(splitline[i-1] + splitline[i] + splitline[i+1], globals())
read_params()
As you can see, it has correctly assigned your variables, but it has also called print, imported the ctypes library, and has then presented you with a dialog box letting you know that your little backyard enrichment facility has been thwarted.
As martineau suggested, you can use configparser. You'd have to modify params.txt so there is only one variable per line.
tl;dr: Using exec is unsafe, and not best practice, but that doesn't matter if your Python script will only be run on a normal computer by users you trust. They can already do malicious things, simply by having access to the computer as a normal user.
Is there an alternative to configparser?
I'm not sure. With your use-case, I don't think you have much to worry about. Just roll your own.
This is similar to some of the answers in your other question, but is uses literal_eval and updates the globals dictionary so you can directly use the variables as you want to.
params.txt:
Lx = 512 Ly = 512
g = 400
================ Dissipation =====================
nupower = 8 nu = 0
alphapower = -0 alpha = 0
================ Timestepping =========================
SOMEFLAG = 1
SOMEOTHERFLAG = 4
dt = 2e-05
some_dict = {"key":[1,2,3]}
print = "builtins_can't_be_rebound"
Script:
import ast
def read_params():
'''Reads the params file and updates the globals dict.'''
_globals = globals()
reserved = dir(_globals['__builtins__'])
with open('params.txt', 'r') as infile:
for line in infile:
tokens = line.strip().split(' ')
zipped_tokens = zip(tokens, tokens[1:], tokens[2:])
for prev_token, curr_token, next_token in zipped_tokens:
if curr_token == '=' and prev_token not in reserved:
#print(prev_token, curr_token, next_token)
try:
_globals[prev_token] = ast.literal_eval(next_token)
except (SyntaxError, ValueError) as e:
print(f'Cannot eval "{next_token}". {e}. Continuing...')
read_params()
# We can now use the variables as expected
Lx += Ly
print(Lx, Ly, SOMEFLAG, some_dict)
Output:
1024 512 1 {'key': [1, 2, 3]}

Reading / modifying fortran file

I have a big fortran (f90) file that is used to generate an input file for numerical simulations. I would like to create a GUI to set the parameters of the simulations written within the f90 file.
Right now I am trying to create a python script that can find, store and modify (if necessary) these variables (there are a lot of them).
I am trying to access values that are stored this way (this is a shortenned version) :
! ib : ply orientation
! ply at 0 : ib = 1
! ply at 90 : ib = 2
! ply at 45 : ib = 3
! ply at -45 : ib = 4
a = 7500.
b = 8.
e = 579.
!--------------Centre
x0 = 0.
y0 = 0.
!--------------Radius
Rfoc = 1600.
!--------------End of geometric parameters
So far, the code looks like this :
import re
geomVar = ["a =","b =","e =","Rfoc ="]
endGeom = "End of geometric parameters"
def goFetchGeom(fileNameVariable, valueInput):
valueOutput = []
for line in fileNameVariable.readlines():
# Looking for svalue numbers associated with search value
if any(string in line for string in valueInput):
result = [float(s) for s in re.findall(r'\b\d+\b', line)]
valueOutput.append(result[0])
# End of search loop to avoid reading the whole file
elif endGeom in line :
fileNameVariable.seek(0) #rewind the file
return valueOutput
with open("mesh_Comp_UD.f90", "r+") as f:
geomValue = goFetchGeom(f,geomVar)
print geomValue
First problem: it finds lines that have "ib = ..." within them because the variable "b =" stored in geomVar.
Second problem: if a line has multiple variables, I only get the first one, not all of them.
Because they are numerous, I would like to pass the variables i'm looking for, in the form of an array, directly in the function. Moreover, since the file is very long, I do not want to start a search from scratch for every variable.

SymPy/SciPy: solving a system of ordinary differential equations with different variables

I am new to SymPy and Python in general, and I am currently working with Python 2.7 and SymPy 0.7.5 with the objective to:
a) read a system of differential equations from a text file
b) solve the system
I already read this question and this other question, and they are almost what I am looking for, but I have an additional issue: I do not know in advance the form of the system of equations, so I cannot create the corresponding function using def inside the script, as in this example. The whole thing has to be managed at run-time.
So, here are some snippets of my code. Suppose I have a text file system.txt containing the following:
dx/dt = 0.0387*x - 0.0005*x*y
dy/dt = 0.0036*x*y - 0.1898*y
What I do is:
# imports
import sympy
import scipy
import re as regex
# define all symbols I am going to use
x = sympy.Symbol('x')
y = sympy.Symbol('y')
t = sympy.Symbol('t')
# read the file
systemOfEquations = []
with open("system.txt", "r") as fp :
for line in fp :
pattern = regex.compile(r'.+?\s+=\s+(.+?)$')
expressionString = regex.search(pattern, line) # first match ends in group(1)
systemOfEquations.append( sympy.sympify( expressionString.group(1) ) )
At this point, I am stuck with the two symbolic expressions inside the systemOfEquation list. Provided that I can read the initial conditions for the ODE system from another file, in order to use scipy.integrate.odeint, I would have to convert the system into a Python-readable function, something like:
def dX_dt(X, t=0):
return array([ 0.0387*X[0] - 0.0005*X[0]*X[1] ,
-0.1898*X[1] + 0.0036*X[0]*X[1] ])
Is there a nice way to create this at run-time? For example, write the function to another file and then import the newly created file as a function? (maybe I am being stupid here, but remember that I am relatively new to Python :-D)
I've seen that with sympy.utilities.lambdify.lambdify it's possible to convert a symbolic expression into a lambda function, but I wonder if this can help me...lambdify seems to work with one expression at the time, not with systems.
Thank you in advance for any advice :-)
EDIT:
With minimal modifications, Warren's answer worked flawlessly. I have a list of all symbols inside listOfSymbols; moreover, they appear in the same order as the columns of data X that will be used by odeint. So, the function I used is
def dX_dt(X, t):
vals = dict()
for index, s in enumerate(listOfSymbols) :
if s != time :
vals[s] = X[index]
vals[time] = t
return [eq.evalf(subs=vals) for eq in systemOfEquations]
I just make an exception for the variable 'time' in my specific problem. Thanks again! :-)
If you are going to solve the system in the same script that reads the file (so systemOfEquations is available as a global variable), and if the only variables used in systemOfEquations are x, y and possibly t, you could define dX_dt in the same file like this:
def dX_dt(X, t):
vals = dict(x=X[0], y=X[1], t=t)
return [eq.evalf(subs=vals) for eq in systemOfEquations]
dX_dt can be used in odeint. In the following ipython session, I have already run the script that creates systemOfEquations and defines dX_dt:
In [31]: odeint(dX_dt, [1,2], np.linspace(0, 1, 5))
Out[31]:
array([[ 1. , 2. ],
[ 1.00947534, 1.90904183],
[ 1.01905178, 1.82223595],
[ 1.02872997, 1.73939226],
[ 1.03851059, 1.66032942]]

Categories