import numpy as np
import scipy.spatial
import scipy.linalg

def nullspace(A, atol=1e-13, rtol=0):
  u, s, vh = np.linalg.svd(A)
  tol = max(atol, rtol * s[0])
  nnz = (s >= tol).sum()
  ns = vh[nnz:].conj().T
  return ns

def nearest_orthogonal_matrix(R):
  U,S,Vt = np.linalg.svd(R)
  return U @ np.eye(3,dtype=R.dtype) @ Vt

def power_iters(A, n_iters=10):
  b = np.random.uniform(-1,1, size=(A.shape[0], A.shape[1], 1))
  for iter in range(n_iters):
    b = A @ b
    b = b / np.linalg.norm(b, axis=1, keepdims=True)
  return b

def rayleigh_quotient(A, b):
  return (b.transpose(0,2,1) @ A @ b) / (b.transpose(0,2,1) @ b)

def cross_prod_mat(x):
  x = x.reshape(-1,3)
  X = np.empty((x.shape[0],3,3), dtype=x.dtype)
  X[:,0,0] = 0
  X[:,0,1] = -x[:,2]
  X[:,0,2] = x[:,1]
  X[:,1,0] = x[:,2]
  X[:,1,1] = 0
  X[:,1,2] = -x[:,0]
  X[:,2,0] = -x[:,1]
  X[:,2,1] = x[:,0]
  X[:,2,2] = 0
  return X.squeeze()

def hat_operator(x):
  return cross_prod_mat(x)

def vee_operator(X):
  X = X.reshape(-1,3,3)
  x = np.empty((X.shape[0], 3), dtype=X.dtype)
  x[:,0] = X[:,2,1]
  x[:,1] = X[:,0,2]
  x[:,2] = X[:,1,0]
  return x.squeeze()

def rot_x(x, dtype=np.float32):
  x = np.array(x, copy=False)
  x = x.reshape(-1,1)
  R = np.zeros((x.shape[0],3,3), dtype=dtype)
  R[:,0,0] = 1
  R[:,1,1] = np.cos(x).ravel()
  R[:,1,2] = -np.sin(x).ravel()
  R[:,2,1] = np.sin(x).ravel()
  R[:,2,2] = np.cos(x).ravel()
  return R.squeeze()

def rot_y(y, dtype=np.float32):
  y = np.array(y, copy=False)
  y = y.reshape(-1,1)
  R = np.zeros((y.shape[0],3,3), dtype=dtype)
  R[:,0,0] = np.cos(y).ravel()
  R[:,0,2] = np.sin(y).ravel()
  R[:,1,1] = 1
  R[:,2,0] = -np.sin(y).ravel()
  R[:,2,2] = np.cos(y).ravel()
  return R.squeeze()

def rot_z(z, dtype=np.float32):
  z = np.array(z, copy=False)
  z = z.reshape(-1,1)
  R = np.zeros((z.shape[0],3,3), dtype=dtype)
  R[:,0,0] = np.cos(z).ravel()
  R[:,0,1] = -np.sin(z).ravel()
  R[:,1,0] = np.sin(z).ravel()
  R[:,1,1] = np.cos(z).ravel()
  R[:,2,2] = 1
  return R.squeeze()

def xyz_from_rotm(R):
  R = R.reshape(-1,3,3)
  xyz = np.empty((R.shape[0],3), dtype=R.dtype)
  for bidx in range(R.shape[0]):
    if R[bidx,0,2] < 1:
      if R[bidx,0,2] > -1:
        xyz[bidx,1] = np.arcsin(R[bidx,0,2])
        xyz[bidx,0] = np.arctan2(-R[bidx,1,2], R[bidx,2,2])
        xyz[bidx,2] = np.arctan2(-R[bidx,0,1], R[bidx,0,0])
      else:
        xyz[bidx,1] = -np.pi/2
        xyz[bidx,0] = -np.arctan2(R[bidx,1,0],R[bidx,1,1])
        xyz[bidx,2] = 0
    else:
      xyz[bidx,1] = np.pi/2
      xyz[bidx,0] = np.arctan2(R[bidx,1,0], R[bidx,1,1])
      xyz[bidx,2] = 0
  return xyz.squeeze()

def zyx_from_rotm(R):
  R = R.reshape(-1,3,3)
  zyx = np.empty((R.shape[0],3), dtype=R.dtype)
  for bidx in range(R.shape[0]):
    if R[bidx,2,0] < 1:
      if R[bidx,2,0] > -1:
        zyx[bidx,1] = np.arcsin(-R[bidx,2,0])
        zyx[bidx,0] = np.arctan2(R[bidx,1,0], R[bidx,0,0])
        zyx[bidx,2] = np.arctan2(R[bidx,2,1], R[bidx,2,2])
      else:
        zyx[bidx,1] = np.pi / 2
        zyx[bidx,0] = -np.arctan2(-R[bidx,1,2], R[bidx,1,1])
        zyx[bidx,2] = 0
    else:
      zyx[bidx,1] = -np.pi / 2
      zyx[bidx,0] = np.arctan2(-R[bidx,1,2], R[bidx,1,1])
      zyx[bidx,2] = 0
  return zyx.squeeze()

def rotm_from_xyz(xyz):
  xyz = np.array(xyz, copy=False).reshape(-1,3)
  return (rot_x(xyz[:,0]) @ rot_y(xyz[:,1]) @ rot_z(xyz[:,2])).squeeze()

def rotm_from_zyx(zyx):
  zyx = np.array(zyx, copy=False).reshape(-1,3)
  return (rot_z(zyx[:,0]) @ rot_y(zyx[:,1]) @ rot_x(zyx[:,2])).squeeze()

def rotm_from_quat(q):
  q = q.reshape(-1,4)
  w, x, y, z = q[:,0], q[:,1], q[:,2], q[:,3]
  R = np.array([
    [1 - 2*y*y - 2*z*z, 2*x*y - 2*z*w, 2*x*z + 2*y*w],
    [2*x*y + 2*z*w, 1 - 2*x*x - 2*z*z, 2*y*z - 2*x*w],
    [2*x*z - 2*y*w, 2*y*z + 2*x*w, 1 - 2*x*x - 2*y*y]
  ], dtype=q.dtype)
  R = R.transpose((2,0,1))
  return R.squeeze()

def rotm_from_axisangle(a):
  # exponential
  a = a.reshape(-1,3)
  phi = np.linalg.norm(a, axis=1).reshape(-1,1,1)
  iphi = np.zeros_like(phi)
  np.divide(1, phi, out=iphi, where=phi != 0)
  A = cross_prod_mat(a) * iphi
  R = np.eye(3, dtype=a.dtype) + np.sin(phi) * A + (1 - np.cos(phi)) * A @ A
  return R.squeeze()

def rotm_from_lookat(dir, up=None):
  dir = dir.reshape(-1,3)
  if up is None:
    up = np.zeros_like(dir)
    up[:,1] = 1
  dir /= np.linalg.norm(dir, axis=1, keepdims=True)
  up /= np.linalg.norm(up, axis=1, keepdims=True)
  x = dir[:,None,:] @ cross_prod_mat(up).transpose(0,2,1)
  y = x @ cross_prod_mat(dir).transpose(0,2,1)
  x = x.squeeze()
  y = y.squeeze()
  x /= np.linalg.norm(x, axis=1, keepdims=True)
  y /= np.linalg.norm(y, axis=1, keepdims=True)
  R = np.empty((dir.shape[0],3,3), dtype=dir.dtype)
  R[:,0,0] = x[:,0]
  R[:,0,1] = y[:,0]
  R[:,0,2] = dir[:,0]
  R[:,1,0] = x[:,1]
  R[:,1,1] = y[:,1]
  R[:,1,2] = dir[:,1]
  R[:,2,0] = x[:,2]
  R[:,2,1] = y[:,2]
  R[:,2,2] = dir[:,2]
  return R.transpose(0,2,1).squeeze()

def rotm_distance_identity(R0, R1):
  # https://link.springer.com/article/10.1007%2Fs10851-009-0161-2
  # in [0, 2*sqrt(2)]
  R0 = R0.reshape(-1,3,3)
  R1 = R1.reshape(-1,3,3)
  dists = np.linalg.norm(np.eye(3,dtype=R0.dtype) - R0 @ R1.transpose(0,2,1), axis=(1,2))
  return dists.squeeze()

def rotm_distance_geodesic(R0, R1):
  # https://link.springer.com/article/10.1007%2Fs10851-009-0161-2
  # in [0, pi)
  R0 = R0.reshape(-1,3,3)
  R1 = R1.reshape(-1,3,3)
  RtR = R0 @ R1.transpose(0,2,1)
  aa = axisangle_from_rotm(RtR)
  S = cross_prod_mat(aa).reshape(-1,3,3)
  dists = np.linalg.norm(S, axis=(1,2))
  return dists.squeeze()

def axisangle_from_rotm(R):
  # logarithm of rotation matrix
  # R = R.reshape(-1,3,3)
  # tr = np.trace(R, axis1=1, axis2=2)
  # phi = np.arccos(np.clip((tr - 1) / 2, -1, 1))
  # scale = np.zeros_like(phi)
  # div = 2 * np.sin(phi)
  # np.divide(phi, div, out=scale, where=np.abs(div) > 1e-6)
  # A = (R - R.transpose(0,2,1)) * scale.reshape(-1,1,1)
  # aa = np.stack((A[:,2,1], A[:,0,2], A[:,1,0]), axis=1)
  # return aa.squeeze()
  R = R.reshape(-1,3,3)
  omega = np.empty((R.shape[0], 3), dtype=R.dtype)
  omega[:,0] = R[:,2,1] - R[:,1,2]
  omega[:,1] = R[:,0,2] - R[:,2,0]
  omega[:,2] = R[:,1,0] - R[:,0,1]
  r = np.linalg.norm(omega, axis=1).reshape(-1,1)
  t = np.trace(R, axis1=1, axis2=2).reshape(-1,1)
  omega = np.arctan2(r, t-1) * omega
  aa = np.zeros_like(omega)
  np.divide(omega, r, out=aa, where=r != 0)
  return aa.squeeze()

def axisangle_from_quat(q):
  q = q.reshape(-1,4)
  phi = 2 * np.arccos(q[:,0])
  denom = np.zeros_like(q[:,0])
  np.divide(1, np.sqrt(1 - q[:,0]**2), out=denom, where=q[:,0] != 1)
  axis = q[:,1:] * denom.reshape(-1,1)
  denom = np.linalg.norm(axis, axis=1).reshape(-1,1)
  a = np.zeros_like(axis)
  np.divide(phi.reshape(-1,1) * axis, denom, out=a, where=denom != 0)
  aa = a.astype(q.dtype)
  return aa.squeeze()

def axisangle_apply(aa, x):
  # working only with single aa and single x at the moment
  xshape = x.shape
  aa = aa.reshape(3,)
  x = x.reshape(3,)
  phi = np.linalg.norm(aa)
  e = np.zeros_like(aa)
  np.divide(aa, phi, out=e, where=phi != 0)
  xr = np.cos(phi) * x + np.sin(phi) * np.cross(e, x) + (1 - np.cos(phi)) * (e.T @ x) * e
  return xr.reshape(xshape)

def exp_so3(R):
  w = axisangle_from_rotm(R)
  return w

def log_so3(w):
  R = rotm_from_axisangle(w)
  return R

def exp_se3(R, t):
  R = R.reshape(-1,3,3)
  t = t.reshape(-1,3)

  w = exp_so3(R).reshape(-1,3)

  phi = np.linalg.norm(w, axis=1).reshape(-1,1,1)
  A = cross_prod_mat(w)
  Vi = np.eye(3, dtype=R.dtype) - A/2 + (1 - (phi * np.sin(phi) / (2 * (1 - np.cos(phi))))) / phi**2 * A @ A
  u = t.reshape(-1,1,3) @ Vi.transpose(0,2,1)

  # v = (u, w)
  v = np.empty((R.shape[0],6), dtype=R.dtype)
  v[:,:3] = u.squeeze()
  v[:,3:] = w

  return v.squeeze()

def log_se3(v):
  # v = (u, w)
  v = v.reshape(-1,6)
  u = v[:,:3]
  w = v[:,3:]

  R = log_so3(w)

  phi = np.linalg.norm(w, axis=1).reshape(-1,1,1)
  A = cross_prod_mat(w)
  V = np.eye(3, dtype=v.dtype) + (1 - np.cos(phi)) / phi**2 * A + (phi - np.sin(phi)) / phi**3 * A @ A
  t = u.reshape(-1,1,3) @ V.transpose(0,2,1)

  return R.squeeze(), t.squeeze()

def quat_from_rotm(R):
  R = R.reshape(-1,3,3)
  q = np.empty((R.shape[0], 4,), dtype=R.dtype)
  q[:,0] = np.sqrt( np.maximum(0, 1 + R[:,0,0] + R[:,1,1] + R[:,2,2]) )
  q[:,1] = np.sqrt( np.maximum(0, 1 + R[:,0,0] - R[:,1,1] - R[:,2,2]) )
  q[:,2] = np.sqrt( np.maximum(0, 1 - R[:,0,0] + R[:,1,1] - R[:,2,2]) )
  q[:,3] = np.sqrt( np.maximum(0, 1 - R[:,0,0] - R[:,1,1] + R[:,2,2]) )
  q[:,1] *= np.sign(q[:,1] * (R[:,2,1] - R[:,1,2]))
  q[:,2] *= np.sign(q[:,2] * (R[:,0,2] - R[:,2,0]))
  q[:,3] *= np.sign(q[:,3] * (R[:,1,0] - R[:,0,1]))
  q /= np.linalg.norm(q,axis=1,keepdims=True)
  return q.squeeze()

def quat_from_axisangle(a):
  a = a.reshape(-1, 3)
  phi = np.linalg.norm(a, axis=1)
  iphi = np.zeros_like(phi)
  np.divide(1, phi, out=iphi, where=phi != 0)
  a = a * iphi.reshape(-1,1)
  theta = phi / 2.0
  r = np.cos(theta)
  stheta = np.sin(theta)
  q = np.stack((r, stheta*a[:,0], stheta*a[:,1], stheta*a[:,2]), axis=1)
  q /= np.linalg.norm(q, axis=1).reshape(-1,1)
  return q.squeeze()

def quat_identity(n=1, dtype=np.float32):
  q = np.zeros((n,4), dtype=dtype)
  q[:,0] = 1
  return q.squeeze()

def quat_conjugate(q):
  shape = q.shape
  q = q.reshape(-1,4).copy()
  q[:,1:] *= -1
  return q.reshape(shape)

def quat_product(q1, q2):
  # q1 . q2 is equivalent to R(q1) @ R(q2)
  shape = q1.shape
  q1, q2 = q1.reshape(-1,4), q2.reshape(-1, 4)
  q = np.empty((max(q1.shape[0], q2.shape[0]), 4), dtype=q1.dtype)
  a1,b1,c1,d1 = q1[:,0], q1[:,1], q1[:,2], q1[:,3]
  a2,b2,c2,d2 = q2[:,0], q2[:,1], q2[:,2], q2[:,3]
  q[:,0] = a1 * a2 - b1 * b2 - c1 * c2 - d1 * d2
  q[:,1] = a1 * b2 + b1 * a2 + c1 * d2 - d1 * c2
  q[:,2] = a1 * c2 - b1 * d2 + c1 * a2 + d1 * b2
  q[:,3] = a1 * d2 + b1 * c2 - c1 * b2 + d1 * a2
  return q.squeeze()

