@doc """
### EulerEquationMod.calcIsentropicVortex

  This function calculates the isentropic vortex solution to the Euler
  equations at a node.  This function uses an inner radius of 1, an inner
  radius density of 2, an inner radius Mach number of 0.95, and an inner radius
  pressure of 1/gamma.  The denstiy as a function of radius r can be found in,
  for example,
  "Output Error Estimates for Summation-by-parts Finite-difference Schemes",
  JE Hicken.

  This function uses conservative variables regardless of the static parameter
  of params.

  Inputs:
    coords: a vector of length 2 containing the x and y coordinates of the point
    params: the params object.

  Inputs/Outputs:
    sol: vector of length 4 to be populated with the solution

  Aliasing restrictions: none

"""->
function calcIsentropicVortex(params::ParamType2,
                  coords::AbstractArray{Tmsh},
                  sol::AbstractVector{Tsol}) where {Tmsh, Tsol}
  # calculates the solution at a point of the isentropic vortex
  # 2D only

  # unpack arguments
  x = coords[1]
  y = coords[2]

  # get some values out of eqn
  cv = params.cv
  R = params.R
  gamma = params.gamma

  # the (hard coded) parameters are
  r_in = 1  # inner radius of sector of circle
  rho_in = 2 # density at inner radius
  M_in = 0.95  # Mach number
  p_in =  1/gamma

  # calculate r, theta coordinates from x,y
  r = sqrt(x*x + y*y)
  theta = atan2(y, x)  # angle in radians

  # calculate values at r radius
  tmp1 = ((gamma-1)/2)*M_in*M_in
  rho_r = rho_in*(1 + tmp1*(1- (r_in*r_in)/(r*r)))^(1/(gamma-1))

  p_r = p_in*(rho_r/rho_in)^gamma

  a_r = sqrt( gamma*p_r/rho_r )

  M_r = sqrt( (2/(gamma-1))*((rho_in/rho_r)^(gamma-1))*(1 + tmp1) - 2/(gamma-1) )
  U_r = M_r*a_r  # velocity magnitude

  u_r = U_r*sin(theta)
  v_r = -U_r*cos(theta)
  e_r = cv*p_r/(rho_r*R)
  E_r = rho_r*e_r + 0.5*rho_r*U_r*U_r

  # save solution to sol
  sol[1] = rho_r
  sol[2] = rho_r*u_r
  sol[3] = rho_r*v_r
  sol[4] = E_r

  return nothing

end

"""
  Reverse mode
"""
function calcIsentropicVortex_rev(params::ParamType2,
                  coords::AbstractArray{Tmsh}, coords_bar::AbstractVector{Tres},
                  sol_bar::AbstractVector{Tsol}) where {Tmsh, Tsol, Tres}
  # calculates the solution at a point of the isentropic vortex
  # 2D only

  # unpack arguments
  x = coords[1]
  y = coords[2]

  # get some values out of eqn
  cv = params.cv
  R = params.R
  gamma = params.gamma
  gamma_1 = params.gamma_1

  # the (hard coded) parameters are
  r_in = 1  # inner radius of sector of circle
  rho_in = 2 # density at inner radius
  M_in = 0.95  # Mach number
  p_in =  1/gamma

  # calculate r, theta coordinates from x,y
  r = sqrt(x*x + y*y)
  theta = atan2(y, x)  # angle in radians

  # calculate values at r radius
  tmp1 = ((gamma-1)/2)*M_in*M_in
  rho_r = rho_in*(1 + tmp1*(1- (r_in*r_in)/(r*r)))^(1/(gamma-1))

  p_r = p_in*(rho_r/rho_in)^gamma

  a_r = sqrt( gamma*p_r/rho_r )

  M_r = sqrt( (2/(gamma-1))*((rho_in/rho_r)^(gamma-1))*(1 + tmp1) - 2/(gamma-1) )
  U_r = M_r*a_r  # velocity magnitude

  u_r = U_r*sin(theta)
  v_r = -U_r*cos(theta)
  e_r = cv*p_r/(rho_r*R)
  E_r = rho_r*e_r + 0.5*rho_r*U_r*U_r

  # save solution to sol
  #sol[1] = rho_r
  #sol[2] = rho_r*u_r
  #sol[3] = rho_r*v_r
  #sol[4] = E_r

  #----------------------------------------------------------------------------
  # reverse sweep

  E_r_bar = zero(Tres)
  rho_r_bar = zero(Tres)
  v_r_bar = zero(Tres)
  u_r_bar = zero(Tres)
  U_r_bar = zero(Tres)
  e_r_bar = zero(Tres)
  p_r_bar = zero(Tres)
  a_r_bar = zero(Tres)
  M_r_bar = zero(Tres)
  r_bar   = zero(Tres)
  x_bar   = zero(Tres)
  y_bar   = zero(Tres)
  theta_bar = zero(Tres)

  E_r_bar   += sol_bar[4]

  rho_r_bar += v_r*sol_bar[3]
  v_r_bar   += rho_r*sol_bar[3]

  rho_r_bar += u_r*sol_bar[2]
  u_r_bar   += rho_r*sol_bar[2]

  rho_r_bar += sol_bar[1]

  # E_r through u_r
  rho_r_bar += (e_r + 0.5*U_r*U_r)*E_r_bar
  e_r_bar   += rho_r*E_r_bar
  U_r_bar   += rho_r*U_r*E_r_bar

  p_r_bar   += cv/(rho_r*R)*e_r_bar
  rho_r_bar += -e_r/rho_r*e_r_bar

  U_r_bar +=  sin(theta)*u_r_bar
  U_r_bar +=  -cos(theta)*v_r_bar
  theta_bar += U_r*sin(theta)*v_r_bar + U_r*cos(theta)*u_r_bar

  # calculating values at r radius
  a_r_bar += M_r*U_r_bar
  M_r_bar += a_r*U_r_bar

  rho_r_bar += (M_r_bar/M_r)*((rho_in/rho_r)^(gamma - 2))*(-rho_in/(rho_r*rho_r))*(1 + tmp1)
#  tmp1_bar   = M_r_bar/(M_r*gamma_1)*(rho_in/rho_r)^(gamma_1)

  p_r_bar   += (0.5/a_r)*gamma*a_r_bar/rho_r
  rho_r_bar += (0.5/a_r)*(-gamma*p_r/(rho_r*rho_r))*a_r_bar

  rho_r_bar += p_in*gamma*((rho_r/rho_in)^gamma_1)*p_r_bar/rho_in

  t2 = (rho_in/gamma_1)*(1 + tmp1*(1 - (r_in*r_in)/(r*r)))^(1/gamma_1 - 1)
#  tmp1_bar  += t2*(1 - (r_in*r_in)/(r*r))
  r_bar += rho_r_bar*t2*2*tmp1*(r_in*r_in)/(r*r*r)

  # r, theta
  theta, y_bar, x_bar = atan2_rev(y, x, theta_bar)
  x_bar += (0.5/r)*2*x*r_bar
  y_bar += (0.5/r)*2*y*r_bar

  coords_bar[1] += x_bar
  coords_bar[2] += y_bar

  return nothing
end

function calcIsentropicVortex(params::ParamType3,
                  coords::AbstractArray{Tmsh},
                  sol::AbstractVector{Tsol}) where {Tmsh, Tsol}
# calculates the solution at a point of the isentropic vortex

  # unpack arguments
  x = coords[1]; x_orig = x
  y = coords[2]; y_orig = y
  z = coords[3]; z_orig = z;

  #println("x = ", x, ", y = ", y, ", z = ", z)

  phi_z = pi/4  # angle of axis relative to z axis
  sgn = 1.0

  # calculate new x coordinate in rotated coordinate system
  theta1 = atan2(z, x)
  #println("theta1 = ", theta1)
  phi2 = 0.5*pi - theta1
  #println("phi2 = ", phi2)
  r_xz = sqrt(x*x + z*z)  # distance from origin to point in xz plane
  #println("r_xz = ", r_xz)
  x = r_xz*sin(phi_z + phi2)  # x coordinate in rotated system
  #println("x = ", x)
  #println("diff = ", abs(x - z_orig))

  theta3 = theta1 + phi_z + phi2 - 0.5*pi

  # get some values out of params
  cv = params.cv
  R = params.R
  gamma = params.gamma

  # the (hard coded) parameters are
  r_in = 1  # inner radius of sector of circle
  rho_in = 2 # density at inner radius
  M_in = 0.95  # Mach number
  p_in =  1/gamma

  # calculate r, theta coordinates from x,y
  r = sqrt(x*x + y*y)
  if phi_z > 0
    theta = atan2(x, y)
  else
    theta = atan2(y, x)
  end

  # calculate values at r radius
  tmp1 = ((gamma-1)/2)*M_in*M_in
  rho_r = rho_in*(1 + tmp1*(1- (r_in*r_in)/(r*r)))^(1/(gamma-1))
  p_r = p_in*(rho_r/rho_in)^gamma
  a_r = sqrt( gamma*p_r/rho_r )
  M_r = sqrt( (2/(gamma-1))*((rho_in/rho_r)^(gamma-1))*(1 + tmp1) - 2/(gamma-1) )
  U_r = M_r*a_r  # velocity magnitude

  if phi_z > 0
    v_r = U_r*sin(theta)
    u_r = -U_r*cos(theta)
  else
    u_r = U_r*sin(theta)
    v_r = -U_r*cos(theta)
  end
  e_r = cv*p_r/(rho_r*R)
  E_r = rho_r*e_r + 0.5*rho_r*U_r*U_r

  # convert velocities back to base frame of reference
  w_r = u_r*sin(theta3)
  u_r = u_r*cos(theta3)

  # save solution to sol
  sol[1] = rho_r
  sol[2] = rho_r*u_r
  sol[3] = rho_r*v_r
  sol[4] = rho_r*w_r
  sol[5] = E_r

  return nothing

end

@doc """
### EulerEquationMod.calcFreeStream

  This function calculates the free stream solution for an airfoil problem
  based on the angle of attack and Mach number in nondimensionalized variables.

  Density and energy are set to params.rho_free (usually 1.0) and params.E_free,
  (usually 1/(gamma*gamma_1) + 0.5*Ma*Ma), and the x and y momenta as

  rho*Ma*cos(angle of attack)  and rho*Ma*sin(angle of attack).

  The angle of attack must be in radians.

  This function uses conservative variables regardless of the static parameter
  of params.

  Inputs:
    coords: a vector of length 2 containing the x and y coordinates of the point
    params: the params object.

  Inputs/Outputs:
    sol: vector of length 4 to be populated with the solution

  Aliasing restrictions: none

"""->
function calcFreeStream(params::ParamType2,
            coords::AbstractArray{Tmsh, 1},
            sol::AbstractArray{Tsol, 1}) where {Tmsh, Tsol}
# calculate the free stream conditions using the fields of params

  rho = sol[1] = params.rho_free
  E = sol[4] = params.E_free

  Ma = params.Ma

  sol[2] = rho*Ma*cos(params.aoa)
  sol[3] = rho*Ma*sin(params.aoa)

  return nothing
end

function calcFreeStream(params::ParamType3,
            coords::AbstractArray{Tmsh, 1},
            sol::AbstractArray{Tsol, 1}) where {Tmsh, Tsol}
# calculate the free stream conditions using the fields of params

  # calculate the free stream conditions using the fields of params

  rho = sol[1] = params.rho_free
  E = sol[5] = params.E_free

  Ma = params.Ma

  sol[2] = rho*Ma*cos(params.aoa)
  sol[3] = 0.0
  sol[4] = -rho*Ma*sin(params.aoa)

  return nothing
end

"""
  Like [`calcFreeStream`](@ref), but assumes zero angle of attack, regardless
  of what the options dictionary says.
"""
function calcFreeStream0(params::ParamType2,
            coords::AbstractArray{Tmsh, 1},
            sol::AbstractArray{Tsol, 1}) where {Tmsh, Tsol}
# calculate the free stream conditions using the fields of params

  rho = sol[1] = params.rho_free
  E = sol[4] = params.E_free

  Ma = params.Ma

  sol[2] = rho*Ma
  sol[3] = 0

  return nothing
end

@doc """
### EulerEquationMod.calcFreeStream_daoa

  This function calculates the free stream solution for an airfoil problem
  based on the angle of attack and Mach number in nondimensionalized variables.

  Density and energy are set to params.rho_free (usually 1.0) and params.E_free,
  (usually 1/(gamma*gamma_1) + 0.5*Ma*Ma), and the x and y momenta as

  rho*Ma*cos(angle of attack)  and rho*Ma*sin(angle of attack).

  The angle of attack must be in radians.

  This function uses conservative variables regardless of the static parameter
  of params.

  Inputs:
    coords: a vector of length 2 containing the x and y coordinates of the point
    params: the params object.

  Inputs/Outputs:
    sol: vector of length 4 to be populated with the solution

  Aliasing restrictions: none

"""->

function calcFreeStream_dAlpha(params::ParamType2,
                   coords::AbstractArray{Tmsh, 1},
                   sol::AbstractArray{Tsol, 1}) where {Tmsh, Tsol}
# calculate the free stream conditions using the fields of params

  rho = params.rho_free
  Ma = params.Ma

  sol[2] = -rho*Ma*sin(params.aoa)
  sol[3] = -rho*Ma*cos(params.aoa)

  return nothing
end

function calcFreeStream_daoa(params::ParamType3,
                 coords::AbstractArray{Tmsh, 1},
                 sol::AbstractArray{Tsol, 1}) where {Tmsh, Tsol}

  # calculate the free stream conditions using the fields of params

  rho = params.rho_free
  Ma = params.Ma

  sol[2] = -rho*Ma*sin(params.aoa)
  sol[3] = 0.0
  sol[4] = -rho*Ma*cos(params.aoa)

  return nothing
end

@doc """
### EulerEquationMod.calcUnsteadyVortex

  This function calculates the unsteady vortex solution to the Euler equations
  at time params.t, where the vortex was centered at x = params.vortex_x0 at
  time t=0.  The analytical solution can be found in, for example,
  K. Mattsson et al./ Computers & Fluxs 36 (2007) 636-649

  This function uses conservative variables regardless of the static parameter
  of params.

  Inputs:
    coords: a vector of length 2 containing the x and y coordinates of the point
    params: the params object.

  Inputs/Outputs:
    sol: vector of length 4 to be populated with the solution

  Aliasing restrictions: none

"""->
function calcUnsteadyVortex(params::ParamType{Tdim},
          coords::AbstractArray{Tmsh, 1},
          sol::AbstractArray{Tsol, 1}) where {Tmsh, Tsol, Tdim}

  fval = vortex_f(coords, params)
  t = params.t
  epsilon = params.vortex_strength
  Ma = params.Ma
  gamma = params.gamma
  gamma_1 = params.gamma_1
  x = coords[1]
  y = coords[2]
  x0 = params.vortex_x0

  coeff1 = epsilon*epsilon*gamma_1*Ma*Ma/(8*pi*pi)
  rho = (1 -coeff1* e^(fval))^(1/gamma_1)

  coeff2 = epsilon*y/(2*pi)
  u = 1 - coeff2*e^(fval/2)

  coeff3 = epsilon*(x - x0 - t)/(2*pi)
  v = coeff3*e^(fval/2)

  q2 = rho*u
  q3 = rho*v

  # calculate E
  # the paper gives pressure, so calculate E from that and the
  # pressure expression in terms of E
  term1 = (rho^gamma)/(gamma_1*gamma*Ma*Ma)
  term2 = 0.5*(q2*q2 + q3*q3)/rho
  E = term1 + term2

  sol[1] = rho
  sol[2] = q2
  sol[3] = q3

  if Tdim == 2
    sol[4] = E
  else
    sol[4] = 0
    sol[5] = E
  end

  return nothing
