SymPy: parse string as function on manifold - python

I'm trying to define an equation on a manifold using SymPy, and SymPy's diffgeom package. Since this equation is input by a user, it's sent into the program as a string and is therefore defined prior to the manifold definition in the code. To perform meaningful calculations, I'm trying to replace the "sympified" symbols with symbols defined on the manifold.
Here's a function, supplied by the user.
H = sympify('m*a - f')
Coordinates for the manifold are also supplied by the user as strings.
# Variables defined as symbols (non-diffgeom)
a = Symbol('a')
f = Symbol('f')
And everything afterwards is automated.
from sympy.diffgeom import Manifold, Patch, CoordSystem
from sympy import sympify, Symbol
# Standard manifold definitions
M = Manifold(name='Temp', dim=2)
P = Patch('P', M)
R = CoordSystem('R', P, ['a','f'])
coords = R.coord_functions()
Dx = R.base_vectors()
print(H.diff(a)) # Returns 'm' as expected
print(Differential(H)(Dx[0])) # Returns '0' as expected
The first substitution works great. I can take derivatives as expected using Differential().
H = H.subs(a,coords[0])
print(H.diff(a)) # Returns '0' as expected
print(Differential(H)(Dx[0])) # Returns 'm' as expected
print(Differential(H)(Dx[1])) # Returns '0' as expected
The second substitution is where things get strange. The ().diff() command works fine and returns 0, since the new coordinates are defined on the manifold and not as standard symbols, but I can no longer take derivatives using Differential().
H = H.subs(f,coords[1])
print(H.diff(f)) # Returns '0' as expected
print(Differential(H)(Dx[0])) # Crashes code
print(Differential(H)(Dx[1])) # Also crashes code
It appears as if diffgeom is trying to perform a transformation to calculate the derivatives, but there shouldn't really be any transforming going on since this is all in the same coordinate system. Am I fundamentally missing something here? Or is there an easier method to parse strings as expressions on manifolds?
Here's the full error thrown. I really can't make much out of it other than SymPy is attempting to transform coordinates when I wouldn't expect it.
Traceback (most recent call last):
File "/Users/msparapa/Documents/Python/gprops/examples/test.py", line 37, in <module>
print(Differential(H)(Dx[0])) # Crashes code
File "/Users/msparapa/anaconda/lib/python3.5/site-packages/sympy/diffgeom/diffgeom.py", line 765, in __call__
return vector_fields[0].rcall(self._form_field)
File "/Users/msparapa/anaconda/lib/python3.5/site-packages/sympy/core/basic.py", line 538, in rcall
return Basic._recursive_call(self, args)
File "/Users/msparapa/anaconda/lib/python3.5/site-packages/sympy/core/basic.py", line 552, in _recursive_call
return expr_to_call(*on_args)
File "/Users/msparapa/anaconda/lib/python3.5/site-packages/sympy/diffgeom/diffgeom.py", line 592, in __call__
jac = self._coord_sys.jacobian(b._coord_sys, coords)
File "/Users/msparapa/anaconda/lib/python3.5/site-packages/sympy/diffgeom/diffgeom.py", line 277, in jacobian
to_sys, self._dummies).jacobian(self._dummies)
File "/Users/msparapa/anaconda/lib/python3.5/site-packages/sympy/diffgeom/diffgeom.py", line 270, in coord_tuple_transform_to
transf = self.transforms[to_sys]
KeyError: CoordSystem(R, Patch(P, Manifold(Temp, 2)), (a, f))

After further investigation, the cause was having Symbols of the same name. The fix is to essentially substitute using methods described here, or to use different naming conventions altogether.
To perform the substitution, I needed to swap all of the variables at once. This was done with the following block of code.
set = dict(zip([a,f],coords))
H = H.subs(set, simultaneous=True)
Where "a" and "f" are basic symbols, and "coords" is the list of symbols on the manifold. Substitutions not performed simultaneously, but rather sequentially, such as
set = dict(zip([a,f],coords))
H = H.subs(set)
throw the same error. I believe this is due to the fact that both "a" and "f" are buried within each manifold coordinate. To see where this exactly comes up, we can look at the Repr output of coords[0].
BaseScalarField(CoordSystem(Symbol('R'), Patch(Symbol('P'), Manifold(Symbol('M'), Integer(2))), Tuple(Symbol('f'), Symbol('a'))), Integer(0))
Both Symbol('f') and Symbol('a') show up under the coordinate system. What happens here is that when I substituted the second variable, f, in my expression, SymPy was looking at my manifold variable, a, and seeing a variable of the same name. Thus it attempted to not only substitute the basic symbol, f, but also the symbol f buried within my CoordSystem definition, probably then prompting a coordinate system transformation.

