Sympy tensorproduct and tensorcontraction - sympy

I want to do two computations with sympy and one substitution:
The first is: "E:E" or in index notation "E_{ij}E{ji}"
The second is: "T_{ijmn} T_{mnkl}"
After I do these calculations I would like to replace the symbols with numeric values.
I have the following code:
import sympy as sp
import numpy as np
sp.init_printing(pretty_print=False)
# e = sp.MatrixSymbol('e',3,3)
# E = sp.Matrix(e)
def foo():
e = sp.MatrixSymbol('e',3,3)
E = sp.Matrix(e)
result1 = sp.tensorcontraction( sp.tensorproduct(E, E), (0, 2), (1,3))
T = sp.tensorproduct(E, E)
result2 = sp.tensorcontraction( sp.tensorproduct(T, T), (2,4), (3,5))
return [result1, result2]
# Verification
res1, res2 = foo()
E_num = np.array([[1,2,3],
[4,5,6],
[7,8,12]])
res1 = res1.subs({ E[0,0]:E_num[0,0], E[0,1]:E_num[0,1], E[0,2]:E_num[0,2],
E[1,0]:E_num[1,0], E[1,1]:E_num[1,1], E[1,2]:E_num[1,2],
E[2,0]:E_num[2,0], E[2,1]:E_num[2,1], E[2,2]:E_num[2,2]})
res2 = res2.subs({ E[0,0]:E_num[0,0], E[0,1]:E_num[0,1], E[0,2]:E_num[0,2],
E[1,0]:E_num[1,0], E[1,1]:E_num[1,1], E[1,2]:E_num[1,2],
E[2,0]:E_num[2,0], E[2,1]:E_num[2,1], E[2,2]:E_num[2,2]})
check1 = np.einsum("ij,ji", E_num, E_num) - res1
T1 = np.einsum("ij,mn->ijmn", E_num, E_num)
T2 = np.einsum("mn,kl->mnkl", E_num, E_num)
check2 = np.einsum("ijmn,mnkl->ijkl", T1,T2) - res2
In the above code I cannot replace the E matrix with numerical values as the symbolic variable is defined inside the function. I can do it if I define them outside. Any way to look into the expression and get the symbols to replace?
Additionally, check1 is not zero as it appears to be doing E_{ij}E_{ij}, whilst check2 appears to be correct. Am I not asking to contract the correct indices?
Best Regards

Your res1 - before numeric substitute is
In [18]: res1
Out[18]:
2 2 2 2 2 2 2 2 2
e₀₀ + e₀₁ + e₀₂ + e₁₀ + e₁₁ + e₁₂ + e₂₀ + e₂₁ + e₂₂
That's the same as
np.einsum('ij,ij', E_num, E_num)
or
np.sum(E_num * E_num)
The einsum contraction of the array with its transpose:
In [18]: np.einsum("ij,ji", E_num, E_num)
Out[18]: 324
In [19]: np.einsum("ij,ij", E_num, E_num.T)
Out[19]: 324
Using E.T in your res1 calcultion gets the same thing
In [24]: sp.tensorcontraction( sp.tensorproduct(E, E.T), (0, 2), (1,3))
Out[24]:
2 2 2
e₀₀ + 2⋅e₀₁⋅e₁₀ + 2⋅e₀₂⋅e₂₀ + e₁₁ + 2⋅e₁₂⋅e₂₁ + e₂₂
In [25]: _.subs({ E[0,0]:E_num[0,0], E[0,1]:E_num[0,1], E[0,2]:E_num[0,2],
...: E[1,0]:E_num[1,0], E[1,1]:E_num[1,1], E[1,2]:E_num[1,2],
...: E[2,0]:E_num[2,0], E[2,1]:E_num[2,1], E[2,2]:E_num[2,2]})
Out[25]: 324
I haven't read the docs for tensorproduct or tensorcontraction.

Related

Substitute compound expression in SymPy