end

function vortex_f(coords, params)
  t = params.t
  x = coords[1]
  y = coords[2]
  x0 = params.vortex_x0
  return 1 - ( (  (x-x0) - t)^2 + y*y)
end

"""
  Unsteady vortex for Carpenters paper "Entropy Stable Staggered Grid Spectral
  Collocation for hte Burgers and Compressible Navier-Stokes Equations

  It is similar to the other unsteady vortex, except the vortex travels
  at an angle

  Does *not* use params.Ma or params.vortex_strength, or vortex.x0

"""
function calcUnsteadyVortex2(params::ParamType{Tdim},
          coords::AbstractArray{Tmsh, 1},
          sol::AbstractArray{Tsol, 1}) where {Tmsh, Tsol, Tdim}
  t = params.t
  gamma = params.gamma
  gamma_1 = params.gamma_1
  x = coords[1]
  y = coords[2]
  x0 = 0.0
  y0 = 0
  epsilon = 5.0
  Ma = 0.5
#  alpha = 45.0  # degrees
  sind_alpha = 0.7071067811865476
  cosd_alpha = 0.7071067811865476
#  cinf = 1.0  # assumption, I think this is a free parameter
#  Uinf = Ma*cinf

  Uinf = 1.0
  cinf = Ma/Uinf

  ycoeff = y - y0 - Uinf*sind_alpha*t
  xcoeff = x - x0 - Uinf*cosd_alpha*t
  f = 1 - ( xcoeff^2 + ycoeff^2 )

  coeff1 = epsilon*epsilon*Ma*Ma*gamma_1/(8*pi*pi)
  T = 1 - coeff1*exp(f)
#  T = 1 - coeff1*e^f
  rho = T^(1/gamma_1)

#  u1 = Uinf - (epsilon*y/(2*pi))*e^(f/2)
  u1 = Uinf*cosd_alpha - (epsilon*ycoeff/(2*pi))*exp(f/2)
#  u2 = (epsilon*xcoeff/(2*pi))*e^(f/2)
  u2 = Uinf*sind_alpha + ((epsilon*xcoeff)/(2*pi))*exp(f/2)

  q2 = rho*u1
  q3 = rho*u2

#  p = rho*params.R*T
#  p = (rho^gamma)/(gamma*Ma*ma)
#  term1 = (p)/(gamma_1*gamma*Ma*Ma)
  term1 = (rho^gamma)/(gamma_1*gamma*Ma*Ma)
  term2 = 0.5*(q2*q2 + q3*q3)/rho
  E = term1 + term2

  sol[1] = rho
  sol[2] = q2
  sol[3] = q3
  sol[4] = E

  return nothing
end

@doc """
### EulerEquationMod.calcRho1Energy2

  This function sets the density at a node to 1, energy to 2 and the momenta to
  zero.

  This function uses conservative variables regardless of the static parameter
  of params.

  Inputs:
    coords: a vector of length 2 containing the x and y coordinates of the point
    params: the params object.

  Inputs/Outputs:
    sol: vector of length 4 to be populated with the solution

  Aliasing restrictions: none

"""->
function calcRho1Energy2(params::ParamType2,
             coords::AbstractArray{Tmsh, 1},
             sol::AbstractArray{Tsol,1}) where {Tmsh, Tsol}
  # for square test case with rho = 1, everything else  = 0

  sol[1] = 1.0
  sol[2] = 0.0
  sol[3] = 0.0
  sol[4] = 2.0

  return nothing
end
function calcRho1Energy2(params::ParamType3,
             coords::AbstractArray{Tmsh, 1},
             sol::AbstractArray{Tsol,1}) where {Tmsh, Tsol}
  # for square test case with rho = 1, everything else  = 0

  sol[1] = 1.0
  sol[2] = 0.0
  sol[3] = 0.0
  sol[4] = 0.0
  sol[5] = 2.0

  return nothing
end