Related

Integrating a function of two variables over an array of values

I'm currently trying to solve this integral using SciPy:
I was first advised to use interpolation, which I tried but cannot figure out for some reason, but would probably be a good approach. I found this post about using np.vectorize and I think it might still work, but I am getting an error. Here is the code that I have written thus far (also note that n and n,eq are not indices, they're just variable names):
import numpy as np
from scipy import integrate
def K(x): #This is a function in the integral.
b = 0.252
return b*(((4/(x**3))+(3/(x**2))+1/x) + (4/(x**3) + 1/(x**2))*np.exp(-x))
def Xntot_integrand(x,z): #Defining the integrand
Xneq_x = (1+np.exp(x))**(-1) #This is the term outside the integral and squared within it.
return Xneq_x(x)**2 * np.exp(K(z) - K(x)) * np.exp(x)
Xntot_integrand = np.vectorize(Xntot_integrand)
def Xntot_integrated(x,z):
return quad(Xntot_integrand, 0, z)
Xntot_integrated=np.vectorize(Xntot_integrated)
T_narrow = np.linspace(1,0.01,100) #Narrow T range from 1 to 0.01 MeV
z_narrow = Q/T_narrow
final_integrated_Xneq = Xntot_integrated(z_narrow)
I am getting the error that I am missing a positional argument when I call Xntot_integrated (which makes sense, I think it is still in the two variables x and z).
So I suppose the issue is stemming from where I use quad() because after it is integrated, x should go away. Any advice? Should I use tabulation/interpolation instead?
You need to be using the args keyword argument of integrate.quad to pass additional inputs to the function, so it would look like this:
def Xntot_integrated(z):
return integrate.quad(Xntot_integrand, 0, z, args=(z,))
Note here x is not an input to the integrated function, only z, the first input to the integrand is the integration variable and any extra information is passed via args=(z,) tuple.
alternatively you can define a wrapper that knows z from context and only takes the integration variable as input:
def Xntot_integrated(z):
def integrand(x):return Xntot_integrand(x,z)
return integrate.quad(integrand, 0, z)
but most API's that take a function typically have a keyword argument to specify those inputs. (threading.Thread comes to mind.)
also your Xneq_x should probably be a function itself since you accidentally use it as such inside your integrand (it is just a value there right now) and you will need to use it outside the integration anyway :)

perform derivative inside a loop

