import sympy as sp
import matplotlib.pyplot as plt
from IPython.display import Math
from shenfun import *
from shenfun.la import Solver2D

"""
Explanation: Non-orthogonal curvilinear coordinates
In this notebook we solve Poisson's equation on a 2D wavy domain, using non-orthogonal basis vectors.
End of explanation
"""


u = sp.Symbol('x', real=True, positive=True)
v = sp.Symbol('y', real=True)
psi = (u, v)
rv = (u, v*(1-sp.sin(2*u)/10))

"""
Explanation: Define the non-orthogonal curvilinear coordinates. Here psi=(u, v) are the new coordinates and rv is the position vector
$$
\vec{r} = u \mathbf{i} + v\left(1- \frac{\sin 2u}{10} \right) \mathbf{j}
$$
where $\mathbf{i}$ and $\mathbf{j}$ are the Cartesian unit vectors in $x$- and $y$-directions, respectively.
End of explanation
"""


N = 20
#B0 = FunctionSpace(N, 'C', bc=(0, 0), domain=(0, 2*np.pi))
B0 = FunctionSpace(N, 'F', dtype='D', domain=(0, 2*np.pi))
B1 = FunctionSpace(N, 'L', bc=(0, 0), domain=(-1, 1))

T = TensorProductSpace(comm, (B0, B1), dtype='D', coordinates=(psi, rv, sp.Q.negative(sp.sin(2*u)-10) & sp.Q.negative(sp.sin(2*u)/10-1)))
p = TrialFunction(T)
q = TestFunction(T)
b = T.coors.get_covariant_basis()
T.coors.sg

sp.Matrix(T.coors.get_covariant_metric_tensor())

Math(T.coors.latex_basis_vectors(symbol_names={u: 'u', v: 'v'}))

"""
Explanation: Now choose basis functions and create tensor product space. Notice that one has to use complex Fourier space and not the real, because the integral measure is a function of u.
End of explanation
"""


mesh = T.local_cartesian_mesh()
x, y = mesh
plt.figure(figsize=(10, 4))
for i, (xi, yi) in enumerate(zip(x, y)):
    plt.plot(xi, yi, 'b')
    plt.plot(x[:, i], y[:, i], 'r')

"""
Explanation: Plot the mesh to see the domain.
End of explanation
"""


dp = div(grad(p))
g = sp.Symbol('g', real=True, positive=True)
replace = [(1-sp.sin(2*u)/10, sp.sqrt(g)), (sp.sin(2*u)-10, -10*sp.sqrt(g)), (5*sp.sin(2*u)-50, -50*sp.sqrt(g))]
Math((dp*T.coors.sg**2).tolatex(funcname='p', symbol_names={u: 'u', v: 'v'}, replace=replace))

"""
Explanation: Print the Laplace operator in curvilinear coordinates. We use replace to simplify the expression.
End of explanation
"""


ue = sp.sin(2*u)*(1-v**2)
f = (div(grad(p))).tosympy(basis=ue, psi=psi)
fj = Array(T, buffer=f*T.coors.sg)
f_hat = Function(T)
f_hat = inner(q, fj, output_array=f_hat)

"""
Explanation: Solve Poisson's equation. First define a manufactured solution and assemble the right hand side
End of explanation
"""


M = inner(q, div(grad(p))*T.coors.sg)
#M = inner(grad(q*T.coors.sg), -grad(p))
u_hat = Function(T)
Sol1 = Solver2D(M)
u_hat = Sol1(f_hat, u_hat)
uj = u_hat.backward()
uq = Array(T, buffer=ue)
print('Error =', np.linalg.norm(uj-uq))

for i in range(len(M)):
    print(len(M[i].mats[0].keys()), len(M[i].mats[1].keys()), M[i].mats[0].measure, M[i].mats[1].measure)

"""
Explanation: Then assemble the left hand side and solve using a generic 2D solver
End of explanation
"""


xx, yy = T.local_cartesian_mesh()
plt.figure(figsize=(12, 4))
plt.contourf(xx, yy, uj.real)
plt.colorbar()

"""
Explanation: Plot the solution in the wavy Cartesian domain
End of explanation
"""


from matplotlib.pyplot import spy
plt.figure()
spy(Sol1.mat, markersize=0.1)

from scipy.sparse.linalg import eigs
mats = inner(q*T.coors.sg, -div(grad(p)))
Sol1 = Solver2D(mats)
BB = inner(p, q*T.coors.sg)
Sol2 = Solver2D([BB])
f = eigs(Sol1.mat, k=20, M=Sol2.mat, which='LM', sigma=0)

mats

l = 10
u_hat = Function(T)
u_hat[:, :-2] = f[1][:, l].reshape(T.dims())
plt.contourf(xx, yy, u_hat.backward().real, 100)
plt.colorbar()

"""
Explanation: Inspect the sparsity pattern of the generated matrix on the left hand side
End of explanation
"""