@doc """
### EulerEquationMod.calcOnes

  This function sets all the solution variables at a node to 1.0

  This function uses conservative variables regardless of the static parameter
  of params.

  Inputs:
    coords: a vector of length 2 containing the x and y coordinates of the point
    params: the params object.

  Inputs/Outputs:
    sol: vector of length 4 to be populated with the solution

  Aliasing restrictions: none

"""->
function calcOnes(params::ParamType2,
      coords::AbstractArray{Tmsh, 1},
      sol::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  fill!(sol, 1.0)

  return nothing
end  # end function calcOnes

@doc """
### EulerEquationMod.calcZeros

  This function sets all the solution variables at a node to 0.0

  This function uses conservative variables regardless of the static parameter
  of params.

  Inputs:
    coords: a vector of length 2 containing the x and y coordinates of the point
    params: the params object.

  Inputs/Outputs:
    sol: vector of length 4 to be populated with the solution

  Aliasing restrictions: none

"""->

function calcZeros(params::ParamType2,
       coords::AbstractArray{Tmsh, 1},
       sol::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  fill!(sol, 0.0)

  return nothing
end  # end function calcZeros

@doc """
### EulerEquationMod.calcRho1Energy2U1VW0

  Sets the density values 1.0, x momentum to 1.0,
  v & w momenta to 0.0, and energy to 2.0 at a node.

  It should work for 2D and 3D meshes.

  This function uses conservative variables regardless of the static parameter
  of params.

  Inputs:
    coords: a vector of length 2 containing the x and y coordinates of the point
    params: the params object.

  Inputs/Outputs:
    sol: vector of length 4 to be populated with the solution

  Aliasing restrictions: none

"""->
function calcRho1Energy2U1VW0(params::ParamType2,
                           coords::AbstractArray{Tmsh},
                           sol::AbstractArray{Tsol, 1}) where {Tmsh, Tsol}
  # for square test case with rho = 1, digonal momentum, energy

  sol[1] = 1.0
  sol[2] = 1.0
  sol[3] = 0.0
  sol[4] = 2.0

  return nothing
end

function calcRho1Energy2U1VW0(params::ParamType3,
                           coords::AbstractArray{Tmsh},
                           sol::AbstractArray{Tsol, 1}) where {Tmsh, Tsol}
  # for square test case with rho = 1, digonal momentum, energy

  sol[1] = 1.0
  sol[2] = 1.0
  sol[3] = 0.0
  sol[4] = 0.0
  sol[5] = 2.0

  return nothing
end

@doc """
### EulerEquationMod.calcRho1Energy2U3

  Sets the density values 1.0, momenta to 0.35355, and
  energy to 2.0 at a node.

  This function uses conservative variables regardless of the static parameter
  of params.

  Inputs:
    coords: a vector of length 2 containing the x and y coordinates of the point
    params: the params object.

  Inputs/Outputs:
    sol: vector of length 4 to be populated with the solution

  Aliasing restrictions: none

"""->
function calcRho1Energy2U3(params::ParamType2,
               coords::AbstractArray{Tmsh},
               sol::AbstractArray{Tsol, 1}) where {Tmsh, Tsol}
  # for square test case with rho = 1, digonal momentum, energy

  sol[1] = 1.0
  sol[2] = 0.35355
  sol[3] = 0.35355
  sol[4] = 2.0

  return nothing
end

function calcRho1Energy2U3(params::ParamType3,
               coords::AbstractArray{Tmsh},
               sol::AbstractArray{Tsol, 1}) where {Tmsh, Tsol}
  # for square test case with rho = 1, digonal momentum, energy

  sol[1] = 1.0
  sol[2] = 0.35355
  sol[3] = 0.35355
  sol[4] = 0.35355
  sol[5] = 2.0

  return nothing
end

"""
  Alias for [`calcRho1Energy2U3`](@ref) used by initial conditions.
"""
const calcRho1E2U3 = calcRho1Energy2U3

@doc """
### EulerEquationMod.calcVortex

  Sets the density 1.0, energy to 2.0 at a node.  The momenta are calculated
  according to solid body rotation with an angular velocity of 0.5 centered
  at x = 0.

  This function uses conservative variables regardless of the static parameter
  of params.

  Inputs:
    coords: a vector of length 2 containing the x and y coordinates of the point
    params: the params object.

  Inputs/Outputs:
    sol: vector of length 4 to be populated with the solution

  Aliasing restrictions: none

"""->
function calcVortex(params::ParamType2,
        coords::AbstractArray{Tmsh,1},
        sol::AbstractArray{Tsol,1}) where {Tmsh, Tsol}
# solid body rotation
  x = coords[1]
  y = coords[2]

  r = sqrt(x*x + y*y)
  theta = atan2(y, x)

  omega = 0.5

  u_norm = omega*r

  u = -u_norm*sin(theta)
  v = u_norm*cos(theta)

  rho = 1.0
  E = 2.0

  sol[1] = rho
  sol[2] = rho*u
  sol[3] = rho*v
  sol[4] = E

  return nothing
end

"""
  Calculates a manufactured solution based on exponentials
"""
function calcExp(params::ParamType2, coords::AbstractArray{Tmsh,1},
                 q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  x = coords[1]
  y = coords[2]
  # a and b are parameters determine the scale and offset of the solution
  # the must be the same here and in the source term
  af = 1/5  # a = 1/af
  b = 0.01
  gamma_1 = params.gamma_1
  q[1] = exp(af*x*y + b)
  q[2] = exp(af*2*x*y + b)
  q[3] = exp(af*3*x*y + b)
  q[4] = (1/gamma_1 + 0.5)*exp(af*5*x*y + b) + 0.5*exp(af*3*x*y + b)

  return nothing
end

function calcExp_rev(params::ParamType2, coords::AbstractArray{Tmsh, 1},
                 coords_bar::AbstractArray{Tmsh, 1},
                 q_bar::AbstractArray{Tsol, 1}) where {Tmsh, Tsol}

  af = 1/5  # a = 1/af
  b = 0.01
  gamma_1 = params.gamma_1

  x = coords[1]
  y = coords[2]

  x_bar = zero(Tmsh)
  y_bar = zero(Tmsh)

  # some reused quantities
  t1 = (1/gamma_1 + 0.5)
  e1 = exp(af*x*y + b)
  e2 = exp(af*2*x*y + b)
  e3 = exp(af*3*x*y + b)
  e5 = exp(af*5*x*y + b)

  x_bar += (t1*af*5*y*e5 + 0.5*af*3*y*e3)*q_bar[4]
  y_bar += (t1*af*5*x*e5 + 0.5*af*3*x*e3)*q_bar[4]

  x_bar += af*3*y*e3*q_bar[3]
  y_bar += af*3*x*e3*q_bar[3]

  x_bar += af*2*y*e2*q_bar[2]
  y_bar += af*2*x*e2*q_bar[2]

  x_bar += af*y*e1*q_bar[1]
  y_bar += af*x*e1*q_bar[1]

  coords_bar[1] += x_bar
  coords_bar[2] += y_bar

  return nothing
end

function calcExp(params::ParamType3, coords::AbstractArray{Tmsh,1},
                 q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}
  x = coords[1]
  y = coords[2]
  z = coords[3]

  # constant parameters
  a = MMSExp_a
  b = MMSExp_b
  c1 = MMSExp_c1
  c2 = MMSExp_c2
  c3 = MMSExp_c3
  c4 = MMSExp_c4
  c5 = MMSExp_c5
  d1 = MMSExp_d1
  d2 = MMSExp_d2
  d3 = MMSExp_d3
  d4 = MMSExp_d4
  d5 = MMSExp_d5

  # af = 1/50, b = 0.01, c = 8, d = 0.5, f = 10 works
  gamma_1 = params.gamma_1

  t2 = exp(b);
  t3 = a*c1*x*y*z;
  q[1] = d1*t2*exp(t3);
  q[2] = d2*t2*exp(a*c2*x*y*z);
  q[3] = d3*t2*exp(a*c3*x*y*z);
  q[4] = d4*t2*exp(a*c4*x*y*z);
  q[5] = (t2*exp(-t3)*((d2*d2)*exp(a*c2*x*y*z*2.0)+(d3*d3)*exp(a*c3*x*y*z*2.0)+(d4*d4)*exp(a*c4*x*y*z*2.0))*(1.0/2.0))/d1+(d5*t2*exp(a*c5*x*y*z))/gamma_1;

  return nothing
end

"""
  Calculates a manufactured solution from Gassner, Winters, Kopriva: Split
  Form Nodal DG Schemes with SBP Propertiy for the Compressible Euler Equations.
  This is typically used with a mesh that spans [-1, 1] in all directions

"""
function calcPeriodicMMS(params::ParamType2,
             coords::AbstractArray{Tmsh,1},
             q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  x = coords[1]
  y = coords[2]
  t = params.t

  t7 = t*2.0;
  t2 = -t7+x+y;
  t3 = 3.141592653589793*t2;
  t4 = sin(t3);
  t5 = t4*(1.0/1.0E1);
  t6 = t5+2.0;
  q[1] = t6;
  q[2] = t6;
  q[3] = t6;
  q[4] = t6*t6;

  return nothing
end

function calcPeriodicMMS(params::ParamType3,
             coords::AbstractArray{Tmsh,1},
             q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  x = coords[1]
  y = coords[2]
  z = coords[3]
  t = params.t
#=
  rho = 2 + 0.1*sin(pi*(x + y + z))
#  rho = 2 + 0.5*(x*x + y*y + z*z)
  q[1] = rho
  q[2] = 0
  q[3] = 0
  q[4] = 0
  q[5] = 10
=#
  t7 = t*2.0;
  t2 = -t7+x+y+z;
  t3 = 3.141592653589793*t2;
  t4 = sin(t3);
  t5 = t4*(1.0/1.0E1);
  t6 = t5+2.0;

  q[1] = t6
  q[2] = t6
  q[3] = t6
  q[4] = t6
  q[5] = t6*t6

  return nothing
end

"""
  Manufactured condition for a channel with y in [0, 1].
  No source term.  2D only
"""
function calcChannelMMS(params::ParamType2,
            coords::AbstractArray{Tmsh,1},
            q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  R = params.R
  gamma_1 = params.gamma_1

  rho_inf = 1
  u_inf = 1
  offset = 0.1
  T_inf = 1  # make this smaller to reduce Mach number

  y = coords[2]

  q[1] = rho_inf
  q[2] = rho_inf*u_inf*( y*(1-y) + offset)
  q[3] = 0
  q[4] = 0.5*rho_inf*u_inf*u_inf*( offset - y*(y-1))^2 + (R*T_inf*rho_inf)/gamma_1

  return nothing
end

"""
  One dimensional square wave, 2D only

  The base flow is uniform, and if  -0.05 < x < 0.05, then it is perturbed
  by a small amount.
"""
function calcSquare1D(params::ParamType2,
            coords::AbstractArray{Tmsh,1},
            q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  # apply base state
  q[1] = 1.0
  q[2] = 0.3
  q[3] = 0.3
  q[4] = 5.0

  x = coords[1]
  if x > -0.05 && x < 0.05
    # apply pertubration
    q[1] += 0.1
    q[2] += 0.1
    q[3] += 0.1
    q[4] += 0.1
  end

  return nothing
end

"""
  Two dimensional square wave, 2D only

  The base flow is uniform, and if  -0.05 < x, y < 0.05, then it is perturbed
  by a small amount.
"""
function calcSquare2D(params::ParamType2,
            coords::AbstractArray{Tmsh,1},
            q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  # apply base state
  q[1] = 1.0
  q[2] = 0.3
  q[3] = 0.3
  q[4] = 5.0

  x = coords[1]
  y = coords[2]
  if x > 7.5 && x < 12.5 && y > -1.25 && y < 1.25
    # apply pertubration
    q[1] += 0.1
    q[2] += 0.1
    q[3] += 0.1
    q[4] += 0.1
  end

  return nothing
end

"""
  Sedov explosion, 2D only

  Uniform fluid at rest with energetic region in the circle of radius
  0.05 centered at the origin

"""
function calcSedovExplosion(params::ParamType2,
                        coords::AbstractArray{Tmsh,1},
                        q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  # parameters
  E = 1.0 # total amount of energy contained in the blast (not the usual E)
  nu = 2  # cylindrical blast wave
  dr = 0.05  # radius of initial blast

  q[1] = 1.0
  q[2] = 0.0
  q[3] = 0.0
  q[4] = 100*(1e-5)/params.gamma_1

  x = coords[1]
  y = coords[2]
  if x*x + y*y < dr*dr
    q[4] = 100*3*params.gamma_1*E/( (nu+1)*Float64(pi)*(dr^nu) )
  end

  return nothing
end

"""
  Initial condition for SU2 inviscid channel
"""
function calcInvChannelIC(params::ParamType2,
             coords::AbstractArray{Tmsh,1},
             q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  rho = 1.22531
  rhou = rho*170.104
  rhov = 0
  E = 101300/params.gamma_1 + 0.5*rho*(rhou*rhou + rhov*rhov)

  q[1] = rho
  q[2] = rhou
  q[3] = rhov
  q[4] = E

  return nothing
end

"""
  Free stream conditions for SU2 inviscid channel
"""
function calcInvChannelFreeStream(params::ParamType2,
                         coords::AbstractArray{Tmsh,1},
                         q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  rho = 1.22531
  rhou = rho*170.104
  rhov = 0
  E = 101100/params.gamma_1 + 0.5*rho*(rhou*rhou + rhov*rhov)

  q[1] = rho
  q[2] = rhou
  q[3] = rhov
  q[4] = E

  return nothing
end

"""
  Used for testing shock capturing scheme
"""
function calcLaplaceSolution(params::ParamType,
                         coords::AbstractArray{Tmsh,1},
                         q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  x = coords[1]; y = coords[2]
  val = x*x*x + y*y*y
  val = sin(x)*sin(2*y)
  for i=1:length(q)
    q[i] = val + i
  end

  return nothing
end

global const wedge20_tanbeta = tand(53.422940527228654)
global const tan20 = tand(20)
global const cos20 = cosd(20)
global const sin20 = sind(20)

"""
  Compute the exact solution for a supersonic wedge where the semi-angle is
  20 degrees.  The vertex of the wedge must be at x, y = (-1, 0), and the
  free stream Mach number must be 2.0
"""
function calcWedge20(params::ParamType2,
                     coords::AbstractArray{Tmsh,1},
                     q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

#  @assert params.Ma == 2.0
#  @assert params.aoa == 0.0

  # figure out if the given point is post-shock or pre-shock

  x = coords[1]; y = coords[2]; tanbeta = wedge20_tanbeta

  if (y > 0 && y > tanbeta*x + tanbeta) || (y <= 0 && y < -tanbeta*x - tanbeta)
    # pre-shock
    calcFreeStream(params, coords, q)
  else
    # computed from the oblique shock relations in Anderson's Aerodynamics
    M2 = 1.2102184008268027
    a2 = 1.1799115865336027

    q[1] = 2.0420057206059625
    q[2] = q[1]*M2*a2*cos20
    q[3] = q[1]*M2*a2*sin20
    q[4] = 7.1584095248438615

    if y < 0
      q[3] = -q[3]
    end
  end

  return nothing
end

"""
  Computes the potential flow solution for flow over a 20 degree wedge.
  Potential flow assumes incompressible flow, so the Mach number should be
  small when running this case
"""
function calcWedge20Potential(params::ParamType2,
                     coords::AbstractArray{Tmsh,1},
                     q::AbstractArray{Tsol,1}) where {Tmsh, Tsol}

  n = 9/8  # this corresponds to a 20 degree semi-angle wedge
  rprime = 2*sqrt(1 + 1.5*1.5)  # define pressure = free stream pressure at this
                                # point
  Vprime = n*params.Ma*rprime^(n-1)
  Vprime2 = Vprime*Vprime

  # Bernoulli's equation constant at rprime.  Use this for *all* streamlines
  c1 = params.p_free/params.rho_free + 0.5*Vprime2

  # the potential flow solution has the wedge geometry as:
  #  \                                    /
  #   \             rather than          /
  #    \                                /
  #     \________                ______/
  # so we transform the coordinates at the beginning of the computation
  # and then transform the velocity at the end.
  # Also, the potential flow solution as the origin at the corner of the wedge,
  # not the lower right corner

  x = -(coords[1] + 1.0); y = coords[2]

  # The potential flow solution si f(z) = z^n, where z = x + i*y, but computing
  # it this way would not be complex-stepable,  Instead apply Eulers identity

  # compute r-theta coordinates
  r = sqrt(x*x + y*y); nrn = n*params.Ma*r^(n-1)
  theta = atan2(y, x); nm1_theta = (n-1)*theta

  # compute velocity
  u = nrn*cos(nm1_theta); v = -nrn*sin(nm1_theta)

  # transform velocity from the potential flow geometry to the real geometry
  v = -v

  # compute pressure using Bernoulli's equation, then energy from it (using
  # ideal gas law)
  v_squared = u*u + v*v
  p = params.rho_free*(c1 - 0.5*v_squared)
  E = p/params.gamma_1 + 0.5*params.rho_free*v_squared

  q[1] = params.rho_free
  q[2] = params.rho_free*u
  q[3] = params.rho_free*v
  q[4] = E

  return nothing
end


using CartesianDDM
using Printf
using LinearAlgebra
using SparseArrays
using IterativeSolvers
using CartesianCutCell

dofs = (8, 12)
procs = (2, 3)
overs = (4, 2)

dims = CartesianDDMDimension.(dofs, procs, overs)
context = CartesianDDMContext(dims)

A = laplacian(dofs)
dA = decompose(context, A)

P = Diagonal(A)
dP = CartesianDDMRAS(dA)

rhs = rand(prod(dofs))
drhs = decompose(context, rhs)

u = zeros(prod(dofs))
du = decompose(context, u)

res = similar(rhs)
dres = similar(drhs)

io = stdout

str = "%12s%2s" * "%12s" ^ 2 * "\n"
fmt = Printf.Format(str)

Printf.format(io, fmt, "Step", "  ", "Diagonal", "Block diag.")

str = "%12d%2s" * "%12.5E" ^ 2 * "\n"
fmt = Printf.Format(str)

itmax = 50

for it in 1:itmax
    res .= rhs - A * u
    dres .= drhs - dA * du

    u .+= P \ res
    du .+= dP \ dres

    Printf.format(io, fmt, it, "  ", norm(res), norm(dres))
end


using FRGRealTime.MathFuns

function massflow2(k, m2, T, lambda, Nf=4)
    (1 / 6) * (Nf + 2) * lambda * TF.Fb1(k, m2, T)
end

#zero momentum lambda flow
function lambdaflow0(k, m2, T, lambda, Nf=4)
    -(1 / 6) * lambda^2 * (Nf + 8) * TF.Fb2(k, m2, T)
end

function RelambdaflowEu(
    p0,
    q0,
    k,
    m2,
    lambda,
    T,
    RelambdaEu,
    Nf = 4,
)
    -(1 / 3) * (TF.ReFb2Eu(p0, k, m2, T) * RelambdaEu^2) -
    (1 / 3) * (TF.ReFb2Eu(p0, k, m2, T) * RelambdaEu^2) -
    (1 / 6) * lambda^2 * (Nf + 4) * TF.Fb2(k, m2, T)
end

function RelambdaflowEutest(
    p0,
    q0,
    k,
    m2,
    lambda,
    T,
    RelambdaEu,
    Nf = 4,
)
    -(1 / 3) * (ReFb2Eutest(p0, k, m2, T) * RelambdaEu^2)
end

function ReFb2Eutest(p0, k, m2, T)
    -1 / 12 * k^4 / (pi^2 * Eb(k, m2)^2 * (p0^2 + 4 * Eb(k, m2)^2))
end

Applications/O(N) t-chanel Scheme2 Eu/flow.jl


function outliers{T <: Real}(x::AbstractVector{T}; range::Float64 = 1.5)
    n = length(x)
    Q1, Q3 = quantile(x, 0.25), quantile(x, 0.75)
    LIF = Q1 - range * (Q3 - Q1)
    UIF = Q3 + range * (Q3 - Q1)

    o, idc = Float64[], Int64[]

    @inbounds for i in 1:n
        if !(LIF < x[i] < UIF)
            push!(o, x[i])
            push!(idc, i)
        end
    end
    o, idc
end

function outliers{T <: Real}(x::AbstractMatrix{T})
    n,d = size(x)
    x = zscore(x)
    μ,σ = mean(x, 1), std(x, 1)
    x = x'
    Σ = cov(x, vardim = 2)
    MDist(x,μ) = (x .- μ)' * pinv(Σ) * (x .- μ)
    ChiSq(x) = sum(x .^2)
    o = Array(Int64, 0)
    for i in 1:n
        all(MDist(x[:,i], μ)[1] .> 3*σ) && push!(o, i)
        ## MDist(x[:,i], μ)[1] > ChiSq(x[:,i]) && push!(o, i)
    end
    o
end


structeq(a::T, b::T) where {T} = all(f->getfield(a, f) == getfield(b, f), fieldnames(T))

@testset "parsefunction" begin
    @test structeq(MOIU.parsefunction(:x),
                   MOIU.ParsedSingleVariable(:x))
    @test structeq(MOIU.parsefunction(:([x,y,z])),
                   MOIU.ParsedVectorOfVariables([:x,:y,:z]))

    @test structeq(MOIU.parsefunction(:(x + y + 2.0)),
                   MOIU.ParsedScalarAffineFunction(MOIU.ParsedScalarAffineTerm.([1.0,1.0],[:x,:y]), 2.0))
    @test structeq(MOIU.parsefunction(:(x + -3y + 2.0)),
                   MOIU.ParsedScalarAffineFunction(MOIU.ParsedScalarAffineTerm.([1.0,-3.0],[:x,:y]), 2.0))
    @test structeq(MOIU.parsefunction(:(2*x*y + y + 1.0)),
                   MOIU.ParsedScalarQuadraticFunction(MOIU.ParsedScalarAffineTerm.([1.0],[:y]), MOIU.ParsedScalarQuadraticTerm.([2.0],[:x],[:y]), 1.0))

    @test_throws AssertionError MOIU.parsefunction(:(x - y))

    @test structeq(MOIU.parsefunction(:([x, 2x+y+5.0])),
                   MOIU.ParsedVectorAffineFunction(MOIU.ParsedVectorAffineTerm.([1,2,2],MOIU.ParsedScalarAffineTerm.([1.0,2.0,1.0],[:x,:x,:y])), [0.0,5.0]))
    @test structeq(MOIU.parsefunction(:([x, 2x+y+5.0, 1*x*x])),
                   MOIU.ParsedVectorQuadraticFunction(MOIU.ParsedVectorAffineTerm.([1,2,2],MOIU.ParsedScalarAffineTerm.([1.0,2.0,1.0],[:x,:x,:y])), MOIU.ParsedVectorQuadraticTerm.([3],MOIU.ParsedScalarQuadraticTerm.([2.0],[:x],[:x])), [0.0,5.0,0.0]))

end

@testset "separatelabel" begin
    @test MOIU.separatelabel(:(variables: x)) == (:variables, :x)
    @test MOIU.separatelabel(:(variables: x, y)) == (:variables, :((x,y)))
    @test MOIU.separatelabel(:(minobjective: x + y)) == (:minobjective, :(x+y))
    @test MOIU.separatelabel(:(con1: 2x <= 1)) == (:con1, :(2x <= 1))
    @test MOIU.separatelabel(:(con1: [x,y] in S)) == (:con1, :([x,y] in S))
end

@MOIU.model GeneralModel (MOI.ZeroOne, MOI.Integer) (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval) (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives, MOI.SecondOrderCone, MOI.RotatedSecondOrderCone, MOI.PositiveSemidefiniteConeTriangle) () (MOI.SingleVariable,) (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction) (MOI.VectorOfVariables,) (MOI.VectorAffineFunction,)

@testset "loadfromstring" begin
    @testset "one variable" begin
        s = """
        variables: x
        bound: x >= 1.0
        """
        model = GeneralModel{Float64}()
        x = MOI.add_variable(model)
        MOI.set(model, MOI.VariableName(), x, "x")
        bound = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(1.0))
        MOI.set(model, MOI.ConstraintName(), bound, "bound")

        model2 = GeneralModel{Float64}()
        MOIU.loadfromstring!(model2, s)
        MOIU.test_models_equal(model, model2, ["x"], ["bound"])
    end

    @testset "linear constraints" begin
        s = """
        variables: x, y
        linear1: x + y >= 1.0
        linear2: x + y <= 1.0
        linear3: x + y == 1.0
        """
        model = GeneralModel{Float64}()
        x = MOI.add_variable(model)
        y = MOI.add_variable(model)
        MOI.set(model, MOI.VariableName(), x, "x")
        MOI.set(model, MOI.VariableName(), y, "y")
        linear1 = MOI.add_constraint(model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), 0.0), MOI.GreaterThan(1.0))
        linear2 = MOI.add_constraint(model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), 0.0), MOI.LessThan(1.0))
        linear3 = MOI.add_constraint(model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), 0.0), MOI.EqualTo(1.0))
        MOI.set(model, MOI.ConstraintName(), linear1, "linear1")
        MOI.set(model, MOI.ConstraintName(), linear2, "linear2")
        MOI.set(model, MOI.ConstraintName(), linear3, "linear3")

        model2 = GeneralModel{Float64}()
        MOIU.loadfromstring!(model2, s)
        MOIU.test_models_equal(model, model2, ["x", "y"], ["linear1", "linear2", "linear3"])
    end

    @testset "minimization: linear objective" begin
        s = """
        variables: x, y
        minobjective: x + -2y + 1.0
        """
        model = GeneralModel{Float64}()
        x = MOI.add_variable(model)
        y = MOI.add_variable(model)
        MOI.set(model, MOI.VariableName(), x, "x")
        MOI.set(model, MOI.VariableName(), y, "y")
        MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, -2.0], [x, y]), 1.0))
        MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

        model2 = GeneralModel{Float64}()
        MOIU.loadfromstring!(model2, s)
        MOIU.test_models_equal(model, model2, ["x", "y"], String[])
    end

    @testset "maximization: linear objective" begin
        s = """
        variables: x, y
        maxobjective: x + -2y + 1.0
        """
        model = GeneralModel{Float64}()
        x = MOI.add_variable(model)
        y = MOI.add_variable(model)
        MOI.set(model, MOI.VariableName(), x, "x")
        MOI.set(model, MOI.VariableName(), y, "y")
        MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, -2.0], [x, y]), 1.0))
        MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

        model2 = GeneralModel{Float64}()
        MOIU.loadfromstring!(model2, s)
        MOIU.test_models_equal(model, model2, ["x", "y"], String[])
    end

    @testset "SOC constraints" begin
        s = """
        variables: x, y, z
        varsoc: [x,y,z] in SecondOrderCone(3)
        affsoc: [2x,y+1,-1*z] in SecondOrderCone(3)
        affsoc2: [1.0,2.0,3.0] in SecondOrderCone(3)
        """
        model = GeneralModel{Float64}()
        x = MOI.add_variable(model)
        y = MOI.add_variable(model)
        z = MOI.add_variable(model)
        MOI.set(model, MOI.VariableName(), x, "x")
        MOI.set(model, MOI.VariableName(), y, "y")
        MOI.set(model, MOI.VariableName(), z, "z")
        varsoc = MOI.add_constraint(model, MOI.VectorOfVariables([x,y,z]), MOI.SecondOrderCone(3))
        affsoc = MOI.add_constraint(model, MOI.VectorAffineFunction(MOI.VectorAffineTerm.([1,2,3], MOI.ScalarAffineTerm.([2.0,1.0,-1.0], [x,y,z])), [0.0,1.0,0.0]), MOI.SecondOrderCone(3))
        affsoc2 = MOI.add_constraint(model, MOI.VectorAffineFunction(MOI.VectorAffineTerm{Float64}[],[1.0,2.0,3.0]), MOI.SecondOrderCone(3))
        MOI.set(model, MOI.ConstraintName(), varsoc, "varsoc")
        MOI.set(model, MOI.ConstraintName(), affsoc, "affsoc")
        MOI.set(model, MOI.ConstraintName(), affsoc2, "affsoc2")

        model2 = GeneralModel{Float64}()
        MOIU.loadfromstring!(model2, s)
        MOIU.test_models_equal(model, model2, ["x", "y", "z"], ["varsoc", "affsoc", "affsoc2"])
    end

