How to substitute a SymPy symbol with an array of values? - python

I am currently have an array that has the bounds for an integral (bounds_symbolic). I am trying to substitute the symbolic bounds with an array of values.
At a high level, what I am trying to do is solve the following integral (here it is using the variable names):
integral_variables
And here is an example:
example_integral
Here is my code thus far:
import sympy
import numpy as np
a, b, x, y = sympy.symbols("a b x y")
# Equation of the ellipse solved for y
ellipse = sympy.sqrt((b ** 2) * (1 - ((x ** 2) / (a ** 2))))
# Functions to be tested
test_functions = [(a * b * x), (((a * b) ** 2) * x), (((a * b) ** 3) * x), (((a * b) ** 4) * x), (((a * b) ** 5) * x)]
# Equating ellipse and test_functions so their intersection can be symbolically solved for
equate = [sympy.Eq(ellipse, test_functions[0]), sympy.Eq(ellipse, test_functions[1]), sympy.Eq(ellipse, test_functions[2]), sympy.Eq(ellipse, test_functions[3]), sympy.Eq(ellipse, test_functions[4])]
# Calculating the intersection points of the ellipse and the testing functions
# Array that holds the bounds of the integral solved symbolically
bounds_symbolic = []
for i in range(0, 3):
bounds_symbolic.append(sympy.solve(equate[i], x))
# Array of a-values to plug into the bounds of the integral
a_values = np.linspace(-10, 10, 201)
# Setting b equal to a constant of 1
b = 1
integrand = []
for j in range(0, 3):
integrand.append(ellipse - test_functions[j])
# New array with a-values substituted into the bounds
bounds_a = bounds_symbolic
for j in range(0, 201):
bounds_a.subs(a, a_values[j])
When I run it, I get an error when I attempt to perform bounds_a.subs(a, a_values[j]) operation:
AttributeError: 'list' object has no attribute 'subs'
What I would like to have happen is that the bounds_a array has the same bounds that I solved for above, but with all of the "a" values substituted into it, as opposed to just having a symbolic "a". I would also like to be able to substitute in a "b" value of 1.
How would I go about doing this? Would it be better if I used a NumPy array instead of a list?
Thank you!

In an isympy session with x defined as a variable:
In [51]: x
Out[51]: x
I can make a list of expressions:
In [52]: alist = [2*x, 3*x, 4+x]
and perform subs on each of them:
In [53]: [expr.subs({x:12}) for expr in alist]
Out[53]: [24, 36, 16]
and multiple subs:
In [54]: [[expr.subs({x:val}) for expr in alist] for val in [1,2,3]]
Out[54]: [[2, 3, 5], [4, 6, 6], [6, 9, 7]]
With lambdify I can create a function that takes a numpy array as input (default is numpy mode):
In [61]: f = lambdify(x, alist)
In [62]: print(f.__doc__)
Created with lambdify. Signature:
func(x)
Expression:
[2*x, 3*x, x + 4]
Source code:
def _lambdifygenerated(x):
return ([2*x, 3*x, x + 4])
I can give f a number:
In [63]: [f(i) for i in [1,2,3]]
Out[63]: [[2, 3, 5], [4, 6, 6], [6, 9, 7]]
or array:
In [64]: f(np.array([1,2,3]))
Out[64]: [array([2, 4, 6]), array([3, 6, 9]), array([5, 6, 7])]

Related

Why aren't these numpy operations equivalent, and how can I fix that?

Given the following arrays:
X = np.array([[1, 27, 3], [4, -1, 6]])
W = np.array([2, 4, 3])
These two equations are equivalent:
sum_a = 0
for l in range(len(X)):
sum_a += np.sum(W * X[l]) # 141
sum_b = np.sum(np.sum(W * X)) # 141
But these two are not:
sum_a = 0
for l in range(len(X)):
sum_a += np.exp(np.sum(W * X[l])) # 4.797813327299302e+51
sum_b = np.sum(np.exp(np.sum(W * X))) # 1.7199742630376623e+61
Why is that, and how can I make sum_b equivalent to sum_a for the second case? I specifically want to equate the sum using numpy so I can vectorize a bigger equation.
Those are not the same operations. You are hoping that e**119 + e**22 == e**141, but that's not how exponentiation works. You can MAKE it the same, but it's a multiplicative operation, not an additive one:
sum_a = 1
for l in range(len(X)):
sum_a *= np.exp(np.sum(W * X[l]))
sum_b = np.sum(np.exp(np.sum(W * X)))
And, by the way, the outer np.sum in that last line is useless. The inner np.sum returns a single value.
FOLLOWUP
Ah, so you WANT the summation version. You can do that by only summing over one axis:
sum_b = np.sum(np.exp(np.sum(W * X, axis=1)))
In this particular case, it's silly, because e**119 is so much larger than e**22 that the latter doesn't contribute.