def quat_apply(q, x):
  xshape = x.shape
  x = x.reshape(-1, 3)
  qshape = q.shape
  q = q.reshape(-1, 4)

  p = np.empty((x.shape[0], 4), dtype=x.dtype)
  p[:,0] = 0
  p[:,1:] = x

  r = quat_product(quat_product(q, p), quat_conjugate(q))
  if r.ndim == 1:
    return r[1:].reshape(xshape)
  else:
    return r[:,1:].reshape(xshape)

def quat_random(rng=None, n=1):
  # http://planning.cs.uiuc.edu/node198.html
  if rng is not None:
    u = rng.uniform(0, 1, size=(3,n))
  else:
    u = np.random.uniform(0, 1, size=(3,n))
  q = np.array((
    np.sqrt(1 - u[0]) * np.sin(2 * np.pi * u[1]),
    np.sqrt(1 - u[0]) * np.cos(2 * np.pi * u[1]),
    np.sqrt(u[0]) * np.sin(2 * np.pi * u[2]),
    np.sqrt(u[0]) * np.cos(2 * np.pi * u[2])
  )).T
  q /= np.linalg.norm(q,axis=1,keepdims=True)
  return q.squeeze()

def quat_distance_angle(q0, q1):
  # https://math.stackexchange.com/questions/90081/quaternion-distance
  # https://link.springer.com/article/10.1007%2Fs10851-009-0161-2
  q0 = q0.reshape(-1,4)
  q1 = q1.reshape(-1,4)
  dists = np.arccos(np.clip(2 * np.sum(q0 * q1, axis=1)**2 - 1, -1, 1))
  return dists

def quat_distance_normdiff(q0, q1):
  # https://link.springer.com/article/10.1007%2Fs10851-009-0161-2
  # \phi_4
  # [0, 1]
  q0 = q0.reshape(-1,4)
  q1 = q1.reshape(-1,4)
  return 1 - np.sum(q0 * q1, axis=1)**2

def quat_distance_mineucl(q0, q1):
  # https://link.springer.com/article/10.1007%2Fs10851-009-0161-2
  # http://users.cecs.anu.edu.au/~trumpf/pubs/Hartley_Trumpf_Dai_Li.pdf
  q0 = q0.reshape(-1,4)
  q1 = q1.reshape(-1,4)
  diff0 = ((q0 - q1)**2).sum(axis=1)
  diff1 = ((q0 + q1)**2).sum(axis=1)
  return np.minimum(diff0, diff1)

def quat_slerp_space(q0, q1, num=100, endpoint=True):
  q0 = q0.ravel()
  q1 = q1.ravel()
  dot = q0.dot(q1)
  if dot < 0:
    q1 *= -1
    dot *= -1
  t = np.linspace(0, 1, num=num, endpoint=endpoint, dtype=q0.dtype)
  t = t.reshape((-1,1))
  if dot > 0.9995:
    ret = q0 + t * (q1 - q0)
    return ret
  dot = np.clip(dot, -1, 1)
  theta0 = np.arccos(dot)
  theta = theta0 * t
  s0 = np.cos(theta) - dot * np.sin(theta) / np.sin(theta0)
  s1 = np.sin(theta) / np.sin(theta0)
  return (s0 * q0) + (s1 * q1)

def cart_to_spherical(x):
  shape = x.shape
  x = x.reshape(-1,3)
  y = np.empty_like(x)
  y[:,0] = np.linalg.norm(x, axis=1)  # r
  y[:,1] = np.arccos(x[:,2] / y[:,0]) # theta
  y[:,2] = np.arctan2(x[:,1], x[:,0]) # phi
  return y.reshape(shape)

def spherical_to_cart(x):
  shape = x.shape
  x = x.reshape(-1,3)
  y = np.empty_like(x)
  y[:,0] = x[:,0] * np.sin(x[:,1]) * np.cos(x[:,2])
  y[:,1] = x[:,0] * np.sin(x[:,1]) * np.sin(x[:,2])
  y[:,2] = x[:,0] * np.cos(x[:,1])
  return y.reshape(shape)

def spherical_random(r=1, n=1):
  # http://mathworld.wolfram.com/SpherePointPicking.html
  # https://math.stackexchange.com/questions/1585975/how-to-generate-random-points-on-a-sphere
  x = np.empty((n,3))
  x[:,0] = r
  x[:,1] = 2 * np.pi * np.random.uniform(0,1, size=(n,))
  x[:,2] = np.arccos(2 * np.random.uniform(0,1, size=(n,)) - 1)
  return x.squeeze()

def color_pcl(pcl, K, im, color_axis=0, as_int=True, invalid_color=[0,0,0]):
  uvd = K @ pcl.T
  uvd /= uvd[2]
  uvd = np.round(uvd).astype(np.int32)
  mask = np.logical_and(uvd[0] >= 0, uvd[1] >= 0)
  color = np.empty((pcl.shape[0], 3), dtype=im.dtype)
  if color_axis == 0:
    mask = np.logical_and(mask, uvd[0] < im.shape[2])
    mask = np.logical_and(mask, uvd[1] < im.shape[1])
    uvd = uvd[:,mask]
    color[mask,:] = im[:,uvd[1],uvd[0]].T
  elif color_axis == 2:
    mask = np.logical_and(mask, uvd[0] < im.shape[1])
    mask = np.logical_and(mask, uvd[1] < im.shape[0])
    uvd = uvd[:,mask]
    color[mask,:] = im[uvd[1],uvd[0], :]
  else:
    raise Exception('invalid color_axis')
  color[np.logical_not(mask),:3] = invalid_color
  if as_int:
    color = (255.0 * color).astype(np.int32)
  return color

def center_pcl(pcl, robust=False, copy=False, axis=1):
  if copy:
    pcl = pcl.copy()
  if robust:
    mu = np.median(pcl, axis=axis, keepdims=True)
  else:
    mu = np.mean(pcl, axis=axis, keepdims=True)
  return pcl - mu

def to_homogeneous(x):
  # return np.hstack((x, np.ones((x.shape[0],1),dtype=x.dtype)))
  return np.concatenate((x, np.ones((*x.shape[:-1],1),dtype=x.dtype)), axis=-1)

def from_homogeneous(x):
  return x[:,:-1] / x[:,-1]

def project_uvn(uv, Ki=None):
  if uv.shape[1] == 2:
    uvn = to_homogeneous(uv)
  else:
    uvn = uv
  if uvn.shape[1] != 3:
    raise Exception('uv should have shape Nx2 or Nx3')
  if Ki is None:
    return uvn
  else:
    return uvn @ Ki.T

def project_uvd(uv, depth, K=np.eye(3), R=np.eye(3), t=np.zeros((3,1)), ignore_negative_depth=True, return_uvn=False):
  Ki = np.linalg.inv(K)

  if ignore_negative_depth:
    mask = depth >= 0
    uv = uv[mask,:]
    d = depth[mask]
  else:
    d = depth.ravel()

  uv1 = to_homogeneous(uv)

  uvn1 = uv1 @ Ki.T
  xyz = d.reshape(-1,1) * uvn1
  xyz = (xyz - t.reshape((1,3))) @ R

  if return_uvn:
    return xyz, uvn1
  else:
    return xyz

def project_depth(depth, K, R=np.eye(3,3), t=np.zeros((3,1)), ignore_negative_depth=True, return_uvn=False):
  u, v = np.meshgrid(range(depth.shape[1]), range(depth.shape[0]))
  uv = np.hstack((u.reshape(-1,1), v.reshape(-1,1)))
  return project_uvd(uv, depth.ravel(), K, R, t, ignore_negative_depth, return_uvn)

def project_xyz(xyz, K=np.eye(3), R=np.eye(3,3), t=np.zeros((3,1))):
  uvd = K @ (R @ xyz.T + t.reshape((3,1)))
  uvd[:2] /= uvd[2]
  return uvd[:2].T, uvd[2]

def relative_motion(R0, t0, R1, t1, Rt_from_global=True):
  t0 = t0.reshape((3,1))
  t1 = t1.reshape((3,1))
  if Rt_from_global:
    Rr = R1 @ R0.T
    tr = t1 - Rr @ t0
  else:
    Rr = R1.T @ R0
    tr = R1.T @ (t0 - t1)
  return Rr, tr.ravel()

def translation_to_cameracenter(R, t):
  t = t.reshape(-1,3,1)
  R = R.reshape(-1,3,3)
  C = -R.transpose(0,2,1) @ t
  return C.squeeze()

def cameracenter_to_translation(R, C):
  C = C.reshape(-1,3,1)
  R = R.reshape(-1,3,3)
  t = -R @ C
  return t.squeeze()

def decompose_projection_matrix(P, return_t=True):
  if P.shape[0] != 3 or P.shape[1] != 4:
    raise Exception('P has to be 3x4')
  M = P[:, :3]
  C = -np.linalg.inv(M) @ P[:, 3:]

  R,K = np.linalg.qr(np.flipud(M).T)
  K = np.flipud(K.T)
  K = np.fliplr(K)
  R = np.flipud(R.T)

  T = np.diag(np.sign(np.diag(K)))
  K = K @ T
  R = T @ R

  if np.linalg.det(R) < 0:
    R *= -1

  K /= K[2,2]
  if return_t:
    return K, R, cameracenter_to_translation(R, C)
  else:
    return K, R, C

def compose_projection_matrix(K=np.eye(3), R=np.eye(3,3), t=np.zeros((3,1))):
  return K @ np.hstack((R, t.reshape((3,1))))

def point_plane_distance(pts, plane):
  pts = pts.reshape(-1,3)
  return np.abs(np.sum(plane[:3] * pts, axis=1) + plane[3]) / np.linalg.norm(plane[:3])

def fit_plane(pts):
  pts = pts.reshape(-1,3)
  center = np.mean(pts, axis=0)
  A = pts - center
  u, s, vh = np.linalg.svd(A, full_matrices=False)
  # if pts.shape[0] > 100:
  #   import ipdb; ipdb.set_trace()
  plane = np.array([*vh[2], -vh[2].dot(center)])
  return plane

def tetrahedron(dtype=np.float32):
  verts = np.array([
    (np.sqrt(8/9), 0, -1/3), (-np.sqrt(2/9), np.sqrt(2/3), -1/3),
    (-np.sqrt(2/9), -np.sqrt(2/3), -1/3), (0, 0, 1)], dtype=dtype)
  faces = np.array([(0,1,2), (0,2,3), (0,1,3), (1,2,3)], dtype=np.int32)
  normals = -np.mean(verts, axis=0) + verts
  normals /= np.linalg.norm(normals, axis=1).reshape(-1,1)
  return verts, faces, normals

def cube(dtype=np.float32):
  verts = np.array([
    [-0.5,-0.5,-0.5], [-0.5,0.5,-0.5], [0.5,0.5,-0.5], [0.5,-0.5,-0.5],
    [-0.5,-0.5,0.5], [-0.5,0.5,0.5], [0.5,0.5,0.5], [0.5,-0.5,0.5]], dtype=dtype)
  faces = np.array([
    (0,1,2), (0,2,3), (4,5,6), (4,6,7),
    (0,4,7), (0,7,3), (1,5,6), (1,6,2),
    (3,2,6), (3,6,7), (0,1,5), (0,5,4)], dtype=np.int32)
  normals = -np.mean(verts, axis=0) + verts
  normals /= np.linalg.norm(normals, axis=1).reshape(-1,1)
  return verts, faces, normals

def octahedron(dtype=np.float32):
  verts = np.array([
    (+1,0,0), (0,+1,0), (0,0,+1),
    (-1,0,0), (0,-1,0), (0,0,-1)], dtype=dtype)
  faces = np.array([
    (0,1,2), (1,2,3), (3,2,4), (4,2,0),
    (0,1,5), (1,5,3), (3,5,4), (4,5,0)], dtype=np.int32)
  normals = -np.mean(verts, axis=0) + verts
  normals /= np.linalg.norm(normals, axis=1).reshape(-1,1)
  return verts, faces, normals

def icosahedron(dtype=np.float32):
  p = (1 + np.sqrt(5)) / 2
  verts = np.array([
    (-1,0,p), (1,0,p), (1,0,-p), (-1,0,-p),
    (0,-p,1), (0,p,1), (0,p,-1), (0,-p,-1),
    (-p,-1,0), (p,-1,0), (p,1,0), (-p,1,0)
    ], dtype=dtype)
  faces = np.array([
    (0,1,4), (0,1,5), (1,4,9), (1,9,10), (1,10,5), (0,4,8), (0,8,11), (0,11,5),
    (5,6,11), (5,6,10), (4,7,8), (4,7,9),
    (3,2,6), (3,2,7), (2,6,10), (2,10,9), (2,9,7), (3,6,11), (3,11,8), (3,8,7),
    ], dtype=np.int32)
  normals = -np.mean(verts, axis=0) + verts
  normals /= np.linalg.norm(normals, axis=1).reshape(-1,1)
  return verts, faces, normals

def xyplane(dtype=np.float32, z=0, interleaved=False):
  if interleaved:
    eps = 1e-6
    verts = np.array([
      (-1,-1,z), (-1,1,z), (1,1,z),
      (1-eps,1,z), (1-eps,-1,z), (-1-eps,-1,z)], dtype=dtype)
    faces = np.array([(0,1,2), (3,4,5)], dtype=np.int32)
  else:
    verts = np.array([(-1,-1,z), (-1,1,z), (1,1,z), (1,-1,z)], dtype=dtype)
    faces = np.array([(0,1,2), (0,2,3)], dtype=np.int32)
  normals = np.zeros_like(verts)
  normals[:,2] = -1
  return verts, faces, normals

def mesh_independent_verts(verts, faces, normals=None):
  new_verts = []
  new_normals = []
  for f in faces:
    new_verts.append(verts[f[0]])
    new_verts.append(verts[f[1]])
    new_verts.append(verts[f[2]])
    if normals is not None:
      new_normals.append(normals[f[0]])
      new_normals.append(normals[f[1]])
      new_normals.append(normals[f[2]])
  new_verts = np.array(new_verts)
  new_faces = np.arange(0, faces.size, dtype=faces.dtype).reshape(-1,3)
  if normals is None:
    return new_verts, new_faces
  else:
    new_normals = np.array(new_normals)
    return new_verts, new_faces, new_normals

def stack_mesh(verts, faces):
  n_verts = 0
  mfaces = []
  for idx, f in enumerate(faces):
    mfaces.append(f + n_verts)
    n_verts += verts[idx].shape[0]
  verts = np.vstack(verts)
  faces = np.vstack(mfaces)
  return verts, faces

def normalize_mesh(verts):
  # all the verts have unit distance to the center (0,0,0)
  return verts / np.linalg.norm(verts, axis=1, keepdims=True)

def mesh_triangle_areas(verts, faces):
  a = verts[faces[:,0]]
  b = verts[faces[:,1]]
  c = verts[faces[:,2]]
  x = np.empty_like(a)
  x = a - b
  y = a - c
  t = np.empty_like(a)
  t[:,0] = (x[:,1] * y[:,2] - x[:,2] * y[:,1]);
  t[:,1] = (x[:,2] * y[:,0] - x[:,0] * y[:,2]);
  t[:,2] = (x[:,0] * y[:,1] - x[:,1] * y[:,0]);
  return np.linalg.norm(t, axis=1) / 2