end


function normout!(A::SparseMatrixCSC{Float64,Int64})
    d = sum(A,2) # sum over rows
    # use some internal julia magic
    for i=1:length(A.nzval)
        A.nzval[i] = A.nzval[i] / d[A.rowval[i]]
    end
    return A
end

export normout!

##

""" Filter out the entries that are not in the topk list for each column. """
function filter_topk!(A::SparseMatrixCSC, k::Integer)

  k < 0 && throw(ArgumentError("The value of k = $k must be non-negative"))
  rows = rowvals(A)
  vals = nonzeros(A)
  m, n = size(A)
  sortarr = Array{eltype(A)}(m)
  permarr = Array{Int}(m)

  for i = 1:n
    currange = nzrange(A,i)
    if length(currange) <= k
      continue
    end
    first = currange[1]
    last = currange[end]
    len = last-first+1

    resize!(sortarr, len)
    resize!(permarr, len)
    copy!(sortarr, 1, vals, first, len)
    sortperm!(permarr, sortarr, order=Base.Order.Reverse)
    for j in nzrange(A, i) # clear out the results
      vals[j] = 0
    end
    # at this point, sortarr is still untouched, so
    # sortarr[j] -> currange[j]
    for (j, ji) in enumerate(permarr)
      if j > k # last one! we are done
        break
      end
      #sortarr[ji] is an entry we want to return!
      vals[currange[ji]] = sortarr[ji]
    end
  end
  dropzeros!(A)
end
filter_topk(A,k) = dropzeros!(filter_topk!(copy(A),k))

export filter_topk, filter_topk!


# Collection of utility functions for dealing with arrays

"""
Convert a matrix of real values into complex values by treating alternating
columns as real and imaginary parts
"""
function real_cols_to_complex(mat::Matrix{T}) where {T}
    ncols = size(mat,2)
    out = Matrix{Complex{T}}(undef, size(mat,1), div(ncols,2))
    for col in 1:div(ncols,2)
        out[:,col] .= mat[:,2col-1] .+ mat[:,2col]*im
    end
    return out
end

"Turns vector of vectors into vector by concatenating them together"
function flatten(arr::Vector{Vector{T}}) where {T}
    flattened_arr = Vector{T}(undef, 0)
    for i = 1:length(arr)
        append!(flattened_arr, arr[i])
    end
    return flattened_arr
end

"Turns n-dim array into vector"
flatten(a::AbstractArray) = reshape(a, :)

function rel_abs_error(exact::Complex{T}, vec::Complex{T})::T where {T}
    abs(exact - vec)/abs(exact)
end

function max_rel_abs_error(exact::Vector{Complex{T}}, vec::Vector{Complex{T}})::T where {T}
    current_max = zero(T)
    for i in eachindex(exact)
        current_max = max(current_max, rel_abs_error(exact[i], vec[i]))
    end
    return current_max
end

"""
    offsetmap(fnc::Function, inds::StepRange)::OffsetArray

Maps `fnc` over `inds`, returning an OffsetArray with indices of `inds`.
"""
offsetmap(fnc, inds) = OffsetArray(map(fnc, inds), inds)


# Test disjoint set

using DataStructures
using Base.Test

s = IntDisjointSets(10)

@test length(s) == 10
@test num_groups(s) == 10

for i = 1:10
    @test find_root(s, i) == i
end
@test_throws BoundsError find_root(s, 11)

@test !in_same_set(s, 2, 3)

union!(s, 2, 3)
@test num_groups(s) == 9
@test in_same_set(s, 2, 3)
@test find_root(s, 3) == 2
union!(s, 3, 2)
@test num_groups(s) == 9
@test in_same_set(s, 2, 3)
@test find_root(s, 3) == 2

# We cannot support arbitrary indexing and still use @inbounds with IntDisjointSets
# (and it's not useful anyway)
@test_throws MethodError push!(s, 22)

@test push!(s) == 11
@test num_groups(s) == 10

union!(s, 8, 7)
union!(s, 5, 6)
union!(s, 8, 5)
@test num_groups(s) == 7
@test find_root(s, 6) == 8
union!(s, 2, 6)
@test find_root(s, 2) == 8

s = DisjointSets{Int}(1:10)

@test length(s) == 10
@test num_groups(s) == 10

r = [find_root(s, i) for i in 1 : 10]
@test isa(r, Vector{Int})
@test isequal(r, [1:10])

for i = 1 : 5
    x = 2 * i - 1
    y = 2 * i
    union!(s, x, y)
end

@test length(s) == 10
@test num_groups(s) == 5

r0 = [1, 1, 3, 3, 5, 5, 7, 7, 9, 9]
r = [find_root(s, i) for i in 1 : 10]
@test isa(r, Vector{Int})
@test isequal(r, r0)

union!(s, 1, 4)
union!(s, 3, 5)
union!(s, 7, 9)

@test length(s) == 10
@test num_groups(s) == 2

r0 = [1, 1, 1, 1, 1, 1, 7, 7, 7, 7]
r = [find_root(s, i) for i in 1 : 10]
@test isa(r, Vector{Int})
@test isequal(r, r0)

push!(s, 17)

@test length(s) == 11
@test num_groups(s) == 3

r0 = [1, 1, 1, 1, 1, 1, 7, 7, 7, 7, 11]
r = [find_root(s, i) for i in [1 : 10, 17] ]
@test isa(r, Vector{Int})
@test isequal(r, r0)


@testset "NSGA-II" begin
    @testset "nondominated sorting" begin
        ws=[-1, -1]
        let fA = [20, 2.2], fB = [60, 4.4], fD = [15, 4.4]
            @test !dominate(fA, fD, ws)
            @test !dominate(fD, fA, ws)
            @test !dominate(fA, fA, ws)
            @test dominate(fA, fB, ws)
            @test dominate(fD, fB, ws)
            @test !dominate(fB, fD, ws)
        end
        fitness = [20 2.2; 60 4.4; 65 3.5; 15 4.4; 55 4.5; 50 1.8; 80 4.0; 25 4.6]'
        fronts = nondominated_sort(fitness, ws)
        # the order may differ, though we don't care about the order within each front
        @test all(issetequal.(fronts, [[1, 4, 6], [2, 3, 5, 8], [7]]))
    end

    @testset "crowding distance assignment" begin
        fitness = [20 2.2; 60 4.4; 65 3.5; 15 4.4; 55 4.5; 50 1.8; 80 4.0; 25 4.6]'
        ws=[-1, -1]
        fronts = nondominated_sort(fitness, ws)
        cds = assign_crowding_distance(fitness, fronts)
        @test cds[1] == 2.0
        @test cds[4] == Inf
        @test cds[6] == Inf
        @test cds[8] == Inf
        @test cds[3] == Inf
        @test cds[5] ≈ 1.0568181818181819
        @test cds[2] ≈ 1.1590909090909092
        @test cds[7] == Inf
    end
    @testset "crowded comparison" begin
        @test crowded_compare(1, 0.1, 4, Inf) == -1
        @test crowded_compare(5, 0.1, 4, Inf) == 1
        @test crowded_compare(1, 0.1, 1, Inf) == 1
        @test crowded_compare(1, Inf, 1, Inf) == 0
    end
end


export DegreeVertexCover

struct DegreeVertexCover end

"""
    vertex_cover(g, DegreeVertexCover())

Obtain a vertex cover using a greedy heuristic.

### Implementation Notes
An edge is said to be covered if it has at least one end-point in the vertex cover.
Initialise the vertex cover to an empty set and iteratively choose the vertex with the most uncovered
edges.

### Performance
Runtime: O((|V|+|E|)*log(|V|))
Memory: O(|V|)

# Examples
```jldoctest
julia> using LightGraphs

julia> vertex_cover(PathGraph(3), DegreeVertexCover())
1-element Array{Int64,1}:
 2

julia> vertex_cover(CycleGraph(3), DegreeVertexCover())
2-element Array{Int64,1}:
 1
 3
```
"""
function vertex_cover(
    g::AbstractGraph{T},
    alg::DegreeVertexCover
    ) where T <: Integer

    nvg = nv(g)
    in_cover = falses(nvg)
    length_cover = 0
    degree_queue = PriorityQueue(Base.Order.Reverse, enumerate(degree(g)))

    while !isempty(degree_queue) && peek(degree_queue)[2] > 0
        v = dequeue!(degree_queue)
        in_cover[v] = true
        length_cover += 1

        @inbounds @simd for u in neighbors(g, v)
            if !in_cover[u]
                degree_queue[u] -= 1
            end
        end
    end
    return LightGraphs.findall!(in_cover, Vector{T}(undef, length_cover))
end


ENV["GKS_ENCODING"]="utf-8"

using Test, Flux, NeuralNetDiffEq, Plots, Plots.PlotMeasures
using DiffEqDevTools

# Run a solve on vectors

linear = (u,p,t) -> [cos(2pi*t)]
tspan = (0.0f0, 1.0f0)
u0 = 0.0f0
prob = ODEProblem(linear, u0 ,tspan)
chain = Flux.Chain(Dense(1,5,σ),Dense(5,1))
opt = Flux.ADAM(0.1, (0.9, 0.95))
sol = solve(prob, NeuralNetDiffEq.NNODE(chain,opt), initial_condition = [u0], dt=1/20f0, abstol=1e-10,
           verbose = true, maxiters=200)

# Second-Order ODE - Dirichlet
linear = (u,p,t) -> cos(2pi*t)
tspan = (0.0f0, 1.0f0)
u0 = 0.0f0
u1 = 1.0f0
initial_condition = [u0, u1]
prob = ODEProblem(linear, u0, tspan)

chain = Flux.Chain(Dense(1,5,σ),Dense(5,1))
opt = Flux.ADAM(0.1, (0.9, 0.95))
sol = solve(prob, NeuralNetDiffEq.NNODE(chain,opt), initial_condition = [u0, u1], dt=1/20f0, verbose = true,
            abstol=1e-10, maxiters = 200)

analytical_sol = t -> ((4pi^2 * t) - cos(2pi*t) + 1) / (4 * pi^2)
plot(analytical_sol, label = "analytical", linestyle = :dot, linewidth = 8, legend=:bottomright, bottom_margin = 10mm, left_margin=10mm, top_margin=10mm, right_margin=10mm)
plot!(sol, title = "Second-Order Dirichlet : Cos(2𝝅t)", label = "phi", fillalpha = .5, linewidth = 4)
xlabel!("Time (t)")
display(ylabel!("f(x)", ylims = (0,1)))
savefig("plot.png")

#=
anat_sum = zeros(0)
append!(anat_sum, 0)
for i = 1:20
    k = i * .05
    append!(anat_sum, analytical_sol(k))
end

print("length of sol is: ", length(sol))
print("length of anat_sum is:", length(anat_sum))
#error = sol .- analytical_sol
#print("Error: ", error)
#@test error < 0.5

=#

# Run a solve on vectors
linear = (u,p,t) -> [cos(2pi*t)]
tspan = (0.0f0, 1.0f0)
u0 = [0.0f0]
prob = ODEProblem(linear, u0 ,tspan)
chain = Flux.Chain(Dense(1,5,σ),Dense(5,1))
opt = Flux.ADAM(0.1, (0.9, 0.95))
sol = solve(prob, NeuralNetDiffEq.NNODE(chain,opt), initial_condition = [u0], dt=1/20f0, abstol=1e-10,
            verbose = true, maxiters=200)

#Example 1
linear = (u,p,t) -> @. t^3 + 2*t + (t^2)*((1+3*(t^2))/(1+t+(t^3))) - u*(t + ((1+3*(t^2))/(1+t+t^3)))
linear_analytic = (u0,p,t) -> [exp(-(t^2)/2)/(1+t+t^3) + t^2]
prob = ODEProblem(ODEFunction(linear,analytic=linear_analytic),[1f0],(0.0f0,1.0f0))
chain = Flux.Chain(Dense(1,5,σ),Dense(5,1))
opt = Flux.ADAM(0.1, (0.9, 0.95))
sol  = solve(prob,NeuralNetDiffEq.NNODE(chain,opt),initial_condition = [prob.u0], verbose = true, dt=1/5f0)
err = sol.errors[:l2]
sol  = solve(prob,NeuralNetDiffEq.NNODE(chain,opt),initial_condition = [prob.u0], verbose = true, dt=1/20f0)
sol.errors[:l2]/err < 0.5

#=
dts = 1f0 ./ 2f0 .^ (6:-1:2)
sim = test_convergence(dts, prob, NeuralNetDiffEq.NNODE(chain, opt))
@test abs(sim.𝒪est[:l2]) < 0.1
@test minimum(sim.errors[:l2]) < 0.5
=#

#Example 2
linear = (u,p,t) -> -u/5 + exp(-t/5).*cos(t)
linear_analytic = (u0,p,t) ->  exp(-t/5)*(u0 + sin(t))
prob = ODEProblem(ODEFunction(linear,analytic=linear_analytic),0.0f0,(0.0f0,1.0f0))
chain = Flux.Chain(Dense(1,5,σ),Dense(5,1))
opt = Flux.ADAM(0.1, (0.9, 0.95))
sol  = solve(prob,NeuralNetDiffEq.NNODE(chain,opt), initial_condition = [prob.u0], verbose = true, dt=1/5f0)
err = sol.errors[:l2]
sol  = solve(prob,NeuralNetDiffEq.NNODE(chain,opt),initial_condition = [prob.u0], verbose = true, dt=1/20f0)
sol.errors[:l2]/err < 0.5

#=
dts = 1f0 ./ 2f0 .^ (6:-1:2)
sim = test_convergence(dts, prob, NeuralNetDiffEq.NNODE(chain, opt))
@test abs(sim.𝒪est[:l2]) < 0.5
@test minimum(sim.errors[:l2]) < 0.1
=#


subtract(x, y; kwargs...) = -(x, y; kwargs...)
subtract(x::Vector, y::Vector; kwargs...) = [subtract(x[i], y[i]; kwargs...) for i in 1:length(x)]