Hi I wrote the following code,
Z=1 #particle position
dz=0.1
time=1
dt=0.1
v=0
Fz=0
for time in np.arange(1, 10, dt):
#####simulation loop#######
theta=np.arccos(-Z/R) #contact angle
theta_e=((math.pi*110)/180) #equilibrium contact angle
Z_e=-R*np.cos(theta_e)#equilibrium position of particle
C=3.14*gamma*(R-Z_e) #additive constant
Fsz= (gamma*math.pi*(Z-Z_e)**2)+(tau*2*math.pi*math.sqrt(R**2-Z**2))+C
Fz=Fsz+(0.5*deltaF*np.sin((2*math.pi/lamda)*(Z-Z_e)-phi))#surface force
w_a=gamma*lamda_m**2*(1-np.cos(theta_e)) #work of adhesion
epsilon_z=2*math.pi*R*np.sin(theta)*mu*(nu/(lamda_m**3))*np.exp(w_a/KbT)#transitional drag
epsilon_s=khi*mu*((4*math.pi**2*R**2)/math.sqrt(Ad))*(1-(Z/R)**2)
epsilon=epsilon_z+epsilon_s
Ft=math.sqrt(2*KbT*epsilon)*series #thermal force
v=(-np.diff(Fz,Z)+Ft)/epsilon ##new velocity
Z=v*dt #new position
I was trying to calculate dFz/dzbut it is gave me following error,
File "C:/Users/mohammad.hossain1/Desktop/particle.py", line 62, in <module>
v=(-np.diff(Fz,Z)+Ft)/epsilon ##new velocity
File "C:\Users\mohammad.hossain1\AppData\Local\Continuum\anaconda3\lib\site-packages\numpy\lib\function_base.py", line 1924, in diff
slice1[axis] = slice(1, None)
IndexError: list assignment index out of range
As my initial condition is Fz=0 Z=0and it changes with time I suppose to get dFz/dz. I have imported all necessary module and all variables are defined properly at the beginning of the code.But I got error while I have introduced the derivative. So most likely my approach is not going with argument. Is it possible to show me the mistake that I have done during my coding.
It's hard too say for sure even with the stack trace. Obviously numpy is trying to access a list called slice1 with an axis index that the list does not have. Not sure why this is the case, but it must be something with this line:
v=(-np.diff(Fz,Z)+Ft)/epsilon
I suspect specifically the part of this line that is causing this issue is the np.diff(), since it is np code throwing this error. My best guess is that Fz and Z are equal in this case, or otherwise unacceptable values for the .diff method. Try adding the following if like so:
if Fz != Z:
v=(-np.diff(Fz,Z)+Ft)/epsilon
If that doesn't stop the crash try printing the values of Fz and Z right before this line and seeing if they look weird/suspicious.

How to read a system of differential equations from a text file to solve the system with scipy.odeint?