In sympy how can I make a substitution of a compound expression for a single variable as in the following example that only works for one of the instances of the common factor?
from sympy import *
x, y, z = symbols('x y z')
eq = Eq(2*(x+y) + 3*(x+y)**2, 0)
print(eq)
eq1 = Eq(z, x+y)
print(eq1)
eq2 = eq.subs(eq1.rhs, eq1.lhs)
print(eq2)
Output
Eq(2*x + 2*y + 3*(x + y)**2, 0)
Eq(z, x + y)
Eq(2*x + 2*y + 3*z**2, 0)
Desired output for last line
Eq(2*z + 3*z**2, 0)
Thanks to Oscar Benjamin's comment. I've solved the case I was actually interested in:
from sympy import *
t, L, C0, R, a, w0, h = symbols('t L C_0 R alpha omega_0 h')
Q = Function('Q')
ex0 = L*Q(t).diff(t, t) + R*Q(t).diff(t) + Q(t)*(1/(C0/(1+h*cos(a*t))))
print(ex0)
ex1 = ex0/L
ex1 = ex1.collect(Q(t)).expand()
print(ex1)
# substitute the following compound expression
ex2 = Eq(w0*w0, 1/(L*C0))
print(ex2)
ex3 = ex1.subs(L*C0, 1/(w0*w0))
ex4 = ex3.collect(Q(t))
print(ex4)
Output:
L*Derivative(Q(t), (t, 2)) + R*Derivative(Q(t), t) + (h*cos(alpha*t) + 1)*Q(t)/C_0
Derivative(Q(t), (t, 2)) + R*Derivative(Q(t), t)/L + h*Q(t)*cos(alpha*t)/(C_0*L) + Q(t)/(C_0*L)
Eq(omega_0**2, 1/(C_0*L))
(h*omega_0**2*cos(alpha*t) + omega_0**2)*Q(t) + Derivative(Q(t), (t, 2)) + R*Derivative(Q(t), t)/L
The substitution fails because subs does not encounter any argument x + y in the (sub)expression 2*(x + y): that expression automatically expands to 2*x + 2*y. So one solution is to do as Oscar suggested: make an algebraic substitution. I often follow this up with a restoration step to handle anything that didn't change as I expected. The other thing you can do is to use a helper function that groups together terms that are in the multi-term old object that you desire to replace:
def mvsubs(eq, old, new):
from sympy.core.exprtools import factor_terms
if not old.is_Add:
return eq.subs(old, new)
Add = old.func
free = old.free_symbols
for i in eq.atoms(Add):
reps = {}
for i in i.args:
if not all(i.has(x) for x in free):
reps.setdefault(i, Dummy())
eq = eq.subs(reps).subs(Add(*reps.values()),
factor_terms(Add(*reps.keys()))).subs(
old, new).xreplace({v:k for k,v in reps.items()})
return eq
>>> mvsubs(eq, x+y, z)
Eq(3*z**2 + 2*z, 0)

trim np arrays according to a list of starting points