# Simple gradient descent to minimize the function `f`
# given the gradient `gradf`.
function minimize(f, gradf, x; nsteps, gamma, print_frequency=10, kwargs...)
  for n in 1:nsteps
    x = subtract(x, gamma * gradf(x); kwargs...)
    if n % print_frequency == 0
      println("Step = $(n), f(x) = $(f(x))")
    end
  end
  return x
end


#train the neural net for dataset (datax,datay)
#requires that the function nnDCost be defined representing the derivative of the cost function
function nnTrain(nn::neuralNet,datax,datay,epochs::Int64,learnRate::Float64;batchSize=size(datax,2),batchLength=epochs,verbose=false)

    epCnt = 0;
    bCnt  = 1;

    while epCnt < epochs
        #get indices of the data to be used
        (batchSize == size(datax,2)) ? (dIdx=1:batchSize) : (dIdx=sample(1:size(datax,2),batchSize,replace=false));

        #loop over epochs
        for ep=1:min(batchLength,epochs-epCnt)
            #verbose && @printf("starting epoch %3d...\n",ep);

            #these vectors will store the running sums
            c  = 0.0;
            dw = zeros(length(nn.dCdw));
            db = zeros(length(nn.dCdb));

            #loop over training data
            for d=dIdx
                #pick out the dataset
                dx = datax[:,d];
                dy = datay[:,d];

                #forward run
                y = nnForward(nn,dx);

                #add costs to running sum
                c += nnCost(dy,y);

                #back propagate
                nnBackPropagate(nn,dy);

                #add computed gradients to running sum
                dw += nn.dCdw;
                db += nn.dCdb;
            end

            #shift by mean * learning rate
            nn.w -= learnRate*(dw ./ batchSize);
            nn.b -= learnRate*(db ./ batchSize);

            #verbose && println("finished epoch $ep.");
            verbose && @printf("Epoch %5d (iteration %5d of batch %5d): Cost = %10.8f\n",ep+epCnt,ep,bCnt,c/batchSize);
        end
        bCnt  += 1;
        epCnt += batchLength;
    end

end


#!/usr/bin/env julia

# publication11

using Base.Test
using MathProgBase
using Logging
using Drs

A = Float64[3 4; 6 1]
b = Float64[6, 3]
c = Float64[-2, -1]

s = linprog(c, A, '<', b, -Inf, Inf, DrsMathProgSolver())

@test s.status == :Optimal
@test s.objval == -13/7
@test s.sol == [2/7, 9/7]


#=
Foil Shape equation - equal contour
1-1-2021
Author: Brigham Ostergaard
=#

#=
  foilShape(x, m)

  foilShape is used to determine the thickness of the foil at a given position
    along its lateral axis. x is the position along the foil and m is the value
    of maximum thickness

  t = 10m * (0.2969√(x) - 0.1260x -0.3537x^2 + 0.2843x^3 - 0.1015x^4)
=#

function foilShape(x, m)
    #Determines foil thickness at length x from left side.

  a =  0.2969*sqrt(x);
  b = 0.1260*x;
  c = 0.3537*(x^2);
  d = 0.2843 * (x^3);
  e = 0.1015 * (x^4);

  t = (10*m) * (a - b - c + d - e)

  return t
end

function _init_()
  m = 0.1;                  #coefficient specific to wing shape.

  print("x   | y")          #title for table
  println()                 #formating

  for x = 0:0.1:1
    y = foilShape(x, m);
    print(x, " | ", y)
    #println()              #formating
  end

  #println()                #formating
end


# Mitigation applied after C > 500
# 7+1
function linearu!(du,u,p,t)
	I,C = u
	beta,sigmac,sigma,q,f,mbase,mlocation,mrate,cmax,c50 = p
    beta *= mbase + (1-mbase)/(1 + exp(mrate*(t - mlocation)))
    sigmac *= 1 + cmax*t/(c50+t)
    du[1] = dI = beta*(I+q*C) - sigmac*I - f*sigma*I
	du[2] = dC = sigmac*I - sigma*C
end
function nonlinearu!(du,u,p,t)
	I,C,S = u
	beta,sigmac,sigma,q,f,mbase,mlocation,mrate,cmax,c50 = p
    beta *= mbase + (1-mbase)/(1 + exp(mrate*(t - mlocation)))
    sigmac *= 1 + cmax*t/(c50+t)
    du[1] = dI = beta*(I+q*C)*S- sigmac*I - f*sigma*I
	du[2] = dC = sigmac*I - sigma*C
    du[3] = dI = beta*(I+q*C)*S
end

function linearrd!(du,u,p,t)
	I,C = u
	beta,sigmac,sigmar,sigmad,q,f,mbase,mlocation,mrate,cmax,c50 = p
    beta *= mbase + (1-mbase)/(1 + exp(mrate*(t - mlocation)))
    sigmac *= 1 + cmax*t/(c50+t)
    du[1] = dI = beta*(I+q*C) - sigmac*I - f*(sigmar+sigmad)*I
	du[2] = dC = sigmac*I - (sigmar+sigmad)*C
end

function nonlinearrd!(du,u,p,t)
	I,C,S = u
	beta,sigmac,sigmar,sigmad,q,f,mbase,mlocation,mrate,cmax,c50 = p
    beta *= mbase + (1-mbase)/(1 + exp(mrate*(t - mlocation)))
    sigmac *= 1 + cmax*t/(c50+t)
    du[1] = dI = beta*(I+q*C)*S - sigmac*I - f*(sigmar+sigmad)*I
	du[2] = dC = sigmac*I - (sigmar+sigmad)*C
    du[3] = dS = -beta*(I+q*C)*S
end


using Dierckx,LinearAlgebra

"Tristimulus values of digital color, based on spectral measurement and matching functions"
function matchcolors(C,λ,I,cmf;f=nothing)
    n = length(C);MC=Vector{Vector{Float64}}(undef,n)
    @views r = cmf[:,1]
    if isnothing(f)
        f=1;dr=1
    else
        dr = r[2]-r[1]
    end
    for i in 1:n
        @views s = Spline1D(λ[i],I[i],k=3,bc="zero")(r)'
        @views MC[i] = f*dr*[s*cmf[:,2],s*cmf[:,3],s*cmf[:,4]]
    end
    return MC,C