I have a large (>2000 equations) system of ODE's that I want to solve with python scipy's odeint.
I have three problems that I want to solve (maybe I will have to ask 3 different questions?).
For simplicity, I will explain them here with a toy model, but please keep in mind that my system is large.
Suppose I have the following system of ODE's:
dS/dt = -beta*S
dI/dt = beta*S - gamma*I
dR/dt = gamma*I
with beta = cpI
where c, p and gamma are parameters that I want to pass to odeint.
odeint is expecting a file like this:
def myODEs(y, t, params):
c,p, gamma = params
beta = c*p
S = y[0]
I = y[1]
R = y[2]
dydt = [-beta*S*I,
beta*S*I - gamma*I,
- gamma*I]
return dydt
that then can be passed to odeint like this:
myoutput = odeint(myODEs, [1000, 1, 0], np.linspace(0, 100, 50), args = ([c,p,gamma], ))
I generated a text file in Mathematica, say myOdes.txt, where each line of the file corresponds to the RHS of my system of ODE's, so it looks like this
#myODEs.txt
-beta*S*I
beta*S*I - gamma*I
- gamma*I
My text file looks similar to what odeint is expecting, but I am not quite there yet.
I have three main problems:
How can I pass my text file so that odeint understands that this is the RHS of my system?
How can I define my variables in a smart way, that is, in a systematic way? Since there are >2000 of them, I cannot manually define them. Ideally I would define them in a separate file and read that as well.
How can I pass the parameters (there are a lot of them) as a text file too?
I read this question that is close to my problems 1 and 2 and tried to copy it (I directly put values for the parameters so that I didn't have to worry about my point 3 above):
systemOfEquations = []
with open("myODEs.txt", "r") as fp :
for line in fp :
systemOfEquations.append(line)
def dX_dt(X, t):
vals = dict(S=X[0], I=X[1], R=X[2], t=t)
return [eq for eq in systemOfEquations]
out = odeint(dX_dt, [1000,1,0], np.linspace(0, 1, 5))
but I got the error:
odepack.error: Result from function call is not a proper array of floats.
ValueError: could not convert string to float: -((12*0.01/1000)*I*S),
Edit: I modified my code to:
systemOfEquations = []
with open("SIREquationsMathematica2.txt", "r") as fp :
for line in fp :
pattern = regex.compile(r'.+?\s+=\s+(.+?)$')
expressionString = regex.search(pattern, line)
systemOfEquations.append( sympy.sympify( expressionString) )
def dX_dt(X, t):
vals = dict(S=X[0], I=X[1], R=X[2], t=t)
return [eq for eq in systemOfEquations]
out = odeint(dX_dt, [1000,1,0], np.linspace(0, 100, 50), )
and this works (I don't quite get what the first two lines of the for loop are doing). However, I would like to do the process of defining the variables more automatic, and I still don't know how to use this solution and pass parameters in a text file. Along the same lines, how can I define parameters (that will depend on the variables) inside the dX_dt function?
Thanks in advance!
This isn't a full answer, but rather some observations/questions, but they are too long for comments.
dX_dt is called many times by odeint with a 1d array y and tuple t. You provide t via the args parameter. y is generated by odeint and varies with each step. dX_dt should be streamlined so it runs fast.
Usually an expresion like [eq for eq in systemOfEquations] can be simplified to systemOfEquations. [eq for eq...] doesn't do anything meaningful. But there may be something about systemOfEquations that requires it.
I'd suggest you print out systemOfEquations (for this small 3 line case), both for your benefit and ours. You are using sympy to translated the strings from the file into equations. We need to see what it produces.
Note that myODEs is a function, not a file. It may be imported from a module, which of course is a file.
The point to vals = dict(S=X[0], I=X[1], R=X[2], t=t) is to produce a dictionary that the sympy expressions can work with. A more direct (and I think faster) dX_dt function would look like:
def myODEs(y, t, params):
c,p, gamma = params
beta = c*p
dydt = [-beta*y[0]*y[1],
beta*y[0]*y[1] - gamma*y[1],
- gamma*y[1]]
return dydt
I suspect that the dX_dt that runs sympy generated expressions will be a lot slower than a 'hardcoded' one like this.
I'm going add sympy tag, because, as written, that is the key to translating your text file into a function that odeint can use.
I'd be inclined to put the equation variability in the t parameters, rather a list of sympy expressions.
That is replace:
dydt = [-beta*y[0]*y[1],
beta*y[0]*y[1] - gamma*y[1],
- gamma*y[1]]
with something like
arg12=np.array([-beta, beta, 0])
arg1 = np.array([0, -gamma, -gamma])
arg0 = np.array([0,0,0])
dydt = arg12*y[0]*y[1] + arg1*y[1] + arg0*y[0]
Once this is right, then the argxx definitions can be move outside dX_dt, and passed via args. Now dX_dt is just a simple, and fast, calculation.
This whole sympy approach may work fine, but I'm afraid that in practice it will be slow. But someone with more sympy experience may have other insights.

Why is scipy.optimize.minimize trying to pass in weird arguments to my objective function?

I have a class that helps instantiate a statistical model. Some of its data members are parameters. I am trying to write a method that optimizes these parameters. The objection function is based on the negative of the likelihood function. This likelihood function is implemented itself as a class method, which uses the values of the class data members in its calculation.
I know this is bad style, but every time the objective function gets called by scipy.optimize.minimize(), it changes the objects data members to the better ones. I am less concerned with why this is bad, and more concerned with why this isn't working. Below the code is the full traceback.
It seems like it works partially. It runs for a few seconds on test data, but then it triggers my assertion. It seems that minimize() does something weird when it's nearing the end of its optimizations. Why would it try to pass in different types of arguments to the objective function obj_fun()? In my IPython interpreter I check the object's parameters afterwards, and it seems to be arriving at the expected result.
I tried looking through some of the source of scipy. It's very confusing, though. There is a lot of ambiguous variable naming and function wrapping. Can anybody give me some color on why this is happening, and how to fix it? Again I would like to keep this optimization stuff inside my class.
class MyThing(object):
.
.
.
def mle_fit(self, y, inpt, optim_these):
#step 1: figure out what you want to optimize
self.optimize_these = optim_these
#step 2: get inital flat parameter vector
self._make_list_optimizable_hyp_pars()
init_guess = self.flat_hyper_params
#step 3: run minimize
def obj_fun(pars):
# returns negative log likelihood
assert len(pars) == len(init_guess) # HERE #
self.flat_hyper_params = pars
self._unflatten_new_hps()
self.like(y, inpt)
return .5 * self.neg_2_log_like
res = minimize(obj_fun, init_guess, method = 'BFGS')
Traceback:
File "/home/taylor/anaconda/lib/python2.7/site-packages/scipy/optimize/_minimize.py", line 419, in minimize
return _minimize_bfgs(fun, x0, args, jac, callback, **options)
File "/home/taylor/anaconda/lib/python2.7/site-packages/scipy/optimize/optimize.py", line 850, in _minimize_bfgs
old_fval, old_old_fval)
File "/home/taylor/anaconda/lib/python2.7/site-packages/scipy/optimize/optimize.py", line 690, in _line_search_wolfe12
old_fval, old_old_fval)
File "/home/taylor/anaconda/lib/python2.7/site-packages/scipy/optimize/linesearch.py", line 263, in line_search_wolfe2
derphi0, c1, c2, amax)
File "/home/taylor/anaconda/lib/python2.7/site-packages/scipy/optimize/linesearch.py", line 363, in scalar_search_wolfe2
phi0, derphi0, c1, c2)
File "/home/taylor/anaconda/lib/python2.7/site-packages/scipy/optimize/linesearch.py", line 498, in _zoom
phi_aj = phi(a_j)
File "/home/taylor/anaconda/lib/python2.7/site-packages/scipy/optimize/linesearch.py", line 239, in phi
return f(xk + alpha * pk, *args)
File "/home/taylor/anaconda/lib/python2.7/site-packages/scipy/optimize/optimize.py", line 281, in function_wrapper
return function(*(wrapper_args + args))
File "MyThing.py", line 222, in obj_fun
assert len(pars) == len(init_guess)
If I remember correctly, SciPy, when optimizing, can send both scalars and arrays to the function to be minimized. Thus, if you optimize f(x), you will get, say,x = 3.14 and x = array([1, 4]). (This would be for speeding up calculations, for objective functions f that use NumPy array functions.)
If this is indeed the case, having your code handle this would solve the problem.
Now, you can easily check what the situation is with a check like print pars, type(pars), len(pars) in obj_fun().