I have a table, represented by an np.array like the following:
A = [[12,412,42,54],
[144,2,42,4],
[2,43,22,10]]
And a list that contains the desired starting point of each row in A:
L=[0,2,1]
The desired output would be:
B = [[12,412,42,54],
[42,4,np.nan,np.nan],
[43,22,10,np.nan]]
Edit
I prefer to avoid using a for-loop for obvious reasons.
Try compare the L with column index, then use boolean set/get items:
# convert A to numpy array for advanced indexing
A = np.array(A)
ll = A.shape[1]
keep = np.arange(ll) >= np.array(L)[:,None]
out = np.full(A.shape, np.nan)
out[keep[:,::-1]] = A[keep]
print(out)
Output:
[[ 12. 412. 42. 54.]
[ 42. 4. nan nan]
[ 43. 22. 10. nan]]
My guess would be that a vectorized approach for this would be less efficient than explicit looping, because the result is fundamentally a jagged array, which NumPy does not support well.
However, a loop-based solution is simple, that can be made faster with Numba's nb.njit(), if needed.:
import numpy as np
import numba as nb
#nb.njit
def jag_nb(arr, starts, empty=np.nan):
result = np.full(arr.shape, empty)
for i, x in enumerate(starts):
if x != 0:
result[i, :-x] = arr[i, x:]
else:
result[i, :] = arr[i, :]
return result
A = np.array([[12,412,42,54], [144,2,42,4], [2,43,22,10]])
L = np.array([0,2,1])
jag(A, L)
# array([[ 12., 412., 42., 54.],
# [ 42., 4., nan, nan],
# [ 43., 22., 10., nan]])
Compared to the pure NumPy vectorized approach proposed in #QuangHoang's answer:
def jag_np(arr, starts, empty=np.nan):
m, _ = arr.shape
keep = np.arange(m) >= starts[:, None]
result = np.full(arr.shape, np.nan)
result[keep[:, ::-1]] = arr[keep]
return result
The Numba based approach is noticeably faster, as shown with the following benchmarks:
import pandas as pd
import matplotlib.pyplot as plt
def benchmark(
funcs,
ii=range(4, 10, 1),
is_equal=lambda x, y: np.allclose(x, y, equal_nan=True),
seed=0,
unit="ms",
verbose=True,
use_str=True
):
labels = [func.__name__ for func in funcs]
units = {"s": 0, "ms": 3, "µs": 6, "ns": 9}
assert unit in units
np.random.seed(seed)
timings = {}
for i in ii:
m = n = 2 ** i
if verbose:
print(f"i={i}, n={n}")
arr = np.random.random((m, n))
starts = np.random.randint(0, n, m)
base = funcs[0](arr, starts)
timings[n] = []
for func in funcs:
res = func(arr, starts)
is_good = is_equal(base, res)
timed = %timeit -n 64 -r 8 -q -o func(arr, starts)
timing = timed.best
timings[n].append(timing if is_good else None)
if verbose:
print(
f"{func.__name__:>24}"
f" {is_good!s:5}"
f" {timing * (10 ** units[unit]):10.3f} {unit}"
f" {timings[n][0] / timing:5.1f}x")
return timings, labels
def plot(timings, labels, title=None, xlabel="Input Size / #", unit="ms"):
n_rows = 1
n_cols = 3
fig, axs = plt.subplots(n_rows, n_cols, figsize=(8 * n_cols, 6 * n_rows), squeeze=False)
units = {"s": 0, "ms": 3, "µs": 6, "ns": 9}
df = pd.DataFrame(data=timings, index=labels).transpose()
base = df[[labels[0]]].to_numpy()
(df * 10 ** units[unit]).plot(marker="o", xlabel=xlabel, ylabel=f"Best timing / {unit}", ax=axs[0, 0])
(df / base * 100).plot(marker='o', xlabel=xlabel, ylabel='Relative speed / %', logx=True, ax=axs[0, 1])
(base / df).plot(marker='o', xlabel=xlabel, ylabel='Speed Gain / x', ax=axs[0, 2])
if title:
fig.suptitle(title)
fig.patch.set_facecolor('white')
funcs = jag_np, jag_nb
timings, labels = benchmark(funcs, ii=range(4, 11))
plot(timings, labels, unit="ms")

I want diophantine convert t_0 ---> n