end
newcmf(cmf,m) = hcat(cmf[:,1],(m*cmf[:,2:end]')')
divsum(m)=m./sum(m,dims=1)
"Color matching Tristimulus values of single Wavelength unit spectral"
function matchlambda(λ,cmf)
    fs = [Spline1D(cmf[:,1],cmf[:,i+1],k=3,bc="zero") for i in 1:3]
    return map(f->f(λ),fs)
end
"Cone activations of digital colors, based on spectral measurement and cone fundamentals"
function lmsresponse(C,λ,I;observer=10)
    conef = observer==10 ? sscone10le : sscone2le
    matchcolors(C,λ,I,conef)
end
"""
CIE XYZ Tristimulus values of digital colors, based on following function:

XYZ = 683∫s(λ)x̄ȳz̄(λ)dλ

where s(λ) is the power spectrum, x̄ȳz̄(λ) are the matching functions.
Y is the luminance(cd/m²)
"""
function xyzresponse(C,λ,I;observer=10)
    if observer == 10
        conef = sscone10le
        m = LMSToXYZ10
    else
        conef = sscone2le
        m = LMSToXYZ2
    end
    matchcolors(C,λ,I,newcmf(conef,m),f=683)
end
"Michelson Contrast, where ``michelson(Lmax, Lmin) = weber(Lmax, Lmean), Lmean = (Lmax+Lmin)/2``"
contrast_michelson(Lmax,Lmin) = (Lmax-Lmin)/(Lmax+Lmin)
"Weber Contrast, where ``weber(L, Lb) = michelson(L, 2Lb-L)``"
contrast_weber(L,Lb) = (L/Lb)-1

TranslateXYZMatrix(;x=0,y=0,z=0) = TranslateXYZMatrix([x,y,z])
function TranslateXYZMatrix(t::Vector)
    tm =  [1.0 0.0 0.0   t[1];
           0.0 1.0 0.0   t[2];
           0.0 0.0 1.0   t[3];
           0.0 0.0 0.0 1.0]
end
function RotateXYZMatrix(rad::Real;dims=1)
    rads = [0.0,0.0,0.0]
    if 1<=dims<=3
        rads[dims]=rad
    end
    RotateXYZMatrix(rads)
end
function RotateXYZMatrix(rads::Vector)
    axis=nothing
    ri = findall(!iszero,rads)
    if length(ri) == 1 && ri[1] <= 3
        axis = ri[1]
    end
    isnothing(axis) && return Matrix{Float64}(I,4,4)
    c = cos(rads[axis])
    s = sin(rads[axis])
    if axis==1
      rm = [1.0 0.0 0.0 0.0;
            0.0   c  -s 0.0;
            0.0   s   c 0.0;
            0.0 0.0 0.0 1.0]
    elseif axis==2
      rm = [  c 0.0   s 0.0;
            0.0 1.0 0.0 0.0;
             -s 0.0   c 0.0;
            0.0 0.0 0.0 1.0]
    else
      rm = [  c  -s 0.0 0.0;
              s   c 0.0 0.0;
            0.0 0.0 1.0 0.0;
            0.0 0.0 0.0 1.0]
    end
end
quavectors(vs) = vcat(vs,ones(size(vs,2))')
trivectors(vs) = vs[1:3,:]
quamatrix(m) = vcat(hcat(m,zeros(3)),[0.0 0.0 0.0 1.0])
trimatrix(m) = m[1:3,1:3]
"Converting Matrix between digital RGB and LMS Space, based on spectral measurement and cone fundamentals"
function RGBLMSMatrix(C,λ,I;observer=10)
    TC,C = lmsresponse(C,λ,I,observer=observer)
    # Solve System of Linear Equations in form of Space Converting Matrix
    mc=hcat(C...);mtc=hcat(TC...)
    isqua = size(mc,1)==4
    if isqua
        mc = trivectors(mc)
    end
    RGBToLMS = mtc/mc
    LMSToRGB = inv(RGBToLMS)
    if isqua
        RGBToLMS = quamatrix(RGBToLMS)
        LMSToRGB = quamatrix(LMSToRGB)
    end
    return RGBToLMS,LMSToRGB
end
"Converting Matrix between digital RGB and CIE XYZ Space, based on spectral measurement and xyz matching functions"
function RGBXYZMatrix(C,λ,I;observer=10)
    TC,C = xyzresponse(C,λ,I,observer=observer)
    # Solve System of Linear Equations in form of Space Converting Matrix
    mc=hcat(C...);mtc=hcat(TC...)
    isqua = size(mc,1)==4
    if isqua
        mc = trivectors(mc)
    end
    RGBToXYZ = mtc/mc
    XYZToRGB = inv(RGBToXYZ)
    if isqua
        RGBToXYZ = quamatrix(RGBToXYZ)
        XYZToRGB = quamatrix(XYZToRGB)
    end
    return RGBToXYZ,XYZToRGB
end
"""
Converting Matrix between LMS and Cone Contrast(Weber) Space
(DH Brainard, Cone contrast and opponent modulation color spaces, human color vision, 1996)
"""
function LMSContrastMatrix(bg)
    # translate origin to bg to get differential cone activation
    t = [1 0 0 -bg[1];
         0 1 0 -bg[2];
         0 0 1 -bg[3];
         0 0 0 1]
    # scale relative to bg to get cone contrast
    s = [inv(bg[1]) 0 0 0;
         0 inv(bg[2]) 0 0;
         0 0 inv(bg[3]) 0;
         0 0 0 1]
    LMSToContrast = s*t
    ContrastToLMS = inv(LMSToContrast)
    return LMSToContrast,ContrastToLMS
end
"""
Converting Matrix between differential LMS and DKL[L+M, L-M, S-(L+M)] Space
(DH Brainard, Cone contrast and opponent modulation color spaces, human color vision, 1996)
"""
function dLMSDKLMatrix(bg;cone=nothing,v=nothing,isnorm=true)
    wl = 1;wm = 1
    if !isnothing(cone) && !isnothing(v)
        wl,wm = v[:,2]'/cone[:,2:3]'
    end
    dLMSToDKL = [wl  wm           0;
                1 -bg[1]/bg[2] 0;
               -wl -wm (wl*bg[1] + wm*bg[2])/bg[3]]
    if isnorm
        # Each column of the inverse of dLMSToDKL is the differential LMS relative to bg that isolating each DKL mechanism
        dlms_dkliso = inv(dLMSToDKL)
        # Cone contrast relative to bg
        cc = dlms_dkliso./bg[1:3]
        # Pooled cone contrast of each differential LMS relative to bg that isolating each DKL mechanism
        pcc = [norm(cc[:,i]) for i in 1:3]
        # Scale differential LMS vector by its pooled cone contrast
        udlms_dkliso = dlms_dkliso./pcc'
        # Rescale dLMSToDKL so that differential LMS which isolating DKL mechanism and having unit pooled cone contrast will result unit DKL response
        dLMSToDKL = inv(dLMSToDKL*udlms_dkliso)*dLMSToDKL
    end
    DKLTodLMS = inv(dLMSToDKL)
    return dLMSToDKL,DKLTodLMS
end
"Converting Matrix between LMS and DKL[L+M, L-M, S-(L+M)] Space"
function LMSDKLMatrix(bg;observer=10,isnorm=true)
    if observer == 10
        conef = sscone10le
        v = v10le
    else
        conef = sscone2le
        v = v2le
    end
    dLMSToDKL,DKLTodLMS = dLMSDKLMatrix(bg,cone=conef,v=v,isnorm=isnorm)
    t = [1 0 0 -bg[1];
         0 1 0 -bg[2];
         0 0 1 -bg[3];
         0 0 0 1]
    LMSToDKL = quamatrix(dLMSToDKL)*t
    DKLToLMS = inv(LMSToDKL)
    return LMSToDKL,DKLToLMS
end
"""
Intersection point of a line and a plane.
points of a line are defined as a direction(Dₗ) through a point(Pₗ): P = Pₗ + λDₗ , where λ is a scaler
points of a plane are defined as a plane through a point(Pₚ) and with normal vector(Nₚ) : Nₚᵀ(P - Pₚ) = 0 , where Nᵀ is the transpose of N

return point of intersection and if it's on direction
"""
function intersectlineplane(Pₗ,Dₗ,Pₚ,Nₚ)
    NₚᵀDₗ = Nₚ'*Dₗ
    NₚᵀDₗ == 0 && return nothing
    λ = Nₚ'*(Pₚ - Pₗ) / NₚᵀDₗ
    return Pₗ + λ*Dₗ, λ >=0
end
"""
Intersection point of a line and the six faces of the unit cube with origin as a vertex and three axies as edges.
points of a line are defined as a direction(Dₗ) through a point(Pₗ)

return intersection point on direction
"""
function intersectlineunitorigincube(Pₗ,Dₗ)
    ps = [zeros(3,3) ones(3,3)]
    ns = [Matrix{Float64}(I,3,3) Matrix{Float64}(I,3,3)]
    for i in 1:6
        p,d = intersectlineplane(Pₗ,Dₗ,ps[:,i],ns[:,i])
        if !isnothing(p) && all(i->-eps(1.0)<=i<=1+eps(1.0),p) && d
            return p
        end
    end
    return nothing
end
"""
Points of a line segment defined by two points P₀ and P₁
d: points density of unit line length
"""
function linepoints(P₀,P₁;d=10)
    d = max(1,d)
    Dₗ = P₁-P₀
    l =norm(Dₗ)
    hcat([P₀.+λ*Dₗ for λ in 0:1/(d*l):1]...)
end
function XYZ2xyY(m)
    xyz = divsum(trivectors(m))
    xyz[3,:]=m[2,:]
    replace!(xyz,NaN=>0.0)
    return xyz
end
function xyY2XYZ(m)
    s = m[3:3,:]./m[2:2,:]
    XYZ = s.*vcat(m[1:2,:], 1 .- sum(m[1:2,:],dims=1))
    replace!(XYZ,NaN=>0.0)
    return XYZ
end
function desaturate2gamut!(vs)
    for i in 1:size(vs,2)
        minc = minimum(vs[:,i])
        if minc<0
            vs[:,i].-=minc
        end
        maxc = maximum(vs[:,i])
        if maxc>0
            vs[:,i]./=maxc
        end
    end
    return vs
end
function cam16uniquehueindex(h′)
    h′ᵢ = zeros(Int,size(h′))
    for j in 1:length(h′)
        for i in 1:4
            if cam16uniquehue[1,i] <= h′[j] < cam16uniquehue[1,i+1]
                h′ᵢ[j]=i
                break
            end
        end
    end
    return h′ᵢ
end
function cam16uniquehuecomposition(H,i)
    Pl = round(Int,cam16uniquehue[3,i+1]-H)
    Pr = round(Int,H-cam16uniquehue[3,i])
    return (cam16uniquehue[4,i]=>Pl,cam16uniquehue[4,i+1]=>Pr)
end
function cam16nonlinearcompression(x,Fl;checksign=true)
    if checksign
        p = (Fl*abs(x)/100.0)^0.42
        sign(x)*400*p/(27.13 + p) + 0.1
    else
        p = (Fl*x/100.0)^0.42
        400*p/(27.13 + p) + 0.1
    end
end
"""
CAM16 Viewing Conditions

W: White in test illuminant [Xw, Yw, Zw]
Yb: Background in test conditions
La: Luminance of test adapting field (cd/m2)
Surround: Surround condition {Average, Dim, Dark}, Nc and F are functions of c, and their values can be linearly interpolated.
          Sr = Lsw / Ldw, where Lsw is the luminance of reference white in surround and Ldw in the display area.
          Sr == 0: Dark
          0 < Sr < 0.2: Dim
          Sr >= 0.2: Average
"""
function cam16view(;W=100*WP_D65,Surround=:Average,La=40,Yb=20)
    F,c,Nc = cam16surround[cam16surround[:,1].==Surround,2:end]
    k = 1.0/(5*La + 1)
    Fl = La*(k^4) + 0.1 * ((1 - k^4)^2) * ((5*La)^(1/3))

    Yw = W[2]
    n = Yb/Yw
    Nbb = Ncb = 0.725*(1/n)^0.2
    z = 1.48 + sqrt(n)
    D = clamp(F * (1 - (1/3.6)*exp(-(La+42)/92)), 0, 1)

    RGBw = cat16*W
    Da = Diagonal([D*Yw/RGBw[i] + 1 - D for i in 1:3])
    RGBwc = Da*RGBw
    RGBaw = cam16nonlinearcompression.(RGBwc,Fl,checksign=false)
    Aw = Nbb*(([2 1 1/20]*RGBaw)[1] - 0.305)

    return (c=c,Nc=Nc,Nbb=Nbb,Ncb=Ncb,Fl=Fl,n=n,z=z,Da=Da,Aw=Aw)
end
"""
Convert CIE XYZ to CIE Color Appearance Model 2016
Li, C., Li, Z., Wang, Z., Xu, Y., Luo, M.R., Cui, G., Melgosa, M., Brill, M.H., and Pointer, M. (2017). Comprehensive color solutions: CAM16, CAT16, and CAM16‐UCS.

return:
J is the lightness
C is the chroma
h is the hue angle
Q is the brightness
M is the colourfulness
s is the saturation
"""
function XYZ2CAM16(XYZ;Fl,Nc,Ncb,Nbb,n,c,z,Da,Aw)
    # Chromatic Adaptation
    RGBc = Da*cat16*XYZ
    # Non Linear Response Compression
    RGBa = cam16nonlinearcompression.(RGBc,Fl,checksign=true)
    # Perceptual Attribute Correlates
    a = ([1 -12/11 1/11]*RGBa)[:]
    b = ([1/9 1/9 -2/9]*RGBa)[:]
    h = mod.(2pi .+ atan.(b,a), 2pi)

    h′ = rad2deg.(h)
    i = h′ .< cam16uniquehue[1,1]
    h′[i] = h′[i] .+ 360

    h′ᵢ = cam16uniquehueindex(h′)
    H = cam16uniquehue[3,h′ᵢ] .+ (100*(h′.-cam16uniquehue[1,h′ᵢ])./cam16uniquehue[2,h′ᵢ]) ./ ( (h′.-cam16uniquehue[1,h′ᵢ])./cam16uniquehue[2,h′ᵢ] .+ (cam16uniquehue[1,h′ᵢ.+1] .- h′)./cam16uniquehue[2,h′ᵢ.+1] )
    Hc = cam16uniquehuecomposition.(H,h′ᵢ)

    et = 0.25 .* (cos.(deg2rad.(h′) .+ 2) .+ 3.8)
    t = (50000/13)*Nc*Ncb .* et .* sqrt.(a.^2 .+ b.^2) ./ (([1 1 21/20]*RGBa)[:])
    A = (Nbb.*([2 1 1/20]*RGBa .- 0.305))[:]

    J = 100 .* (A./Aw).^(c*z)
    Q = (4/c) .* sqrt.(J./100) .* (Aw + 4)*Fl^0.25
    C = t.^0.9 .* sqrt.(J./100) .* (1.64 - 0.29^n)^0.73
    M = (Fl^0.25).*C
    s = 100 .* sqrt.(M./Q)

    return (J=J,Q=Q,C=C,M=M,h=h,s=s,H=H,Hc=Hc)
end
"""
CAM16 Uniform Color Space in polar[J′, M′, h] or cartesian[J′, a′, b′]
Luo, M.R., Cui, G., and Li, C. (2006). Uniform colour spaces based on CIECAM02 colour appearance model. Color Research & Application 31, 320–330.
"""
function CAM16UCS(J,M,h;c1=0.007,c2=0.0228,form=:cartesian)
    J′ = (1 + 100c1).*J ./ (1 .+ c1.*J)
    M′ = log.(1 .+ c2.*M) ./ c2
    a′ = M′.*cos.(h)
    b′ = M′.*sin.(h)
    return form == :cartesian ? hcat(J′,a′,b′)' : hcat(J′,M′,h)'
end
function CAM16UCSinv(UCS;c1=0.007,c2=0.0228,form=:cartesian)
    J′ = UCS[1,:]
    if form == :cartesian
        M′ = sqrt.(UCS[2,:].^2 .+ UCS[3,:].^2)
        h = mod.(2pi .+ atan.(UCS[3,:],UCS[2,:]), 2pi)
    else
        M′ = UCS[2,:]
        h = UCS[3,:]
    end
    M = (exp.(c2.*M′) .- 1) ./ c2
    J = J′ ./ (1 .+ 100c1 .- c1.*J′)
    return (J=J,M=M,h=h)
end
function cam16nonlinearcompressioninv(x,Fl)
    p = x-0.1
    sign(p)*(100/Fl)*(27.13*abs(p) / (400-abs(p)))^(1/0.42)
end
"""
Convert CIE Color Appearance Model 2016 to CIE XYZ
"""
function CAM162XYZ(J,M,h;Fl,Nc,Ncb,Nbb,n,c,z,Da,Aw)
    C = M ./ (Fl^0.25)

    t = (C ./ (sqrt.(J./100) .* (1.64 - 0.29^n)^0.73)).^(1/0.9)
    et = 0.25 .* (cos.(h .+ 2) .+ 3.8)
    A = Aw.*(J./100).^(1/(c*z))
    p2 = A./Nbb .+ 0.305
    p3 = 21/20
    sh = sin.(h)
    ch = cos.(h)

    a=b=zeros(length(h))
    for i in 1:length(h)
        if t[i] !=0
            p1 = 50000/13 * Nc*Ncb * et[i] * (1 / t[i])
            if abs(sh[i]) >= abs(ch[i])
                p4 = p1/sh[i]
                b[i] = p2[i]*(2+p3)*(460/1403) / ( p4+(2+p3)*(220/1403)*(ch[i]/sh[i]) - (27/1403) + p3*(6300/1403) )
                a[i] = b[i]*ch[i]/sh[i]
            else
                p5 = p1/ch[i]
                a[i] = p2[i]*(2+p3)*(460/1403) / ( p5+(2+p3)*(220/1403) - (27/1403 - p3*6300/1403)*sh[i]/ch[i] )
                b[i] = a[i]*sh[i]/ch[i]
            end
        end
    end

    RGBa = [460/1403 451/1403 288/1403;
            460/1403 -891/1403 -261/1403;
            460/1403 -220/1403 -6300/1403]*([p2 a b]')
    RGBc = cam16nonlinearcompressioninv.(RGBa,Fl)
    XYZ = cat16\Da\RGBc
end


function ellipse!(μ,Σ;kw...)
    for i=1:3
        plot!([tuple((i*sqrt(Σ)*[cos(t),sin(t)]+μ)...) for t in range(0,stop=2π,length=100)];
              seriestype=:shape,linealpha=0.5,linecolor=:gray,fillalpha=[0.4,0.2,0.1][i],kw...)
    end
    current()
end

function mixtureplot(X₁,X₂,μ₀,Σ₀,μ₁,Σ₁,Π=nothing)
    scatter(X₁,X₂;fillalpha=0.5,markerstrokewidth=0.5,
        marker_z=Π,mc=ColorGradient([:lightblue,:orange]),
        colorbar=:false)
    ellipse!(μ₀,Σ₀,fillcolor=:lightblue)
    ellipse!(μ₁,Σ₁,fillcolor=:orange)
    plot!(;bg=:transparent,xlims=(-4.5,8.5),ylims=(-3.5,10.5),
        leg=false,ticks=:none,ratio=:equal,bgcolor=:white)
end


include("yeast_dyn.jl")
include("coop.jl")

listt = getpointattr(interc,selfact)
natt = length(listt[1,:])
nc=0;
for (i=1:natt)
    nc += ncoop(listt[:,i],interc,selfact)
end
println("yeast's avg nc = ",nc/natt)

# --- generate a good random (n,k,p)_yeast network
function randomnet (intr,slf)
    n = length(slf)
    nw = similar(intr)                  # this will be the randomized version
    shf = shuffle(slf)                  # fix number of self degredating genes
    tl = tril(intr)                     # remove diag and join upper/lower triang
    tu = triu(intr)
    rect = tu[:,2:end]+tl[:,1:end-1]    # shuffle this
    rect = reshape(shuffle(reshape(rect,length(rect),1)[:,1]),n,n-1)
    rrect = [rect zeros(Int64,n)]       # now insert back the shuffled diagonal
    lrect = [zeros(Int64,n) rect]
    nw = tril(rrect - diagm(diag(rrect))) + triu(lrect-diagm(diag(lrect)))
    return (nw,shf)
end

# --- calculate the average cooperativity over random networks with yeast's (n,k,p) ---
listc=Float64[]
ntotal = 1000;
notconn=0;
for(i=1:ntotal)
    (intersh,selfsh) = randomnet(interc,selfact)
    if (isconnected(symadj(intersh)))
        listt = getpointattr(intersh,selfsh)
        natt = length(listt[1,:])
        ncop=0;
        for (i=1:natt)
            ncop += ncoop(listt[:,i],intersh,selfsh)
        end
        push!(listc,ncop/natt)
    else
        notconn += 1
    end
end
println(100*notconn/ntotal,"\% not connected")
println("(avg,std) of nc over random networks = (",mean(listc),",",std(listc),")")


######## ISENTROPIC RELATIONS #########

function TemperatureRatio(pratio::PressureRatio,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    γ = SpecificHeatRatio(gas)
    TemperatureRatio(pratio^((γ-1)/γ))
end

function TemperatureRatio(ρratio::DensityRatio,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    γ = SpecificHeatRatio(gas)
    TemperatureRatio(ρratio^(γ-1))
end

function PressureRatio(Tratio::TemperatureRatio,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    γ = SpecificHeatRatio(gas)
    PressureRatio(Tratio^(γ/(γ-1)))
end

function T0OverT(M::MachNumber,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    γ = SpecificHeatRatio(gas)
    return TemperatureRatio(1+0.5*(γ-1)*M^2)
end

T0OverT(M::MachNumber;gas::PerfectGas=DefaultPerfectGas) = T0OverT(M,Isentropic,gas=gas)

function P0OverP(M::MachNumber,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    γ = SpecificHeatRatio(gas)
    Tratio = T0OverT(M,Isentropic,gas=gas)
    PressureRatio(Tratio,Isentropic,gas=gas)
end

function DensityRatio(Tratio::TemperatureRatio,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    γ = SpecificHeatRatio(gas)
    DensityRatio(Tratio^(1/(γ-1)))
end

function ρ0Overρ(M::MachNumber,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    γ = SpecificHeatRatio(gas)
    Tratio = T0OverT(M,Isentropic,gas=gas)
    DensityRatio(Tratio,Isentropic,gas=gas)
end

# Temperature relations
function StagnationTemperature(T::Temperature,M::MachNumber,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    StagnationTemperature(T*T0OverT(M,Isentropic,gas=gas))
end

StagnationTemperature(T::Temperature,M::MachNumber;gas::PerfectGas=DefaultPerfectGas) = StagnationTemperature(T,M,Isentropic,gas=gas)

Temperature(T0::StagnationTemperature,M::MachNumber,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas) = Temperature(T0/T0OverT(M,gas=gas))

Temperature(T0::StagnationTemperature,M::MachNumber;gas::PerfectGas=DefaultPerfectGas) = Temperature(T0,M,Isentropic,gas=gas)

function MachNumber(T_over_T0::TemperatureRatio,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    value(T_over_T0) <= 1.0 || error("T/T0 must be 1 or smaller. Maybe you need to supply the inverse?")
    γ = SpecificHeatRatio(gas)
    M2 = ((1.0/T_over_T0)-1)*2/(γ-1)
    MachNumber(sqrt(M2))
end

MachNumber(T_over_T0::TemperatureRatio;gas::PerfectGas=DefaultPerfectGas) = MachNumber(T_over_T0,Isentropic,gas=gas)

MachNumber(T::Temperature,T0::StagnationTemperature,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas) = MachNumber(TemperatureRatio(T/T0),gas=gas)

MachNumber(T::Temperature,T0::StagnationTemperature;gas::PerfectGas=DefaultPerfectGas) = MachNumber(T,T0,Isentropic,gas=gas)

# Pressure relations
function StagnationPressure(p::Pressure,M::MachNumber,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    StagnationPressure(p*PressureRatio(T0OverT(M,gas=gas),Isentropic,gas=gas))
end
function Pressure(p0::StagnationPressure,M::MachNumber,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    Pressure(p0/PressureRatio(T0OverT(M,gas=gas),Isentropic,gas=gas))
end

function MachNumber(p_over_p0::PressureRatio,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    value(p_over_p0) <= 1.0 || error("p/p0 must be 1 or smaller. Maybe you need to supply the inverse?")
    γ = SpecificHeatRatio(gas)
    MachNumber(TemperatureRatio(p_over_p0,Isentropic,gas=gas),gas=gas)
end

MachNumber(p::Pressure,p0::StagnationPressure,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas) = MachNumber(PressureRatio(p/p0),Isentropic,gas=gas)

# Density relations
function StagnationDensity(ρ::Density,M::MachNumber,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    StagnationDensity(ρ*DensityRatio(T0OverT(M,gas=gas),Isentropic,gas=gas))
end
function Density(ρ0::StagnationDensity,M::MachNumber,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    Density(ρ0/DensityRatio(T0OverT(M,gas=gas),Isentropic,gas=gas))
end

function MachNumber(ρ_over_ρ0::DensityRatio,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    value(ρ_over_ρ0) <= 1.0 || error("ρ/ρ0 must be 1 or smaller. Maybe you need to supply the inverse?")
    γ = SpecificHeatRatio(gas)
    MachNumber(TemperatureRatio(ρ_over_ρ0,Isentropic,gas=gas))
end

MachNumber(ρ::Density,ρ0::StagnationDensity,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas) = MachNumber(DensityRatio(ρ/ρ0),gas=gas)

# Area-Mach number relation
function AOverAStar(M::MachNumber,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    γ = SpecificHeatRatio(gas)
    return AreaRatio(1/M*(2/(γ+1)*(T0OverT(M,gas=gas)))^(0.5*(γ+1)/(γ-1)))
end
AOverAStar(M::MachNumber;gas::PerfectGas=DefaultPerfectGas) = AOverAStar(M,Isentropic,gas=gas)

function AStar(A::Area,M::MachNumber,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    return Area(A/AOverAStar(M,gas=gas))
end
AStar(A::Area,M::MachNumber;gas::PerfectGas=DefaultPerfectGas) = AStar(A,M,Isentropic,gas=gas)

function SubsonicMachNumber(A_over_Astar::AreaRatio,::Type{Isentropic}; gas::PerfectGas=DefaultPerfectGas)
    value(A_over_Astar) >= 1.0 || error("A/A* must be 1 or larger. Maybe you need to supply the inverse?")
    Msub = find_zero(x -> AOverAStar(MachNumber(x),gas=gas)-A_over_Astar,(0,1),order=16)
    return MachNumber(Msub)
end
function SupersonicMachNumber(A_over_Astar::AreaRatio,::Type{Isentropic}; gas::PerfectGas=DefaultPerfectGas)
  value(A_over_Astar) >= 1.0 || error("A/A* must be 1 or larger. Maybe you need to supply the inverse?")
  Msup = find_zero(x -> AOverAStar(MachNumber(x),gas=gas)-A_over_Astar,(1,Inf),order=16)
  return MachNumber(Msup)
end

function SubsonicMachNumber(A::Area,Aloc::Area,ploc_over_p0::PressureRatio,::Type{Isentropic}; gas::PerfectGas=DefaultPerfectGas)
    Mloc = MachNumber(ploc_over_p0,Isentropic,gas=gas)
    Astar = AStar(Aloc,Mloc,gas=gas)
    SubsonicMachNumber(A,Astar,Isentropic,gas=gas)
end

function SupersonicMachNumber(A::Area,Aloc::Area,ploc_over_p0::PressureRatio,::Type{Isentropic}; gas::PerfectGas=DefaultPerfectGas)
    Mloc = MachNumber(ploc_over_p0,Isentropic,gas=gas)
    Astar = AStar(Aloc,Mloc,gas=gas)
    SupersonicMachNumber(A,Astar,Isentropic,gas=gas)
end

SubsonicMachNumber(A::Area,Astar::Area, ::Type{Isentropic}; gas::PerfectGas=DefaultPerfectGas) =
    SubsonicMachNumber(AreaRatio(A/Astar),Isentropic,gas=gas)

SupersonicMachNumber(A::Area,Astar::Area, ::Type{Isentropic}; gas::PerfectGas=DefaultPerfectGas) =
    SupersonicMachNumber(AreaRatio(A/Astar),Isentropic,gas=gas)

function MachNumber(A_over_Astar::AreaRatio,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    return SubsonicMachNumber(A_over_Astar,Isentropic,gas=gas),
           SupersonicMachNumber(A_over_Astar,Isentropic,gas=gas)
end

MachNumber(A_over_Astar::AreaRatio;gas::PerfectGas=DefaultPerfectGas) = MachNumber(A_over_Astar,Isentropic,gas=gas)

function MachNumber(M1::MachNumber,A1::Area,A2::Area,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
    M2sub, M2sup = MachNumber(AreaRatio(A2/AStar(A1,M1,gas=gas)),gas=gas)
end
MachNumber(M1::MachNumber,A1::Area,A2::Area;gas::PerfectGas=DefaultPerfectGas) = MachNumber(M1,A2,A2,Isentropic,gas=gas)

function SubsonicPOverP0(A::Area,Aloc::Area,ploc_over_p0::PressureRatio,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
  Mloc = MachNumber(ploc_over_p0,Isentropic,gas=gas)
  Astar = AStar(Aloc,Mloc,gas=gas)
  SubsonicPOverP0(A,Astar,Isentropic,gas=gas)
end

function SupersonicPOverP0(A::Area,Aloc::Area,ploc_over_p0::PressureRatio,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
  Mloc = MachNumber(ploc_over_p0,Isentropic,gas=gas)
  Astar = AStar(Aloc,Mloc,gas=gas)
  SupersonicPOverP0(A,Astar,Isentropic,gas=gas)
end

function SubsonicPOverP0(A::Area,Astar::Area,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
  M = SubsonicMachNumber(A,Astar,Isentropic,gas=gas)
  PressureRatio(1/P0OverP(M,Isentropic,gas=gas))
end

function SupersonicPOverP0(A::Area,Astar::Area,::Type{Isentropic};gas::PerfectGas=DefaultPerfectGas)
  M = SupersonicMachNumber(A,Astar,Isentropic,gas=gas)
  PressureRatio(1/P0OverP(M,Isentropic,gas=gas))
end


"""
    _xi(r)

Returns the value of 'xi' given by the change of variable `r->xi`.
See `Aoki et al. (1979)`.
"""
function _xi(r::Float64)
    r_a = r/a
    return (r_a^2-1)/(r_a^2+1)
end

"""
    radius(xi)

Returns the value of the radius given the value of `xi`.
See `Aoki et al. (1979)`.
"""
function radius(xi::Float64)
    return a*sqrt((1+xi)/(1-xi))
end

"""
    Omega(xi,x,q)

Computes the dimensionless angular velocity of the system.
See `Reddish et al. (2021)`.
"""
function Omega(xi::Float64,x::Float64=0.0, q::Float64=1.0)
    r = radius(xi)
    bulb = (a/c)^3*x/(1-x)*((1-xi)/2)^(-3/2)*(1+(r/c)^2)^(-3/2)
    halo = (a/b)^3*(1/q-1)*((1-xi)/2)^(-3/2)*(1+(r/b)^2)^(-3/2)
    disk = 1-eps/((1-x)^(2/3))
    return sqrt(1-x)*((1-xi)/2)^(3/4)*sqrt(bulb+halo+disk)
end

"""
    dOmegadxi(xi,x,q)

Computes the `xi`-gradient of the dimensionless angular velocity of the system.
See `Aoki et al. (1979)` and `Reddish et al. (2021)`.
"""
function dOmegadxi(xi::Float64,x::Float64=0.0, q::Float64=1.0)
    rc = a/c
    rb = a/b
    num = (-3+3*eps/(1-x)^(2/3)+(12*(-1+q)*rb^5*sqrt((2-2*xi)/(1-xi+rb^2*(1+xi))))/(q*sqrt(1-xi)*(1-xi+rb^2*(1+xi))^2)
           +(12*rc^5*x*sqrt((2-2*xi)/(1-xi+rc^2*(1+xi)))/((-1+x)*sqrt(1-xi)*(1-xi+rc^2*(1+xi))^2)))
    den = (4*2^(3/4)*(1-xi)^(1/4)*sqrt(1-eps/(1-x)^(2/3)+(2*sqrt(2)*(-1+1/q)*rb^3*(1-(rb^2*(1+xi))/(-1+xi))^(-3/2))
            /(1-xi)^(3/2)+(2*sqrt(2)*x*rc^3*(1-(rc^2*(1+xi))/(-1+xi))^(-3/2))
                    /((1-x)*(1-xi)^(3/2))))

    return sqrt(1-x)*(num/den)
end

"""
    kappaSqOverTwoOmega(xi,x,q)

Computes the dimensionless ratio of the squared epicyclic frequency with twice
the angular velocity of the system.
See `Aoki et al. (1979)` and `Reddish et al. (2021)`.
"""
function kappaSqOverTwoOmega(xi::Float64,x::Float64=0.0, q::Float64=1.0)
    omega = Omega(xi,x,q)
    domega = dOmegadxi(xi,x,q)
#    return (1-eps)^(1/2)*((1-xi)/2)^(3/4)*(1/2+(3/2)*(1-xi)/2)
    return 2*omega*(1+(1+xi)*(1-xi)*domega/(2*omega))
end

##############################
# Test
##############################

"""
    dOmegadxi(xi,x,q)

Computes the numerical `xi`-gradient of the dimensionless angular velocity of the system.
"""
function dOmegadxiNum(xi::Float64,x::Float64=0.0, q::Float64=1.0,prec::Float64=0.0001)
    return (Omega(xi+prec,x,q)-Omega(xi-prec,x,q))/(2*prec)
end


"""
   vbmf_parameters

Compound type for vbmf computation. Contains the following fields:\n

    L::Int - number of rows of the original matrix
    M::Int - number of columns of the original matrix
    H::Int - internal product dimension
    H1::Int - number of columns of B that should belong to a contaminated class
    labels::Array{Int64, 1} - which columns of Y are labeled as non-contaminated
    AHat::Array{Float64, 2} - mean value of A, size (M, H)
    BHat::Array{Float64, 2} - mean value of B, size (L, H)
    SigmaA::Array{Float64, 2} - covariance of A, size (H, H)
    SigmaB::Array{Float64, 2} - covariance of B, size (H, H)
    CA::Array{Float64, 2} - prior covariance of A, size (H, H)
    CB::Array{Float64, 2} - prior covariance of B, size (H, H)
    invCA::Array{Float64, 2} - inverse of cA
    invCB::Array{Float64, 2} - inverse of cB
    sigma::Float64 - variance of data
    YHat::Array{Float64, 2} - estimate of Y, size (L, M)
"""
type vbmf_parameters
    L::Int
    M::Int
    H::Int
    H1::Int
    labels::Array{Int64,1}
    AHat::Array{Float64, 2}
    BHat::Array{Float64, 2}
    SigmaA::Array{Float64, 2}
    SigmaB::Array{Float64, 2}
    CA::Array{Float64, 2}
    CB::Array{Float64, 2}
    invCA::Array{Float64, 2}
    invCB::Array{Float64, 2}
    sigma2::Float64
    YHat::Array{Float64, 2}

    vbmf_parameters() = new()
end

"""
    vbmf_init(L::Int, M::Int, H::Int; ca::Float64, cb::Float64, sigma::Float64, H1::Int = 0,
    labels::Array{Int64,1} = Array{Int64,1}())

Returns an initialized structure of type vbmf_parameters.
"""
function vbmf_init(Y::Array{Float64,2}, H::Int; ca::Float64 = 1.0, cb::Float64 = 1.0, sigma2::Float64 = 1.0,
    H1::Int = 0, labels::Array{Int64,1} = Array{Int64,1}())
    params = vbmf_parameters()
    L, M = size(Y)

    params.L, params.M = L, M
    params.H = H
    params.H1 = H1
    params.labels = labels

    params.AHat = randn(M, H)
    # now set zeroes to where the columns of Y labeled as non-infected are
    # in A, these are rows
    params.AHat[labels, end-H1+1:end] = 0.0
    params.BHat = randn(L, H)
    params.SigmaA = zeros(H, H)
    params.SigmaB = zeros(H, H)
    params.CA = ca*eye(H)
    params.CB = cb*eye(H)
    params.invCA = inv(params.CA)
    params.invCB = inv(params.CB)
    params.sigma2 = sigma2
    params.YHat = params.BHat*params.AHat'

    return params
end

"""
   copy(params_in::vbmf_parameters)

Copy function for vbmfa_parameters.
"""
function copy(params_in::vbmf_parameters)
    params = vbmf_parameters()

    for field in fieldnames(params_in)
        setfield!(params, field, getfield(params_in, field))
    end

    return params
end

"""
    updateA!(Y::Array{Float64,2}, params::vbmf_parameters)

Updates mean and covariance of the A matrix.
"""
function updateA!(Y::Array{Float64,2}, params::vbmf_parameters)
    params.SigmaA = params.sigma2*inv(params.BHat'*params.BHat +
        params.L*params.SigmaB + params.sigma2*params.invCA)
    params.AHat =  Y'*params.BHat*params.SigmaA/params.sigma2
    # now set zeroes to where the columns of Y labeled as non-infected are
    # in A, these are rows
    params.AHat[params.labels, end-params.H1+1:end] = 0.0
end

"""
    updateB!(Y::Array{Float64,2}, params::vbmf_parameters)

Updates mean and covariance of the B matrix.
"""
function updateB!(Y::Array{Float64,2}, params::vbmf_parameters)
    params.SigmaB = params.sigma2*inv(params.AHat'*params.AHat +
        params.M*params.SigmaA + params.sigma2*params.invCB)
    params.BHat =  Y*params.AHat*params.SigmaB/params.sigma2
end

"""
    updateYHat!(params::vbmf_parameters)

Updates estimate of Y.
"""
function updateYHat!(params::vbmf_parameters)
    params.YHat = params.BHat*params.AHat'
end

"""
    updateCA!(params::vbmf_parameters)

Updates the estimate of CA.
"""
function updateCA!(params::vbmf_parameters)
    for h in 1:params.H
        params.CA[h,h] = norm2(params.AHat[:,h])/params.M + params.SigmaA[h,h]
    end
    params.invCA = inv(params.CA)
end

"""
    updateCB!(params::vbmf_parameters)

Updates the estimate of CB.
"""
function updateCB!(params::vbmf_parameters)
    for h in 1:params.H
        params.CB[h,h] = norm2(params.BHat[:,h])/params.L + params.SigmaB[h,h]
    end
    params.invCB = inv(params.CB)
end

"""
    updateSigma2!(Y::Array{Float64,2}, params::vbmf_parameters)

Updates estimate of sigma^2, the measurement noise.
"""
function updateSigma2!(Y::Array{Float64,2}, params::vbmf_parameters)
    params.sigma2 = (norm2(Y) - trace(2*Y'*params.BHat*params.AHat') +
        trace((params.AHat'*params.AHat + params.M*params.SigmaA)*
            (params.BHat'*params.BHat + params.L*params.SigmaB)))/(params.L*params.M)
end

"""
    vbmf!(Y::Array{Float64, 2}, params::vbmf_parameters, niter::Int, eps::Float64 = 1e-6, est_covs = false,
    est_var = false, logdir = "", desc = "", verb = false)

Computes variational bayes matrix factorization of Y = AB' + E. Independence of A and B is assumed.
Estimation of prior covariance cA and cB and of variance sigma can be turned on and off. Estimates
of cA and cB are empirical.
The prior model is following:

    p(Y|A,B) = N(Y|BA^T, sigma^2*I)
    p(A) = N(A|0, C_A), C_A = diag(c_a)
    p(B) = N(B|0, C_B), C_B = diag(c_b)

The params argument with initialized data is modified and contains the resulting estimates after the
algorithm stops.
"""
function vbmf!(Y::Array{Float64, 2}, params::vbmf_parameters, niter::Int; eps::Float64 = 1e-6, est_covs::Bool = false,
    est_var::Bool = false, logdir = "", desc = "", verb = false)
    priors = Dict()

    # create the log dictionary
    log = false
    if logdir !=""
        log = true
        logVar = create_log(params)
    end

    # choice of convergence control variable
    convergence_var = :BHat
    old = getfield(params, convergence_var)
    d = eps + 1.0 # delta
    i = 1

    # run the loop for a given number of iterations
    while ((i <= niter) && (d > eps))
        updateA!(Y, params)
        updateB!(Y, params)

        if est_covs
            updateCA!(params)
            updateCB!(params)
        end

        if est_var
            updateSigma2!(Y, params)
        end

        if log
            update_log!(logVar, params)
        end

        # check convergence
        d = delta(getfield(params, convergence_var), old)
        old = getfield(params, convergence_var)
        i += 1
    end

    # finally, compute the estimate of Y
    updateYHat!(params)

    # convergence info
    if verb
        print("Factorization finished after ", i-1, " iterations, eps = ", d, "\n")
    end

    # save inputs and outputs
    if log
        println("Saving outputs and inputs under ", logdir, "/")
        save_log(logVar, Y, priors, logdir, desc = desc)
    end

    return params
end

"""
    vbmf(Y::Array{Float64, 2}, params_in::vbmf_parameters, niter::Int, eps::Float64 = 1e-6, est_covs = false, est_var = false)

Calls vbmf_sparse!() but copies the params_in argument so that it is not modified and can be reused.
"""
function vbmf(Y::Array{Float64, 2}, params_in::vbmf_parameters, niter::Int; eps::Float64 = 1e-6, est_covs::Bool = false,
    est_var::Bool = false, logdir = "", desc = "", verb = false)
    # copy the input params
    params = copy(params_in)

    # run the algorithm
    vbmf!(Y, params, niter, eps = eps, est_covs = est_covs, est_var = est_var,
        logdir = logdir, desc = desc, verb = verb)

    return params
end


module AnalyticalFieldsTests

using Test
using Gridap
import Gridap: ∇

p1 = Point(1.0,2.0)
p2 = Point(0.0,3.0)
p3 = Point(7.0,4.0)
p4 = Point(8.0,1.0)
p = [p1,p2,p3,p4]

fun(x) = 3*x[1]

f = AnalyticalField(fun,2)
v = fun.(p)
test_field_without_gradient(f,p,v)

fungrad(x) = VectorValue(3.0,0.0)

∇(::typeof(fun)) = fungrad

f = AnalyticalField(fun,2)
v = fun.(p)
g = fungrad.(p)
test_field(f,p,v,g)

end # module


##

import ACE, ACEbase
using LinearAlgebra, StaticArrays, Test, Printf, ACE.Testing
using ACE: evaluate, evaluate_d, evaluate_ed, Trig1pBasis, Trig

##

@info(" --------- Trig Tests --------- ")
@info("Test conversion to angle")
for ntest = 1:30
   θ = 2*π*rand() - π
   rr = [cos(θ), sin(θ)]
   print_tf(@test( ACE._theta(rr) ≈ θ ))
end
println()

##

@info("test gradient of coordinate transform")
for ntest = 1:30
   rr = randn(2) # [cos(θ), sin(θ)]
   G = rr -> ACE._theta(rr)
   dG = rr -> ACE._theta_ed(rr)[2] |> Vector
   print_tf(@test( all(
            ACEbase.Testing.fdtest(G, dG, rr; verbose=false)
         )))
end
println()

##

@info("test evaluation of trig basis")

bE = Trig(10)

for ntest = 1:30
   L = bE.L
   θ = π * rand()
   E = [ exp(im * l * θ) for l = -L:L ]
   rr = [cos(θ), sin(θ)]
   print_tf( @test evaluate(bE, rr) ≈ E )
end
println()

##

@info("test jacobian of trig basis ")
θ = π * rand()
rr = [cos(θ), sin(θ)]
E, dE = evaluate_ed(bE, rr)

for ntest = 1:30
   rr = randn(2)
   U = randn(length(E))
   F = rr -> ACE.contract(evaluate(bE, rr), U)
   dF = rr -> Vector(ACE.contract(evaluate_ed(bE, rr)[2], U))
   print_tf(@test( all(
          ACEbase.Testing.fdtest(F, dF, rr; verbose=false)
      )))
end
println()

##


struct ProjCGWork{T}
    r::Vector{T}
    g::Vector{T}
    d::Vector{T}
    rp::Vector{T}
    gp::Vector{T}
    Ad::Vector{T}
    Utr::Vector{T}
end

ProjCGWork(n::Int, m::Int) = ProjCGWork([Array{Float64}(undef, n) for i in 1:6]..., Array{Float64}(undef, m))

#=
PROJCG does projected conjugate gradient with orthonormal projection matrix
U and PSD matrix A. That is, the following system is solved:

[ A  U ] [ x ] = [ b ]
[ U' 0 ] [ λ ]   [ c ]

Returns a direction of negative curvature of A if found, storing the direction
in x and setting λ to all NaNs.

Usage:
  [x, lambda, resvec, Uvec, dvec] = projcg(A, b, U, c, tol, maxit)

Inputs:
A, b, U, c - as defined above
tol [1e-6] - absolute tolerance for convergence of 2-norm of residual on the
  constraint manifold
maxit [size(A,1) - size(U,2)] - maximum number of iterations if tol is not met

Overwrites:
x, λ - solution

Output:
i - number of iterations taken
nr - final tolerance achieved for projected residual, (I - U*U')*(A*x - b)
=#
function projcg!(x, λ, A, U, b, c;
    tol::Float64=1e-6, maxit::Int=length(b)+length(c), work::ProjCGWork{T}=ProjCGWork(length(b), length(c))) where T

n = length(b)
m = length(c)

# work variables
r = view(work.r, 1:n)
g = view(work.g, 1:n)
d = view(work.d, 1:n)
rp = view(work.rp, 1:n)
gp = view(work.gp, 1:n)
Ad = view(work.Ad, 1:n)
Utr = view(work.Utr, 1:m)

mul!(x, U, c)                       # satisfies U' x = c
r .= b
mul!(r, A, x, 1.0, -1.0)            # r = Ax - b
g .= r
mul!(Utr, U', r)
mul!(g, U, Utr, -1.0, 1.0)          # g = r - U*(U'*r);
r .= g
d .= -1.0.*g                        # multiplication avoids allocation

nr0 = norm(g);

# resvec = [nr0; zeros(maxit, 1)];

i = 0
nr = Inf

while i < min(maxit, n+m)
    i += 1

    mul!(Ad, A, d)                  # Ad = A*d
    dAd = dot(d, Ad);

    if dAd <= 0
        # resvec = resvec(1:i);
        x .= d / norm(d)
        λ .= NaN
        return i, Inf
    end

    rg = dot(r, g);

    # rg cannot be negative
    if rg <= 0
        break
    end

    α = rg / dAd
    x .+= α.*d
    rp .= r .+ α.*Ad

    gp .= rp
    mul!(Utr, U', rp)
    mul!(gp, U, Utr, -1.0, 1.0)      # gp = rp - U*(U'*rp);
    β = dot(rp, gp) / rg
    d .= β.*d .- gp
    g .= gp
    r .= gp

    nr = norm(g);
    # resvec(i+1) = nr;

    if nr < tol
        # resvec = resvec(1:i+1);

        break
    end
end

# solve for Lagrange multipliers
r .= b
mul!(r, A, x, -1.0, 1.0)            # r = b - A*x

mul!(λ, U', r);          # λ = U'*(b - A*x)

return i, nr
end


function gen_dr( r, center )
    Npoints = size(r)[2]
    dr = Array{Float64}(undef,Npoints)
    #
    for ip=1:Npoints
        dx2 = ( r[1,ip] - center[1] )^2
        dy2 = ( r[2,ip] - center[2] )^2
        dz2 = ( r[3,ip] - center[3] )^2
        dr[ip] = sqrt( dx2 + dy2 + dz2 )
    end
    return dr
end


include("vcm.jl")

# sample new sigma_g from InverseGamma with IG(a, b)
# see: supplemental of http://papers.nips.cc/paper/4579-a-nonparametric-variable-clustering-model
function random_sigma_g(B::VCMBuffer, γ_a::Float64, γ_b::Float64)

    Gm = (B.G.*B.C)

    a = γ_a + B.D  / 2.0
    b = γ_b + .5 * sum(Gm.*Gm)

    dist = InverseGamma(a, b)

    return sqrt( rand(dist) )

end

# sample new sigma_noise from InverseGamma with IG(a, b)
# see: supplemental of http://papers.nips.cc/paper/4579-a-nonparametric-variable-clustering-model
function random_sigma_noise(B::VCMBuffer, γ_a::Float64, γ_b::Float64)

    E = B.Y - (B.G.*B.C) * B.X

    a = ( γ_a + (B.D*B.N) ) / 2.0
    b = γ_b + .5 * trace(E'*E)

    dist = InverseGamma(a, b)

    return sqrt( rand(dist) )

end

# sample latent factor matrix X
# see: supplemental of http://papers.nips.cc/paper/4579-a-nonparametric-variable-clustering-model
function sample_latent_factors!(Y::Array, B::VCMBuffer)

    Gm = B.G .* B.C
    tem = Gm' / B.σ_noise^2

    sigma_x_p = try
            B.σ_x^(-2)
        catch
            1
        end

    λ = (B.σ_noise^(-2)) * sum( Gm .* Gm, 1 ) + sigma_x_p

    for n in 1:B.N

        μ = (tem * Y[:,n]) ./ λ'
        B.X[:,n] = λ'.^(-0.5) .* randn(B.K, 1) + μ

    end

    B
end

function sample_vcm_data(N::Int, D::Int, G::Array{Float64}, X::Array{Float64}, C::Array{Float64},
    mu_noise::Float64, sigma_noise::Float64)

    ϵ = mu_noise + sigma_noise * randn(D, N)

    return (G .* C) * X + ϵ

end

# sample cluster assignments c and lattent factor loadings G by integrating out g and instantiating X.
# See: Neal (2000) Algorithm 8 for details and
# supplemental of http://papers.nips.cc/paper/4579-a-nonparametric-variable-clustering-model
function gibbs_aux_assignment!(B::VCMBuffer; out_dim = -1)

    (D, N) = size(B.Y)

    if out_dim != -1
        perm = collect(out_dim)
    else
        perm = randperm(D)
    end

    auxDist = Normal(B.μ_x, B.σ_x)

    X2Cache = sum(B.X.^2, 2)

    for d in perm

        #println("out dim", out_dim)

        if out_dim == -1
            # get cluster of current item
            cluster = B.cc[d]

            #println("got cluster")

            # check if cluster is empty
            if sum(B.cc .== cluster) == 1

                #println("found empty cluster")

                B.K -= 1
                idx = find(B.cc .> cluster)
                B.cc[idx] -= 1

                #C = C[:,[1:cluster-1, cluster+1:end]]
                B.G = B.G[:,[1:cluster-1; cluster+1:end]]

                r = B.X[cluster,:]
                B.X = B.X[[1:cluster-1; cluster+1:end],:]

                #println("removed empty cluster")

            end
        else
            B.G = cat(1, B.G, zeros(1, B.K))
            B.cc = cat(1, B.cc, 0)
            #C = cat(1, C, zeros(Int, 1, K))
        end

        #println("generate cache...")

        Xm = reduce(hcat, map(m -> rand(auxDist, B.N), 1:B.m_aux))'
        X2m = sum(Xm.^2, 2)

        XX = cat(1, B.X, Xm)
        XX2Cache = cat(1, X2Cache, X2m)

        #println("prepare p..")

        # compute P(c_i = c | c_-i, y_i, ϕ_1, ..., ϕ_h ) according to Neal (2000)
        p = ones(B.K+B.m_aux) .* -Inf
        p[1:B.K] = log( map( c -> sum(B.cc .== c) / (B.α + D - 1), 1:B.K ) )
        p[B.K + 1:B.K + B.m_aux] = log( B.α / B.m_aux ) - log( B.α + D - 1 )

        Gsamps = zeros(1, B.K + B.m_aux)

        xy = XX * B.Y[d,:]'

        for k in 1:B.K + B.m_aux

            λ = XX2Cache[k] / B.σ_noise^2 + 1.0 / B.σ_g^2
            μ = ( xy[k] / B.σ_noise^2 + B.μ_g/B.σ_g^2 ) / λ

            p[k] = p[k] - .5 * ( log(λ) - λ * μ^2 )

            Gsamps[k] = μ + λ^(-.5) * randn()
        end

        #println("finished comp. of p...")

        # common normalization
        p = exp( p - maximum(p) )

        kk = rand_indices(p)

        #println("got new k")

        g = Gsamps[kk]

        #println("found g")

        if kk > B.K
            # open new cluster
            B.X = cat(1, B.X, XX[kk,:])
            #C = cat(2, C, zeros(Int, D, 1))
            B.G = cat(2, B.G, zeros(D, 1))

            B.K += 1
            kk = B.K

            X2Cache = sum(B.X.^2, 2)

        end

        if out_dim == -1
            B.G[d, kk] = g
            B.cc[d] = kk
            #C[d,:] = zeros(1, K)
            #C[d,kk] = 1
        else
            B.G[out_dim, kk] = g
            B.cc[out_dim] = kk
            #C[out_dim,:] = zeros(1, K)
            #C[out_dim,kk] = 1
        end

        #println("loop end")

    end

    B.C = spzeros(D, B.K)
    B.C[B.cc] = 1

    B
end


# This file is part of the IntervalArithmetic.jl package; MIT licensed

for f in (:exp, :expm1)
    @eval begin
        ($f)(a::Interval{T}) where T = @round( ($f)(a.lo), ($f)(a.hi) )
        ($f)(x::EmptyInterval) = x
    end
end

ln_interval(::Type{T}, ::Val{N}) where {T, N} = log(unsafe_atomic(T, N))
ln2_interval(::Type{T}) where T = ln_interval(T, Val(2))
ln10_interval(::Type{T}) where T = ln_interval(T, Val(10))

# no CRlibm version
exp2(x::Interval{T}) where T = exp(ln2_interval(T) * x)
exp10(x::Interval{T}) where T = exp(ln10_interval(T) * x)

for f in (:exp2, :exp10)
  # add BigFloat functions with rounding:
    @eval ($f)(a::Interval{BigFloat}) = @round( ($f)(a.lo), ($f)(a.hi) )
    @eval ($f)(x::EmptyInterval) = x
end

for f in (:log, :log2, :log10)
    @eval ($(Symbol(f, "_inner")))(x::EmptyInterval) = x
    @eval ($(Symbol(f, "_inner")))(a::Interval) =
        a == zero(a) ? emptyinterval(a) : @round(($f)(a.lo), ($f)(a.hi))
    @eval function ($f)(a::Interval{T}) where T
            domain = nonnegatives(T)
            return ($(Symbol(f, "_inner")))(a ∩ domain)
        end
    @eval ($f)(x::EmptyInterval) = x
end

log1p_inner(a::Interval{T}) where T =
    a == -one(a) ? emptyinterval(T) : @round( log1p(a.lo), log1p(a.hi) )
log1p_inner(x::EmptyInterval) = x

function log1p(a::Interval{T}) where T
    domain = unsafe_interval(-one(T), T(Inf))
    return log1p_inner(a ∩ domain)
end
log1p(x::EmptyInterval) = x