Sampling from degree distribution of graph

I have a simple, stupid Python problem. Given a graph, I'm trying to sample from a random variable whose distribution is the same as that of the degree distribution of the graph.
This seems like it should pretty straightforward. Yet somehow I am still managing to mess this up. My code looks like this:
import numpy as np
import scipy as sp
import graph_tool.all as gt
G = gt.random_graph(500, deg_sampler=lambda: np.random.poisson(1), directed=False)
deg = gt.vertex_hist(G,"total",float_count=False)
# Extract counts and values
count = list(deg[0])
value = list(deg[1])
# Generate vector of probabilities for each node
p = [float(x)/sum(count) for x in count]
# Load into a random variable for sampling
x = sp.stats.rv_discrete(values=(value,p))
print x.rvs(1)
However, upon running this it returns an error:
Traceback (most recent call last):
File "temp.py", line 16, in <module>
x = sp.stats.rv_discrete(values=(value,p))
File "/usr/lib/python2.7/dist-packages/scipy/stats/distributions.py", line 5637, in __init__
self.pk = take(ravel(self.pk),indx, 0)
File "/usr/lib/python2.7/dist-packages/numpy/core/fromnumeric.py", line 103, in take
return take(indices, axis, out, mode)
IndexError: index out of range for array
I'm not sure why this is. If in the code above I write instead:
x = sp.stats.rv_discrete(values=(range(len(count)),p))
Then the code runs fine, but it gives a weird result--clearly the way I've specified this distribution, a value of "0" ought to be most common. But this code gives "1" with high probability and never returns a "0," so something is getting shifted over somehow.
Can anyone clarify what is going on here? Any help would be greatly appreciated!
I believe the first argument for x.rvs() would be the loc arg. If you make loc=1 by calling x.rvs(1), you're adding 1 to all values.
Instead, you want
x.rvs(size=1)
As an aside, I'd recommend that you replace this:
# Extract counts and values
count = list(deg[0])
value = list(deg[1])
# Generate vector of probabilities for each node
p = [float(x)/sum(count) for x in count]
With:
count, value = deg # automatically unpacks along first axis
p = count.astype(float) / count.sum() # count is an array, so you can divide all elements at once

Categories

Resources