I want diophantine convert (t_0 ---> n)
from sympy import *
var('x y t_0 n')
def myDiophantine(myf):
myf_diox = list(diophantine(myf))[0][0]
myf_dioy = list(diophantine(myf))[0][1]
print("#1#",type(myf_diox),myf_diox)
myf_diox = myf_diox.subs({t_0:n})
print("#2#",type(myf_diox),myf_diox)
return myf_diox,myf_dioy
myf=5**4*x-2**4*y-1
print("#3#",myDiophantine(myf))
var('z')
print("#4#",type(myf),myf)
print("#5#",type(myf.subs({x:z})),myf.subs({x:z}))
#1# <class 'sympy.core.add.Add'> 16*t_0 + 1
#2# <class 'sympy.core.add.Add'> 16*t_0 + 1
#3# (16*t_0 + 1, 625*t_0 + 39)
#4# <class 'sympy.core.add.Add'> 625*x - 16*y - 1
#5# <class 'sympy.core.add.Add'> -16*y + 625*z - 1
i used subs? It could not be replaced.
Is there a better way?
i want
#2# <class 'sympy.core.add.Add'> 16*n + 1
ref
I want function convert from xy to cells
20220401
from sympy import *
var('x y t_0 n')
def myDiophantine(myf):
d=diophantine(myf)
params = Tuple(*d).free_symbols - myf.free_symbols;
d=Tuple(*d).xreplace(dict(zip(params, [n])))
myf_diox = list(d)[0][0]
myf_dioy = list(d)[0][1]
return myf_diox,myf_dioy
myf=5**4*x-2**4*y-1
myg=myDiophantine(myf)
print("#",myg[0],myg[1])
# 16*n + 1 625*n + 39
https://www.wolframalpha.com/input?i=5%5E4*x-2%5E4*y%3D1
5^4x-2^4y=1
Integer solution
x = 16 n + 1, y = 625 n + 39, n element Z
>>> myf = 5**4*x-2**4*y-1
>>> d = diophantine(myf)
Get a list of parameters that were used for the solution (converting set-of-tuples solution to Tuple-of-Tuples):
>>> params = Tuple(*d).free_symbols - myf.free_symbols; params
{t_0}
Create a replacement dictionary with desired replacements
>>> Tuple(*d).xreplace(dict(zip(params, [n])))
((16*n + 1, 625*n + 39),)
Why didn't your approach work?
>>> Symbol('t_0') in params
False
>>> Symbol('t_0',integer=True) in params
True
Symbols are matched based on name and assumptions, not name alone.
As to getting smaller coefficients for Diophantine equations: this is a known issue.

how to rewrite (a+b+c)**2 as a**2 + b**2 + c*2 + 2*(a*b + b*c + c*a) & ^2-->**2