def subdivde_mesh(verts_in, faces_in, n=1):
  for iter in range(n):
    verts = []
    for v in verts_in:
      verts.append(v)
    faces = []
    verts_dict = {}
    for f in faces_in:
      f = np.sort(f)
      i0,i1,i2 = f
      v0,v1,v2 = verts_in[f]

      k = i0*len(verts_in)+i1
      if k in verts_dict:
        i01 = verts_dict[k]
      else:
        i01 = len(verts)
        verts_dict[k] = i01
        v01 = (v0 + v1) / 2
        verts.append(v01)

      k = i0*len(verts_in)+i2
      if k in verts_dict:
        i02 = verts_dict[k]
      else:
        i02 = len(verts)
        verts_dict[k] = i02
        v02 = (v0 + v2) / 2
        verts.append(v02)

      k = i1*len(verts_in)+i2
      if k in verts_dict:
        i12 = verts_dict[k]
      else:
        i12 = len(verts)
        verts_dict[k] = i12
        v12 = (v1 + v2) / 2
        verts.append(v12)

      faces.append((i0,i01,i02))
      faces.append((i01,i1,i12))
      faces.append((i12,i2,i02))
      faces.append((i01,i12,i02))

    verts_in = np.array(verts, dtype=verts_in.dtype)
    faces_in = np.array(faces, dtype=np.int32)
  return verts_in, faces_in

def mesh_adjust_winding_order(verts, faces, normals):
  n0 = normals[faces[:,0]]
  n1 = normals[faces[:,1]]
  n2 = normals[faces[:,2]]
  fnormals = (n0 + n1 + n2) / 3

  v0 = verts[faces[:,0]]
  v1 = verts[faces[:,1]]
  v2 = verts[faces[:,2]]

  e0 = v1 - v0
  e1 = v2 - v0
  fn = np.cross(e0, e1)

  dot = np.sum(fnormals * fn, axis=1)
  ma = dot < 0

  nfaces = faces.copy()
  nfaces[ma,1], nfaces[ma,2] = nfaces[ma,2], nfaces[ma,1]

  return nfaces

def pcl_to_shapecl(verts, colors=None, shape='cube', width=1.0):
  if shape == 'tetrahedron':
    cverts, cfaces, _ = tetrahedron()
  elif shape == 'cube':
    cverts, cfaces, _ = cube()
  elif shape == 'octahedron':
    cverts, cfaces, _ = octahedron()
  elif shape == 'icosahedron':
    cverts, cfaces, _ = icosahedron()
  else:
    raise Exception('invalid shape')

  sverts = np.tile(cverts, (verts.shape[0], 1))
  sverts *= width
  sverts += np.repeat(verts, cverts.shape[0], axis=0)

  sfaces = np.tile(cfaces, (verts.shape[0], 1))
  sfoffset = cverts.shape[0] * np.arange(0, verts.shape[0])
  sfaces += np.repeat(sfoffset, cfaces.shape[0]).reshape(-1,1)

  if colors is not None:
    scolors = np.repeat(colors, cverts.shape[0], axis=0)
  else:
    scolors = None

  return sverts, sfaces, scolors


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Nov  3 03:11:49 2019

@author: sameerhussain
"""
import math
import matplotlib.pyplot as plt
from scipy import integrate
#import scipy.stats
import numpy

sig = 12.4
mew = 73.4
x = 90 #for 90%

def gauss(x, sd, mu):
    fx = (1/(sd*math.sqrt(2*math.pi))) * (math.exp((-(x-mu)**2)/(2*sd**2)))
    return (fx)

dx = lambda x: (1/(sig*math.sqrt(2*math.pi))) * (math.exp((-(x-mew)**2)/(2*sig**2)))

pts = []

for i in range(101):
    pts.append(gauss(i, sig, mew))

print(pts)

plt.plot(pts)
plt.xlim(0,105)
plt.show()

print("Yes this is a reasonable distribution to represent grades as there is a distinct, appropriate mean and scores that are centered around a normal distribution bell curve")

#integration from 2 places

def gauss_integrate(x):
    v = gauss(x, sig, mew)
    return v

x1 = mew + sig
x2 = mew + 2.0 * sig

#res = quad(gauss(), x1, x2)
#print("Integration from {} and {} -->".format(x1,x2), res)
#x2 = lambda x: x**2
#r = integrate.quad(x2, 0, 4)[0]
#print(r)
res1 = integrate.quad(dx, -numpy.inf, 90)[0]
err = integrate.quad(dx, -numpy.inf, 90)[1]
ans1 = 1-res1
#print(res1, res2, ans1, ans2)
print("\nThe probability of scoring 90% is", ans1)

students = (ans1 * 48) //1
print("In class of 48 students, ", students, " students will score above a 90")

#ans = 1-res1
#print("The probability of scoring a 90% is", ans)


import numpy as np

from calie.aux import matrices
from calie.fields import queries as qr

def initialise_jacobian(input_vf):
    d = qr.check_is_vf(input_vf)
    sh = list(input_vf.shape)
    while len(sh) < 5:
        sh.extend([1])
    sh[-1] = d ** 2

    return np.zeros(sh, dtype=np.float64)

def compute_jacobian(input_vf, affine=np.eye(4), is_lagrangian=False):
    """
    :param input_vf: input vecgor field
    :param affine: The affine transformation optionally associated to the field.
    :param is_lagrangian: if the identity matrix should be added to each jacobian matrix
    See itk documentation:
    http://www.itk.org/Doxygen/html/classitk_1_1DisplacementFieldJacobianDeterminantFilter.html

    On the diagonal it possess the sample distances for each dimension.
    Jacobian matrix at each point of the grid is stored in a vector of size 9 in row major order.
    """
    # TODO It works only for svf (1 time point) - provisional - do with multiple time point
    d = qr.check_is_vf(input_vf)

    jacobian = initialise_jacobian(input_vf)

    dims = []

    for i in range(d):
        dims.append(affine[i, i])

    output_data = jacobian.squeeze()

    for i in range(d):
        grad = np.gradient(np.squeeze(input_vf[..., i]), *dims)

        for j in range(d):
            output_data[..., i * d + j] = grad[j].squeeze()

    if is_lagrangian:
        jacobian += matrices.id_matrix_field(input_vf.shape[:d])

    return jacobian

def compute_jacobian_determinant(input_vf, is_lagrangian=False):
    """
    :param input_vf: The Field or children whose jacobian we need to compute.
    :param is_lagrangian: add the identity to the jacobian matrix before the computation of the
    jacobian determinant.
    If it is none, it is allocated.
    Jacobian matrix at each point of the grid is stored in a vector of size 9 in row major order.
    !! It works only for 1 time point - provisional !!
    """
    d = qr.check_is_vf(input_vf)

    vf_jacobian = compute_jacobian(input_vf, is_lagrangian=is_lagrangian)

    sh = list(input_vf.shape)
    while len(sh) < 5:
        sh.extend([1])
    sh = sh[:-1]

    sh.extend([d, d])

    v = vf_jacobian.reshape(sh)
    return np.linalg.det(v)

def jacobian_product(vf_left, vf_right):
    """
    Compute the jacobian product between two 2d or 3d SVF self and right: J_{self}(right)
    the results is a second SVF.
    :param vf_left : svf
    :param vf_right: svf
    :return: J_{right}(left) : jacobian product between 2 svfs
    """
    left  = np.copy(vf_left)
    right = np.copy(vf_right)

    result_array = matrices.matrix_vector_field_product(compute_jacobian(right), left)

    return result_array

def iterative_jacobian_product(vf, n):
    """
    :param vf: input SVF
    :param n: number of iterations
    :return: a new SVF defined by the jacobian product J_v^(n-1) v
    """
    jv_field = compute_jacobian(vf)[...]
    v_field = vf[...]

    jv_n_prod_v = matrices.matrix_vector_field_product(matrices.matrix_fields_product_iterative(jv_field, n-1), v_field)

    return jv_n_prod_v

def lie_bracket(vf_left, vf_right):
    """
    Compute the Lie bracket of two velocity fields.

    Parameters:
    -----------
    :param vf_left: Left velocity field.
    :param vf_right: Right velocity field.
    Order of Lie bracket: [left,right] = Jac(left)*right - Jac(right)*left
    :return Return the resulting velocity field
    """

    return jacobian_product(vf_left, vf_right) - jacobian_product(vf_right, vf_left)


# performance_metrics.py
# By: Miles Izydorczak
# Date: 22 September 2020
# Purpose: Exports calc_mean_squared_error and calc_mean_absolute_error
#          helper functions.
#

import numpy as np

def calc_mean_squared_error(y_N, yhat_N):
    ''' Compute the mean squared error given true and predicted values

    Args
    ----
    y_N : 1D array, shape (N,)
        Each entry represents 'ground truth' numeric response for an example
    yhat_N : 1D array, shape (N,)
        Each entry representes predicted numeric response for an example

    Returns
    -------
    mse : scalar float
        Mean squared error performance metric
        .. math:
            mse(y, \hat{y}) = \frac{1}{N} \sum_{n=1}^N (y_n - \hat{y}_n)^2

    Examples
    --------
    >>> y_N = np.asarray([-2, 0, 2], dtype=np.float64)
    >>> yhat_N = np.asarray([-4, 0, 2], dtype=np.float64)
    >>> calc_mean_squared_error(y_N, yhat_N)
    1.3333333333333333
    '''
    mse = 0.0
    N = y_N.shape[0]
    for y in range(N):
        difference = y_N[y] - yhat_N[y]
        difference_squared = difference ** 2
        mse += difference_squared
    if N > 0:
        mse = mse / float(N)
    return mse

def calc_mean_absolute_error(y_N, yhat_N):
    ''' Compute the mean absolute error given true and predicted values

    Args
    ----
    y_N : 1D array, shape (N,)
        Each entry represents 'ground truth' numeric response for an example
    yhat_N : 1D array, shape (N,)
        Each entry representes predicted numeric response for an example

    Returns
    -------
    mae : scalar float
        Mean absolute error performance metric
        .. math:
            mae(y, \hat{y}) = \frac{1}{N} \sum_{n=1}^N | y_n - \hat{y}_n |

    Examples
    --------
    >>> y_N = np.asarray([-2, 0, 2], dtype=np.float64)
    >>> yhat_N = np.asarray([-4, 0, 2], dtype=np.float64)
    >>> calc_mean_absolute_error(y_N, yhat_N)
    0.6666666666666666
    '''

    mae = 0.0
    N = y_N.shape[0]
    for y in range(N):
        difference = abs(y_N[y] - yhat_N[y])
        mae += difference
    if (N > 0):
        mae = mae / float(N)

    return mae

#### UNIT TESTING ####

# y_N = np.asarray([-2, 0, 2], dtype=np.float64)
# yhat_N = np.asarray([-4, 0, 2], dtype=np.float64)
# mae = calc_mean_absolute_error(y_N, yhat_N)
# print('MAE 1: ', mae)
# mse = calc_mean_squared_error(y_N, yhat_N)
# print('MSE 1: ', mse)

# y_N = np.asarray([1, 3, 10], dtype=np.float64)
# yhat_N = np.asarray([1, 0, 10], dtype=np.float64)
# mae = calc_mean_absolute_error(y_N, yhat_N)
# print('MAE 2: ', mae)
# mse = calc_mean_squared_error(y_N, yhat_N)
# print('MSE 2: ', mse)

# y_N = np.asarray([5, 3, 10], dtype=np.float64)
# yhat_N = np.asarray([1, 0, 10], dtype=np.float64)
# mae = calc_mean_absolute_error(y_N, yhat_N)
# print('MAE 3: ', mae)
# mse = calc_mean_squared_error(y_N, yhat_N)
# print('MSE 3: ', mse)

# y_N = np.asarray([], dtype=np.float64)
# yhat_N = np.asarray([], dtype=np.float64)
# mae = calc_mean_absolute_error(y_N, yhat_N)
# print('MAE 3: ', mae)
# mse = calc_mean_squared_error(y_N, yhat_N)
# print('MSE 3: ', mse)


import numpy as np
import matplotlib.pyplot as plt

def affine(x1,x2,y1,y2):
    a = (y2 - y1) / (x2 - x1)
    b = y1 - a * x1
    return lambda x : a * x + b

def find(x, l):
    s = len(l)
    if s == 0:
        return -1
    for i in range (s):
        if x <  l[i]:
            return i - 1
    return s - 1

def interpolation(xs, ys, x):
    l = len(xs)
    if x < xs[0]:
        return ys[0]
    elif x > xs[l-1]:
        return ys[l-1]
    else:
        i = find(x, xs)
        f = affine(xs[i], xs[i+1], ys[i], ys[i+1])
        return f(x)

class LinearSpline:
    def __init__(self):
        self.T = []
        self.X = []

    def add_entry(self, t, x):
        i = find(t, self.T)
        self.T.insert(i+1,t)
        self.X.insert(i+1,x)

    def interpolate(self, t):
        return interpolation(self.T, self.X, t)

class LinearSpline3D:
    def __init__(self):
        self.X = LinearSpline()
        self.Y = LinearSpline()
        self.Z = LinearSpline()

    def add_entry(self, t, x, y ,z):
        self.X.add_entry(t, x)
        self.Y.add_entry(t, y)
        self.Z.add_entry(t, z)

    def interpolate(self, t):
        return self.X.interpolate(t), self.Y.interpolate(t), self.Z.interpolate(t)


"""
pyart.util.circular_stats
=========================

Functions for computing statistics on circular (directional) distributions.

.. autosummary::
    :toctree: generated/

    angular_mean
    angular_std
    angular_mean_deg
    angular_std_deg
    interval_mean
    interval_std