I want to make a list from the outcome of a function using lists as arguments

I'm trying to make a list by using other lists as arguments of a function. However, i can't seem to get the right syntax.
This is my code:
f1 = theta0 + theta1*(X1_train) + theta2*(X2_train)+theta3*(X3_train)
The expected outcome would be a list of the same length of X1_train, X2_train and X3_train (which is the same for those 3).
I expect to get a list of the outcomes of each element on the lists X1_train, X2_train and X3_train as arguments of the funcion. For example, if my lists were
X1_train = [0, 1]
X2_train = [1, 2]
X3_train = [0, 2]
I'd expect a list of numbers like
f1 = [theta0 + theta2, theta0 + theta1 + theta2 + 2*theta3]
The thethas are random numbers.
This lists are columns of a dataframe I converted into lists so I could do the function.
I hope this helps:
import random
X1_train = [0,1]
X2_train = [1,2]
X3_train = [0,1]
amnt = 2
theta0 = random.sample(range(1, 10), amnt)
theta1 = random.sample(range(1, 10), amnt)
theta2 = random.sample(range(1, 10), amnt)
theta3 = random.sample(range(1, 10), amnt)
EndValues = []
for i in range(0, len(X1_train)):
f1 = theta0[i] + theta1[i] * X1_train[i] + theta2[i] * X2_train[i] + theta3[i] * X3_train[i]
EndValues.append(f1)
print(EndValues)
This returns
[3, 6] [5, 1] [2, 5] [7, 8]
[5, 25]
Use zip to zip the three lists into a list of 3-tuples, unpack the 3-tuple, then multiply each theta value by its corresponding element before summing the results.
f1 = [theta0 + theta1*x1 + theta2*x2 + theta3*x3
for x1, x2, x3 in zip(X1_train, X2_train, X3_train)]
If you think of theta0 as being multiple by 1, you can generalize this to
from itertools import repeat
f1 = [theta0*x0 + theta1*x1 + theta2*x2 + theta3*x3
for x0, x1, x2, x3 in zip(repeat(1), X1_train, X2_train, X3_train)]
which you can reduce to
from operator import mul
thetas = [theta0, theta1, theta2, theta3]
trains = [repeat(1), X1_train, X2_train, X3_train]
f1 = [sum(map(mul, t, thetas)) for t in zip(*trains)]
sum(map(mul, t, thetas)) is just the dot product of t and thetas.
def dotp(x, y):
return sum(map(mul, x, y))
f1 = [dotp(t, thetas) for t in zip(*trains)]
I suggest you try this simple code:
def f(X: tuple, theta: tuple):
if not isinstance(X, tuple):
raise TypeError('X must be a tuple')
if not isinstance(theta, tuple):
raise TypeError('theta must be a tuple')
if not X.__len__() == theta.__len__():
raise ValueError('length of tuples is not equal')
return sum([np.array(x_)*t_ for x_, t_ in zip(X, theta)])
Note it would throw an error if X or Theta are not tuples of the same length.
Example: (should work as is
import numpy as np
X1_train = [0, 1]
X2_train = [1, 2]
X3_train = [0, 2]
theta_1 = 1
theta_2 = 1
theta_3 = 3
print(f(
(X1_train, X2_train, X3_train),
(theta_1, theta_2, theta_3)
))
>>> [1 9]

multiplying a vector and a matrix using for nested for loops

I want to write a function matvec_row_variant_scalar(A,x) that implements the scalar-wise, row-variant of the matrix-vector multiplication, where A is a 2D array, and x is a 1D array. It MUST use two nested loops and scalar-wise access to the entries of 𝐴 and 𝑥 .
this is what i have tried.
def matvec_row_variant_scalar(A,x):
y = np.zeros(x.shape)
for i in range(A.shape[0]):
for j in range(A.shape[0]):
A[i,j] =int(x[j])*A[i,j]
y[j] = A[i,:].sum()
return y
A= np.array([[1,0,0],[0,,0],[0,0,1]])
x= np.array([[1], [2], [3]])
print(matvec_row_variant_scalar(A,x))
I understand you want a column-vector b = A * x where b[i] = (A[i, :] * x[i]).sum(), where x is also a column-vector. You don't really need a loop for this, because numpy can do this for you without loops much faster.
A = np.array([[1,0,0],[0,1,0],[0,0,1]])
x = np.array([[1], [2], [3]])
b = (A * x).sum(axis=1, keepdims=True)
# b = array([[1],
# [2],
# [3]])
Alternatively, you can simplify your problem before calculating it:
(A[i, :] * x[i]).sum() is the same as A[i, :].sum() * x[i], so you can sum across the rows of A first and then multiply by x to get the same result.
b = A.sum(axis=1, keepdims=True) * x
However, since you must have two loops, (Note that this will be much slower especially for larger arrays)
y = np.zeros(x.shape)
for i in range(A.shape[0]): # iterate over rows
for j in range(A.shape[1]): # iterate over columns
y[i, 0] = y[i, 0] + x[i, 0] * A[i,j] # Keep adding x[i] * A[i, j] to y[i]
# y = array([[1.],
# [2.],
# [3.]])
Speed comparison:
import timeit
from matplotlib import pyplot as plt
sizes = [1, 5, 10, 50, 100, 500, 1000, 5000, 10000]
def mult1(A, x):
return (A * x).sum(axis=1, keepdims=True)
def mult2(A, x):
return A.sum(axis=1, keepdims=True) * x
def mult3(A, x):
y = np.zeros(x.shape)
for i in range(A.shape[0]): # iterate over rows
for j in range(A.shape[1]): # iterate over columns
y[i, 0] = y[i, 0] + x[i, 0] * A[i,j] # Keep adding x[i] * A[i, j] to y[i]
return y
time_vals = np.zeros((len(size),3))
for i, size in enumerate(sizes):
reps = 10 if size < 1000 else 1
print(sizes, reps)
A = np.random.random((size, size))
x = np.random.random((size, 1))
time_vals[i, 0] = timeit.timeit("mult1(A, x)", setup="from __main__ import A, x, mult1", number=reps) / reps
time_vals[i, 1] = timeit.timeit("mult2(A, x)", setup="from __main__ import A, x, mult2", number=reps) / reps
time_vals[i, 2] = timeit.timeit("mult3(A, x)", setup="from __main__ import A, x, mult3", number=reps) / reps
plt.plot(sizes, time_vals[:, 0], label="mult1")
plt.plot(sizes, time_vals[:, 1], label="mult2")
plt.plot(sizes, time_vals[:, 2], label="mult3")
Gives this plot. The iterative approach is consistently 1+ order of magnitude slower than the vectorized approach for arrays of significant size.

Polynomial which satisfies integral and two points

Consider two points (x_0, f_0) and (x_1, f_1)
let p(x) be the degree two polynomial for which
p(x_0) = f_0
p(x_1) = f_1
and the integral of p(x) from -1 to 1 is equal to 0
Write a function which accepts two arguments
1. a length 2 NumPy vector 'x' of floating point values, with 'x[i]' containing the value of x_i,
2. a length 2 NumPy vector 'f' of floating point values, with 'f[i]' containing the value of f_i,
and which returns
a length 3 NumPy vector of floating point values containing the power series coefficients, in order from the highest order term to the constant term, for p(x)
I'm not sure where to start. My intial thought would be to have a differential equation P(1)=P(-1) with initial values p(x_0) = f_0 and p(x_1) = f_1, but I'm also having issues with the implementation.
Using sympy, Python's symbolic math library, the problem can be formulated as follows:
from sympy import symbols Eq, solve, integrate
def give_coeff(x, f):
a, b, c, X = symbols('a, b, c, X')
F = a * X * X + b * X + c # we have a second order polynomial
sol = solve([Eq(integrate(F, (X, -1, 1)), 0), # the integral should be zero (2/3*a + 2*c)
Eq(F.subs(X, x[0]), f[0]), # filling in x[0] should give f[0]
Eq(F.subs(X, x[1]), f[1])], # filling in x[1] should give f[1]
(a, b, c)) # solve for a, b and c
return sol[a].evalf(), sol[b].evalf(), sol[c].evalf()
import numpy as np
coeff = give_coeff(np.array([1, 2]), np.array([3, 4]))
print(coeff)
The code can even be expanded to polynomials of any degree:
from sympy import Eq, solve, symbols, integrate
def give_coeff(x, f):
assert len(x) == len(f), "x and f need to have the same length"
degree = len(x)
X = symbols('X')
a = [symbols(f'a_{i}') for i in range(degree + 1)]
F = 0
for ai in a[::-1]:
F = F * X + ai
sol = solve([Eq(integrate(F, (X, -1, 1)), 0)] +
[Eq(F.subs(X, xi), fi) for xi, fi in zip(x, f)],
(*a,))
# print(sol)
# print(F.subs(sol).expand())
return [sol[ai].evalf() for ai in a[::-1]]
import numpy as np
coeff = give_coeff(np.array([1, 2]), np.array([3, 4]))
print(coeff)
print(give_coeff(np.array([1, 2, 3, 4, 5]), np.array([3, 4, 6, 9, 1])))
PS: To solve the second degree equation only using numpy, np.linalg.solve can be used to solve the linear system of 3 unknowns with 3 equations. The equations need to be "hand calculated" which is are more error prone and more elaborated to extend to higher degrees.
import numpy as np
def np_give_coeff(x, f):
# general equation: F = a*X**2 + b*X + c
# 3 equations:
# integral (F, (X, -1, 1)) == 0 or (2/3*a + 2*c) == 0
# a*x[0]**2 + b*x[0] + c == f[0]
# a*x[1]**2 + b*x[1] + c == f[1]
A = np.array([[2/3, 0, 2],
[x[0]**2, x[0], 1],
[x[1]**2, x[1], 1]])
B = np.array([0, f[0], f[1]])
return np.linalg.solve(A, B)
coeff = np_give_coeff(np.array([1, 2]), np.array([3, 4]))
print(coeff)
You can solve this generically, taking advantage of the fact that
and adding that as a constraint. Then you have 3 equations for 3 unknowns (a, b, c).
There are other interesting tricks, it is a neat question. Try playing around with writing your formula in terms of a(x-b)(x-c), then you have 3bc + 1 = 0., also any solution starting with points (x0,y0),(x1,x1) has a similar solution for (k*x0,k*y0),(k*x1,k*y1).

sympy collect undefined functions

I have an expression in sympy that is a linear combination of an evaluated function, f. Schematically
expr = Sum_{m,n} c_{m,n} f(x+a_m,y+a_n)
where c_{m,n} is a coefficient depending on the variables x,y. A very simple example is
import sympy as sp
x, y = sp.symbols("x, y")
f = sp.Function("f")(x,y)
expr = 0
for i in range(0,3):
expr += (x-i)* f.subs({x: x+2*i, y: y+3*i})
In my actual code expr is the result of a long succession of sums and the function g not simplify like here. Is there an efficient way of grouping functions with different argument together, like collect(expr) does for polynomials? What I am after is to obtain a structured list:
In: someFunction(...)
Out: [..., [c_{m,n}, x+a_m, y+a_n ], ...]
in the example above
In: someFunction(expr)
Out: [[x, x, y], [x - 1, x + 2, y + 3], [x - 2, x + 4, y + 6]]
I'm not sure if this does exactly what you want but you can use pattern matching:
In [27]: expr
Out[27]: x⋅f(x, y) + (x - 2)⋅f(x + 4, y + 6) + (x - 1)⋅f(x + 2, y + 3)
In [28]: a, b, c = symbols('a, b, c', cls=Wild)
In [29]: pattern = a*f(b, c)
In [30]: for term in Add.make_args(expr):
...: print(term.match(pattern))
...:
{b_: x, c_: y, a_: x}
{b_: x + 2, c_: y + 3, a_: x - 1}
{b_: x + 4, c_: y + 6, a_: x - 2}

Categories

Resources