sympy conversion:(a+b+c)^2 --> a^2 +b^2+c^2+2*(ab + bc + c*a) : I want
sorry add
sympy conversion: I want
(a+b+c)**2 --> a**2 +b**2+c**2+2*(a*b + b*c + c*a)
I try
from sympy import *
var('a b c')
f=(a+b+c)**2
print("#f",f)
print("#e",expand(f))
#f (a + b + c)**2
#e a**2 + 2*a*b + 2*a*c + b**2 + 2*b*c + c**2
(2022-02-02)
i try use factor atom
from sympy import *
var('a b c x')
f_str="a+b+c"
g_str="a**2+b**2+c**2"
f =factor(simplify(f_str))
g =factor(simplify(g_str))
mySubs={f:1,g:13}
#
h =factor(f**2-g)
ha=list(h.atoms(Number))[0]
hb=h/ha
Le=(f.subs(mySubs))**2
Ri1=g.subs(mySubs)
Ri2=h.subs({hb:x})
print("#","(",f_str,")**2=",g_str,"+",ha,"*(",hb,")")
print("#",f_str,"=",mySubs[f],",",g_str,"=",mySubs[g])
print("#",Le,"**2=",Ri1,"+",ha,"*(",solve(Eq(Le,Ri1+Ri2))[0],")")
# ( a+b+c )**2= a**2+b**2+c**2 + 2 *( a*b + a*c + b*c )
# a+b+c = 1 , a**2+b**2+c**2 = 13
# 1 **2= 13 + 2 *( -6 )
(2022-02-04) value subs
from sympy import *
var('a b c')
f_str="a+b+c "
g_str="a**2+b**2+c**2"
h_str="a*b+a*c+b*c "
mySubs={a:0,b:-2,c:3}
f=factor(simplify(f_str)).subs(mySubs)
g=factor(simplify(g_str)).subs(mySubs)
h=factor(simplify(h_str)).subs(mySubs)
print("#",mySubs)
print("#",f_str,"=",f)
print("#",g_str,"=",g)
print("#",h_str,"=",h)
print("#",f_str,"**2=",g_str,"+2*(",h_str,")")
print("#",f,"**2=",g,"+2*(",h,")")
# {a: 0, b: -2, c: 3}
# a+b+c = 1
# a**2+b**2+c**2 = 13
# a*b+a*c+b*c = -6
# a+b+c **2= a**2+b**2+c**2 +2*( a*b+a*c+b*c )
# 1 **2= 13 +2*( -6 )
(2022-02-05) value subs
from sympy import *
var('a b c')
f=a+b+c
g=a**2+b**2+c**2
h=a*b+a*c+b*c
mySubs={a:0,b:-2,c:3}
#
f_val=factor(f).subs(mySubs)
g_val=factor(g).subs(mySubs)
h_val=factor(h).subs(mySubs)
print("#",mySubs)
print("#",f,"=",f_val)
print("#",g,"=",g_val)
print("#",h,"=",h_val)
print("#",f,"**2=",g_val,"+2*(",h,")")
print("#",f_val,"**2=",g_val,"+2*(",h_val,")")
# {a: 0, b: -2, c: 3}
# a + b + c = 1
# a**2 + b**2 + c**2 = 13
# a*b + a*c + b*c = -6
# a + b + c **2= 13 +2*( a*b + a*c + b*c )
# 1 **2= 13 +2*( -6 )
(Reference)
2022 Mathematics IA Q1 < The Common Test for University Admissions is a common entrance examination for Japanese universities
japanese only
https://cdn.mainichi.jp/item/jp/etc/kyotsu-2022/pdf/MT1P.pdf#page=1
>>> from sympy.parsing.sympy_parser import *
>>> f = parse_expr('(a+b+c)^2', transformations=(auto_symbol, convert_xor))
>>> f
(a + b + c)**2
>>> from sympy import Pow
>>> nopow, pow = f.expand().as_independent(Pow)
>>> pow + nopow.factor()
a**2 + b**2 + c**2 + 2*(a*b + a*c + b*c)

Polynomial Coefficients from Sympy to Array

In the below code, L1 simplifies to the transfer function that I want:
import sympy as sy
z = sy.symbols('z')
L1 = sy.simplify(((z**2 - 0.5*z + 0.16) / (z-1)**2 ) - 1)
L1
After this, I manually enter the coefficients for the numerator and denominator as follow:
num = [1.5, -0.84]
den = [1., -2., 1.]
Is there a way to do this from code? I'm not sure how to convert the sympy result to something that I can work with again without manually creating the arrays num and den.
You can use as_numer_denom to get the numerator and denominator and then as_poly and coeffs to get the coefficients:
In [16]: import sympy as sy
...: z = sy.symbols('z')
...: L1 = sy.simplify(((z**2 - 0.5*z + 0.16) / (z-1)**2 ) - 1)
...: L1
Out[16]:
1.0⋅(1.5⋅z - 0.84)
────────────────────
2
1.0⋅z - 2.0⋅z + 1.0
In [17]: num, den = L1.as_numer_denom()
In [18]: num.as_poly(z).coeffs()
Out[18]: [1.5, -0.84]
In [19]: den.as_poly(z).coeffs()
Out[19]: [1.0, -2.0, 1.0]
Or to get the whole expression, you could do :
from sympy import *
z = symbols('z')
L1 = simplify(((z**2 - 0.5*z + 0.16) / (z-1)**2 ) - 1)
srepr(L1)
output:
"Mul(Float('1.0', precision=53), Add(Mul(Float('1.5', precision=53), Symbol('z')),
Float('-0.83999999999999997', precision=53)), Pow(Add(Mul(Float('1.0', precision=53),
Pow(Symbol('z'), Integer(2))), Mul(Integer(-1), Float('2.0', precision=53),
Symbol('z')), Float('1.0', precision=53)), Integer(-1)))"