"""

import numpy as np

# For details on these computation see:
# https://en.wikipedia.org/wiki/Directional_statistics
# https://en.wikipedia.org/wiki/Mean_of_circular_quantities

def angular_mean(angles):
    """
    Compute the mean of a distribution of angles in radians.

    Parameters
    ----------
    angles : array like
        Distribution of angles in radians.

    Returns
    -------
    mean : float
        The mean angle of the distribution in radians.

    """
    angles = np.asanyarray(angles)
    x = np.cos(angles)
    y = np.sin(angles)
    return np.arctan2(y.mean(), x.mean())

def angular_std(angles):
    """
    Compute the standard deviation of a distribution of angles in radians.

    Parameters
    ----------
    angles : array like
        Distribution of angles in radians.

    Returns
    -------
    std : float
        Standard deviation of the distribution.

    """
    angles = np.asanyarray(angles)
    x = np.cos(angles)
    y = np.sin(angles)
    norm = np.sqrt(x.mean()**2 + y.mean()**2)
    return np.sqrt(-2 * np.log(norm))

def angular_mean_deg(angles):
    """
    Compute the mean of a distribution of angles in degrees.

    Parameters
    ----------
    angles : array like
        Distribution of angles in degrees.

    Returns
    -------
    mean : float
        The mean angle of the distribution in degrees.

    """
    return np.rad2deg(angular_mean(np.deg2rad(angles)))

def angular_std_deg(angles):
    """
    Compute the standard deviation of a distribution of angles in degrees.

    Parameters
    ----------
    angles : array like
        Distribution of angles in degrees.

    Returns
    -------
    std : float
        Standard deviation of the distribution.

    """
    return np.rad2deg(angular_std(np.deg2rad(angles)))

def interval_mean(dist, interval_min, interval_max):
    """
    Compute the mean of a distribution within an interval.

    Return the average of the array elements which are interpreted as being
    taken from a circular interval with endpoints given by interval_min and
    interval_max.

    Parameters
    ----------
    dist : array like
        Distribution of values within an interval.
    interval_min, interval_max : float
        The endpoints of the interval.

    Returns
    -------
    mean : float
        The mean value of the distribution

    """
    # transform distribution from original interval to [-pi, pi]
    half_width = (interval_max - interval_min) / 2.
    center = interval_min + half_width
    a = (np.asarray(dist) - center) / (half_width) * np.pi

    # compute the angular mean and convert back to original interval
    a_mean = angular_mean(a)
    return (a_mean * (half_width) / np.pi) + center

def interval_std(dist, interval_min, interval_max):
    """
    Compute the standard deviation of a distribution within an interval.

    Return the standard deviation of the array elements which are interpreted
    as being taken from a circular interval with endpoints given by
    interval_min and interval_max.

    Parameters
    ----------
    dist : array_like
        Distribution of values within an interval.
    interval_min, interval_max : float
        The endpoints of the interval.

    Returns
    -------
    std : float
        The standard deviation of the distribution.

    """
    # transform distribution from original interval to [-pi, pi]
    half_width = (interval_max - interval_min) / 2.
    center = interval_min + half_width
    a = (np.asarray(dist) - center) / (half_width) * np.pi

    # compute the angular standard dev. and convert back to original interval
    a_std = angular_std(a)
    return (a_std * (half_width) / np.pi)


import numpy as np
import matplotlib.pyplot as plt
import matplotlibex as plx
import numerical.numpyext.linalg as ntlinalg

def gauss_pdf(xs, mean, covar):
    if len(xs.shape) == 1:
        xs = xs[np.newaxis, :]
    precision = np.linalg.inv(covar)
    z = np.sqrt(np.linalg.det(2*np.pi*covar))
    e = np.exp(-0.5 * np.sum((xs-mean) * precision.dot(xs.T-mean).T, axis=1))
    return 1.0/z * e

def product_of_gaussians(means, covars):
    M = len(means)
    assert len(means) == len(covars)
    precisions = [np.linalg.inv(c) for c in covars]
    precision = np.sum(np.stack(precisions), axis=0)
    covar = np.linalg.inv(precision)
    precisionsmeans = [p.dot(m) for p, m in zip(precisions, means)]
    precisionsmeanssum = np.sum(np.stack(precisionsmeans), axis=0)
    mean = covar.dot(precisionsmeanssum)

    k = np.prod([gauss_pdf(m[np.newaxis, :], 0.0, c) for m, c in zip(means, covars)]) / \
            gauss_pdf(mean[np.newaxis, :], 0.0, covar)
    scale = 1.0 / k
    return mean, covar, scale

if __name__ == "__main__":
    np.random.seed(6)
    N = 100
    X = np.linspace(-5, 5, N)[:, np.newaxis]
    mean = np.array([1.0])
    covar = np.array([[1.0]])
    g = gauss_pdf(X, mean, covar)
    print(np.sum(g)*(X[1]-X[0]))

    fig = plt.figure(figsize=(5, 5))
    plt.plot(X, g)
    plt.show()

    means = [np.array([-1.0]), np.array([1.0])]
    covars = [np.array([[1.0]]), np.array([[1.0]])]
    m, c, s = product_of_gaussians(means, covars)
    print(m, c, s)
    a = gauss_pdf(means[0] - means[1], 0, covars[0]+covars[1])
    print(1.0/a)

    fig = plt.figure(figsize=(5, 5))
    g0 = gauss_pdf(X, means[0], covars[0])
    g1 = gauss_pdf(X, means[1], covars[1])
    plt.plot(X, g0)
    plt.plot(X, g1)
    plt.plot(X, g0*g1)
    plt.plot(X, g0*g1*s, "*")
    plt.plot(X, gauss_pdf(X, m, c))
    plt.show()


#COEN 129 HW 3
#Yi Fang
#Nicholas Fong
#5-9-17
#Coded in Python 3.4.1
#Numpy 1.8.1

import numpy as np  #for matrices
import math #for log

#calculate w using the closed for linear regression formula w = (X.T*X)^-1*X.T*y
def linReg(X, y):
    return np.dot(np.dot(np.linalg.inv(np.dot(X.T, X)), X.T), y)

#calculate w using closed form ridge regression w = (X.T*X-lambda*I)^-1*X.T*y
def ridgeReg(X, y, l):
    return np.dot(np.dot(np.linalg.inv(np.dot(X.T, X)-l*np.eye(len(X[0]))), X.T), y)

#calculate the RMSE given y and yHat. RMSE = sqrt(sum((y-yHat)^2)/n)
def RMSE(y, yHat):
    return math.sqrt(sum((y-yHat)**2)/len(y))

#evaluate if any element in w changes by more than epsilon
def convergence(w, w2, e):
    for i in range(len(w)):
        if(abs(w[i]-w2[i]) > e):
            return False
    return True

#gradient descent for either linear or ridge regression. wt+1 = wt + gradientDescent()
def gradientDescent(X, y, l, a, w):    #l = lambda, a = alpha
    return a*(np.dot(X.T, (y - np.dot(X, w))) - l * w)

#Section 0: Get and parse the data -----------------------------------
#get the data
trainingData = np.genfromtxt('C:\\Nicholas\\CSCI\\COEN 129\\crime-training-data.txt', delimiter='\t')
testData = np.genfromtxt('C:\\Nicholas\\CSCI\\COEN 129\\crime-test-data.txt', delimiter='\t')

#delete the first row because it is just the column names
trainingData = np.delete(trainingData, (0), axis=0)
testData = np.delete(testData, (0), axis=0)

#randomize the data to reduce bias
np.random.shuffle(trainingData)
np.random.shuffle(testData)

#split the X and y values where the first column are the y values
trainingX = trainingData[:,1:]
trainingY = trainingData[:,0]
testX = testData[:,1:]
testY = testData[:,0]
#add a column of 1s to the X matrices for the constant
ones = np.ones((len(trainingX),1))
trainingX = np.append(trainingX, ones, axis=1)
ones = np.ones((len(testX),1))
testX = np.append(testX, ones, axis=1)

#Section 1: Linear Regression using the closed form solution ---------
#do linear regression with the closed form solution
wClosedFormLinReg = linReg(trainingX, trainingY)

#calculate y-hat and the RMSE on training & test data
yHatLinRegTraining = np.dot(trainingX, wClosedFormLinReg)
RMSELinRegTraining = RMSE(trainingY, yHatLinRegTraining)
yHatLinRegTest = np.dot(testX, wClosedFormLinReg)
RMSELinRegTest = RMSE(testY, yHatLinRegTest)
print('Section 1: Linear Regression Closed Form Solution')
print('RMSE training data:', RMSELinRegTraining)
print('RMSE test data:', RMSELinRegTest)

#Section 2: 5-fold CV with closed form ridge regression --------------
#loop over 10 different lambda values and then 5-fold CV for each lambda
print('\nSection 2: Closed form Ridge Regression using 5-fold CV')
lambdaRMSE = np.zeros(10)   #the lowest RMSE is the ideal lambda
for i in range(10):
    Lambda = 400 / pow(2,i)
    kFoldRMSE = np.zeros(5) #RMSE for each fold
    for j in range(5):
        #split the data into validation and training data
        validationX = trainingX[len(trainingX)/5*j:len(trainingX)/5*(j+1),:]
        validationY = trainingY[len(trainingY)/5*j:len(trainingY)/5*(j+1)]
        leftX = trainingX[0:len(trainingX)/5*j,:]
        rightX = trainingX[len(trainingX)/5*(j+1):,:]
        lambdaTrainingX = np.concatenate((leftX, rightX), axis=0)
        leftY = trainingY[0:len(trainingY)/5*j]
        rightY = trainingY[len(trainingY)/5*(j+1):]
        lambdaTrainingY = np.concatenate((leftY, rightY), axis=0)

        #do ridge regression
        w = ridgeReg(lambdaTrainingX, lambdaTrainingY, Lambda)
        yHat = np.dot(validationX, w)
        kFoldRMSE[j] = RMSE(validationY, yHat)  #store the information
    lambdaRMSE[i] = np.mean(kFoldRMSE)
optimalLambda = 400 / pow(2, np.argmin(lambdaRMSE))
print('RMSE is smallest with Lambda =', optimalLambda)

#calculate ridge regression over the whole training set using the ideal lambda
w = ridgeReg(trainingX, trainingY, optimalLambda)
yHat = np.dot(testX, w)
RMSERidgeReg = RMSE(testY, yHat)
print('RMSE for ridge regression at the optimal lambda =', RMSERidgeReg)

#Section 3: Gradient descent using linear regression
wt = np.random.normal(0, 1, 96)  #initial guess of w with random gaussian numbers
iterations = 0
keepGoing = True
while keepGoing:
    iterations += 1
    wt2 = wt + gradientDescent(trainingX, trainingY, 0, pow(10,-5), wt)
    if convergence(wt, wt2, pow(10, -6)):
        keepGoing = False
    wt = wt2

#use results from gradient descent to calculate RMSE and print results
yHat = np.dot(trainingX, wt)
RSMELinGradDescTraining = RMSE(trainingY, yHat)
yHat = np.dot(testX, wt)
RSMELinGradDescTest = RMSE(testY, yHat)
print('\nSection 3: Gradient Descent with linear regression converged after', iterations, 'iterations')
print('RMSE training data:', RSMELinGradDescTraining)
print('RMSE test data:', RSMELinGradDescTest)

#Section 4: Gradient descent using ridge regression
wt = np.random.normal(0, 1, 96)  #initial guess of w with random gaussian numbers
iterations = 0
keepGoing = True
while keepGoing:
    iterations += 1
    wt2 = wt + gradientDescent(trainingX, trainingY, optimalLambda, pow(10,-5), wt)
    if convergence(wt, wt2, pow(10, -5)):
        keepGoing = False
    wt = wt2

#use results from gradient descent to calculate RMSE and print results
yHat = np.dot(trainingX, wt)
RSMERidgeGradDescTraining = RMSE(trainingY, yHat)
yHat = np.dot(testX, wt)
RSMELRidgeGradDescTest = RMSE(testY, yHat)
print('\nSection 4: Gradient Descent with ridge regression converged after', iterations, 'iterations')
print('RMSE training data:', RSMERidgeGradDescTraining)
print('RMSE test data:', RSMELRidgeGradDescTest)

COEN 129 -Machine Learning and Data Mining/COEN 129 HW 3.py


import math
from typing import Any, Callable, Dict, Iterator, List

import numpy as np
from toolz import itertoolz

def get_cosine_learning_rates(lr_min: float, lr_max: float, f: float, N: int):
    """Decay the learning rate based on a cosine schedule of frequency `f`.
    Returns a list of `N` learning rate values in the interval `[lr_min, lr_max]`.
    """
    lr = []

    for i in range(N):
        freq = f * i / N
        scaler = 0.5 * (1 + math.cos(2 * math.pi * freq))  # [0, 1]
        l = lr_min + scaler * (lr_max - lr_min)
        lr.append(l)

    return lr

def batchify(
    data: np.ndarray, batch_size: int, func: Callable[[np.ndarray], np.ndarray] = None
) -> Iterator[np.ndarray]:
    """Batchify `data`. If `func` is not None, then the emitted item is `func(batch)`.

    Args:
        data (np.ndarray): NumPy array of items to batchify.
        batch_size (int): Batch size; must be between 1 and `len(data)`.
        func (Callable[[np.ndarray], np.ndarray], optional): Optional function to apply to each emitted batch.
            Defaults to identity function.

    Returns:
        Iterator[np.ndarray]: Generator object containing batches.
    """
    if not isinstance(batch_size, int) or not (1 <= batch_size <= len(data)):
        raise ValueError(f"Batch size must be an int in [1, {data.shape[0]}].")

    if func is None:
        func = lambda x: x

    n = len(data)
    for i in range(0, n, batch_size):
        yield func(data[i : min(i + batch_size, n)])

def split_data(
    data: List[Any], train_f: float, test_f: float, shuffle: bool = False
) -> Dict[str, List[Any]]:
    """Get `train / test / valid` splits from `data`.
    If `shuffle` is True, then use a random permutation of `data`.
    `valid` split size is given by `(1 - train_f - test_f) * len(data)`.

    Args:
        data (List[Any]): Any collection of items to be split.
        train_f (float): Train size factor from the entire length (must be between 0 and 1).
        test_f (float): Test size factor from the entire length (must be between 0 and 1).
        shuffle (bool): Whether to use a random permutation of `data`.

    Returns:
        Dict[str, List[Any]]: Keys are {train, test, valid}, and values are corresponding splits
    """
    n = len(data)

    # use a generator to keep offset internally when taking elements
    if shuffle:
        rand_idx = np.random.permutation(n)
        gen = (data[i] for i in rand_idx)
    else:
        gen = (x for x in data)

    return {
        "train": list(itertoolz.take(int(n * train_f), gen)),  # take first
        "test": list(itertoolz.take(int(n * test_f), gen)),  # take next
        "valid": list(gen),  # take remaining
    }

if __name__ == "__main__":
    lrs = get_cosine_learning_rates(lr_min=1e-5, lr_max=1e-3, f=2, N=100)
    for lr in lrs:
        print(f"{lr:.7f}")


import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit

r_val = [4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768]  # Rank, x
n_val = [96499, 26057, 10501, 4183, 2861, 913, 509, 298, 158, 85, 31, 19, 8, 3]  # Occurances, y

log_r_val = [np.log(r) for r in r_val]  # Log values, x
log_n_val = [np.log(n) for n in n_val]  # Log values, y

#print(len(r_val), len(n_val), len(log_r_val), len(log_n_val))  # Debugging information
#print(r_val, "\n", n_val, "\n", log_r_val, "\n", log_n_val)

def z(r, A, z):  # Zipf's Law
	return A*(r**z)

def f(x, m, b):  # Linear Equation
	return m*x + b

plt.scatter(r_val, n_val, label="(1a) Linear-Linear Scatter Plot")  # 1a
plt.xlabel("r")
plt.ylabel("n")

#plt.scatter(log_r_val, n_val, label="(1b) Log-Linear Scatter Plot")  # 1b
#plt.xlabel("log r")
#plt.ylabel("n")

#plt.scatter(r_val, log_n_val, label="(1c) Linear-Log Scatter Plot")  # 1c
#plt.xlabel("r")
#plt.ylabel("log n")

#plt.scatter(log_r_val, log_n_val, label="(1d) Log-Log Scatter Plot")  # 1d / 2a
#plt.xlabel("log r")
#plt.ylabel("log n")

#param, cov = curve_fit(f, log_r_val, log_n_val)  # 2b, Line of best Fit
#print(*param)  # -1.073121586396731 12.394727328954843
#domain = np.arange(log_r_val[0], log_r_val[-1], 0.1)
#plt.plot(domain, f(domain, *param), label="Line of Best Fit", color="red")

param, cov = curve_fit(z, r_val, n_val)  # 3, Curve of best fit
print(*param)  # 1126022.8400433052 -1.7748319987527623
domain = np.arange(r_val[0], r_val[-1], 0.1)
plt.plot(domain, z(domain, *param), label="Curve of Best Fit", color="red")

plt.show()


import numpy as np
import sys

def Swap(a,Row):
    n=len(a)
    r=Row
    a=np.array(a)
    a=a.astype(float)
    m=int(np.size(a)/n)
    num=1
    while (a[r][r]==0):
        if (r==n-1):
            a[[n-1, 0],:]=a[[0, n-1],:]
        else:
            a[[r+num, r],:]=a[[r, r+num],:]
        num=num+1
        if num==n:
            return a,0
    return a,1

def Rank_m(a):
    n=len(a)
    m=int(np.size(a)/n)
    a=np.array(a)
    no_zero_rows=len(np.where(~a.any(axis=1))[0])
    coeff_mat=np.zeros((n,m-1))
    for i in range(n):
        for j in range(m-1):
            coeff_mat[i][j]=a[i][j]
    no_zero_rows2=len(np.where(~coeff_mat.any(axis=1))[0])
    rank1=n-no_zero_rows
    rank2=n-no_zero_rows2
    return rank1,rank2

def Clmn_r(a,rmv):
    n=len(a)
    m=int((np.size(a))/n)
    new_a=np.zeros((n,m-1))
    for i in range(n):
        for j in range(1,m,1):
            new_a[i][j-1]=a[i][j]
    return new_a

def Gauss_Elimination(a):
    n=len(a)
    m=int(np.size(a)/len(a))
    flag=1
    for i in range(n):
        if a[i][i]==0:
            print("row interchange")
            a,p=Swap(a,i)
            print(a)
            if p==0:
                print("removing redundant column")
                a=Clmn_r(a,i)
                m=m-1
        for j in range(i+1,n): #elements lower than pivot element moving columnwise
            r=a[j][i]/a[i][i]  #ratio according to the columnn
            for k in range(m):
                a[j][k]=round((a[j][k]-(r*a[i][k])),3) #row operation
            print("intermediate steps")
            print(np.array(a))
            rank1,rank2=Rank_m(a)
            if rank1<n:
                return a,rank1,rank2
    return a,rank1,rank2

def back_subs(a,n):
    x=np.zeros([n])
    x[n-1] =round(a[n-1][n]/a[n-1][n-1],4) #solving last row
    for i in range(n-2,-1,-1):     #moving upwards
        x[i] = a[i][n]             #assigning last column value to x
        for j in range(i+1,n):     #solving for each row
            x[i] = x[i] - a[i][j]*x[j]  #subtracting the values from last column
        x[i] = round(x[i]/a[i][i],4)    #dividing it by appropriate pivot value
    return x

Mat=[[-4,1,0,1],
         [1,-5,1,-2],
         [0,1,-3,1]]

#Mat=[[6,8,1,3],[3,7,9,1],[4,6,1,2],[3,3,8,1]]   #overdetermined
#Mat=[[5,7,2,3,6],[8,4,6,1,1,0],[7,4,5,6,7]]        #undetermined

n=len(Mat)
m=int(np.size(Mat)/n)
coeff_mat=np.zeros((n,m-1))

print("Coefficient Matrix:")
for i in range(n):
    for j in range(m-1):
        coeff_mat[i][j]=Mat[i][j]
print(np.array(coeff_mat))

print("Augmented Matrix")
print(np.array(Mat))

ans,r1,r2=Gauss_Elimination(Mat)

print("Reduced Row Echelon Form")
print(np.array(ans))
print("Rank (Augmented Matrix): ",r1)
print("Rank (Coefficient Matrix): ",r2)

if m-1==r1: #no. of variable is equal to rank
    x=back_subs(ans,r1)
    print("Unique Solution")
    for i in range(len(x)):
        print("x"+str(i+1),":",x[i])
elif(m-1<r1):
    print("Overdetermined")
else:
    print("Underdetermined")


#!/usr/bin/python
# -*- coding: utf-8 -*-

from sympy import *
from sympy.abc import *

# compute stencil for FE Δ

xi,xi1,xi2,xi3 = symbols('xi xi1 xi2 xi3')

##########################################################################
### 1D element contributions for linear Lagrange ansatz function (phi) ###
def phi0(xi):
  return (1-xi)

def phi1(xi):
  return xi

print "1D stencil"
print "=========="

# left (center) node
integrand = diff(phi0(xi),xi)**2
entry0 = integrate(-integrand,(xi,0,1))

# right node
integrand = diff(phi0(xi),xi)*diff(phi1(xi),xi)
entry1 = integrate(-integrand,(xi,0,1))

print "element contribution:"
print "[",entry0,entry1,"]"

print "nodal stencil:"
print "[",entry1,2*entry0,entry1,"]"

########################################################
### 1D element contributions for cubic Hermite (phi) ###

def phi0(xi):
  return 1 - 3*xi*xi + 2*xi*xi*xi

def phi1(xi):
  return xi * (xi-1) * (xi-1)      # xi*xi*xi - 2*xi*xi + xi

def phi2(xi):
  return xi*xi * (3 - 2*xi)        # -2*xi*xi*xi + 3*xi*xi

def phi3(xi):
  return xi*xi * (xi-1)              # xi*xi*xi - xi*xi

print "1D stencil Hermite"
print "=========="

integrand = diff(phi0(xi),xi) * diff(phi2(xi),xi)
entry02 = integrate(-integrand,(xi,0,1))

integrand = diff(phi1(xi),xi) * diff(phi2(xi),xi)
entry12 = integrate(-integrand,(xi,0,1))

integrand = diff(phi2(xi),xi) * diff(phi2(xi),xi)
entry22 = integrate(-integrand,(xi,0,1))

integrand = diff(phi3(xi),xi) * diff(phi2(xi),xi)
entry32 = integrate(-integrand,(xi,0,1))

integrand = diff(phi0(xi),xi) * diff(phi3(xi),xi)
entry03 = integrate(-integrand,(xi,0,1))

integrand = diff(phi1(xi),xi) * diff(phi3(xi),xi)
entry13 = integrate(-integrand,(xi,0,1))

integrand = diff(phi2(xi),xi) * diff(phi3(xi),xi)
entry23 = integrate(-integrand,(xi,0,1))

integrand = diff(phi3(xi),xi) * diff(phi3(xi),xi)
entry33 = integrate(-integrand,(xi,0,1))

print "entry 02: ", entry02, "=", float(entry02)
print "entry 12: ", entry12, "=", float(entry12)
print "entry 22: ", entry22, "=", float(entry22)
print "entry 32: ", entry32, "=", float(entry32)
print ""
print "entry 03: ", entry03, "=", float(entry03)
print "entry 13: ", entry13, "=", float(entry13)
print "entry 23: ", entry23, "=", float(entry23)
print "entry 33: ", entry33, "=", float(entry33)

# linear line in geometry, given by Hermite ansatz functions
c0,c1,c2,c3 = symbols('c0,c1,c2,c3')
xa,xb = symbols('xa,xb')

def x1(t):
  return (xb-xa)*t + xa
def x2(t):
  return c0*phi0(t) + c1*phi1(t) + c2*phi2(t) + c3*phi3(t)

print "x(t) =",factor(x2(t),t)
p1 = Poly(x1(t), t)
coeffs1 = p1.all_coeffs()
p2 = Poly(x2(t), t)
coeffs2 = p2.all_coeffs()
print "coefficients of x1: ", coeffs1
print "coefficients of x2: ", coeffs2

print "solve for c0,c1,c2,c3"
result = solve([coeffs2[0], coeffs2[1], coeffs2[2] - coeffs1[0], coeffs2[3] - coeffs1[1]],[c0,c1,c2,c3])
print result
print "test:"
p2 = x2(t)
print p2
print ""
print factor(p2.subs(result),t)
print ""
print "conclusion: to represent a 1D linear geometry in an element between xa and xb, use the following nodal dofs:"
print "node0: dof0 (c0):",result[c0]
print "node0: dof1 (c1):",result[c1]
print "node1: dof0 (c2):",result[c2]
print "node1: dof1 (c3):",result[c3]

##########################################################################
### 2D element contributions for linear Lagrange ansatz function (phi) ###

# 2 3
# 0 1

def phi0(xi1,xi2):
  return (1-xi1)*(1-xi2)

def phi1(xi1,xi2):
  return xi1*(1-xi2)

def phi2(xi1,xi2):
  return (1-xi1)*xi2

def phi3(xi1,xi2):
  return xi1*xi2

print "2D stencil"
print "=========="

# lower left node
integrand = diff(phi0(xi1,xi2),xi1)**2 + diff(phi0(xi1,xi2),xi2)**2
entry00 = integrate(-integrand,(xi1,0,1),(xi2,0,1))      # -2/3

# lower right node
integrand = diff(phi0(xi1,xi2),xi1)*diff(phi1(xi1,xi2),xi1) + diff(phi0(xi1,xi2),xi2)*diff(phi1(xi1,xi2),xi2)
entry01 = integrate(-integrand,(xi1,0,1),(xi2,0,1))      # 1/6

# upper left node
integrand = diff(phi0(xi1,xi2),xi1)*diff(phi2(xi1,xi2),xi1) + diff(phi0(xi1,xi2),xi2)*diff(phi2(xi1,xi2),xi2)
entry10 = integrate(-integrand,(xi1,0,1),(xi2,0,1))      # 1/6

# upper right node
integrand = diff(phi0(xi1,xi2),xi1)*diff(phi3(xi1,xi2),xi1) + diff(phi0(xi1,xi2),xi2)*diff(phi3(xi1,xi2),xi2)
entry11 = integrate(-integrand,(xi1,0,1),(xi2,0,1))      # 1/3

print "element contribution:"
print "[",entry10,entry11,"]"
print "[",entry00,entry01,"]"

print "nodal stencil:"
print "[",entry11,2*entry10,entry11,"]"
print "[",2*entry01,4*entry00,2*entry01,"]"
print "[",entry11,2*entry10,entry11,"]"

# the element contribution stencil is
# [   1/6   1/3 ]
# [ _-2/3_  1/6 ]

# the full stencil is therefore
# [ 1/3   1/3   1/3 ]       [ 1   1   1 ]
# [ 1/3 _-8/3_  1/3 ] = 1/3 [ 1 _-8_  1 ]
# [ 1/3   1/3   1/3 ]       [ 1   1   1 ]

## analytically compute |det(DPhi(xi))|
x11,x12,x21,x22,x31,x32,x41,x42 = symbols('x11,x12,x21,x22,x31,x32,x41,x42')

def Phi(xi1,xi2):
  p1 = (1-xi1)*(1-xi2)*x11 + xi1*(1-xi2)*x21 + (1-xi1)*xi2*x31 + xi1*xi2*x41
  p2 = (1-xi1)*(1-xi2)*x12 + xi1*(1-xi2)*x22 + (1-xi1)*xi2*x32 + xi1*xi2*x42

  return [p1,p2]

matrix = Matrix([[diff(Phi(xi1,xi2)[0],xi1), diff(Phi(xi1,xi2)[0],xi2)],[diff(Phi(xi1,xi2)[1],xi1), diff(Phi(xi1,xi2)[1],xi2)]])
term = det(matrix)
print "det(DPhi(xi)):"
print collect(term,(xi1,xi2))

# lower left node
integrand = (diff(phi0(xi1,xi2),xi1)**2 + diff(phi0(xi1,xi2),xi2)**2)*term
print "00", factor(integrate(-integrand,(xi1,0,1),(xi2,0,1)))

##############################################################
# linear area in geometry, given by Hermite ansatz functions #

def phi0(xi):
  return 1 - 3*xi*xi + 2*xi*xi*xi

def phi1(xi):
  return xi * (xi-1) * (xi-1)      # xi*xi*xi - 2*xi*xi + xi

def phi2(xi):
  return xi*xi * (3 - 2*xi)        # -2*xi*xi*xi + 3*xi*xi

def phi3(xi):
  return xi*xi * (xi-1)              # xi*xi*xi - xi*xi

c00,c01,c02,c03,c10,c11,c12,c13,c20,c21,c22,c23,c30,c31,c32,c33 = symbols('c00,c01,c02,c03,c10,c11,c12,c13,c20,c21,c22,c23,c30,c31,c32,c33')
x0,x1,x2,x3 = symbols('x0,x1,x2,x3')

def x_1(xi1,xi2):
  return (1-xi1)*(1-xi2)*x0 + xi1*(1-xi2)*x1 + (1-xi1)*xi2*x2 + xi1*xi2*x3

def x_2(xi1,xi2):
  return (c00*phi0(xi1)*phi0(xi2) + c01*phi0(xi1)*phi1(xi2) + c02*phi1(xi1)*phi0(xi2) + c03*phi1(xi1)*phi1(xi2))*x0 \
    + (c10*phi2(xi1)*phi0(xi2) + c11*phi2(xi1)*phi1(xi2) + c12*phi3(xi1)*phi0(xi2) + c13*phi3(xi1)*phi1(xi2))*x1 \
    + (c20*phi0(xi1)*phi2(xi2) + c21*phi0(xi1)*phi3(xi2) + c22*phi1(xi1)*phi2(xi2) + c23*phi1(xi1)*phi3(xi2))*x2 \
    + (c30*phi2(xi1)*phi2(xi2) + c31*phi2(xi1)*phi3(xi2) + c32*phi3(xi1)*phi2(xi2) + c33*phi3(xi1)*phi3(xi2))*x3

print ""
print "x(t) =",factor(x_2(xi1,xi2),[xi1,xi2])
p1 = factor(x_1(xi1,xi2), [xi1,xi2])
p2 = factor(x_2(xi1,xi2), [xi1,xi2])
print ""
#print p1
print ""

print "factors:"
print [xi1**ex1*xi2**ex2 for ex1 in range(0,4) for ex2 in range(0,4)]
coeff1 = [p1.coeff(xi1**ex1*xi2**ex2) for ex1 in range(0,4) for ex2 in range(0,4)]
coeff2 = [p2.coeff(xi1**ex1*xi2**ex2) for ex1 in range(0,4) for ex2 in range(0,4)]

print "coefficients of x_1: ", coeff1
print "coefficients of x_2: ", coeff2

print "solve for c's"
result = solve([coeff1[i] - coeff2[i] for i in range(16)],[c00,c01,c02,c03,c10,c11,c12,c13,c20,c21,c22,c23,c30,c31,c32,c33])
print result
print "test:"
p2 = x_2(xi1,xi2)
print ""
print factor(p2.subs(result),[xi1,xi2])
print ""
print "conclusion: to represent a 1D linear geometry in an element between xa and xb, use the following nodal dofs:"
print "node0: dof0 (c0):",result[c00]
print "node0: dof1 (c1):",result[c01]
print "node1: dof0 (c2):",result[c02]
print "node1: dof1 (c3):",result[c03]

###########################################################################
### 3D element contributions for linear Lagrange ansatz function (phi)  ###

def phi0(xi1,xi2,xi3):
  return (1-xi1)*(1-xi2)*(1-xi3)

def phi1(xi1,xi2,xi3):
  return xi1*(1-xi2)*(1-xi3)

def phi2(xi1,xi2,xi3):
  return (1-xi1)*xi2*(1-xi3)

def phi3(xi1,xi2,xi3):
  return xi1*xi2*(1-xi3)

def phi4(xi1,xi2,xi3):
  return (1-xi1)*(1-xi2)*xi3

def phi5(xi1,xi2,xi3):
  return xi1*(1-xi2)*xi3

def phi6(xi1,xi2,xi3):
  return (1-xi1)*xi2*xi3

def phi7(xi1,xi2,xi3):
  return xi1*xi2*xi3

print ""
print "3D stencil"
print "=========="

print diff(phi0(xi1,xi2,xi3),xi1).subs(xi1,0.5).subs(xi2,0.5).subs(xi3,0.5)
print diff(phi0(xi1,xi2,xi3),xi2).subs(xi1,0.5).subs(xi2,0.5).subs(xi3,0.5)
print diff(phi0(xi1,xi2,xi3),xi3).subs(xi1,0.5).subs(xi2,0.5).subs(xi3,0.5)

a1 = diff(phi0(xi1,xi2,xi3),xi1)
a2 = diff(phi0(xi1,xi2,xi3),xi2)
a3 = diff(phi0(xi1,xi2,xi3),xi3)

integrand = a1*a1 + a2*a2 + a3*a3
print "integrand: ",integrand
print "integrand: ",integrand.subs(xi1,0.5).subs(xi2,0.5).subs(xi3,0.5)
print "symbolic ", integrate(integrand,(xi1,0,1),(xi2,0,1),(xi3,0,1))

# bottom lower left node
integrand = diff(phi0(xi1,xi2,xi3),xi1)*diff(phi0(xi1,xi2,xi3),xi1) + diff(phi0(xi1,xi2,xi3),xi2)*diff(phi0(xi1,xi2,xi3),xi2) + diff(phi0(xi1,xi2,xi3),xi3)*diff(phi0(xi1,xi2,xi3),xi3)
entry000 = integrate(-integrand,(xi1,0,1),(xi2,0,1),(xi3,0,1))      # -1/3

# bottom lower right node
integrand = diff(phi0(xi1,xi2,xi3),xi1)*diff(phi1(xi1,xi2,xi3),xi1) + diff(phi0(xi1,xi2,xi3),xi2)*diff(phi1(xi1,xi2,xi3),xi2) + diff(phi0(xi1,xi2,xi3),xi3)*diff(phi1(xi1,xi2,xi3),xi3)
entry001 = integrate(-integrand,(xi1,0,1),(xi2,0,1),(xi3,0,1))      # 0

# bottom upper left node
integrand = diff(phi0(xi1,xi2,xi3),xi1)*diff(phi2(xi1,xi2,xi3),xi1) + diff(phi0(xi1,xi2,xi3),xi2)*diff(phi2(xi1,xi2,xi3),xi2) + diff(phi0(xi1,xi2,xi3),xi3)*diff(phi2(xi1,xi2,xi3),xi3)
entry010 = integrate(-integrand,(xi1,0,1),(xi2,0,1),(xi3,0,1))      # 0

# bottom upper right node
integrand = diff(phi0(xi1,xi2,xi3),xi1)*diff(phi3(xi1,xi2,xi3),xi1) + diff(phi0(xi1,xi2,xi3),xi2)*diff(phi3(xi1,xi2,xi3),xi2) + diff(phi0(xi1,xi2,xi3),xi3)*diff(phi3(xi1,xi2,xi3),xi3)
entry011 = integrate(-integrand,(xi1,0,1),(xi2,0,1),(xi3,0,1))      # 1/12

# top lower left node
integrand = diff(phi0(xi1,xi2,xi3),xi1)*diff(phi4(xi1,xi2,xi3),xi1) + diff(phi0(xi1,xi2,xi3),xi2)*diff(phi4(xi1,xi2,xi3),xi2) + diff(phi0(xi1,xi2,xi3),xi3)*diff(phi4(xi1,xi2,xi3),xi3)
entry100 = integrate(-integrand,(xi1,0,1),(xi2,0,1),(xi3,0,1))      # 0

# top lower right node
integrand = diff(phi0(xi1,xi2,xi3),xi1)*diff(phi5(xi1,xi2,xi3),xi1) + diff(phi0(xi1,xi2,xi3),xi2)*diff(phi5(xi1,xi2,xi3),xi2) + diff(phi0(xi1,xi2,xi3),xi3)*diff(phi5(xi1,xi2,xi3),xi3)
entry101 = integrate(-integrand,(xi1,0,1),(xi2,0,1),(xi3,0,1))      # 1/12

# top upper left node
integrand = diff(phi0(xi1,xi2,xi3),xi1)*diff(phi6(xi1,xi2,xi3),xi1) + diff(phi0(xi1,xi2,xi3),xi2)*diff(phi6(xi1,xi2,xi3),xi2) + diff(phi0(xi1,xi2,xi3),xi3)*diff(phi6(xi1,xi2,xi3),xi3)
entry110 = integrate(-integrand,(xi1,0,1),(xi2,0,1),(xi3,0,1))      # 1/12

# top upper right node
integrand = diff(phi0(xi1,xi2,xi3),xi1)*diff(phi7(xi1,xi2,xi3),xi1) + diff(phi0(xi1,xi2,xi3),xi2)*diff(phi7(xi1,xi2,xi3),xi2) + diff(phi0(xi1,xi2,xi3),xi3)*diff(phi7(xi1,xi2,xi3),xi3)
entry111 = integrate(-integrand,(xi1,0,1),(xi2,0,1),(xi3,0,1))      # 1/12

print "element contribution:"
print "center: [",entry010,entry011,"]"
print "        [",entry000,entry001,"]"
print ""
print "top:    [",entry110,entry111,"]"
print "        [",entry100,entry101,"]"
print ""
print "nodal stencil:"
print "bottom: [",entry111,2*entry110,entry111,"]"
print "        [",2*entry101,4*entry100,2*entry101,"]"
print "        [",entry111,2*entry110,entry111,"]"
print ""
print "center: [",2*entry011,4*entry010,2*entry011,"]"
print "        [",4*entry001,8*entry000,4*entry001,"]"
print "        [",2*entry011,4*entry010,2*entry011,"]"
print ""
print "top:    [",entry111,2*entry110,entry111,"]"
print "        [",2*entry101,4*entry100,2*entry101,"]"
print "        [",entry111,2*entry110,entry111,"]"

# the element contribution stencil is
# center: [   0     1/12 ]
#         [ _-1/3_  0    ]
#
# top:    [   1/12  1/12 ]
#         [   0     1/12 ]

# the full stencil is therefore
# bottom: [   1/3    1/6   1/3  ]         [   1   2   1  ]
#         [   1/6    0     1/6  ]         [   2   0   2  ]
#         [   1/3    1/6   1/3  ]         [   1   2   1  ]
#
# center: [   1/6    0     1/6  ]         [   2   0   2  ]
#         [   0    _-8/3_  0    ] =  1/12*[   0 _-32_ 0  ]
#         [   1/6    0     1/6  ]         [   2   0   2  ]
#
# top:    [   1/3    1/6   1/3  ]         [   1   2   1  ]
#         [   1/6    0     1/6  ]         [   2   0   2  ]
#         [   1/3    1/6   1/3  ]         [   1   2   1  ]


import numpy as np
import matplotlib.pyplot as plt
import seaborn as sb

delta=0.000000000000000000001
def entropy(Y):
    """
    Also known as Shanon Entropy
    Reference: https://en.wikipedia.org/wiki/Entropy_(information_theory)
    """
    unique, count = np.unique(Y, return_counts=True, axis=0)
    prob = count / len(Y)
    en = np.sum((-1) * prob * np.log2(prob))
    return en

# Joint Entropy
def jEntropy(Y, X):
    """
    H(Y;X)
    Reference: https://en.wikipedia.org/wiki/Joint_entropy
    """
    YX = np.c_[Y, X]
    #print(YX)
    #print(len(YX))
    return entropy(YX)

# Conditional Entropy
def cEntropy(Y, X):
    """
    conditional entropy = Joint Entropy - Entropy of X
    H(Y|X) = H(Y;X) - H(X)
    Reference: https://en.wikipedia.org/wiki/Conditional_entropy
    """
    return jEntropy(Y, X) - entropy(X)

def getConditionalProba4Positive(A,B):
    count=0.0
    total=0.0
    i=0
    for value in A:
        if(value==1 and B.at[i] ==1):
            count=count+1
            total=total+1
        elif (value==1 and B.at[i]==0):
            total=total+1
        i=i+1
    return count/(total+delta),total

def getConditionalProba4Negative(A,B):
    count=0.0
    total=0.0
    i=0
    for value in A:
        if(value==0 and B.at[i] ==0):
            count=count+1
            total=total+1
        elif (value==0 and B.at[i]==1):
            total=total+1
        i=i+1
    return count/(total+delta), total

def getConditionalProbability(Y1,Y2):
    pfsP, lengthP = getConditionalProba4Positive(Y1, Y2)
    pfsN, lengthN = getConditionalProba4Negative(Y1, Y2)
    conditionalProb=(lengthP/(lengthP+lengthN))*(1-pfsP)+(lengthN/(lengthP+lengthN))*(1-pfsN)
    return conditionalProb

def getMissingratio(Y):
    Ydropped = Y.dropna()  # , inplace=True)
    droppedindex = Y[~Y.index.isin(Ydropped.index)].index.values
    #print(len(droppedindex)/len(Y))
    return (len(droppedindex)/len(Y))


import numpy as np

from math_utils import *
from log_utils import *

def exact_vs_approx():
  exact = lambda k, n, a: G(n+1)/G(n+1-1/a)*G(n-k+1-1/a)/G(n-k+1)
  approx = lambda k, n, a: (1 - k/n)**(-1/a)

  def print_():
    s = ''
    for k in np.arange(6, 20, 2):
      n_l = np.arange(k+1, 2*k, 2)
      s += '\multirow{' + str(len(n_l)) + '}{*}{' + str(k) + '}'
      for n in n_l:
        s += ' & {}'.format(n) if n == k+1 else '  & {}'.format(n)
        for a in np.arange(2, 10, 1):
          exact_v = exact(k, n, a)
          approx_v = approx(k, n, a)
          approx_err = round(100*abs(exact_v-approx_v)/exact_v, 2)
          # print("k= {}, n= {}, a= {}, approx_err= {}".format(k, n, a, approx_err) )
          s += ' & {}'.format(approx_err)
        s += ' \\\\ \n'
      s += '\hline\n'
    print(s)

  def plot_():
    k_n__exact_approx_l_m = {}
    a_l = np.linspace(2, 10, 10)
    for k in np.arange(10, 20, 1):
      for n in np.arange(k, 2*k, 3):
        exact_l, approx_l = [], []
        for a in a_l:
          exact_l.append(exact(k, n, a) )
          approx_l.append(approx(k, n, a) )
        k_n__exact_approx_l_m[(k, n)] = {'exact_l': exact_l, 'approx_l': approx_l}

    nrows = len(k_n__exact_approx_l_m)
    fig, axs = plot.subplots(nrows, 1, sharex='col')
    figsize = [4, nrows*3]
    i = 0
    for (k, n), y_m in k_n__exact_approx_l_m.items():
      ax = axs[i]
      plot.sca(ax)

      plot.plot(a_l, y_m['exact_l'], label='Exact', color='black', marker='.', linestyle='-', mew=2, ms=5)
      plot.plot(a_l, y_m['approx_l'], label='Approx', color='blue', marker='x', linestyle=':', mew=2, ms=5)

      plot.ylabel(r'$\alpha$', fontsize=14)
      plot.title('k= {}, n= {}'.format(k, n), fontsize=14)

      i += 1
    fig.set_size_inches(figsize[0], figsize[1] )
    plot.subplots_adjust(wspace=0.25) # , hspace=0.1
    fig.patch.set_alpha(0.5)
    plot.savefig('plot_exact_vs_approx.png', bbox_inches='tight')
    fig.clear()

  print_()
  # plot_()

  log(WARNING, "done.")

if __name__ == "__main__":
  exact_vs_approx()


import numpy as np
from numpy import math
from matplotlib import pyplot as plt

x = [-1.5, -1., -.5, 0., .5, 1., 1.5, .75, -.75]
y = [0., -1., -2., -2., -2., -1., 0., 2., 2.]

A = np.array([x, y])

def strc(A, x, y):
    strec = np.array([[x, 0.], [0., y]])
    newA = np.dot(strec, A)

    plt.subplot(2, 1, 1)
    plt.plot(A[0], A[1], '.')
    plt.xlim(-5, 5)
    plt.ylim(-5, 5)
    plt.subplot(2, 1, 2)
    plt.plot(newA[0], newA[1], '.')
    plt.xlim(-5, 5)
    plt.ylim(-5, 5)
    plt.show()

def rotate(A, ang):
    c, s = math.cos(ang), math.sin(ang)

    rotat = np.array([[c, -s], [s, c]])
    newA = np.dot(rotat, A)

    plt.subplot(2, 1, 1)
    plt.plot(A[0], A[1], '.')
    plt.xlim(-5, 5)
    plt.ylim(-5, 5)
    plt.subplot(2, 1, 2)
    plt.plot(newA[0], newA[1], '.')
    plt.xlim(-5, 5)
    plt.ylim(-5, 5)
    plt.show()

def shift(A, x, y):
    shift = np.array([[x], [y]])
    newA = A+shift

    plt.subplot(2, 1, 1)
    plt.plot(A[0], A[1], '.')
    plt.xlim(-5, 5)
    plt.ylim(-5, 5)
    plt.subplot(2, 1, 2)
    plt.plot(newA[0], newA[1], '.')
    plt.xlim(-5, 5)
    plt.ylim(-5, 5)
    plt.show()

def combo(A, strX, strY, ang, shiftX, shiftY):
    c, s = math.cos(ang), math.sin(ang)

    strec = np.array([[strX, 0.], [0., strY]])
    rotat = np.array([[c, -s], [s, c]])
    shift = np.array([[shiftX], [shiftY]])
    newA = rotat.dot(strec).dot(A)+shift

    plt.subplot(2, 1, 1)
    plt.plot(A[0], A[1], '.')
    plt.xlim(-8, 8)
    plt.ylim(-8, 8)
    plt.subplot(2, 1, 2)
    plt.plot(newA[0], newA[1], '.')
    plt.xlim(-8, 8)
    plt.ylim(-8, 8)
    plt.show()

def imgR(img, ang):
    m, n = img.shape[:2]
    c, s = math.cos(ang), math.sin(ang)

    q = int(max(m, n)*1.5)
    newImg = np.ones((q, q, 3))
    for i in xrange(m):
        for j in xrange(n):
            k = int(round((i-m/2)*c+(j-n/2)*-s+q/2))
            l = int(round((i-m/2)*s+(j-n/2)*c+q/2))
            newImg[k, l,:] = img[i, j,:]
    plt.imshow(newImg)
    plt.show()

def rotImg():
    img_color = plt.imread('dream.png')

    ang = 2*np.pi/8.
    imgR(img_color[:,:], ang)

    plt.imshow(img_color)
    plt.show()

if __name__ == "__main__":
    strc(A, 2, 1.5)

    ang = 3*np.pi/16.
    rotate(A, ang)

    shift(A, 2, 1.5)

    ang = 3*np.pi/4.
    combo(A, 2., 2., ang, 1., -2.)

    rotImg()


from decimal import Decimal
import plotly
import plotly.graph_objs as go
import numpy as np
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot

class Gini(object):
    def __init__(self, data_array):
        self.data_array = data_array
        self.gini_coefficient = self.generate_gini()

    def generate_gini(self):
        n = len(self.data_array)
        coefficient = Decimal(2. / n)
        constant = Decimal((n + 1.) / n)
        weighted_sum = sum([(index + 1) * item for index, item in enumerate(self.data_array)])
        gini = coefficient * weighted_sum / (self.data_array.sum()) - constant
        return float(gini)

    def get_gini(self):
        return self.gini_coefficient

class LorenzPlot(object):
    def __init__(self, plot_notebook, plot_image_path, data_array, file_name, title):
        self.plot_notebook = plot_notebook
        self.plot_image_path = plot_image_path
        self.data_array = data_array
        self.file_name = file_name
        self.title = title
        self.lorenz_data = self.generate_lorenz_curve()

    def generate_lorenz_curve(self):
        data_copy = self.data_array.copy()
        lorenz = data_copy.cumsum() / data_copy.sum()
        lorenz = np.insert(lorenz, 0, 0)
        lorenz = lorenz.astype(float)
        return lorenz

    def get_lorenz_data(self):
        return self.lorenz_data

    def generate_plot(self):
        color = np.array(['rgb(255,255,255)'] * self.lorenz_data.shape[0])
        color[self.lorenz_data < 0.51] = 'blue'
        color[self.lorenz_data >= 0.51] = 'red'

        trace = go.Bar(
            x = np.arange(self.lorenz_data.size)/(self.lorenz_data.size-1),
            y = self.lorenz_data,
            name = 'Lorenz Curve',
            marker=dict(color=color.tolist())
        )

        trace2 = go.Scatter(
            x = [0,1],
            y = [0,1],
            name = 'Equality Line'
        )

        trace3 = go.Scatter(
            x = [0, 1],
            y = [0.51, 0.51],
            name = '51% Cutoff'
        )

        layout = go.Layout(
                    title=self.title,
                    xaxis=dict(
                        title='Entities'
                    ),
                    yaxis=dict(
                        title='Distribution'
                    ),
        )

        data = [trace, trace2, trace3]
        figure = go.Figure(data=data, layout=layout)
        return figure

    def get_plot(self):
        figure = self.generate_plot()
        init_notebook_mode(connected=True)
        if self.plot_notebook and self.plot_image_path:
            plot(figure, image='png', image_filename=self.plot_image_path)
            iplot(figure, image='png', filename=self.plot_image_path)
        elif self.plot_notebook:
            iplot(figure)
        elif self.plot_image_path:
            plot(figure, image='png', filename=self.plot_image_path)
        else:
            raise Exception("No Image File Path specified for Plot Generation. Must pass plot_image_path")


import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf

sess = tf.Session()

x_vals = tf.linspace(-1., 1., 500)
target = tf.constant(0.)

l2_y_vals = tf.square(target - x_vals)
l2_y_out = sess.run(l2_y_vals)

l1_y_vals = tf.abs(target - x_vals)
l1_y_out = sess.run(l1_y_vals)

delta1 = tf.constant(0.25)
phuber1_y_vals = tf.multiply(tf.square(delta1), tf.sqrt(1. +tf.square((target - x_vals)/delta1)) - 1.)
phuber1_y_out = sess.run(phuber1_y_vals)

x_vals = tf.linspace(-3., 5., 500)
target = tf.constant(1.)
targets = tf.fill([500,], 1.)

hinge_y_vals = tf.maximum(0., 1. - tf.multiply(target, x_vals))
hinge_y_out = sess.run(hinge_y_vals)

xentropy_y_vals = - tf.multiply(target, tf.log(x_vals)) - tf.multiply((1. -target), tf.log(1. - x_vals))
xentropy_y_out = sess.run(xentropy_y_vals)

weight = tf.constant(0.5)
xentropy_weighted_y_vals = tf.nn.weighted_cross_entropy_with_logits(x_vals, targets, weight)
xentropy_weighted_y_out = sess.run(xentropy_weighted_y_vals)

x_value = sess.run(x_vals)

x_array = sess.run(x_vals)
plt.plot(x_array, l2_y_out, 'b-', label='L2 Loss')
plt.plot(x_array, l1_y_out, 'r--', label='L1 Loss')
plt.plot(x_array, phuber1_y_out, 'k-.', label='P-Huber Loss (0.25)')
plt.ylim(-0.2, 0.4)
plt.legend(loc='lower right', prop={'size': 11})
plt.title("loss functions plot")
plt.grid()
plt.show()


import numpy as np
import scipy.spatial

def cosine_similarity(A: np.ndarray, B: np.ndarray) -> np.ndarray:
    """For samples x features matrixes or 1 x features column vectors

    [
        [cosine_similarity(A[0], B[0]),              ... ,cosine_similarity(A[0], B[len(B)-1])],
        ...,
        [cosine_similarity(A[len(A)-1], B[len(B]-1), ... ,cosine_similarity(A[len(A)-1], B[len(B)-1])],
    ]

    [
        [A_1 . B_1, ... ,A_1 . B_m],
        ...
        [A_n . B_1, ... ,A_n . B_m],
    ]
    Args:
        A:
        B:

    Examples:
        >>> x = np.array([3, 45, 7, 2] )
        >>> y = np.array([2,54,13,15] )
        >>> cosine_similarity(x, y)[0]
        0.9722842517123499
        >>> assert cosine_similarity(x, y) == cosine_similarity(y, x)
        >>> cosine_similarity(x, x)[0]
        1.0
        >>> cosine_similarity(x, -x)[0]
        -1.0
        >>> cosine_similarity(x, np.zeros(x.shape))[0]
        0.0
        >>> np.testing.assert_almost_equal(cosine_similarity(x, y), 1 - scipy.spatial.distance.cosine(x, y))
        >>> a, b = np.array([x,y]), np.array([y,x])
        >>> cosine_similarity(a, a)
        array([[1.        , 0.97228425],
               [0.97228425, 1.        ]])
        >>> cosine_similarity(a, b)
        array([[0.97228425, 1.        ],
               [1.        , 0.97228425]])
        >>> np.testing.assert_almost_equal(cosine_similarity(a, b), 1 - scipy.spatial.distance.cdist(a, b, "cosine"))

    """

    # Dot product w/ normalized magnitudes
    # Note:
    #   For *vectors* A,B:
    #   - euclidean_length(B).T == euclidean_length(B)
    #   For *matrices* A,B:
    #   - length vectors multiplication broadcasting will produce a len(A) x len(B)
    #     lengths matrix s.t.
    #     `lengths[i,j] == euclidean_length(A[i]) * euclidean_length(B[j])`
    #     <=> multiplied length magnitudes corresponding to the element (A @ B.T)[i,j]

    #     lengths matrix corresponding to the multiplied lengths of corresponding column
    #     vector dot product elements in A @ B.T
    similarity = A @ B.T / (euclidean_length(A) * euclidean_length(B).T)
    return np.nan_to_num(similarity)  # Map NaN to 0

def euclidean_length(arr: np.ndarray) -> np.ndarray:
    """

    Args:
        arr:

    Examples:
        >>> x = np.array([3, 45, 7, 2] )
        >>> y = np.array([2,54,13,15] )
        >>> euclidean_length(x)[0]
        45.68369512200168
        >>> euclidean_length(y)[0]
        57.56735185849702
        >>> euclidean_length(np.array([x,y]))
        array([[45.68369512],
               [57.56735186]])

    """
    return np.linalg.norm(arr, keepdims=True, axis=len(arr.shape) - 1)


import numpy as np

#funcion que calcula la matriz resultante C después de aplicar la operacion de convolucion de A*B
def convolution(A,B):
    C = np.zeros((len(A) - 2, len(A[0]) - 2))
    suma = 0
    for i in range(len(C)):
        for j in range(len(C[0])):
            suma = 0
            for iaux in range(len(B)):
                for jaux in range(len(B[0])):
                    suma += A[i + iaux][j + jaux]*B[iaux][jaux]
            C[i][j] = suma

    return C #convolution matrix

Matrix1 = [[6,0,0,3],[8,4,9,1],[4,1,3,12],[3,2,1,100]]
Filtro = [[1,0,2],[5,0,9],[6,2,1]]
A = np.array(Matrix1)
B = np.array(Filtro)
#C = np.zeros((2,2))
convolution(A,B)
Matrix2 = [[1,2,3,4,5,6],[7,8,9,10,11,12],[0,0,1,16,17,18],[0,1,0,7,23,24],[1,7,6,5,4,3]]
Filtro2 = [[1,1,1],[0,0,0],[2,10,3]]
D = np.array(Matrix2)
E = np.array(Filtro2)
print(convolution(D,E))
#print(Matrix1)
print(A)
print(A[1][0])
print(convolution(A,B))

#prueba2


import numpy as np
import re, pprint, scipy, scipy.linalg
from numpy import linalg as LA
from numpy.linalg import inv
from scipy.linalg import hilbert, lu
from scipy.interpolate import lagrange
from fractions import Fraction

# get the shape of the data e.g. [10][5]
def getShape(data_str):
    count_semi = 0

    first_semi = data_str.find(';')                 # find first ';' in the line
    pos_semi = data_str[:first_semi].count(',')     # count amount of ',' from start of line to ';'
    count_semi += data_str.count(';')               # count amount of ';' in file

    return count_semi, pos_semi

# push contents of file to matrix
def pushToMatrix(data_str,i,j):
    current_digit = re.findall("[-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?", data_str)
    x = np.asarray(current_digit)
    ans = np.split(x,i)

    m = np.asmatrix(ans).astype(np.float64)
    return m

# convert matrix to comma-semi-colon-format
# print message to terminal & write output to file
def exportTomFormat(m):
    s = ""
    i,j = m.shape
    print "Matrix shape: [" + str(i) +" "+ str(j) +"]\n"
    for row in range(0,i):
        for col in range(0,j):
            s += str(m[row,col])
            if col == j-1:
                s
            else:
                s += ","

        s += ";"

    # write output to a file (will create file if not existing)
    # NOTE: does NOT append to file - will overwrite contents
    f = open('output_file.txt', 'w')
    f.write(s)  # python will convert \n to os.linesep
    f.close()

    print "- Written to 'output.txt' -\n\n**************************************\n"

# # sum contents of matrix
# def addMatrixElement(m):
#     mat = m.flatten().sum()
#     return mat

# sum each row
def sumOfRow(m):
    m = np.asmatrix(m)
    i,j = m.shape
    arr = []

    for row in m:
        r = 0
        r = np.sum(row)
        arr.append(r)

    return arr

# get euclidean norm of matrix
def vectorNorm(m):
    mat = m.flatten()
    norm_matrix = LA.norm(mat)                  # Euclidean norm
    return norm_matrix

# using numpy library for cond and infinity norm
def condNumberNumpy(m):
    inf_norm_matrix = LA.cond(m, np.inf)        # infinity norm inf = max(sum(abs(x), axis=1))
    return inf_norm_matrix

# own cond number fuction
# needs to be passed a matrix
def condNumber(m):
    matrix_total = 0
    inverse_matrix_total = 0

    # matrix
    for i in m:
        print "cond of row: " + str(abs(i).sum())
        if matrix_total < abs(i).sum():
            matrix_total = abs(i).sum()
    print "The largest: [" + str(matrix_total) + "]\n"

    # inverse of matrix
    inv_m = inv(m)
    for i in inv_m:
        print "cond of row: " + str(abs(i).sum())
        if inverse_matrix_total < abs(i).sum():
            inverse_matrix_total = abs(i).sum()
    print "The largest: [" + str(inverse_matrix_total) + "]\n"

    return matrix_total * inverse_matrix_total

# convert matrix elements to fractions
def convertToFractionMatrix(decimal_matrix):
    two_d_fract_array = []
    x,y = decimal_matrix.shape
    for h in decimal_matrix:
        for i in h:
            two_d_fract_array.append(Fraction(str(i)))
    fract_matrix = np.asmatrix(np.split(np.asarray(two_d_fract_array),x)) # reshape the matrix
    return fract_matrix

# create Hilbert Matrix return matrix -> fractions
# Not great - better to use convertToFractionMatrix() after calculation
def createHilbertFractionMatrix(sq_size):
    fract_hill =[]
    hill = hilbert(sq_size)                    # for decimals just use this line (from library)
    for h in hill:
        for i in h:
            fract_hill.append(Fraction(str(i)))

    reshaped_array = np.split(np.asarray(fract_hill),sq_size)
    print "The Hilbert Matrix shape: " + str(np.asarray(reshaped_array).shape) + "\n"
    return np.asmatrix(reshaped_array)

# def gausselim(A,B):
#     """
#     Solve Ax = B using Gaussian elimination and LU decomposition.
#     A = LU   decompose A into lower and upper triangular matrices
#     LUx = B  substitute into original equation for A
#     Let y = Ux and solve:
#     Ly = B --> y = (L^-1)B  solve for y using "forward" substitution
#     Ux = y --> x = (U^-1)y  solve for x using "backward" substitution
#     :param A: coefficients in Ax = B
#     :type A: numpy.ndarray of size (m, n)
#     :param B: dependent variable in Ax = B
#     :type B: numpy.ndarray of size (m, 1)
#     """
#     # LU decomposition with pivot
#     pl, u = lu(A, permute_l=True)
#     # forward substitution to solve for Ly = B
#     y = np.zeros(B.size)
#     for m, b in enumerate(B.flatten()):
#         y[m] = b
#         # skip for loop if m == 0
#         if m:
#             for n in xrange(m):
#                 y[m] -= y[n] * pl[m,n]
#         y[m] /= pl[m, m]

#     # backward substitution to solve for y = Ux
#     x = np.zeros(B.size)
#     lastidx = B.size - 1  # last index
#     for midx in xrange(B.size):
#         m = B.size - 1 - midx  # backwards index
#         x[m] = y[m]
#         if midx:
#             for nidx in xrange(midx):
#                 n = B.size - 1  - nidx
#                 x[m] -= x[n] * u[m,n]
#         x[m] /= u[m, m]
#     return x

# get permutation from LU
def getPermutation(P):
    perm = []
    for i in P:
        for j in i:
            if j == 1:
                 perm.append(i.tolist().index(j)+1)

    return perm

# three column vectors from tridiagonal matrix
def getTridiagonal(m):
    print m
    x,y = m.shape
    vector_one = []
    vector_two = []
    vector_three = []
    i = 0

    while i < y and i < x:

        vector_two.append(m[i][i])
        if i < x-1:
            vector_three.append(m[i+1][i])
        if i < y-1:
            vector_one.append(m[i][i+1])
        i+=1

    return np.asmatrix(vector_one).T, np.asmatrix(vector_two).T, np.asmatrix(vector_three).T

def countNumLen(n):
    count=0
    while(n>0):
        count=count+1
        n=n//10

if __name__ == '__main__':
    f = open('read.txt', 'r').read().strip()    # save file to string

    # print "**************************************\n"

    # x,y = getShape(f)
    # print "Data shape: ["+ str(x) +"]["+ str(y) +"]\n"
    # print "**************************************\n"

    # read_matrix = pushToMatrix(f,x,y)
    # print "The data provided as a matrix: \n"+str(read_matrix)+"\n"
    # print "**************************************\n"

    # sum_matrix = addMatrixElement(read_matrix)
    # print "Elements added together: ["+str(sum_matrix)+"]\n"
    # print "**************************************\n"

    # norm_matrix = vectorNorm(read_matrix)
    # print "Vector Norm: ["+str(norm_matrix)+"]\n"
    # print "**************************************\n"

    # cond_norm_matrix = condNumberNumpy(read_matrix)
    # print "Condition number: ["+str(cond_norm_matrix)+"]\n"
    # print "**************************************\n"

    # print "Condition number extra input test: [[.01, 98796],[.0001, 7896]]\n"
    # print "Cond = " + str(condNumber(np.asmatrix([[.01, 98796],[.0001, 7896]]))) + "\n"
    # print "**************************************\n"

    # own_cond_norm_matrix = condNumber(read_matrix)
    # print "Own condition number: ["+str(own_cond_norm_matrix)+"]\n"
    # print "**************************************\n"

    # hillbert_matrix = createHilbertFractionMatrix(3)
    # print "Hilbert Matrix fractions: \n" + str(hillbert_matrix) + "\n"
    # print "**************************************\n"

    # sum_hilbert = addMatrixElement(hillbert_matrix)
    # print "Sum of Hilbert Matrix fractions: [" + str(sum_hilbert) + "]\n"
    # print "**************************************\n"

    # print "Multiply Hilbert by inverse\n"
    # print np.round(np.dot(hilbert(4),inv(hilbert(4))))
    # print
    # print "**************************************\n"

    # print "LU Decomposition: \n"
    # LU_array = np.asarray([[1,2,3],[4,5,6],[7,8,9]])
    # b = [[6,15,24]]

    # # P = permutation matrix
    # P, L, U = scipy.linalg.lu(LU_array, permute_l=False, overwrite_a=False, check_finite=True)

    # print "Permutation Matrix: \n" + str(P) +"\n"
    # print "Permutation : " + str(getPermutation(P)) + "\n"

    # # L = lower triangle
    # print "Lower triangle: \n" + str(L) +"\n"

    # # U = upper triangle
    # print "Upper triangle: \n" + str(U) +"\n"

    # bb = P*b
    # print "bb: \n" + str(np.asmatrix(bb))

    # y = np.linalg.lstsq(L,bb)
    # print "y: \n" + str(np.asmatrix(y))
    # print "**************************************\n"

    # top_diagonal, main_diagonal, bottom_diagonal = getTridiagonal(LU_array)
    # print
    # print "Tridiagonal of Matrix - three column vectors: \n\n" + str(top_diagonal) + "\n\n" + str(main_diagonal) + "\n\n" + str(bottom_diagonal) +"\n"
    # print "**************************************\n"

    # print main_diagonal.shape
    # print bottom_diagonal.shape
    # # print np.dot(B,c)

    # print
    # print main_diagonal.shape
    # print bottom_diagonal.shape
    # print np.cross(main_diagonal.T, bottom_diagonal.T)
    # print
    # print "**************************************\n"

    # exportTomFormat(LU_array)

    # print "**************************************\n"
    # print "Cholesky: \n"

    # chol_matrix = [[4, 2, 1, 1],[2, 5, 0, 3],[1, 0, 7, 2],[1, 3, 2, 8]]
    # print np.matrix(chol_matrix)
    # print
    # # b = [[8,10,10,14]]
    # # print b
    # # print
    # c = np.linalg.cholesky(chol_matrix)
    # print c

    # print "\n**************************************\n"
    # print "Testing Hilbert: \n"
    # print condNumberNumpy(hilbert(10))
    # print LA.norm(hilbert(10), np.inf)
    # print "\n**************************************\n"
    # print "LaGrange Polynomial Interpolation: \n"
    # x = np.array(read_matrix.flatten())
    # print x
    # y = np.array(read_matrix.flatten())
    # poly = lagrange(x, y)
    # print poly
    # print "\n**************************************\n"
    # LU = np.asarray([[1,2,3],[4,5,6],[7,8,9]])
    # print condNumber(hilbert(7))

    for i in range(1,10):
        print "Hilbert = " + str(i)
        A = hilbert(i)
        print "the hilbert matrix: \n"+str(A)+"\n"
        b = sumOfRow(hilbert(i))
        print "b: "
        print b
        x = np.linalg.lstsq(A,b)
        print "AX = b: "
        print x
        print


import numpy as np
import math

# angle between two vectors
def vector_vector_angle(v1, v2):
    a1 = np.arctan2(v1[1], v1[0])
    a2 = np.arctan2(v2[1], v2[0])
    sign = 1 if a1 > a2 else -1
    angle = a1 - a2
    K = -sign * np.pi * 2
    if np.abs(K + angle) < np.abs(angle):
        angle += K
    return angle

# pair of points on lines p1,p2 and p3,p4 closest to each other
def line_line_closest(p1,p2,p3,p4,check=False):
    p1 = np.asarray(p1)
    p2 = np.asarray(p2)
    p3 = np.asarray(p3)
    p4 = np.asarray(p4)

    p13 = p1 - p3
    p43 = p4 - p3
    p21 = p2 - p1

    d1343 = np.dot(p13, p43)
    d4321 = np.dot(p43, p21)
    d1321 = np.dot(p13, p21)
    d4343 = np.dot(p43, p43)
    d2121 = np.dot(p21, p21)

    denom = d2121 * d4343 - d4321 * d4321
    if check and abs(denom) == 0:
        return None
    numer = d1343 * d4321 - d1321 * d4343

    mua = numer / denom
    mub = (d1343 + d4321 * (mua)) / d4343

    if check:
        if mua < 0 or mua > 1 or mub < 0 or mub > 1:
            return None

    pa = p1 + mua * p21
    pb = p3 + mub * p43

    return pa, pb

# point on line p1,p2 closest to point p3
def line_point_closest(p1, p2, p3):
    p1 = np.asarray(p1)
    p2 = np.asarray(p2)
    p3 = np.asarray(p3)
    u = ((p3 - p1)*(p2 - p1)).sum() / np.square(np.linalg.norm(p2 - p1))
    return p1 + u * (p2 - p1)

# point on ray p1->p2 closest to point p3
def ray_point_closest(p1, p2, p3):
    p1 = np.asarray(p1)
    p2 = np.asarray(p2)
    p3 = np.asarray(p3)
    u = ((p3 - p1)*(p2 - p1)).sum() / np.square(np.linalg.norm(p2 - p1))
    u = max(u, 0)
    return p1 + u * (p2 - p1)

# numpy multiplication order is "reversed" from openFrameworks,
# or the matrices can be transposed: A*B = B^T*A

# from 0/0 to width/height
# to -1,+1 to +1/-1 (y axis is flipped)
# note that depth is not in world units, but normalized by z_near/z_far
# designed for multiple points
def screen_to_world(screen, depth, viewport, extrinsics, camera_matrix):
    principal_point = camera_matrix[:2,2]
    screen -= principal_point - (viewport - 1) / 2
    n = len(screen)
    camera = np.array([
        2 * screen[:,0] / viewport[0] - 1,
        1 - 2 * screen[:,1] / viewport[1],
        [depth] * n
    ])
    camera = camera.T
    return camera_to_world(camera, extrinsics, viewport, camera_matrix)

def to_homogenous(points):
    ones = np.ones((len(points), 1), points.dtype)
    return np.hstack((points, ones))

def from_homogenous(xyzw):
    return xyzw[:,:3] / xyzw[:,3].reshape(-1,1)

def camera_to_world(camera, extrinsics, viewport, camera_matrix):
    mvp_matrix = get_model_view_projection_matrix(extrinsics, viewport, camera_matrix)
    world = np.matmul(to_homogenous(camera), np.linalg.inv(mvp_matrix))
    return from_homogenous(world)

def get_model_view_projection_matrix(extrinsics, viewport, camera_matrix):
    return np.matmul(get_model_view_matrix(extrinsics), get_projection_matrix(viewport, camera_matrix))

def get_model_view_matrix(extrinsics):
    return np.linalg.inv(extrinsics.T)

def perspective(fovy, aspect, z_near, z_far):
    tan_half_fovy = math.tan(fovy / 2)
    result = np.zeros((4,4))
    result[0,0] = 1 / (aspect * tan_half_fovy)
    result[1,1] = 1 / tan_half_fovy
    result[2,3] = -1
    result[2,2] = -(z_far + z_near) / (z_far - z_near)
    result[3,2] = -(2 * z_far * z_near) / (z_far - z_near)
    return result

def get_projection_matrix(viewport, camera_matrix, z_near=1, z_far=1000):
    aspect = viewport[0] / viewport[1]
    fovy = 2 * math.atan(viewport[1] / (2 * camera_matrix[1,1]))
    return perspective(fovy, aspect, z_near, z_far)

def vec_to_extrinsics(rvec,tvec):
    R, jacobian = cv2.Rodrigues(np.mat(rvec))
    arr = np.array([[R[0,0],R[0,1],R[0,2],tvec[0]],
                    [R[1,0],R[1,1],R[1,2],tvec[1]],
                    [R[2,0],R[2,1],R[2,2],tvec[2]],
                    [0,0,0,1]])
    mat = np.mat(arr)
    extrinsics = np.transpose(np.linalg.inv(mat))
    return extrinsics


from formulas import RelativeDiffusivity
import math
import json
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.colors import BoundaryNorm
from matplotlib.ticker import MaxNLocator

f = open('diffusivity.json','r')
data = json.loads(f.read())
actual = np.asarray(data)

T = [298]
P = actual[:,0]
predicted = np.zeros((len(T),len(P)))
for i in range(0,len(T)):
    for j in range(0,len(P)):
        predicted[i][j] = RelativeDiffusivity(T[i],P[j])

E1 = np.zeros(len(P))
E2 = np.zeros(len(P))
mean = np.mean(actual[:,1])
for i in range(0, len(predicted[0])):
    E1[i] = math.pow(actual[i][1] - predicted[0][i],2)
    E2[i] = math.pow(actual[i][1] - mean,2)
sumE1 = np.sum(E1)
sumE2 = np.sum(E2)
R2 = 1-sumE1/sumE2
print(R2)

assert R2 == 0.997460771783034, 'R2 value does not match expected'

fig = plt.figure(figsize=(9,5))
plt.axes([0.1,0.1,0.8,0.8], xlabel='Pressure - P (bar)', ylabel='Relative Diffusivity - D')
plt.plot(P, actual[:,1], 'gs', P, predicted[0], 'r^')
plt.legend(['Observed', f'Model $R^2 = {R2:.5g}$', 'best'])
plt.show()

# T = [298,338,373,523,573,623,673]
# P = np.linspace(0,4000,200)
# R = np.zeros((len(T),len(P)))
# for i in range(0,len(T)):
#     for j in range(0,len(P)):
#         C = U4 + U5/(U6 + T[i])
#         B = U7 + U8/T[i] + U9*T[i]
#         D1000 = U1*math.exp(U2*T[i] + U3*T[i]**2)
#         try:
#             D = D1000 + C*math.log((B + P[j])/(B + 1000))
#         except:
#             D = 0
#         R[i][j] = D

# plt.figure()
# plt.plot(P, R[0], 'r', P, R[1], 'b', P, R[2], 'g', P, R[3], 'r--', P, R[4], 'b--', P, R[5], 'g--', P, R[6], 'k')
# plt.legend(T)
# plt.show()

# print(f'Relative Diffusivity D = {D}') #78.38066291198618 @P=100 T=298.15


# -*- coding: utf-8 -*-
"""
Created on Sun Mar 25 06:45:12 2018

@author: James
"""

import numpy as np
import matplotlib.pyplot as plt

# Show the difference in distribution across different std
samples_std1 = np.random.normal(20, 1, 100000)
samples_std3 = np.random.normal(20, 3, 100000)
samples_std10 = np.random.normal(20, 10, 100000)

# Make histograms
plt.hist(samples_std1, density=True, histtype="step", bins=100)
plt.hist(samples_std3, density=True, histtype="step", bins=100)
plt.hist(samples_std10, density=True, histtype="step", bins=100)
plt.legend(("std = 1", "std = 3", "std = 10"))
plt.ylim(-0.01, 0.42)
plt.title("Visualising std Differences")
plt.show()


import numpy as np

a = np.array([[3, 0.841, -0.841],
              [2, -1250, 0],
              [-2.71, -2.71, 20]])   #gera um array aleatorio(com numeros float e negativos
b = np.array([[1.951],
              [-624.25],
              [-7.809]])

#função pro metodo de gauss:
a = a.astype('float')   #transforma a matriz em float, pois como int daria erro de arredondamento
b = b.astype('float')
##elinação:
###garantir pivos n nulos:
tam = np.size(a, 0) #Numero de elementos na coluna 0
len = len(a[0][:])
print(f'{a}\n{b}')
for i in range(tam):    #busca pivos = 0 e soma alguma linha com eles
    if a[i][i] == 0:
        for c in range(tam):    #encontra uma linha cujo elemento na coluna do pivo[i][i] != 0
            if a[c][i] != 0:
                a[i][:] = a[i][:] + a[c][:]
                b[i][:] = b[i][:] + b[c][:]
                break
###Eliminar td abaixo da diagonal principal mantendo as correlações
for k in range(len):    #coluna a ser operada
    for i in range(k + 1, tam):  # linha a ser corrigida
        base_a = a[k][:]    #linha anterior
        base_b = b[k][:]
        if a[i][k] != 0:
            m = a[i][k]/base_a[k]   #numero para multiplicar e zerar o elemento abaixo
            a[i][:] = a[i][:] - m*base_a    #opera a linha
            b[i][:] = b[i][:] - m * base_b  #opera a linha
print(f'{a}\n{b}')


"""hydrogen.py:
this file contains the implementation of the
real valued hydrogen wave functions.
"""

__author__ = 'Miguel Angel Avila Torres'
__copyright__ = 'Copyright 2021, hydro-wave-visualizer'
__credits__ = ['Miguel Angel Avila Torres']
__license__ = 'The Legacy License'
__version__ = '1.0.0'
__maintainer__ = f'{__author__}'
__email__ = None
__status__ = 'Release'

import numpy as np

a_0: float = 5.29177210903E-11  # First Bohr Radius

def hydrogen_1s(r: float, theta: float = 0, phi: float = 0):
    return np.exp(-r/a_0) / (np.sqrt(np.pi)*a_0**(3/2))

def hydrogen_2s(r: float, theta: float = 0, phi: float = 0):
    return (2 - (r / a_0)) * np.exp(-r/(2*a_0)) / (4*np.sqrt(2*np.pi)*a_0**(3 / 2))

def hydrogen_2p(r: float, theta: float = 0, phi: float = 0):
    return ((r/a_0)*np.exp(-r/(2*a_0))*np.cos(theta)) / (4*np.sqrt(2*np.pi)*a_0**(3 / 2))

def hydrogen_3s(r: float, theta: float = 0, phi: float = 0):
    return (27 - 18*(r/a_0) + 2*((r**2)/(a_0**2))) * (np.exp(-r/(3*a_0))) / (81*np.sqrt(3*np.pi)*a_0**(3/2))

def hydrogen_3p(r: float, theta: float = 0, phi: float = 0):
    return np.sqrt(2) * (6 - (r/a_0)) * (r/a_0) * np.exp(-r/(3*a_0)) * np.cos(theta) / (81*np.sqrt(np.pi)*a_0**(3/2))

def hydrogen_3d(r: float, theta: float = 0, phi: float = 0):
    return ((r**2)/(a_0**2)) * np.exp(-r/(3*a_0)) * (3*(np.cos(theta)**2) - 1) / (81*np.sqrt(6*np.pi)*a_0**(3/2))

def squared_h1s(r: float, theta: float, phi: float):
    return hydrogen_1s(r, theta, phi) ** 2

def squared_h2s(r: float, theta: float, phi: float):
    return hydrogen_2s(r, theta, phi) ** 2

def squared_h2p(r: float, theta: float, phi: float):
    return hydrogen_2p(r, theta, phi) ** 2

def squared_h3s(r: float, theta: float, phi: float):
    return hydrogen_3s(r, theta, phi) ** 2

def squared_h3p(r: float, theta: float, phi: float):
    return hydrogen_3p(r, theta, phi) ** 2

def squared_h3d(r: float, theta: float, phi: float):
    return hydrogen_3d(r, theta, phi) ** 2


from IPython.display import display
import sympy

sympy.var('offset, rho, h, z, z1, z2')

display(sympy.simplify(sympy.integrate(1, (z, -h/2+offset, h/2+offset))))

display(sympy.simplify(sympy.integrate(1, (z, z1, z2))))

display(sympy.simplify(sympy.integrate(z**2, (z, -h/2+offset, h/2+offset))))

display(sympy.simplify(sympy.integrate(z**2, (z, z1, z2))))