# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
# Since these FMAs can increase the performance of many numerical algorithms,
# we need to opt-in explicitly.
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
@muladd begin

@inline num_faces(elem::Tri) = 3
@inline num_faces(elem::Quad) = 4

#     compute_triangle_area(tri)
#
# Computes the area of a triangle given `tri`, which is a tuple of three points (vectors),
# using the [Shoelace_formula](https://en.wikipedia.org/wiki/Shoelace_formula).
function compute_triangle_area(tri)
    A, B, C = tri
    return 0.5 * (A[1] * (B[2] - C[2]) + B[1] * (C[2]-A[2]) + C[1] * (A[2] - B[2]))
end

#   reference_plotting_triangulation(reference_plotting_coordinates)
#
# Computes a triangulation of the points in `reference_plotting_coordinates`, which is a tuple containing
# vectors of plotting points on the reference element (e.g., reference_plotting_coordinates = (r,s)).
# The reference element is assumed to be [-1,1]^d.
#
# This function returns `t` which is a `3 x N_tri` Matrix{Int} containing indices of triangles in the
# triangulation of the plotting points, with zero-volume triangles removed.
#
# For example, r[t[1, i]] returns the first reference coordinate of the 1st point on the ith triangle.
function reference_plotting_triangulation(reference_plotting_coordinates, tol=50*eps())
  # on-the-fly triangulation of plotting nodes on the reference element
  tri_in = Triangulate.TriangulateIO()
  tri_in.pointlist = permutedims(hcat(reference_plotting_coordinates...))
  tri_out, _ = Triangulate.triangulate("Q", tri_in)
  triangles = tri_out.trianglelist

  # filter out sliver triangles
  has_volume = fill(true, size(triangles, 2))
  for i in axes(triangles, 2)
      ids = @view triangles[:, i]
      x_points = @view tri_out.pointlist[1, ids]
      y_points = @view tri_out.pointlist[2, ids]
      area = compute_triangle_area(zip(x_points, y_points))
      if abs(area) < tol
          has_volume[i] = false
      end
  end
  return permutedims(triangles[:, findall(has_volume)])
end

# This function is used to avoid type instabilities when calling `digest_solution_variables`.
function transform_to_solution_variables!(u, solution_variables, equations)
  for (i, u_i) in enumerate(u)
    u[i] = solution_variables(u_i, equations)
  end
end

#     global_plotting_triangulation_triplot(u_plot, rst_plot, xyz_plot)
#
# Returns (plotting_coordinates_x, plotting_coordinates_y, ..., plotting_values, plotting_triangulation).
# Output can be used with TriplotRecipes.DGTriPseudocolor(...).
#
# Inputs:
#   - xyz_plot = plotting points (tuple of matrices of size (Nplot, K))
#   - u_plot = matrix of size (Nplot, K) representing solution to plot.
#   - t = triangulation of reference plotting points
function global_plotting_triangulation_triplot(xyz_plot, u_plot, t)

  @assert size(first(xyz_plot), 1) == size(u_plot, 1) "Row dimension of u_plot does not match row dimension of xyz_plot"

  # build discontinuous data on plotting triangular mesh
  num_plotting_points, num_elements = size(u_plot)
  num_reference_plotting_triangles = size(t, 1)
  num_plotting_elements_total = num_reference_plotting_triangles * num_elements

  # each column of `tp` corresponds to a vertex of a plotting triangle
  tp = zeros(Int32, 3, num_plotting_elements_total)
  zp = similar(tp, eltype(u_plot))
  for e = 1:num_elements
    for i = 1:num_reference_plotting_triangles
      tp[:, i + (e-1)*num_reference_plotting_triangles] .= @views t[i, :] .+ (e-1) * num_plotting_points
      zp[:, i + (e-1)*num_reference_plotting_triangles] .= @views u_plot[t[i, :], e]
    end
  end
  return vec.(xyz_plot)..., zp, tp
end

#     mesh_plotting_wireframe(rd::RefElemData{2}, md::MeshData{2})
#
# Generates data for plotting a mesh wireframe given StartUpDG data types.
# Returns (plotting_coordinates_x, plotting_coordinates_y) for a 2D mesh wireframe.
function mesh_plotting_wireframe(rd::RefElemData{2}, md::MeshData{2}; num_plotting_points=25)

  # Construct 1D plotting interpolation matrix `Vp1D` for a single face
  @unpack N, Fmask = rd
  vandermonde_matrix_1D = StartUpDG.vandermonde(Line(), N, StartUpDG.nodes(Line(), N))
  rplot = LinRange(-1, 1, num_plotting_points)
  Vp1D = StartUpDG.vandermonde(Line(), N, rplot) / vandermonde_matrix_1D

  num_face_points = N+1
  num_faces_total = num_faces(rd.elementType) * md.num_elements
  xf, yf = map(x->reshape(view(x, Fmask, :), num_face_points, num_faces_total), md.xyz)

  num_face_plotting_points = size(Vp1D, 1)
  x_mesh, y_mesh = ntuple(_->zeros(num_face_plotting_points, num_faces_total), 2)
  for f in 1:num_faces_total
    mul!(view(x_mesh, :, f), Vp1D, view(xf, :, f))
    mul!(view(y_mesh, :, f), Vp1D, view(yf, :, f))
  end

  return x_mesh, y_mesh
end

# These methods are used internally to set the default value of the solution variables:
# - If a `cons2prim` for the given `equations` exists, use it
# - Otherwise, use `cons2cons`, which is defined for all systems of equations
digest_solution_variables(equations, solution_variables) = solution_variables
function digest_solution_variables(equations, solution_variables::Nothing)
  if hasmethod(cons2prim, Tuple{AbstractVector, typeof(equations)})
    return cons2prim
  else
    return cons2cons
  end
end

"""
    adapt_to_mesh_level!(u_ode, semi, level)
    adapt_to_mesh_level!(sol::Trixi.TrixiODESolution, level)

Like [`adapt_to_mesh_level`](@ref), but modifies the solution and parts of the
semidiscretization (mesh and caches) in place.
"""
function adapt_to_mesh_level!(u_ode, semi, level)
  # Create AMR callback with controller that refines everything towards a single level
  amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable=first), base_level=level)
  amr_callback = AMRCallback(semi, amr_controller, interval=0)

  # Adapt mesh until it does not change anymore
  has_changed = amr_callback.affect!(u_ode, semi, 0.0, 0)
  while has_changed
    has_changed = amr_callback.affect!(u_ode, semi, 0.0, 0)
  end

  return u_ode, semi
end

adapt_to_mesh_level!(sol::TrixiODESolution, level) = adapt_to_mesh_level!(sol.u[end], sol.prob.p, level)

"""
    adapt_to_mesh_level(u_ode, semi, level)
    adapt_to_mesh_level(sol::Trixi.TrixiODESolution, level)

Use the regular adaptive mesh refinement routines to adaptively refine/coarsen the solution `u_ode`
with semidiscretization `semi` towards a uniformly refined grid with refinement level `level`. The
solution and semidiscretization are copied such that the original objects remain *unaltered*.

A convenience method accepts an ODE solution object, from which solution and semidiscretization are
extracted as needed.

See also: [`adapt_to_mesh_level!`](@ref)
"""
function adapt_to_mesh_level(u_ode, semi, level)
  # Create new semidiscretization with copy of the current mesh
  mesh, _, _, _ = mesh_equations_solver_cache(semi)
  new_semi = remake(semi, mesh=deepcopy(mesh))

  return adapt_to_mesh_level!(deepcopy(u_ode), new_semi, level)
end

adapt_to_mesh_level(sol::TrixiODESolution, level) = adapt_to_mesh_level(sol.u[end], sol.prob.p, level)

# Extract data from a 2D/3D DG solution and prepare it for visualization as a heatmap/contour plot.
#
# Returns a tuple with
# - x coordinates
# - y coordinates
# - nodal 2D data
# - x vertices for mesh visualization
# - y vertices for mesh visualization
#
# Note: This is a low-level function that is not considered as part of Trixi's interface and may
#       thus be changed in future releases.
function get_data_2d(center_level_0, length_level_0, leaf_cells, coordinates, levels, ndims,
                     unstructured_data, n_nodes,
                     grid_lines=false, max_supported_level=11, nvisnodes=nothing,
                     slice=:xy, point=(0.0, 0.0, 0.0))
  # Determine resolution for data interpolation
  max_level = maximum(levels)
  if max_level > max_supported_level
    error("Maximum refinement level $max_level is higher than " *
          "maximum supported level $max_supported_level")
  end
  max_available_nodes_per_finest_element = 2^(max_supported_level - max_level)
  if nvisnodes === nothing
    max_nvisnodes = 2 * n_nodes
  elseif nvisnodes == 0
    max_nvisnodes = n_nodes
  else
    max_nvisnodes = nvisnodes
  end
  nvisnodes_at_max_level = min(max_available_nodes_per_finest_element, max_nvisnodes)
  resolution = nvisnodes_at_max_level * 2^max_level
  nvisnodes_per_level = [2^(max_level - level)*nvisnodes_at_max_level for level in 0:max_level]
  # nvisnodes_per_level is an array (accessed by "level + 1" to accommodate
  # level-0-cell) that contains the number of visualization nodes for any
  # refinement level to visualize on an equidistant grid

  if ndims == 3
    (unstructured_data, coordinates, levels,
        center_level_0) = unstructured_3d_to_2d(unstructured_data,
        coordinates, levels, length_level_0, center_level_0, slice,
        point)
  end

  # Normalize element coordinates: move center to (0, 0) and domain size to [-1, 1]²
  n_elements = length(levels)
  normalized_coordinates = similar(coordinates)
  for element_id in 1:n_elements
    @views normalized_coordinates[:, element_id] .= (
          (coordinates[:, element_id] .- center_level_0) ./ (length_level_0 / 2 ))
  end

  # Interpolate unstructured DG data to structured data
  (structured_data =
      unstructured2structured(unstructured_data, normalized_coordinates,
                              levels, resolution, nvisnodes_per_level))

  # Interpolate cell-centered values to node-centered values
  node_centered_data = cell2node(structured_data)

  # Determine axis coordinates for contour plot
  xs = collect(range(-1, 1, length=resolution+1)) .* length_level_0/2 .+ center_level_0[1]
  ys = collect(range(-1, 1, length=resolution+1)) .* length_level_0/2 .+ center_level_0[2]

  # Determine element vertices to plot grid lines
  if grid_lines
    mesh_vertices_x, mesh_vertices_y = calc_vertices(coordinates, levels, length_level_0)
  else
    mesh_vertices_x = Vector{Float64}(undef, 0)
    mesh_vertices_y = Vector{Float64}(undef, 0)
  end

  return xs, ys, node_centered_data, mesh_vertices_x, mesh_vertices_y
end

# Extract data from a 1D DG solution and prepare it for visualization as a line plot.
# This returns a tuple with
# - x coordinates
# - nodal 1D data
#
# Note: This is a low-level function that is not considered as part of Trixi's interface and may
#       thus be changed in future releases.
function get_data_1d(original_nodes, unstructured_data, nvisnodes)
  # Get the dimensions of u; where n_vars is the number of variables, n_nodes the number of nodal values per element and n_elements the total number of elements.
  n_nodes, n_elements, n_vars = size(unstructured_data)

  # Set the amount of nodes visualized according to nvisnodes.
  if nvisnodes === nothing
    max_nvisnodes = 2 * n_nodes
  elseif nvisnodes == 0
    max_nvisnodes = n_nodes
  else
    @assert nvisnodes >= 2 "nvisnodes must be zero or >= 2"
    max_nvisnodes = nvisnodes
  end

  interpolated_nodes = Array{eltype(original_nodes),    2}(undef, max_nvisnodes, n_elements)
  interpolated_data  = Array{eltype(unstructured_data), 3}(undef, max_nvisnodes, n_elements, n_vars)

  for j in 1:n_elements
    # Interpolate on an equidistant grid.
    interpolated_nodes[:, j] .= range(original_nodes[1,1,j], original_nodes[1,end,j], length = max_nvisnodes)
  end

  nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes)
  nodes_out = collect(range(-1, 1, length = max_nvisnodes))

  # Calculate vandermonde matrix for interpolation.
  vandermonde = polynomial_interpolation_matrix(nodes_in, nodes_out)

  # Iterate over all variables.
  for v in 1:n_vars
    # Interpolate data for each element.
    for element in 1:n_elements
      multiply_scalar_dimensionwise!(@view(interpolated_data[:, element, v]),
        vandermonde, @view(unstructured_data[:, element, v]))
    end
  end
  # Return results after data is reshaped
  return vec(interpolated_nodes), reshape(interpolated_data, :, n_vars), vcat(original_nodes[1, 1, :], original_nodes[1, end, end])
end

# Change order of dimensions (variables are now last) and convert data to `solution_variables`
#
# Note: This is a low-level function that is not considered as part of Trixi's interface and may
#       thus be changed in future releases.
function get_unstructured_data(u, solution_variables, mesh, equations, solver, cache)

  if solution_variables === cons2cons
    raw_data = u
    n_vars = size(raw_data, 1)
  else
    # FIXME: Remove this comment once the implementation following it has been verified
    # Reinterpret the solution array as an array of conservative variables,
    # compute the solution variables via broadcasting, and reinterpret the
    # result as a plain array of floating point numbers
    # raw_data = Array(reinterpret(eltype(u),
    #        solution_variables.(reinterpret(SVector{nvariables(equations),eltype(u)}, u),
    #                   Ref(equations))))
    # n_vars = size(raw_data, 1)
    n_vars_in = nvariables(equations)
    n_vars = length(solution_variables(get_node_vars(u, equations, solver), equations))
    raw_data = Array{eltype(u)}(undef, n_vars, Base.tail(size(u))...)
    reshaped_u = reshape(u, n_vars_in, :)
    reshaped_r = reshape(raw_data, n_vars, :)
    for idx in axes(reshaped_u, 2)
      reshaped_r[:, idx] = solution_variables(get_node_vars(reshaped_u, equations, solver, idx), equations)
    end
  end

  unstructured_data = Array{eltype(raw_data)}(undef,
                                              ntuple((d) -> nnodes(solver), ndims(equations))...,
                                              nelements(solver, cache), n_vars)
  for variable in 1:n_vars
    @views unstructured_data[.., :, variable] .= raw_data[variable, .., :]
  end

  return unstructured_data
end

# Convert cell-centered values to node-centered values by averaging over all
# four neighbors and making use of the periodicity of the solution
#
# Note: This is a low-level function that is not considered as part of Trixi's interface and may
#       thus be changed in future releases.
function cell2node(cell_centered_data)
  # Create temporary data structure to make the averaging algorithm as simple
  # as possible (by using a ghost layer)
  tmp = similar(first(cell_centered_data), size(first(cell_centered_data)) .+ (2, 2))

  # Create output data structure
  resolution_in, _ = size(first(cell_centered_data))
  resolution_out = resolution_in + 1
  node_centered_data = [Matrix{Float64}(undef, resolution_out, resolution_out)
                        for _ in 1:length(cell_centered_data)]

  for (cell_data, node_data) in zip(cell_centered_data, node_centered_data)
    # Fill center with original data
    tmp[2:end-1, 2:end-1] .= cell_data

    # Fill sides with opposite data (periodic domain)
    # x-direction
    tmp[1,   2:end-1] .= cell_data[end, :]
    tmp[end, 2:end-1] .= cell_data[1,   :]
    # y-direction
    tmp[2:end-1, 1, ] .= cell_data[:, end]
    tmp[2:end-1, end] .= cell_data[:, 1, ]
    # Corners
    tmp[1,   1, ] = cell_data[end, end]
    tmp[end, 1, ] = cell_data[1,   end]
    tmp[1,   end] = cell_data[end, 1, ]
    tmp[end, end] = cell_data[1,   1, ]

    # Obtain node-centered value by averaging over neighboring cell-centered values
    for j in 1:resolution_out
      for i in 1:resolution_out
        node_data[i, j] = (tmp[i,   j, ] +
                           tmp[i+1, j, ] +
                           tmp[i,   j+1] +
                           tmp[i+1, j+1]) / 4
      end
    end
  end

  # Transpose
  for (index, data) in enumerate(node_centered_data)
    node_centered_data[index] = permutedims(data)
  end

  return node_centered_data
end

# Convert 3d unstructured data to 2d data.
# Additional to the new unstructured data updated coordinates, levels and
# center coordinates are returned.
#
# Note: This is a low-level function that is not considered as part of Trixi's interface and may
#       thus be changed in future releases.
function unstructured_3d_to_2d(unstructured_data, coordinates, levels,
                               length_level_0, center_level_0, slice,
                               point)
  if slice === :yz
    slice_dimension = 1
    other_dimensions = [2, 3]
  elseif slice === :xz
    slice_dimension = 2
    other_dimensions = [1, 3]
  elseif slice === :xy
    slice_dimension = 3
    other_dimensions = [1, 2]
  else
    error("illegal dimension '$slice', supported dimensions are :yz, :xz, and :xy")
  end

  # Limits of domain in slice dimension
  lower_limit = center_level_0[slice_dimension] - length_level_0 / 2
  upper_limit = center_level_0[slice_dimension] + length_level_0 / 2

  @assert length(point) >= 3 "Point must be three-dimensional."
  if point[slice_dimension] < lower_limit || point[slice_dimension] > upper_limit
    error(string("Slice plane is outside of domain.",
        " point[$slice_dimension]=$(point[slice_dimension]) must be between $lower_limit and $upper_limit"))
  end

  # Extract data shape information
  n_nodes_in, _, _, n_elements, n_variables = size(unstructured_data)

  # Get node coordinates for DG locations on reference element
  nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)

  # New unstructured data has one dimension less.
  # The redundant element ids are removed later.
  @views new_unstructured_data = similar(unstructured_data[1, ..])

  # Declare new empty arrays to fill in new coordinates and levels
  new_coordinates = Array{Float64}(undef, 2, n_elements)
  new_levels = Array{eltype(levels)}(undef, n_elements)

  # Counter for new element ids
  new_id = 0

  # Save vandermonde matrices in a Dict to prevent redundant generation
  vandermonde_to_2d = Dict()

  # Permute dimensions such that the slice dimension is always the
  # third dimension of the array. Below we can always interpolate in the
  # third dimension.
  if slice === :yz
    unstructured_data = permutedims(unstructured_data, [2, 3, 1, 4, 5])
  elseif slice === :xz
    unstructured_data = permutedims(unstructured_data, [1, 3, 2, 4, 5])
  end

  for element_id in 1:n_elements
    # Distance from center to border of this element (half the length)
    element_length = length_level_0 / 2^levels[element_id]
    min_coordinate = coordinates[:, element_id] .- element_length / 2
    max_coordinate = coordinates[:, element_id] .+ element_length / 2

    # Check if slice plane and current element intersect.
    # The first check uses a "greater but not equal" to only match one cell if the
    # slice plane lies between two cells.
    # The second check is needed if the slice plane is at the upper border of
    # the domain due to this.
    if !((min_coordinate[slice_dimension] <= point[slice_dimension] &&
          max_coordinate[slice_dimension] > point[slice_dimension]) ||
        (point[slice_dimension] == upper_limit &&
          max_coordinate[slice_dimension] == upper_limit))
      # Continue for loop if they don't intersect
      continue
    end

    # This element is of interest
    new_id += 1

    # Add element to new coordinates and levels
    new_coordinates[:, new_id] = coordinates[other_dimensions, element_id]
    new_levels[new_id] = levels[element_id]

    # Construct vandermonde matrix (or load from Dict if possible)
    normalized_intercept =
        (point[slice_dimension] - min_coordinate[slice_dimension]) /
        element_length * 2 - 1

    if haskey(vandermonde_to_2d, normalized_intercept)
      vandermonde = vandermonde_to_2d[normalized_intercept]
    else
      # Generate vandermonde matrix to interpolate values at nodes_in to one value
      vandermonde = polynomial_interpolation_matrix(nodes_in, [normalized_intercept])
      vandermonde_to_2d[normalized_intercept] = vandermonde
    end

    # 1D interpolation to specified slice plane
    # We permuted the dimensions above such that now the dimension in which
    # we will interpolate is always the third one.
    for i in 1:n_nodes_in
      for ii in 1:n_nodes_in
        # Interpolate in the third dimension
        data = unstructured_data[i, ii, :, element_id, :]

        value = multiply_dimensionwise(vandermonde, permutedims(data))
        new_unstructured_data[i, ii, new_id, :] = value[:, 1]
      end
    end
  end

  # Remove redundant element ids
  unstructured_data = new_unstructured_data[:, :, 1:new_id, :]
  new_coordinates = new_coordinates[:, 1:new_id]
  new_levels = new_levels[1:new_id]

  center_level_0 = center_level_0[other_dimensions]

  return unstructured_data, new_coordinates, new_levels, center_level_0
end

# Convert 2d unstructured data to 1d slice and interpolate them.
function unstructured_2d_to_1d(original_nodes, unstructured_data, nvisnodes, slice, point)

  if slice === :x
    slice_dimension = 2
    other_dimension = 1
  elseif slice === :y
    slice_dimension = 1
    other_dimension = 2
  else
    error("illegal dimension '$slice', supported dimensions are :x and :y")
  end

  # Set up data structures to stroe new 1D data.
  @views new_unstructured_data = similar(unstructured_data[1, ..])
  @views new_nodes = similar(original_nodes[1, 1, ..])

  n_nodes_in, _, n_elements, n_variables = size(unstructured_data)
  nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)

  # Test if point lies in the domain.
  lower_limit = original_nodes[1, 1, 1, 1]
  upper_limit = original_nodes[1, n_nodes_in, n_nodes_in, n_elements]

  @assert length(point) >= 2 "Point must be two-dimensional."
  if point[slice_dimension] < lower_limit || point[slice_dimension] > upper_limit
    error(string("Slice axis is outside of domain. ",
        " point[$slice_dimension]=$(point[slice_dimension]) must be between $lower_limit and $upper_limit"))
  end

  # Count the amount of new elements.
  new_id = 0

  # Permute dimensions so that the slice dimension is always in the correct place for later use.
  if slice === :y
    original_nodes = permutedims(original_nodes, [1, 3, 2, 4])
    unstructured_data = permutedims(unstructured_data, [2, 1, 3, 4])
  end

  # Iterate over all elements to find the ones that lie on the slice axis.
  for element_id in 1:n_elements
    min_coordinate = original_nodes[:, 1, 1, element_id]
    max_coordinate = original_nodes[:, n_nodes_in, n_nodes_in, element_id]
    element_length = max_coordinate - min_coordinate

    # Test if the element is on the slice axis. If not just continue with the next element.
    if !((min_coordinate[slice_dimension] <= point[slice_dimension] &&
        max_coordinate[slice_dimension] > point[slice_dimension]) ||
        (point[slice_dimension] == upper_limit && max_coordinate[slice_dimension] == upper_limit))

        continue
    end

    new_id += 1

    # Construct vandermonde matrix for interpolation of each 2D element to a 1D element.
    normalized_intercept =
          (point[slice_dimension] - min_coordinate[slice_dimension]) /
          element_length[1] * 2 - 1
    vandermonde = polynomial_interpolation_matrix(nodes_in, normalized_intercept)

    # Interpolate to each node of new 1D element.
    for v in 1:n_variables
      for node in 1:n_nodes_in
        new_unstructured_data[node, new_id, v] = (vandermonde*unstructured_data[node, :, element_id, v])[1]
      end
    end

    new_nodes[:, new_id] = original_nodes[other_dimension, :, 1, element_id]
  end

  return get_data_1d(reshape(new_nodes[:, 1:new_id], 1, n_nodes_in, new_id), new_unstructured_data[:, 1:new_id, :], nvisnodes)
end

# Calculate the arc length of a curve given by ndims x npoints point coordinates (piece-wise linear approximation)
function calc_arc_length(coordinates)
  n_points = size(coordinates)[2]
  arc_length = zeros(n_points)
  for i in 1:n_points-1
    arc_length[i+1] = arc_length[i] + sqrt(sum((coordinates[:,i]-coordinates[:,i+1]).^2))
  end
  return arc_length
end

# Convert 2d unstructured data to 1d data at given curve.
function unstructured_2d_to_1d_curve(original_nodes, unstructured_data, nvisnodes, curve, mesh, solver, cache)

  n_points_curve = size(curve)[2]
  n_nodes, _, n_elements, n_variables = size(unstructured_data)
  nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes)

  # Check if input is correct.
  min = original_nodes[:, 1, 1, 1]
  max = max_coordinate = original_nodes[:, n_nodes, n_nodes, n_elements]
  @assert size(curve) == (2, size(curve)[2]) "Coordinates along curve must be 2xn dimensional."
  for element in 1:n_points_curve
    @assert (prod(vcat(curve[:, n_points_curve] .>= min, curve[:, n_points_curve]
            .<= max))) "Some coordinates from `curve` are outside of the domain.."
  end

  # Set nodes acording to the length of the curve.
  arc_length = calc_arc_length(curve)

  # Setup data structures.
  data_on_curve = Array{Float64}(undef, n_points_curve, n_variables)
  temp_data = Array{Float64}(undef, n_nodes, n_points_curve, n_variables)

  # For each coordinate find the corresponding element with its id.
  element_ids = get_elements_by_coordinates(curve, mesh, solver, cache)

  # Iterate over all found elements.
  for element in 1:n_points_curve

    min_coordinate = original_nodes[:, 1, 1, element_ids[element]]
    max_coordinate = original_nodes[:, n_nodes, n_nodes, element_ids[element]]
    element_length = max_coordinate - min_coordinate

    normalized_coordinates = (curve[:, element] - min_coordinate)/element_length[1]*2 .-1

    # Interpolate to a single point in each element.
    vandermonde_x = polynomial_interpolation_matrix(nodes_in, normalized_coordinates[1])
    vandermonde_y = polynomial_interpolation_matrix(nodes_in, normalized_coordinates[2])
    for v in 1:n_variables
      for i in 1:n_nodes
        temp_data[i, element, v] = (vandermonde_y*unstructured_data[i, :, element_ids[element], v])[1]
      end
      data_on_curve[element, v] = (vandermonde_x*temp_data[:, element, v])[]
    end
  end

  return arc_length, data_on_curve, nothing
end

# Convert 3d unstructured data to 1d data at given curve.
function unstructured_3d_to_1d_curve(original_nodes, unstructured_data, nvisnodes, curve, mesh, solver, cache)

  n_points_curve = size(curve)[2]
  n_nodes, _, _, n_elements, n_variables = size(unstructured_data)
  nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes)

  # Check if input is correct.
  min = original_nodes[:, 1, 1, 1, 1]
  max = max_coordinate = original_nodes[:, n_nodes, n_nodes, n_nodes, n_elements]
  @assert size(curve) == (3, n_points_curve) "Coordinates along curve must be 3xn dimensional."
  for element in 1:n_points_curve
    @assert (prod(vcat(curve[:, n_points_curve] .>= min, curve[:, n_points_curve]
            .<= max))) "Some coordinates from `curve` are outside of the domain.."
  end

  # Set nodes acording to the length of the curve.
  arc_length = calc_arc_length(curve)

  # Setup data structures.
  data_on_curve = Array{Float64}(undef, n_points_curve, n_variables)
  temp_data = Array{Float64}(undef, n_nodes, n_nodes+1, n_points_curve, n_variables)

  # For each coordinate find the corresponding element with its id.
  element_ids = get_elements_by_coordinates(curve, mesh, solver, cache)

  # Iterate over all found elements.
  for element in 1:n_points_curve

    min_coordinate = original_nodes[:, 1, 1, 1, element_ids[element]]
    max_coordinate = original_nodes[:, n_nodes, n_nodes, n_nodes, element_ids[element]]
    element_length = max_coordinate - min_coordinate

    normalized_coordinates = (curve[:, element] - min_coordinate)/element_length[1]*2 .-1

    # Interpolate to a single point in each element.
    vandermonde_x = polynomial_interpolation_matrix(nodes_in, normalized_coordinates[1])
    vandermonde_y = polynomial_interpolation_matrix(nodes_in, normalized_coordinates[2])
    vandermonde_z = polynomial_interpolation_matrix(nodes_in, normalized_coordinates[3])
    for v in 1:n_variables
      for i in 1:n_nodes
        for ii in 1:n_nodes
          temp_data[i, ii, element, v] = (vandermonde_z*unstructured_data[i, ii, :, element_ids[element], v])[1]
        end
        temp_data[i, n_nodes+1, element, v] = (vandermonde_y*temp_data[i, 1:n_nodes, element, v])[1]
      end
      data_on_curve[element, v] = (vandermonde_x*temp_data[:, n_nodes+1, element, v])[1]
    end
  end

  return arc_length, data_on_curve, nothing
end

# Convert 3d unstructured data to 1d slice and interpolate them.
function unstructured_3d_to_1d(original_nodes, unstructured_data, nvisnodes, slice, point)

  if slice === :x
    slice_dimension = 1
    other_dimensions = [2,3]
  elseif slice === :y
    slice_dimension = 2
    other_dimensions = [1,3]
  elseif slice === :z
    slice_dimension = 3
    other_dimensions = [1,2]
  else
    error("illegal dimension '$slice', supported dimensions are :x, :y and :z")
  end

  # Set up data structures to stroe new 1D data.
  @views new_unstructured_data = similar(unstructured_data[1, 1, ..])
  @views temp_unstructured_data = similar(unstructured_data[1, ..])
  @views new_nodes = similar(original_nodes[1, 1, 1,..])

  n_nodes_in, _, _, n_elements, n_variables = size(unstructured_data)
  nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)

  # Test if point lies in the domain.
  lower_limit = original_nodes[1, 1, 1, 1, 1]
  upper_limit = original_nodes[1, n_nodes_in, n_nodes_in, n_nodes_in, n_elements]

  @assert length(point) >= 3 "Point must be three-dimensional."
  if prod(point[other_dimensions] .< lower_limit) || prod(point[other_dimensions] .> upper_limit)
    error(string("Slice axis is outside of domain. ",
        " point[$other_dimensions]=$(point[other_dimensions]) must be between $lower_limit and $upper_limit"))
  end

  # Count the amount of new elements.
  new_id = 0

  # Permute dimensions so that the slice dimensions are always the in correct places for later use.
  if slice === :x
    original_nodes = permutedims(original_nodes, [1, 3, 4, 2, 5])
    unstructured_data = permutedims(unstructured_data, [2, 3, 1, 4, 5])
  elseif slice === :y
    original_nodes = permutedims(original_nodes, [1, 2, 4, 3, 5])
    unstructured_data = permutedims(unstructured_data, [1, 3, 2, 4, 5])
  end

  # Iterate over all elements to find the ones that lie on the slice axis.
  for element_id in 1:n_elements
    min_coordinate = original_nodes[:, 1, 1, 1, element_id]
    max_coordinate = original_nodes[:, n_nodes_in, n_nodes_in, n_nodes_in, element_id]
    element_length = max_coordinate - min_coordinate

    # Test if the element is on the slice axis. If not just continue with the next element.
    if !((prod(min_coordinate[other_dimensions] .<= point[other_dimensions]) &&
        prod(max_coordinate[other_dimensions] .> point[other_dimensions])) ||
        (point[other_dimensions] == upper_limit && prod(max_coordinate[other_dimensions] .== upper_limit)))

        continue
    end

    new_id += 1

    # Construct vandermonde matrix for interpolation of each 2D element to a 1D element.
    normalized_intercept =
          (point[other_dimensions] .- min_coordinate[other_dimensions]) /
          element_length[1] * 2 .- 1
    vandermonde_i = polynomial_interpolation_matrix(nodes_in, normalized_intercept[1])
    vandermonde_ii = polynomial_interpolation_matrix(nodes_in, normalized_intercept[2])

    # Interpolate to each node of new 1D element.
    for v in 1:n_variables
      for i in 1:n_nodes_in
        for ii in 1:n_nodes_in
          temp_unstructured_data[i, ii, new_id, v] = (vandermonde_ii*unstructured_data[ii, :, i, element_id, v])[1]
        end
        new_unstructured_data[i, new_id, v] = (vandermonde_i*temp_unstructured_data[i, :, new_id, v])[1]
      end
    end

    new_nodes[:, new_id] = original_nodes[slice_dimension, 1, 1, :, element_id]
  end

  return get_data_1d(reshape(new_nodes[:, 1:new_id], 1, n_nodes_in, new_id), new_unstructured_data[:, 1:new_id, :], nvisnodes)
end

# Interpolate unstructured DG data to structured data (cell-centered)
#
# This function takes DG data in an unstructured, Cartesian layout and converts it to a uniformely
# distributed 2D layout.
#
# Note: This is a low-level function that is not considered as part of Trixi's interface and may
#       thus be changed in future releases.
function unstructured2structured(unstructured_data, normalized_coordinates,
                                 levels, resolution, nvisnodes_per_level)
  # Extract data shape information
  n_nodes_in, _, n_elements, n_variables = size(unstructured_data)

  # Get node coordinates for DG locations on reference element
  nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)

  # Calculate interpolation vandermonde matrices for each level
  max_level = length(nvisnodes_per_level) - 1
  vandermonde_per_level = []
  for l in 0:max_level
    n_nodes_out = nvisnodes_per_level[l + 1]
    dx = 2 / n_nodes_out
    nodes_out = collect(range(-1 + dx/2, 1 - dx/2, length=n_nodes_out))
    push!(vandermonde_per_level, polynomial_interpolation_matrix(nodes_in, nodes_out))
  end

  # For each element, calculate index position at which to insert data in global data structure
  lower_left_index = element2index(normalized_coordinates, levels, resolution, nvisnodes_per_level)

  # Create output data structure
  structured = [Matrix{Float64}(undef, resolution, resolution) for _ in 1:n_variables]

  # For each variable, interpolate element data and store to global data structure
  for v in 1:n_variables
    # Reshape data array for use in multiply_dimensionwise function
    reshaped_data = reshape(unstructured_data[:, :, :, v], 1, n_nodes_in, n_nodes_in, n_elements)

    for element_id in 1:n_elements
      # Extract level for convenience
      level = levels[element_id]

      # Determine target indices
      n_nodes_out = nvisnodes_per_level[level + 1]
      first = lower_left_index[:, element_id]
      last = first .+ (n_nodes_out - 1)

      # Interpolate data
      vandermonde = vandermonde_per_level[level + 1]
      structured[v][first[1]:last[1], first[2]:last[2]] .= (
          reshape(multiply_dimensionwise(vandermonde, reshaped_data[:, :, :, element_id]),
                  n_nodes_out, n_nodes_out))
    end
  end

  return structured
end

# For a given normalized element coordinate, return the index of its lower left
# contribution to the global data structure
#
# Note: This is a low-level function that is not considered as part of Trixi's interface and may
#       thus be changed in future releases.
function element2index(normalized_coordinates, levels, resolution, nvisnodes_per_level)
  @assert size(normalized_coordinates, 1) == 2 "only works in 2D"

  n_elements = length(levels)

  # First, determine lower left coordinate for all cells
  dx = 2 / resolution
  ndim = 2
  lower_left_coordinate = Array{Float64}(undef, ndim, n_elements)
  for element_id in 1:n_elements
    nvisnodes = nvisnodes_per_level[levels[element_id] + 1]
    lower_left_coordinate[1, element_id] = (
        normalized_coordinates[1, element_id] - (nvisnodes - 1)/2 * dx)
    lower_left_coordinate[2, element_id] = (
        normalized_coordinates[2, element_id] - (nvisnodes - 1)/2 * dx)
  end

  # Then, convert coordinate to global index
  indices = coordinate2index(lower_left_coordinate, resolution)

  return indices
end

# Find 2D array index for a 2-tuple of normalized, cell-centered coordinates (i.e., in [-1,1])
#
# Note: This is a low-level function that is not considered as part of Trixi's interface and may
#       thus be changed in future releases.
function coordinate2index(coordinate, resolution::Integer)
  # Calculate 1D normalized coordinates
  dx = 2/resolution
  mesh_coordinates = collect(range(-1 + dx/2, 1 - dx/2, length=resolution))

  # Find index
  id_x = searchsortedfirst.(Ref(mesh_coordinates), coordinate[1, :], lt=(x,y)->x .< y .- dx/2)
  id_y = searchsortedfirst.(Ref(mesh_coordinates), coordinate[2, :], lt=(x,y)->x .< y .- dx/2)
  return transpose(hcat(id_x, id_y))
end

# Calculate the vertices for each mesh cell such that it can be visualized as a closed box
#
# Note: This is a low-level function that is not considered as part of Trixi's interface and may
#       thus be changed in future releases.
function calc_vertices(coordinates, levels, length_level_0)
  ndim = size(coordinates, 1)
  @assert ndim == 2 "only works in 2D"

  # Initialize output arrays
  n_elements = length(levels)
  n_points_per_element = 2^ndim+2
  x = Vector{Float64}(undef, n_points_per_element*n_elements)
  y = Vector{Float64}(undef, n_points_per_element*n_elements)

  # Calculate vertices for all coordinates at once
  for element_id in 1:n_elements
    length = length_level_0 / 2^levels[element_id]
    index = n_points_per_element*(element_id-1)
    x[index+1] = coordinates[1, element_id] - 1/2 * length
    x[index+2] = coordinates[1, element_id] + 1/2 * length
    x[index+3] = coordinates[1, element_id] + 1/2 * length
    x[index+4] = coordinates[1, element_id] - 1/2 * length
    x[index+5] = coordinates[1, element_id] - 1/2 * length
    x[index+6] = NaN

    y[index+1] = coordinates[2, element_id] - 1/2 * length
    y[index+2] = coordinates[2, element_id] - 1/2 * length
    y[index+3] = coordinates[2, element_id] + 1/2 * length
    y[index+4] = coordinates[2, element_id] + 1/2 * length
    y[index+5] = coordinates[2, element_id] - 1/2 * length
    y[index+6] = NaN
  end

  return x, y
end

# Calculate the vertices to plot each grid line for StructuredMesh
#
# Note: This is a low-level function that is not considered as part of Trixi's interface and may
#       thus be changed in future releases.
function calc_vertices(node_coordinates, mesh)
  @unpack cells_per_dimension = mesh
  @assert size(node_coordinates, 1) == 2 "only works in 2D"

  linear_indices = LinearIndices(size(mesh))

  # Initialize output arrays
  n_lines = sum(cells_per_dimension) + 2
  max_length = maximum(cells_per_dimension)
  n_nodes = size(node_coordinates, 2)

  # Create output as two matrices `x` and `y`, each holding the node locations for each of the `n_lines` grid lines
  # The # of rows in the matrices must be sufficient to store the longest dimension (`max_length`),
  # and for each the node locations without doubling the corner nodes (`n_nodes-1`), plus the final node (`+1`)
  # Rely on Plots.jl to ignore `NaN`s (i.e., they are not plotted) to handle shorter lines
  x = fill(NaN, max_length*(n_nodes-1)+1, n_lines)
  y = fill(NaN, max_length*(n_nodes-1)+1, n_lines)

  line_index = 1
  # Lines in x-direction
  # Bottom boundary
  i = 1
  for cell_x in axes(mesh, 1)
    for node in 1:(n_nodes-1)
      x[i, line_index] = node_coordinates[1, node, 1, linear_indices[cell_x, 1]]
      y[i, line_index] = node_coordinates[2, node, 1, linear_indices[cell_x, 1]]

      i += 1
    end
  end
  # Last point on bottom boundary
  x[i, line_index] = node_coordinates[1, end, 1, linear_indices[end, 1]]
  y[i, line_index] = node_coordinates[2, end, 1, linear_indices[end, 1]]

  # Other lines in x-direction
  line_index += 1
  for cell_y in axes(mesh, 2)
    i = 1
    for cell_x in axes(mesh, 1)
      for node in 1:(n_nodes-1)
        x[i, line_index] = node_coordinates[1, node, end, linear_indices[cell_x, cell_y]]
        y[i, line_index] = node_coordinates[2, node, end, linear_indices[cell_x, cell_y]]

        i += 1
      end
    end
    # Last point on line
    x[i, line_index] = node_coordinates[1, end, end, linear_indices[end, cell_y]]
    y[i, line_index] = node_coordinates[2, end, end, linear_indices[end, cell_y]]

    line_index += 1
  end

  # Lines in y-direction
  # Left boundary
  i = 1
  for cell_y in axes(mesh, 2)
    for node in 1:(n_nodes-1)
      x[i, line_index] = node_coordinates[1, 1, node, linear_indices[1, cell_y]]
      y[i, line_index] = node_coordinates[2, 1, node, linear_indices[1, cell_y]]

      i += 1
    end
  end
  # Last point on left boundary
  x[i, line_index] = node_coordinates[1, 1, end, linear_indices[1, end]]
  y[i, line_index] = node_coordinates[2, 1, end, linear_indices[1, end]]

  # Other lines in y-direction
  line_index +=1
  for cell_x in axes(mesh, 1)
    i = 1
    for cell_y in axes(mesh, 2)
      for node in 1:(n_nodes-1)
        x[i, line_index] = node_coordinates[1, end, node, linear_indices[cell_x, cell_y]]
        y[i, line_index] = node_coordinates[2, end, node, linear_indices[cell_x, cell_y]]

        i += 1
      end
    end
    # Last point on line
    x[i, line_index] = node_coordinates[1, end, end, linear_indices[cell_x, end]]
    y[i, line_index] = node_coordinates[2, end, end, linear_indices[cell_x, end]]

    line_index += 1
  end

  return x, y
end

# Convert `slice` to orientations (1 -> `x`, 2 -> `y`, 3 -> `z`) for the two axes in a 2D plot
function _get_orientations(mesh, slice)
  if ndims(mesh) == 2 || (ndims(mesh) == 3 && slice === :xy)
    orientation_x = 1
    orientation_y = 2
  elseif ndims(mesh) == 3 && slice === :xz
    orientation_x = 1
    orientation_y = 3
  elseif ndims(mesh) == 3 && slice === :yz
    orientation_x = 2
    orientation_y = 3
  else
    orientation_x = 0
    orientation_y = 0
  end
  return orientation_x, orientation_y
end

# Convert `orientation` into a guide label (see also `_get_orientations`)
function _get_guide(orientation::Integer)
  if orientation == 1
    return "\$x\$"
  elseif orientation == 2
    return "\$y\$"
  elseif orientation == 3
    return "\$z\$"
  else
    return ""
  end
end

#   plotting_interpolation_matrix(dg; kwargs...)
#
# Interpolation matrix which maps discretization nodes to a set of plotting nodes.
# Defaults to the identity matrix of size `length(solver.basis.nodes)`, and interpolates
# to equispaced nodes for DGSEM (set by kwarg `nvisnodes` in the plotting function).
#
# Example:
# ```julia
# A = plotting_interpolation_matrix(dg)
# A * dg.basis.nodes # => vector of nodes at which to plot the solution
# ```
#
# Note: we cannot use UniformScaling to define the interpolation matrix since we use it with `kron`
# to define a multi-dimensional interpolation matrix later.
plotting_interpolation_matrix(dg; kwargs...) = I(length(dg.basis.nodes))

function face_plotting_interpolation_matrix(dg::DGSEM; nvisnodes=2*length(dg.basis.nodes))
  return polynomial_interpolation_matrix(dg.basis.nodes, LinRange(-1, 1, nvisnodes))
end

function plotting_interpolation_matrix(dg::DGSEM; nvisnodes=2*length(dg.basis.nodes))
  Vp1D = polynomial_interpolation_matrix(dg.basis.nodes, LinRange(-1, 1, nvisnodes))
  # For quadrilateral elements, interpolation to plotting nodes involves applying a 1D interpolation
  # operator to each line of nodes. This is equivalent to multiplying the vector containing all node
  # node coordinates on an element by a Kronecker product of the 1D interpolation operator (e.g., a
  # multi-dimensional interpolation operator).
  return kron(Vp1D, Vp1D)
end

function reference_node_coordinates_2d(dg::DGSEM)
  @unpack nodes = dg.basis
  r = vec([nodes[i] for i in eachnode(dg), j in eachnode(dg)])
  s = vec([nodes[j] for i in eachnode(dg), j in eachnode(dg)])
  return r, s
end

# Given a reference plotting triangulation, this function generates a plotting triangulation for
# the entire global mesh. The output can be plotted using `Makie.mesh`.
function global_plotting_triangulation_makie(pds::PlotDataSeries{<:UnstructuredPlotData2D};
                                             set_z_coordinate_zero = false)
  @unpack variable_id = pds
  pd = pds.plot_data
  @unpack x, y, data, t = pd

  makie_triangles = Makie.to_triangles(t)

  # trimesh[i] holds GeometryBasics.Mesh containing plotting information on the ith element.
  # Note: Float32 is required by GeometryBasics
  num_plotting_nodes, num_elements = size(x)
  trimesh = Vector{GeometryBasics.Mesh{3, Float32}}(undef, num_elements)
  coordinates = zeros(Float32, num_plotting_nodes, 3)
  for element in Base.OneTo(num_elements)
    for i in Base.OneTo(num_plotting_nodes)
      coordinates[i, 1] = x[i, element]
      coordinates[i, 2] = y[i, element]
      if set_z_coordinate_zero == false
        coordinates[i, 3] = data[i, element][variable_id]
      end
    end
    trimesh[element] = GeometryBasics.normal_mesh(Makie.to_vertices(coordinates), makie_triangles)
  end
  plotting_mesh = merge([trimesh...]) # merge meshes on each element into one large mesh
  return plotting_mesh
end

# Returns a list of `Makie.Point`s which can be used to plot the mesh, or a solution "wireframe"
# (e.g., a plot of the mesh lines but with the z-coordinate equal to the value of the solution).
function mesh_plotting_wireframe(pds::PlotDataSeries{<:UnstructuredPlotData2D};
                                 set_z_coordinate_zero = false)
  @unpack variable_id = pds
  pd = pds.plot_data
  @unpack x_face, y_face, face_data = pd

  if set_z_coordinate_zero
    sol_f = zeros(eltype(first(face_data)), size(face_data)) # plot 2d surface by setting z coordinate to zero
  else
    sol_f = StructArrays.component(face_data, variable_id)
  end

  # This line separates solution lines on each edge by NaNs to ensure that they are rendered
  # separately. The coordinates `xf`, `yf` and the solution `sol_f`` are assumed to be a matrix
  # whose columns correspond to different elements. We add NaN separators by appending a row of
  # NaNs to this matrix. We also flatten (e.g., apply `vec` to) the result, as this speeds up
  # plotting.
  xyz_wireframe = GeometryBasics.Point.(map(x->vec(vcat(x, fill(NaN, 1, size(x, 2)))), (x_face, y_face, sol_f))...)

  return xyz_wireframe
end

end # @muladd


function GAMMAH(N::Integer, over::Real)
    #INTEGER N,I,J
    #DOUBLE PRECISION OVER
    I = N
    J = 2 * I - 1
    retval = 1.0
    while (J>=1) && retval < over
         retval = retval * J/2.0
         I = I-1
         J = 2*I-1
    end
    if J > 1
        retval = 0.0
    end
    return retval
end

function PSI(M::Integer, K::Integer)
    FACTR1=0.0
    FACTR2=0.0
    N=2*K+M
    J=1
    if K>=1
        while J <= K
            FACTR1 = FACTR1+1.0/J
            J += 1
        end
    end
    I = 1
    while I <= N
        FACTR2 = FACTR2 + 1.0/(2.0*I-1.0)
        I=I+1
    end
    if N != 0
        FACTR2 = 0.0
    end

    return FACTR1-2.0*FACTR2

end

function FRAC(Z::Real, M::Integer, NMAX::Integer, ε::Real, TINYSQ::Real)
    N = NMAX
    MM = 0
    DZ2 = Z + Z
    DN0 = N + M
    DN1 = N + 1.0
    DN2 = DN0 + 0.5
    DN3 = DN0 - 0.5
    DN4 = 1.0 - M - M
    B = 2.0*DN1*Z/DN2
    A = 1.0
    FC = TINYSQ
    C0 = FC
    D0 = 0.0
    while true
        D0 = B + A*D0
        if abs(D0)  < TINYSQ
            D0 = TINYSQ
        end
        C0 = B + A/C0
        if abs(C0) < TINYSQ
            C0 = TINYSQ
        end
        D0 = 1.0/D0
        DELTA = C0*D0
        FC = FC*DELTA
        MM = MM + 1
        A = -(1.0 + DN4/(DN3 + MM))
        B = DZ2*(DN1 + MM)/(DN2 + MM)
        if MM < 1000000
            if abs(DELTA-1.0) > ε
                continue
            else
                return FC
            end
        elseif MM == 1000000
            println("CF CONVERGENCE FAILS")
            return FC
        end
    end
    return FC
end

function FACTOR(M::Integer, K::Integer)
    retval = 1.0
    if K >= 1
        X1 = M + 0.5
        N = 2*K - 1
        I = K
        J = N
        while J >= 0
            retval = retval*(J+X1)/I/I
            J = J-1
            I = I -1
            if I == 0
                I = 1
            end
        end
    end
    return retval
end

function EXPAN(Z::Real, MODE::Integer, IPRE::Integer, OVER::Real, QARGU::Real, M::Integer)
    PRECI = [1.e-13 1.e-9]
    πSQ=sqrt(π)
    DB = 2.0*log(2.0)
    FL = M/2.0
    CC = abs(trunc(FL)-FL)
    if (CC < 0.4)
        AR = 1.0
    else
        AR = -1.0
    end
    DZ = 1.0
    for I = 1:M
        DZ = DZ/QARGU
    end
    if MODE == 0
        GAMMA = GAMMAH(M, OVER)*AR*πSQ
    else
        GAMMA = AR
    end
    DFAC = 2.0/π*DZ*GAMMA/πSQ
    DF1 = log(2.0*Z)
    A0 = 1.0/sqrt(2.0*Z)
    Z2I = 1.0/(Z*Z)
    DELTA = 1.0
    SUM = 0.0
    K = 0
    DA2 = FACTOR(M, 0)
    DA1 = DB + PSI(M, 0)
    while true
        DELTA = (DF1+DA1)*DA2*A0
        SUM = SUM+DELTA
        DCC = 0.5+M+2.0*K
        DCCP = DCC + 1.0
        DKK = K + 1.0
        DA2 = DA2*DCCP*DCC/(DKK*DKK)
        DA1 = DA1+1.0/DKK-1.0/DCCP-1.0/DCC
        K = K + 1
        A0 = A0*0.25*Z2I
        if abs(DELTA/SUM) > PRECI[IPRE]
            continue
        else
            break
        end
    end
    return SUM*DFAC
end

function FRACPS(QZ::Real, M::Integer, N::Integer, ε::Real, TINYSQ::Real)
    MM = 0
    DN2 = N*N
    DZ2 = QZ+QZ
    DM = M-0.5
    B = DZ2*M
    A = DN2-DM*DM
    FC = TINYSQ
    C0 = FC
    D0 = 0.0
    while true
        D0 = B + A*D0
        if abs(D0) < TINYSQ
            D0 = TINYSQ
        end
        C0 = B + A/C0
        if abs(C0) < TINYSQ
            C0 = TINYSQ
        end
        D0 = 1.0/D0
        DELTA = C0*D0
        FC = FC*DELTA
        MM = MM + 1
        A = DN2-(MM+DM)*(MM+DM)
        B = DZ2*(MM+M)
        if MM < 1000000
            if  abs(DELTA-1.0) > ε
                continue
            else
                return FC
            end
        elseif MM == 1000000
            println("CF CONVERGENCE FAILS")
            return FC
        end
    end
    return FC
end

function FACTCO(N::Integer, PL::Real, M::Integer)
    retval = 1.0/PL
    X1 =  M + 0.50
    X2 = -M + 0.50
    J = N
    while J >= 0
        retval = retval*(J+X1)/(J+X2)
        J -= 1
    end
    return retval
end

function ELLIP2(AK)
    Q=(1.0-AK)*(1.0+AK)
    return DRF(Q)-(AK)^2*DRD(Q)/3.0
end

function ELLIP1(AK)
    return DRF((1.0-AK)*(1.0+AK))
end

function DRF(Y::Real)
    ERRTOL = 1.e-8
    C1 = 1.0/24.0
    C2 = 3.0/44.0
    C3 = 1.0/14.0

    DRF = 0.0
    XN = 0.
    YN = Y
    ZN = 1.0

    MU = 0.0
    XNDEV = 0.0
    YNDEV = 0.0
    ZNDEV = 0.0

    while true
        MU = (XN + YN + ZN)/3.0
        XNDEV = 2.0 - (MU+XN)/MU
        YNDEV = 2.0 - (MU+YN)/MU
        ZNDEV = 2.0 - (MU+ZN)/MU
        EPSLON = max(abs(XNDEV), abs(YNDEV), abs(ZNDEV))
        if EPSLON < ERRTOL
            break
        end
        XNROOT = sqrt(XN)
        YNROOT = sqrt(YN)
        ZNROOT = sqrt(ZN)
        LAMDA = XNROOT*(YNROOT + ZNROOT) + YNROOT*ZNROOT
        XN = (XN + LAMDA)*0.250
        YN = (YN + LAMDA)*0.250
        ZN = (ZN + LAMDA)*0.250
    end
    E2 = XNDEV*YNDEV - ZNDEV*ZNDEV
    E3 = XNDEV*YNDEV*ZNDEV
    S  = 1.0 + (C1*E2-0.10-C2*E3)*E2 + C3*E3
    return S/sqrt(MU)
end

function DRD(Y::Real)
    ERRTOL = 1.e-8
    C1 = 3.0/14.0
    C2 = 1.0/6.0
    C3 = 9.0/22.0
    C4 = 3.0/26.0

    DRD = 0.0

    XN = 0.0
    YN = Y
    ZN = 1.0
    SIGMA = 0.0
    POWER4 = 1.0

    MU = 0.0
    XNDEV = 0.0
    YNDEV = 0.0
    ZNDEV = 0.0

    while true
        MU = (XN + YN + 3.0*ZN)*0.20
        XNDEV = (MU-XN)/MU
        YNDEV = (MU-YN)/MU
        ZNDEV = (MU-ZN)/MU
        EPSLON = max(abs(XNDEV), abs(YNDEV), abs(ZNDEV))
        if EPSLON < ERRTOL
            break
        end
        XNROOT = sqrt(XN)
        YNROOT = sqrt(YN)
        ZNROOT = sqrt(ZN)
        LAMDA = XNROOT*(YNROOT + ZNROOT) + YNROOT*ZNROOT
        SIGMA = SIGMA + POWER4/(ZNROOT*(ZN+LAMDA))
        POWER4 = POWER4*0.250
        XN = (XN+LAMDA)*0.250
        YN = (YN+LAMDA)*0.250
        ZN = (ZN+LAMDA)*0.250
    end

    EA = XNDEV*YNDEV
    EB = ZNDEV*ZNDEV
    EC = EA - EB
    ED = EA - 6.0*EB
    EF = ED + EC + EC
    S1 = ED*(-C1+0.250*C3*ED-1.50*C4*ZNDEV*EF)
    S2 = ZNDEV*(C2*EF+ZNDEV*(-C3*EC+ZNDEV*C4*EA))
    return 3.0*SIGMA + POWER4*(1.0 + S1 + S2)/(MU*sqrt(MU))
 end


using DynamicPolynomials
# include("D:\\Programs\\TSSOS\\src\\TSSOS.jl")
using TSSOS

# unconstrained optimization using the TSSOS hierarchy
@polyvar x[1:3]
f = 1+x[1]^4+x[2]^4+x[3]^4+x[1]*x[2]*x[3]+x[2]
opt,sol,data = tssos_first(f, x, newton=true, reducebasis=true, TS="block", solution=true, QUIET=true)
# optimum = 0.475274778453039

opt,sol,data = tssos_higher!(data, TS="MD", solution=true, QUIET=true)
# optimum = 0.4752747815682939

# constrained optimization using the TSSOS hierarchy
@polyvar x[1:3]
f = 1+x[1]^4+x[2]^4+x[3]^4+x[1]*x[2]*x[3]+x[2]
g = 1-x[1]^2-2*x[2]^2
h = x[2]^2+x[3]^2-1
pop = [f, g, h]
opt,sol,data = tssos_first(pop, x, 2, numeq=1, TS="block", solution=true, QUIET=true)
# optimum = 0.6861925732538557

opt,sol,data = tssos_higher!(data, TS="MD", solution=true, QUIET=true)
# optimum = 0.6861925611033903

# constrained optimization using the CS-TSSOS hierarchy
n = 6
@polyvar x[1:n]
f = 1+sum(x.^4)+x[1]*x[2]*x[3]+x[3]*x[4]*x[5]+x[3]*x[4]*x[6]+x[3]*x[5]*x[6]+x[4]*x[5]*x[6]
pop = [f, 1-sum(x[1:3].^2), 1-sum(x[3:6].^2)]
d = 2
opt,sol,data = cs_tssos_first(pop, x, d, numeq=1, TS="block", solution=true, QUIET=true)
# optimum = 0.7174253329322346

opt,sol,data = cs_tssos_higher!(data, TS="MD", solution=true, QUIET=true)
# optimum = 0.7174253409780793

println("Run successfully!")


using Distances
using LinearAlgebra
using PointPatternStatistics: iscovered

window = (x=(0.0, 100.0), y=(30.0, 200.0))
x = rand(SimpleSequentialInhibition(window, 10.0))
d = pairwise(Euclidean(), reshape(reinterpret(Float64, x.data), 2, :), dims=2)
d[diagind(d)] .= Inf
@test minimum(d) >= 10.0
@test all(x -> inside(window)(x) >= 0, x.data)

@test !iscovered(0.0, 1.0, 0.0, 1.0, [(0.5, 0.5)], sqrt(0.5) - 0.00001)
@test iscovered(0.0, 1.0, 0.0, 1.0, [(0.5, 0.5)], sqrt(0.5) + 0.00001)
@test iscovered(0.0, 1.0, 0.0, 1.0, [(1.0, 0.5), (0.0,0.5)], sqrt(0.5)+0.00001)
@test !iscovered(0.0, 1.0, 0.0, 1.0, [(1.0, 0.5), (0.0,0.5)], sqrt(0.5)-0.00001)
@test !iscovered(0.0, 1.0, 0.0, 1.0, [(1.5, 0.5), (-0.5,0.5),(0.5,1.5),(0.5,-0.5)], 1-0.00001)
@test iscovered(0.0, 1.0, 0.0, 1.0, [(1.5, 0.5), (-0.5,0.5),(0.5,1.5),(0.5,-0.5)], 1+0.00001)


using Test
using HDF5
using DSGE

path = dirname(@__FILE__)

m = AnSchorfheide()
Γ0, Γ1, C, Ψ, Π = eqcond(m)
stake = 1 + 1e-6
#G1, C, impact, fmat, fwt, ywt, gev, eu, loose = gensys(Γ0, Γ1, C, Ψ, Π, stake)
G1, C, impact, eu = gensys(Γ0, Γ1, C, Ψ, Π, stake)

file = "$path/../reference/gensys.h5"
G1_exp = h5read(file, "G1_gensys")
C_exp = h5read(file,"C_gensys")
impact_exp = h5read(file, "impact")
eu_exp = h5read(file, "eu")

@testset "Check gensys outputs match reference" begin
    @test @test_matrix_approx_eq G1_exp G1
    @test @test_matrix_approx_eq C_exp C
    @test @test_matrix_approx_eq impact_exp impact

    @test isequal(eu_exp, eu)
end


using NNlib: conv, ∇conv_filter, ∇conv_data, ∇maxpool, maxpool

@testset "conv2d" begin
    x = reshape(Float64[1:20;], 5, 4, 1, 1)
    w = reshape(Float64[1:4;], 2, 2, 1, 1)

    @test squeeze(conv(x, w),(3,4)) == [
        29 79 129;
        39 89 139;
        49 99 149;
        59 109 159.]

    @test squeeze(conv(x, w; stride=2),(3,4)) == [
        29 129;
        49 149.]

    @test squeeze(conv(x, w; pad=1),(3,4)) == [
        1.0   9.0   29.0   49.0   48.0;
        4.0  29.0   79.0  129.0  115.0;
        7.0  39.0   89.0  139.0  122.0;
        10.0  49.0   99.0  149.0  129.0;
        13.0  59.0  109.0  159.0  136.0;
        10.0  40.0   70.0  100.0   80.0
    ]

    # for gradients, check only size
    # correctness of gradients is cross-checked with CUDNN.jl
    # (it's assumed convolution code won't change often)

    @test size(∇conv_filter(reshape(rand(4,3), 4, 3, 1, 1), x, w)) == size(w)
    @test size(∇conv_data(reshape(rand(4,3), 4, 3, 1, 1), x, w)) == size(x)

    # Test that stride/pad work backward as well
    y = conv(x, w; stride=2, pad=1)
    @test size(y) == (3, 3, 1, 1)
    @test size(∇conv_filter(y, x, w; stride=2, pad=1)) == size(w)
    @test size(∇conv_data(y, x, w; stride=2, pad=1)) == size(x)
end

@testset "maxpool2d" begin

    x = reshape(Float64[1:20;], 5, 4, 1, 1)

    @test squeeze(maxpool(x, (2,2)), (3,4)) == [7 17; 9 19]
    @test squeeze(maxpool(x, (2,2); stride=(2,2)), (3,4)) == [7 17; 9 19]
    @test squeeze(maxpool(x, (2,2); pad=(1,1)), (3,4)) == [
        1.0  11.0  16.0;
        3.0  13.0  18.0;
        5.0  15.0  20.0;
    ]

    # for gradients, check only size
    # correctness of gradients is cross-checked with CUDNN.jl
    # (it's assumed maxpooling code won't change often)

    y = maxpool(x, (2,2))
    dy = reshape(rand(2,2), 2, 2, 1, 1)
    @test size(∇maxpool(dy, y, x, (2,2))) == size(x)

end

@testset "conv3d" begin

    x = reshape(Float64[1:60;], 5, 4, 3, 1, 1)
    w = reshape(Float64[1:8;], 2, 2, 2, 1, 1)
    res = zeros(4,3,2)
    res[:, :, 1] = [
        322.0  502.0  682.0;
        358.0  538.0  718.0;
        394.0  574.0  754.0;
        430.0  610.0  790.0]
    res[:, :, 2] = [
        1042.0  1222.0  1402.0;
        1078.0  1258.0  1438.0;
        1114.0  1294.0  1474.0;
        1150.0  1330.0  1510.0]
    @test squeeze(conv(x, w),(4,5)) == res

    @test squeeze(conv(x, w; stride=2),(3,4,5)) == [
        322.0 682.0;
        394.0 754.0]

    res = zeros(6,5,4)
    res[:, :, 1] = [
        1.0   9.0   29.0   49.0   48.0;
        4.0  29.0   79.0  129.0  115.0;
        7.0  39.0   89.0  139.0  122.0;
        10.0  49.0   99.0  149.0  129.0;
        13.0  59.0  109.0  159.0  136.0;
        10.0  40.0   70.0  100.0   80.0]
    res[:, :, 2] = [
        26.0  126.0  206.0  286.0  220.0;
        80.0  322.0  502.0  682.0  502.0;
        94.0  358.0  538.0  718.0  524.0;
        108.0  394.0  574.0  754.0  546.0;
        122.0  430.0  610.0  790.0  568.0;
        80.0  260.0  360.0  460.0  320.0]
    res[:, :, 3] = [
        146.0   446.0   526.0   606.0   420.0;
        360.0  1042.0  1222.0  1402.0   942.0;
        374.0  1078.0  1258.0  1438.0   964.0;
        388.0  1114.0  1294.0  1474.0   986.0;
        402.0  1150.0  1330.0  1510.0  1008.0;
        240.0   660.0   760.0   860.0   560.0]
    res[:, :, 4] = [
        205.0   517.0   577.0   637.0  392.0;
        456.0  1133.0  1263.0  1393.0  847.0;
        467.0  1159.0  1289.0  1419.0  862.0;
        478.0  1185.0  1315.0  1445.0  877.0;
        489.0  1211.0  1341.0  1471.0  892.0;
        270.0   660.0   730.0   800.0  480.0]
    @test squeeze(conv(x, w; pad=1),(4,5)) == res

    # for gradients, check only size
    # correctness of gradients is cross-checked with CUDNN.jl
    # (it's assumed convolution code won't change often)

    @test size(∇conv_filter(reshape(rand(4,3,2), 4, 3, 2, 1, 1), x, w)) == size(w)
    @test size(∇conv_data(reshape(rand(4,3,2), 4, 3, 2, 1, 1), x, w)) == size(x)

end

@testset "maxpool3d" begin

    x = reshape(Float64[1:60;], 5, 4, 3, 1, 1)

    @test squeeze(maxpool(x, (2,2,2)), (3,4,5)) == [27 37; 29 39.]
    @test squeeze(maxpool(x, (2,2,2); stride=(2,2,2)), (3,4,5)) == [27 37; 29 39.]
    res = zeros(3,3,2)
    res[:, :, 1] = [
        1.0  11.0  16.0;
        3.0  13.0  18.0;
        5.0  15.0  20.0]
    res[:, :, 2] = [
        41.0  51.0  56.0;
        43.0  53.0  58.0;
        45.0  55.0  60.0]
    @test squeeze(maxpool(x, (2,2,2), pad=(1,1,1)), (4,5)) == res

    # for gradients, check only size
    # correctness of gradients is cross-checked with CUDNN.jl
    # (it's assumed maxpooling code won't change often)

    y = maxpool(x, (2,2,2))
    dy = reshape(rand(2,2), 2, 2, 1, 1, 1)
    @test size(∇maxpool(dy, y, x, (2,2,2))) == size(x)

end


#=
A unival tree (which stands for "universal value") is a tree where all nodes under it have the same value.

Given the root to a binary tree, count the number of unival subtrees.

For example, the following tree has 5 unival subtrees:

   0
  / \
 1   0
    / \
   1   0
  / \
 1   1
=#

using Test
include("Solutions/problem8_univalsubtreescounter.jl")

# The example
#=
    0
   / \
  1   0
     / \
    1   0
   / \
  1   1
=#

node = Node(1, Node(1),Node(1))
node = Node(0, node, Node(0))
node = Node(0, Node(1), node)

@test count_unival_subtrees(node) == 5

# Other example
#=      0
      /  \
     0   1
    / \
   1  1
  /\  /\
 1 1 1 1
 =#

node = Node(1, Node(1), Node(1))
node = Node(0, node, node)
node = Node(0, node, Node(1))

@test count_unival_subtrees(node) == 8


using Plexifications
using Base.Test: @test, @test_throws

@test_throws ErrorException begin
    inv(Nlplex(0, 2))
end

@test begin
    iszerodivisor(Nlplex(0,1))
end

@test begin
    !iszerodivisor(Nlplex(1,1))
end

@test begin
    length(unreal(1)) == 0
end

@test begin
    a = Plexifications.randomBigFloat()
    isreal(Nlplex(a))
end

@test begin
    a = 1
    !isreal(Nlplex(a, a))
end

@test begin
    z = random(Nlplex{Int})
    z == +(z)
end

@test begin
    a = Plexifications.randomBigFloat()
    real(Nlplex(a)) == a
end

@test begin
    io = IOBuffer()
    show(io, Nlplex(1, 2))
    l = String(take!(io))
    r = "[1: 1, A: 2]"
    l == r
end

@test begin
    x = random(Nlplex{BigFloat})
    y = random(Nlplex{BigFloat})
    x != y
end

@test begin
    a = Plexifications.randomBigInt()
    z = random(Nlplex{BigInt})
    l = a + z
    r = z + a
    l == r
end

@test begin
    a = Plexifications.randomBigInt()
    z = random(Nlplex{BigInt})
    l = a - z
    r = -(z - a)
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    l = x + y
    r = y + x
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    l = x * y
    r = y * x
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    l = x - y
    r = -(y - x)
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    z = random(Nlplex{BigInt})
    l = (x + y) + z
    r = x + (y + z)
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    z = random(Nlplex{BigInt})
    l = (x * y) * z
    r = x * (y * z)
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    barA(barA(x)) == x
end

@test begin
    x = random(Nlplex{Rational{BigInt}})
    inv(inv(x)) == x
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    l = barA(x * y)
    r = barA(y) * barA(x)
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    l = barA(x + y)
    r = barA(x) + barA(y)
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    l = barA(x - y)
    r = barA(x) - barA(y)
    l == r
end

@test begin
    x = random(Nlplex{Rational{BigInt}})
    y = random(Nlplex{Rational{BigInt}})
    l = inv(x * y)
    r = inv(y) * inv(x)
    l == r
end

@test begin
    x = random(Nlplex{Rational{BigInt}})
    y = random(Nlplex{Rational{BigInt}})
    l = inv(x / y)
    r = y / x
    l == r
end

@test begin
    x = random(Nlplex{Rational{BigInt}})
    y = random(Nlplex{Rational{BigInt}})
    l = inv(x \ y)
    r = y \ x
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    z = random(Nlplex{BigInt})
    l = x * (y + z)
    r = (x * y) + (x * z)
    l == r
end

@test begin
    a = Plexifications.randomBigInt()
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    l = a * (x + y)
    r = (a * x) + (a * y)
    l == r
end

@test begin
    a = Plexifications.randomBigInt()
    x = random(Nlplex{BigInt})
    l = a * x
    r = x * a
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    z = random(Nlplex{BigInt})
    l = (x + y) * z
    r = (x * z) + (y * z)
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    z = random(Nlplex{BigInt})
    l = x * (y - z)
    r = (x * y) - (x * z)
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    z = random(Nlplex{BigInt})
    l = (x - y) * z
    r = (x * z) - (y * z)
    l == r
end

@test begin
    x = random(Nlplex{BigInt})
    abs2(x) > big(0) || abs2(x) == big(0)
end

@test begin
    x = random(Nlplex{BigInt})
    y = random(Nlplex{BigInt})
    l = abs2(x * y)
    r = abs2(x) * abs2(y)
    l == r
end

@test_throws ErrorException begin
    inv(Nlplex(0, 0.0))
end

@test_throws ErrorException begin
    random(Nlplex{Int}) / 0
end

@test_throws ErrorException begin
    0 \ random(Nlplex{Int})
end

@test begin
    a = Plexifications.randomBigRational()
    z = random(Nlplex{Rational{BigInt}})
    l = z / a
    r = a \ z
    l == r
end

@test begin
    a = Plexifications.randomBigRational()
    z = random(Nlplex{Rational{BigInt}})
    l = a / z
    r = z \ a
    l == r
end

@test begin
    z = random(Nlplex{BigInt})
    z == z + zero(Nlplex{BigInt})
end

@test begin
    z = random(Nlplex{BigInt})
    z == z * one(Nlplex{BigInt})
end

@test begin
    z = random(Nlplex{BigInt})
    z == z + zero(z)
end

@test begin
    z = random(Nlplex{BigInt})
    z == z * one(z)
end


# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors    #src
# This Source Code Form is subject to the terms of the Mozilla Public License   #src
# v.2.0. If a copy of the MPL was not distributed with this file, You can       #src
# obtain one at https://mozilla.org/MPL/2.0/.                                   #src

# # The transportation problem

# Allocation of passenger cars to trains to minimize cars required or car-miles
# run. Based on:
#
# Fourer, D.M. Gay and Brian W. Kernighan, A Modeling Language for Mathematical
# Programming, https://ampl.com/REFS/amplmod.ps.gz Appendix D.
#
# Originally contributed by Louis Luangkesorn, January 30, 2015.

using JuMP
import GLPK
import Test

function example_transp()
	ORIG = ["GARY", "CLEV", "PITT"]
	DEST = ["FRA", "DET", "LAN", "WIN", "STL", "FRE", "LAF"]
	supply = [1_400, 2_600, 2_900]
	demand = [900, 1_200, 600, 400, 1_700, 1_100, 1_000]
	Test.@test sum(supply) == sum(demand)
	cost = [
		39   14   11   14   16   82    8;
		27    9   12    9   26   95   17;
		24   14   17   13   28   99   20
	]
	model = Model(GLPK.Optimizer)
	@variable(model, trans[1:length(ORIG), 1:length(DEST)] >= 0)
	@objective(
		model,
		Min,
		sum(
			cost[i, j] * trans[i, j]
			for i in 1:length(ORIG), j in 1:length(DEST)
		)
	)
	@constraints(model, begin
		[i in 1:length(ORIG)], sum(trans[i, :]) == supply[i]
		[j in 1:length(DEST)], sum(trans[:, j]) == demand[j]
	end)
	optimize!(model)
	Test.@test termination_status(model) == MOI.OPTIMAL
	Test.@test primal_status(model) == MOI.FEASIBLE_POINT
	Test.@test objective_value(model) == 196200.0
	println("The optimal solution is:")
	println(value.(trans))
	return
end

example_transp()


using DelimitedFiles
using Plots; gr()

function plot_iterations_linear(
                                a::Vector{Float64},
                                b::Vector{Float64},
                                filename::String,
                                imagename::String;
                                p_step::Float64=1.0e-2,
                                p_levels::Int64=30,
                                show_sample_set::Bool=false
                                )

    # Reads the execution data
    data = readdlm(filename)

    # Splits the useful data
    data_c = data[1:(end - 1), 14]
    data_g = data[1:(end - 1), 15:16]
    data_xbase = data[1:(end - 1), 17:18]
    data_xopt = data[1:(end - 1), 19:20]

    if show_sample_set

        data_Y = data[1:(end - 1), 24:27]

    end

    # Creates 'x' and 'y'
    x = a[1]:p_step:b[1]
    y = a[2]:p_step:b[2]

    # Generic model function
    function model_i(i, x, y)

        return data_c[i] + data_g[i, 1] * x + data_g[i, 2] * y

    end

    for i=1:length(data_c)

        # i-th iteration model

        model(x, y) = model_i(i, x, y)
        plot_model = contour( x, y, model, levels = p_levels )

        if show_sample_set

            Y_x = [ data_Y[i, 1] + data_xbase[i, 1], data_Y[i, 3] + data_xbase[i, 1] ]
            Y_y = [ data_Y[i, 2] + data_xbase[i, 2], data_Y[i, 4] + data_xbase[i, 2] ]

            scatter!( plot_model, Y_x, Y_y, color = :gray40, markersize = 6, marker = :circle,  lab = "sample point" )

        end

        scatter!( plot_model, [ data_xbase[i, 1] ], [ data_xbase[i, 2] ], color = :green3, markersize = 12, marker = :circle,  lab = "xbase" )
        scatter!( plot_model, [ data_xopt[i, 1] ] , [ data_xopt[i, 2] ], color = :red  , markersize = 9,  marker = :diamond, lab = "xopt" )

        plot( plot_model, size = (900, 600))
        figname = string( imagename, "$(i)" )
        png( figname )

    end

end

# -----------------------------------------------
# Problem definition
# -----------------------------------------------

a = [0.0, 0.0]
b = [5.0, 5.0]
path_to_source = "./data_files/simple_runtest_01.dat"
path_to_image = "./images/test_01/fig_model_"

# -----------------------------------------------
# Function call
# -----------------------------------------------

plot_iterations_linear( a, b, path_to_source, path_to_image; show_sample_set=true)


module OPRBF
#
# Module OPRBF implements functions related to radial basis function fitting
# and interpolation.
#

export RBFmat, RBFfit, RBFkernel, RBFeval, RBFcondition, RBFderiv

using LinearAlgebra

# ===================================================================
function RBFmat(x, s; ker=1)
#
# Compute the matrix used in evaluating the RBF fit.
#
# Version:        Changes:
# --------        -------------
# 13.06.2021      Original code.
#
# Version:        Verification:
# --------        -------------
# 13.06.2021
#
# Inputs:
# -------
# x               : Reference points, Nd-by-Np matrix.
# s               : Radial basis function scale parameter.
# ker             : Flag selecting the type of kernel function: see
#                   RBFkernel.
#
# Outputs:
# --------
# M               : The matrix, Np-by-Np.

   Np = size(x,2)

   # Set up the system of equations.
   M = zeros(Np,Np)
   for irow = 1:Np
      M[irow,irow] = RBFkernel(0.0, 1.0, s, ker=ker)
      for icol = irow+1:Np
         r = norm(x[:,irow] - x[:,icol])
         M[irow,icol] = RBFkernel(r, 1.0, s, ker=ker)
         M[icol,irow] = M[irow,icol]
      end
   end

   return M

end  # RBFmat
function RBFmat(x1, x2, s; ker=1)
#
# This version of RBFmat generates the matrix between two sets of
# points that may be different.
#
# Version:        Changes:
# --------        -------------
# 24.06.2021      Original code.
#
# Version:        Verification:
# --------        -------------
# 24.06.2021
#
# Inputs:
# -------
# x1,x2           : Reference points, Nd-by-N1 and Nd-by-N2 matrices.
# s               : Radial basis function scale parameter.
# ker             : Flag selecting the type of kernel function: see
#                   RBFkernel.
#
# Outputs:
# --------
# M               : The matrix, N1-by-N2.

   N1 = size(x1,2)
   N2 = size(x2,2)

   # Set up the system of equations.
   M = zeros(N1,N2)
   for irow = 1:N1
      for icol = 1:N2
         r = norm(x1[:,irow] - x2[:,icol])
         M[irow,icol] = RBFkernel(r, 1.0, s, ker=ker)
      end
   end

   return M

end  # RBFmat

# ===================================================================
function RBFfit(x, y, s; ker=1, con=false)
#
# Fit a radial basis function of the specified kernel to the given points.
#
# [Note, could also make a version that takes different x and xp points,
#  where Nx > Nxp, performing a least-squares solution at the x points
#  for RBF coefficients at the xp points.]
#
# Version:        Changes:
# --------        -------------
# 10.05.2021      Original code.
#
# Version:        Verification:
# --------        -------------
# 10.05.2021      Checked an example 2D surface-fitting problem.
#
# Inputs:
# -------
# x               : Reference points, Nd-by-Np matrix.
# y               : Scalar values at the reference points, Vector of
#                   length Np.
# s               : Radial basis function scale parameter.
# ker             : Flag selecting the type of kernel function: see
#                   RBFkernel.
# con             : Set to true to compute and return the matrix
#                   condition number.
#
# Outputs:
# --------
# w               : Weights, vector of length Np.

   Np = size(x,2)

   # Set up the system of equations.
   M = RBFmat(x, s, ker=ker)

   if (con)
      c = cond(M)
   else
      c = 0.0
   end

   w = M \ y

   return w, c

end  # RBFfit

# ===================================================================
function RBFkernel(r, w, s; ker=1)
#
# Compute the kernel at the given points.
#
# Version:        Changes:
# --------        -------------
# 10.05.2021      Original code.
#
# Version:        Verification:
# --------        -------------
# 10.05.2021      Checked an example 2D surface-fitting problem.
#
# Inputs:
# -------
# r               : Distances at which to evaluate the kernel function.
#                   Vector of length Np.
# w               : Kernel function weights.
# s               : Kernel function scale parameter.
# ker             : Flag specifying the type of kernel:
#                   1: Gaussian.
#                   2: Multiquadratic.
#                   3: Inverse multiquadratic.
#                   4: Student T with nu = 1.
#
# Outputs:
# --------
# y               : Kernel function values.  Vector of length Np.

   if (ker == 1)      # Gaussian
      y = w .* exp.(-(s .* r) .^ 2)
   elseif (ker == 2)  # Quadratic
      y = w .* sqrt.(1.0 .+ (s .* r) .^ 2)
   elseif (ker == 3)  # Inverse quadratic
      y = w ./ sqrt.(1.0 .+ (s .* r) .^ 2)
   elseif (ker == 4)  # Student T, nu = 1
      y = s .* w ./ (pi*(1.0 .+ (s .* r) .^ 2))
   end

   return y

end  # RBFkernel

# ===================================================================
function RBFeval(x, xp, w, s; ker=1)
#
# Compute the value of the function at the given points.
#
# Version:        Changes:
# --------        -------------
# 10.05.2021      Original code.
#
# Version:        Verification:
# --------        -------------
# 10.05.2021      Checked an example 2D surface-fitting problem.
#
# Inputs:
# -------
# x               : Points at which the function is desired.  Nd-by-
#                   Nx matrix.
# xp              : Points at which the weights are defined.  Nd-by-
#                   Np matrix.
# w               : Kernel function weights.  Vector of length Np.
# s               : Kernel function scale parameter.
# ker             : Flag specifying the type of kernel: see RBFkernel.
#
# Outputs:
# --------
# f               : Function values.  Vector of length Nx.

   Nx = size(x,2)
   Np = size(xp,2)
   Nd = size(x,1)

   f = zeros(Nx)
   for ix in 1:Nx
      r = zeros(Np)
      for id in 1:Nd
         r = r .+ (x[id,ix] .- xp[id,:]).^2
      end
      r = sqrt.(r)
      y = RBFkernel(r, w, s; ker=ker)
      f[ix] = sum(y)

   end

   return f

end  # RBFeval

# ===================================================================
function RBFcondition(x, y, sg; clow=5.0e4, chigh=2.0e5, ker=1)
#
# Iterate on the scale parameter s until the matrix condition number
# lies within the specified bounds.
#
# Version:        Changes:
# --------        -------------
# 12.05.2021      Original code.
#
# Version:        Verification:
# --------        -------------
# 12.05.2021      Checked an example 2D surface-fitting problem.
#
# Inputs:
# -------
# x               : Referance points, Nd-by-Np matrix.
# y               : Scalar values at the reference points, Vector of
#                   length Np.
# sg              : Guess for the radial basis function scale parameter.
# clow            : Lower bound for the matrix condition number.
# chigh           : Upper bound for the matrix condition number.
# ker             : Flag selecting the type of kernel function: see
#                   RBFkernel.
#
# Outputs:
# --------
# w               : Weights, vector of length Np.
# s               : Value of the scale parameter satisfying the
#                   condition number.
# c               : The final condition number.

   s = deepcopy(sg)
   w,c = RBFfit(x, y, s; ker=ker, con=true)

   # First bracket the desired condition number.
   if (c < clow)

      # The condition number is low.  Make the scale parameter smaller,
      # which makes the basis function broader.  (Sorry for the confusion,
      # but this is the typical definition.)
      sl = deepcopy(s)
      sh = deepcopy(s)
      iter = 0
      while (c < clow) && (iter < 10)
         sl = 0.5*sl
         w,c = RBFfit(x, y, sl; ker=ker, con=true)
         iter = iter + 1'
      end

      if (c > chigh)
         flg = false
      else
         # The upper bound now lies within the acceptable range.  If
         # iter reached its limit, well, this is anyways the best that
         # we are going to get, so no additional logic is needed.
         s = deepcopy(sl)
         flg = true
      end

   elseif (c > chigh)

      # The condition number is high.  Make the scale parameter larger,
      # which makes the basis function narrower.
      sl = deepcopy(s)
      sh = deepcopy(s)
      iter = 0
      while (c > chigh) && (iter < 10)
         sh = 2.0*sh
         w,c = RBFfit(x, y, sh; ker=ker, con=true)
         iter = iter + 1
      end

      if (c < clow)
         flg = false
      else
         # The lower bound now lies within the acceptable range.
         s = deepcopy(sh)
         flg = true
      end

   else
      # The initial s lies within the acceptable range.
      flg = true
   end

   # Now, if flg is false, cl and ch bracket the acceptable range.  Do
   # a bisecting search.
   iter = 0
   while (!flg) && (iter < 10)
      s = 0.5*(sh + sl)
      w,c = RBFfit(x, y, s; ker=ker, con=true)
      if (c < clow)
         # The condition number is low.  Make the scale parameter smaller.
         sh = deepcopy(s)
      elseif (c > chigh)
         # The condition number is high.  Make the scale parameter larger.
         sl = deepcopy(s)
      else
         flg = true
      end
      iter = iter + 1
   end

   return w,s,c

end  # RBFcondition

# ===================================================================
function RBFderiv(x, xp, w, s, k; ker=1)
#
# Compute the derivatives of the radial basis functions with respect
# to each of the coordinates, in a specified sequence.
#
# Note that this function does not compute df/dx_1, df/dx_2, ... but
# rather df/dx_1, d^2f/dx_1dx_2, ...  If the former is desired,
# then call this function multiple times with the appropriate values
# of k.
#
# Version:        Changes:
# --------        -------------
# 16.05.2021      Original code.
#
# Version:        Verification:
# --------        -------------
# 16.05.2021      Checked a 2D example problem, comparing with finite
#                 difference using RBFeval.
#
# Inputs:
# -------
# x               : Points at which the function is desired.  Nd-by-
#                   Nx matrix.
# xp              : Points at which the weights are defined.  Nd-by-
#                   Np matrix.
# w               : Kernel function weights.
# s               : Kernel function scale parameter.
# k               : Sequence in which derivatives are to be taken.
#                   Vector of length Ndim, containing the integers
#                   1:Nd in the desired order of differentiation.
#                   Example: d^3f/dx_2 dx_1 dx_3; k = [2, 1, 3]
#                   (It doesn't HAVE to contain all the integers from
#                   1 to Ndim, but there cannot be any repeated
#                   indices.)
# ker             : Flag specifying the type of kernel:
#                   1: Gaussian.
#                   2: Multiquadratic.
#                   3: Inverse multiquadratic.
#                   4: Student T with nu = 1.
#
# Outputs:
# --------
# f               : Values of the RBFs, same as a call to RBFeval.
#                   These are computed here regardless, so might as
#                   well return them as output.
# df              : Matrix of derivatives, dimensions Nk-by-Nx,
#                   containing df/dx_k[1], d^2f/dx_k[1] dx_k[2], ...
#                   d^Nk f/dx_k[1] dx_k[2] ... dx_k[Nk].

   Nx = size(x,2)
   Np = size(xp,2)
   Nd = size(x,1)
   Nk = size(k,1)

   # Build up the formulas recursively.
   f  = zeros(Nx)
   df = zeros(Nk,Nx)

   for ix in 1:Nx
      r = zeros(Np)
      for id in 1:Nd
         r = r .+ (x[id,ix] .- xp[id,:]).^2
      end
      r = sqrt.(r)
      y = RBFkernel(r, w, s; ker=ker)
      if (ker == 1)      # Gaussian
         v  =  s^2
         c  = -2.0
         dc =  0.0
      elseif (ker == 2)  # Quadratic
         v  =  (s^2) ./ (1.0 .+ (s .* r).^2)
         c  =  1.0
         dc = -2.0
      elseif (ker == 3)  # Inverse quadratic
         v  =  (s^2) ./ (1.0 .+ (s .* r).^2)
         c  = -1.0
         dc = -2.0
      elseif (ker == 4)  # Student T.
         v  =  (s^2) ./ (1.0 .+ (s .* r).^2)
         c  = -2.0
         dc = -2.0
      end
      f[ix] = sum(y)
      for ik in 1:Nk
         y = y .* (c * v .* (x[k[ik],ix] .- xp[k[ik],:]))
         df[ik,ix] = sum(y)
         c = c + dc
      end

   end

   return f, df

end # RBFderiv

end  # module.


using Test
using DelimitedFiles, LinearAlgebra
import MLJBase
const Mlj = MLJBase
using StableRNGs
#rng = StableRNG(123)
using BetaML

#TESTRNG = FIXEDRNG # This could change...
TESTRNG = StableRNG(123)

println("*** Testing Decision trees/Random Forest algorithms...")

# ==================================
# NEW TEST
# ==================================
println("Testing basic classification of decision trees...")
# ---------------------
xtrain = [
    "Green"  3.0;
    "Yellow" 3.0;
    "Red"    1.0;
    "Red"    1.0;
    "Yellow" 3.0;
]

ytrain = ["Apple",  "Apple", "Grape", "Grape", "Lemon"]
myTree = buildTree(xtrain,ytrain,rng=copy(TESTRNG))

ŷtrain = predict(myTree, xtrain,rng=copy(TESTRNG))
@test accuracy(ŷtrain,ytrain,rng=copy(TESTRNG)) >= 0.8

xtest = [
    "Green"  3;
    "Yellow" 4;
    "Red"    2;
    "Red"    1;
    "Yellow" 3
]

ytest = ["Apple","Apple","Grape","Grape","Lemon"]
ŷtest = predict(myTree, xtest,rng=copy(TESTRNG))
@test accuracy(ŷtest,ytest,rng=copy(TESTRNG)) >= 0.8

#print(myTree)

# ==================================
# NEW TEST
println("Testing classification of the sepal database using decision trees...")
iris     = readdlm(joinpath(@__DIR__,"data","iris_shuffled.csv"),',',skipstart=1)
x = convert(Array{Float64,2}, iris[:,1:4])
y = convert(Array{String,1}, iris[:,5])

ntrain = Int64(round(size(x,1)*0.8))
xtrain = x[1:ntrain,:]
ytrain = y[1:ntrain]
xtest = x[ntrain+1:end,:]
ytest = y[ntrain+1:end]

myTree = buildTree(xtrain,ytrain, splittingCriterion=entropy,rng=copy(TESTRNG))
ŷtrain = predict(myTree, xtrain,rng=copy(TESTRNG))
@test accuracy(ŷtrain,ytrain,rng=copy(TESTRNG)) >= 0.98
ŷtest = predict(myTree, xtest,rng=copy(TESTRNG))
@test accuracy(ŷtest,ytest,rng=copy(TESTRNG))  >= 0.95

# ==================================
# NEW TEST
println("Testing decision trees regression...")

ϵtrain = [1.023,1.08,0.961,0.919,0.933,0.993,1.011,0.923,1.084,1.037,1.012]
ϵtest  = [1.056,0.902,0.998,0.977]
xtrain = [0.1 0.2; 0.3 0.5; 0.4 0.1; 0.5 0.4; 0.7 0.9; 0.2 0.1; 0.4 0.2; 0.3 0.3; 0.6 0.9; 0.3 0.4; 0.9 0.8]
ytrain = [(0.1*x[1]+0.2*x[2]+0.3)*ϵtrain[i] for (i,x) in enumerate(eachrow(xtrain))]
xtest  = [0.5 0.6; 0.14 0.2; 0.3 0.7; 20.0 40.0;]
ytest  = [(0.1*x[1]+0.2*x[2]+0.3)*ϵtest[i] for (i,x) in enumerate(eachrow(xtest))]

myTree = buildTree(xtrain,ytrain, minGain=0.001, minRecords=2, maxDepth=3,rng=copy(TESTRNG))
ŷtrain = predict(myTree, xtrain,rng=copy(TESTRNG))
ŷtest = predict(myTree, xtest,rng=copy(TESTRNG))
mreTrain = meanRelError(ŷtrain,ytrain)
@test mreTrain <= 0.06
mreTest  = meanRelError(ŷtest,ytest)
@test mreTest <= 0.3

# ==================================
# NEW TEST
println("Testing classification of the sepal database using random forests...")
iris     = readdlm(joinpath(@__DIR__,"data","iris_shuffled.csv"),',',skipstart=1)
x = convert(Array{Float64,2}, iris[:,1:4])
y = convert(Array{String,1}, iris[:,5])

ntrain = Int64(round(size(x,1)*0.8))
xtrain = x[1:ntrain,:]
ytrain = y[1:ntrain]
xtest = x[ntrain+1:end,:]
ytest = y[ntrain+1:end]

myForest = buildForest(xtrain,ytrain,β=0,maxDepth=20,oob=true,rng=copy(TESTRNG))

trees = myForest.trees
treesWeights = myForest.weights
oobError = myForest.oobError
ŷtrain = predict(myForest, xtrain,rng=copy(TESTRNG))
@test accuracy(ŷtrain,ytrain,rng=copy(TESTRNG)) >= 0.96
ŷtest = predict(myForest, xtest,rng=copy(TESTRNG))
@test accuracy(ŷtest,ytest,rng=copy(TESTRNG))  >= 0.96
updateTreesWeights!(myForest,xtrain,ytrain;β=1)
ŷtrain2 = predict(myForest, xtrain,rng=copy(TESTRNG))
@test accuracy(ŷtrain2,ytrain,rng=copy(TESTRNG)) >= 0.98
ŷtest2 = predict(myForest, xtest,rng=copy(TESTRNG))
@test accuracy(ŷtest2,ytest,rng=copy(TESTRNG))  >= 0.96
@test oobError <= 0.1

# ==================================
# NEW TEST
println("Testing random forest regression...")

ϵtrain = [1.023,1.08,0.961,0.919,0.933,0.993,1.011,0.923,1.084,1.037,1.012]
ϵtest  = [1.056,0.902,0.998,0.977]
xtrain = [0.1 0.2; 0.3 0.5; 0.4 0.1; 0.5 0.4; 0.7 0.9; 0.2 0.1; 0.4 0.2; 0.3 0.3; 0.6 0.9; 0.3 0.4; 0.9 0.8]
ytrain = [(0.1*x[1]+0.2*x[2]+0.3)*ϵtrain[i] for (i,x) in enumerate(eachrow(xtrain))]
xtest  = [0.5 0.6; 0.14 0.2; 0.3 0.7; 20.0 40.0;]
ytest  = [(0.1*x[1]+0.2*x[2]+0.3)*ϵtest[i] for (i,x) in enumerate(eachrow(xtest))]

myForest         = buildForest(xtrain,ytrain, minGain=0.001, minRecords=2, maxDepth=3,rng=copy(TESTRNG))
trees            = myForest.trees
treesWeights     = myForest.weights

ŷtrain           = predict(myForest, xtrain,rng=copy(TESTRNG))
ŷtest            = predict(myForest, xtest,rng=copy(TESTRNG))
mreTrain         = meanRelError(ŷtrain,ytrain)
@test mreTrain <= 0.08
mreTest  = meanRelError(ŷtest,ytest)
@test mreTest <= 0.4

updateTreesWeights!(myForest,xtrain,ytrain;β=50)
ŷtrain2 = predict(myForest, xtrain,rng=copy(TESTRNG))
ŷtest2 = predict(myForest, xtest,rng=copy(TESTRNG))
mreTrain = meanRelError(ŷtrain2,ytrain)
@test mreTrain <= 0.08
mreTest  = meanRelError(ŷtest2,ytest)
@test mreTest <= 0.4

# ==================================
# NEW TEST
println("Testing all possible combinations...")
xtrain = [1 "pippo" 1.5; 3 "topolino" 2.5; 1 "amanda" 5.2; 5 "zzz" 1.2; 7 "pippo" 2.2; 1 "zzz" 1.5; 3 "topolino" 2.1]
ytrain = [x[2][1] <= 'q' ? 5*x[1]-2*x[3] : -5*x[1]+2*x[3] for x in eachrow(xtrain)]
xtest = [2 "pippo" 3.4; 1 "amanda" 1.5; 4 "amanda" 0.5; 2 "topolino" 2.2; 7 "zzz" 3.2]
ytest = [x[2][1] <= 'q' ? 5*x[1]-2*x[3] : -5*x[1]+2*x[3] for x in eachrow(xtest)]
ytrainInt = Int64.(round.(ytrain))

myTree1 = buildTree(xtrain,ytrain,rng=copy(TESTRNG))
myForest = buildForest(xtrain,ytrain,oob=true,rng=copy(TESTRNG)) # TO.DO solved 20201130: If I add here β=1 I have no problem, but local testing gives a crazy error!!!
oobError = myForest.oobError
ŷtrain = predict(myForest,xtrain,rng=copy(TESTRNG))
ŷtest = predict(myForest,xtest,rng=copy(TESTRNG))
mreTrain = meanRelError(ŷtrain,ytrain)
mreTest  = meanRelError(ŷtest,ytest)

xtrain[3,3] = missing
xtest[3,2] = missing
myForest = buildForest(xtrain,ytrain,oob=true,β=1,rng=copy(TESTRNG))
oobError = myForest.oobError
ŷtrain = predict(myForest,xtrain,rng=copy(TESTRNG))
ŷtest = predict(myForest,xtest,rng=copy(TESTRNG))
mreTrain2 = meanRelError(ŷtrain,ytrain)
mreTest2  = meanRelError(ŷtest,ytest)
@test mreTest2 <= mreTest * 1.5

myTree2 = buildTree(xtrain,ytrainInt,rng=copy(TESTRNG))
myTree3 = buildTree(xtrain,ytrainInt, forceClassification=true,rng=copy(TESTRNG))
@test typeof(myTree1) <: Trees.DecisionNode && typeof(myTree2) <: Trees.DecisionNode && typeof(myTree3) <: Trees.DecisionNode

# ==================================
# NEW TEST
println("Testing trees with unsortable and missing X values...")

abstract type AType end
mutable struct SortableType<:AType
    x::Int64
    y::Int64
end

mutable struct UnsortableType<:AType
    x::Int64
    y::Int64
end
isless(x::SortableType,y::SortableType) = x.x < y.x

SortableVector = [SortableType(2,4),SortableType(1,5),SortableType(1,8),SortableType(12,5),
    SortableType(6,2),SortableType(2,2),SortableType(2,2),SortableType(2,4),
    SortableType(6,2),SortableType(1,5),missing,SortableType(2,4),
    SortableType(1,8),SortableType(12,5)]
UnSortableVector = [UnsortableType(2,5),UnsortableType(1,3),UnsortableType(1,8),UnsortableType(2,6),
    UnsortableType(6,3),UnsortableType(7,9),UnsortableType(2,5),UnsortableType(2,6),
    missing,UnsortableType(3,2),UnsortableType(6,3),UnsortableType(2,5),
    UnsortableType(7,9),UnsortableType(7,9)]

data = Union{Missing,Float64, String,AType}[
    0.9 0.6 "black" "monitor" 10.1
    0.3 missing "white" "paper sheet" 2.3
    4.0 2.2 missing "monitor"  12.5
    0.6 0.5 "white" "monitor" 12.5
    3.8 2.1 "gray" "car" 54.2
    0.3 0.2 "red" "paper sheet" 2.6
    0.1 0.1 "white" "paper sheet" 2.5
    0.3 0.2 "black" "monitor" 11.3
    0.1 0.2 "black" "monitor" 9.8
    0.31 0.2 "white" "paper sheet" 3.7
    3.2 1.9 "gray" "car" 64.3
    0.4 0.25 "white" "paper" 2.7
    0.9 0.4 "black" "monitor" 12.5
    4.1 2.1 "gray" "monitor" 13.2
]

X = hcat(data[:,[1,2,3]],UnSortableVector)
y = convert(Vector{String},  data[:,4])

((xtrain,xtest),(ytrain,ytest)) = Utils.partition([X,y],[0.7,0.3],shuffle=false,rng=copy(TESTRNG))

modelβ = buildForest(xtrain,ytrain,5,rng=copy(TESTRNG))
ŷtestβ = predict(modelβ,xtest,rng=copy(TESTRNG))
accβ   = accuracy(ŷtestβ,ytest,rng=copy(TESTRNG))
@test accβ >= 0.25

# ==================================
# NEW TEST
println("Testing MLJ interface for Trees models....")
X, y                           = Mlj.@load_boston
model_dtr                      = DecisionTreeRegressor(rng=copy(TESTRNG))
regressor_dtr                  = Mlj.machine(model_dtr, X, y)
(fitresult_dtr, cache, report) = Mlj.fit(model_dtr, 0, X, y)
yhat_dtr                       = Mlj.predict(model_dtr, fitresult_dtr, X)
@test meanRelError(yhat_dtr,y) < 0.02

model_rfr                      = RandomForestRegressor(rng=copy(TESTRNG))
regressor_rfr                  = Mlj.machine(model_rfr, X, y)
(fitresult_rfr, cache, report) = Mlj.fit(model_rfr, 0, X, y)
yhat_rfr                       = Mlj.predict(model_rfr, fitresult_rfr, X)
@test meanRelError(yhat_rfr,y) < 0.06

X, y                           = Mlj.@load_iris
model_dtc                      = DecisionTreeClassifier(rng=copy(TESTRNG))
regressor_dtc                  = Mlj.machine(model_dtc, X, y)
(fitresult_dtc, cache, report) = Mlj.fit(model_dtc, 0, X, y)
yhat_dtc                       = Mlj.predict(model_dtc, fitresult_dtc, X)
@test Mlj.mean(Mlj.LogLoss(tol=1e-4)(yhat_dtc, y)) < 0.0002

model_rfc                      = RandomForestClassifier(maxFeatures=3,rng=copy(TESTRNG))
regressor_rfc                  = Mlj.machine(model_rfc, X, y)
(fitresult_rfc, cache, report) = Mlj.fit(model_rfc, 0, X, y)
yhat_rfc                       = Mlj.predict(model_rfc, fitresult_rfc, X)
@test Mlj.mean(Mlj.LogLoss(tol=1e-4)(yhat_rfc, y)) < 0.04

# Other MLJ classifier models
#=
import MLJ
X, y                       = Mlj.@load_iris
MLJ.models(MLJ.matching(X,y))
Model                      = MLJ.@load XGBoostClassifier #  DecisionTreeClassifier    #    XGBoostClassifier
model                      = Model()
regressor                  = MLJ.machine(model, X, y)
(fitresult, cache, report) = MLJ.fit(model, 0, X, y)
yhat                       = MLJ.predict(model, fitresult, X)
MLJ.mean(MLJ.LogLoss(tol=1e-4)(yhat, y))
MLJ.evaluate!(regressor, measure=MLJ.LogLoss())
#XGBoostClassifier:
#- fit: https://github.com/alan-turing-institute/MLJModels.jl/blob/3687491b132be8493b6f7a322aedf66008caaab1/src/XGBoost.jl#L600
#- predict :
=#


using SDD
const SDD = SententialDecisionDiagrams

# set up vtree and manager
vtree = SDD.read_vtree("$(@__DIR__)/input/rotate-left.vtree")
manager = SDD.sdd_manager(vtree)

# construct the term X_1 ^ X_2 ^ X_3 ^ X_4
x = [SDD.literal(i,manager) for i in 1:5]
α = x[1] & x[2] & x[3] & x[4]

# to perform a rotate, we need the manager's vtree
manager_vtree = SDD.vtree(manager)
manager_vtree_right = SDD.right(manager_vtree)

println("saving vtree & sdd ...")
SDD.dot("$(@__DIR__)/output/before-rotate-vtree.dot", α)

# ref alpha (so it is not gc'd)
SDD.ref(α)

# garbage collect (no dead nodes when performing vtree operations)
println("dead sdd nodes = $(SDD.dead_count(manager))")
println("garbage collection ...")
SDD.garbage_collect(manager)
println("dead sdd nodes = $(SDD.dead_count(manager))")

println("left rotating ... ")
succeeded = SDD.rotate_left(manager_vtree_right,manager,0)
if succeeded == 1; println("succeeded!") else println("did not succeed!") end

# deref alpha, since ref's are no longer needed
SDD.deref(α)

# the root changed after rotation, so get the manager's vtree again
# this time using root_location
manager_vtree = SDD.vtree(manager)

println("saving vtree & sdd ...")
SDD.dot("$(@__DIR__)/output/after-rotate-vtree.dot", manager_vtree)
SDD.dot("$(@__DIR__)/output/after-rotate-sdd.dot", α)


using PyPlot
using UlamSpiral

# text spirals
text_spiral()
savefig("plots/ulam_spiral_0.pdf")
savefig("plots/ulam_spiral_0.png")

text_spiral( 100; fig_n=2, fontsize=16)
savefig("plots/ulam_spiral_1.pdf")
savefig("plots/ulam_spiral_1.png")

text_spiral(fig_n=3, color_comp="white", color_prime="black", width=20, fontsize=10)
savefig("plots/ulam_spiral_2.pdf")
savefig("plots/ulam_spiral_2.png")

# points
point_spiral()
savefig("plots/ulam_spiral_3.pdf")
savefig("plots/ulam_spiral_3.png")

point_spiral( 1000; fig_n=4, markersize=10)
savefig("plots/ulam_spiral_4.pdf")
savefig("plots/ulam_spiral_4.png")

point_spiral(fig_n=5, markersize=1, color="black", width=200)
plot_quadratic(; fig_n=5, width=200);
plot_quadratic(4, 2, 41; fig_n=5, width=200, color_prime="green");

savefig("plots/ulam_spiral_5.pdf")
savefig("plots/ulam_spiral_5.png")

text_spiral(fig_n=6, color_comp="white", color_prime="black", width=20, fontsize=10)
point_spiral(fig_n=6, markersize=3, marker="s", color="green", width=20, clear="off")
savefig("plots/ulam_spiral_6.pdf")
savefig("plots/ulam_spiral_6.png")


#=
You are given an M by N matrix consisting of booleans that represents a board. Each True boolean represents a wall. Each False boolean represents a tile you can walk on.

Given this matrix, a start coordinate, and an end coordinate, return the minimum number of steps required to reach the end coordinate from the start. If there is no possible path, then return null. You can move up, left, down, and right. You cannot move through walls. You cannot wrap around the edges of the board.

For example, given the following board:

[[f, f, f, f],
[t, t, f, t],
[f, f, f, f],
[f, f, f, f]]

and start = (3, 0) (bottom left) and end = (0, 0) (top left), the minimum number of steps required to reach the end is 7, since we would need to go through (1, 2) because there is a wall everywhere else on the second row.
=#


export ExactPermutationTest, ApproximatePermutationTest

function ptstats(x,y,f)
    xy = vcat(x,y)
    rx = 1:length(x)
    ry = (length(xy)-length(y)+1):length(xy)
    (xy, rx, ry)
end

struct PermutationTest{T<:Real} <: HypothesisTest
    observation::T
    samples::Vector{T}
end

"""
    ExactPermutationTest(x::Vector, y::Vector, f::Function)

Perform a permutation test (a.k.a. randomization test) of the null hypothesis
that `f(x)` is equal to `f(y)`.  All possible permutations are sampled.
"""
function ExactPermutationTest(x::AbstractVector{R}, y::AbstractVector{S},
                              f::Function) where {R<:Real,S<:Real}
    xy, rx, ry = ptstats(x,y,f)
    P = permutations(xy)
    samples = [f(view(p,rx)) - f(view(p,ry)) for p in P]
    PermutationTest(f(x) - f(y), samples)
end

"""
    ApproximatePermutationTest(x::Vector, y::Vector, f::Function, n::Int)

Perform a permutation test (a.k.a. randomization test) of the null hypothesis
that `f(x)` is equal to `f(y)`.  `n` of the `factorial(length(x)+length(y))`
permutations are sampled at random.
"""
function ApproximatePermutationTest(x::AbstractVector{R}, y::AbstractVector{S},
                                    f::Function, n::Int) where {R<:Real,S<:Real}
    xy, rx, ry = ptstats(x,y,f)
    samples = [(shuffle!(xy); f(view(xy,rx)) - f(view(xy,ry))) for i = 1:n]
    PermutationTest(f(x) - f(y), samples)
end

function pvalue(apt::PermutationTest; tail=:both)
    if tail == :both
        count = sum(abs(apt.observation) >= abs(x) for x in apt.samples)
    elseif tail == :left
        count = sum(apt.observation >= x for x in apt.samples)
    elseif tail == :right
        count = sum(apt.observation <= x for x in apt.samples)
    end
    return count / length(apt.samples)
end

testname(::PermutationTest) = "Permutation Test"

function show_params(io::IO, apt::PermutationTest, ident)
    println(io, ident, "observation: ", apt.observation)
    println(io, ident, "samples: ", apt.samples)
end


# This file is used for get angle-of-attack
# be carful of the coordination

function aoaget(vall_r,ψ,θ0,θ_lat,θ_lon,beta=0.0)
#The solution is directly solving the angle between
#pitch angle vector and wind angle vector.

  α_aero = Array{Float64}(NR,Nbe)
  θ = Array{Float64}(NR,Nbe)

  for k in 1:NR
    ψk = ψ+(k-1)*2*π/NR
    for i in 1:Nbe
      v_per = vall_r[k,i][3]
      v_tan = vall_r[k,i][2]
      v_rad = vall_r[k,i][1]
      θ[k,i] = θ0[k,i]+θ_lat[k]*cos(ψk)+θ_lon[k]*sin(ψk) #pitch angle

      wind_v = [0,v_tan,v_per] #wind velocity
      wind_ = wind_v/norm(wind_v) #unit vector of wind velocity
      wind_tmp = rotobe(wind_,beta,θ[k,i])
      wind_be = [wind_tmp[2],wind_tmp[3]] # 叶素当地二维气流速度
      α_aero[k,i] = aoaang(wind_be) # 最终的气动迎角解
    end
  end
  return α_aero,θ #,dF_be
end


# Solves diffusion equation for zoned crystal using Crank-Nicolson scheme

using LinearAlgebra

Nt = 100
t_end = 1.0
dt = t_end/(Nt-1)
t1 = collect(LinRange(70.0,50.0,ceil(Int,Nt/2)).*3.1558e7*1e6)
t2 = collect(LinRange(49.9,0.01,floor(Int,Nt/2)).*3.1558e7*1e6)
t = vcat(t1,t2)

T1 = collect(LinRange(900.0,600.0,ceil(Int,Nt/2)).+273.15)
T2 = collect(LinRange(600.0,400.0,floor(Int,Nt/2)).+273.15)
T = vcat(T1,T2)
Ea = 250.0*1e3
D0 = 3.9*1e-10
#Rjoules = 8.314
L = 1e-4
Nx = 513
U238 = 1.0
# Nt,t_end
function zonation_forward(Ea,R,D0,U235,Th232,L,Nt,Nx,tTrU...)

tTcopy = collect(tTrU)
t = tTcopy[1:Nt]#[1:ceil(Int,length(tT)/2)] # type unstable but fastest so far
T = tTcopy[(Nt+1):(2*Nt)]#[ceil(Int,length(tT)/2)+1:end]
r = tTcopy[(2*Nt+1):(2*Nt+Nx)]
U238 = tTcopy[(2*Nt+Nx+1):end]
U238_0 = U238*(exp(COAST.lambda_38*t[1])) # U238 is measured at present
U235_0 = U235*(exp(COAST.lambda_35*t[1])) # U238 is measured at present
Th232_0 = Th232*(exp(COAST.lambda_32*t[1])) # U238 is measured at present
D_diffn = [0.0]

Pb = 0.0*ones(Nx)
Pb_solns = zeros(Nx,Nt) # includes time t0
Pb_solns[:,1] = Pb
Pb_rad = 0.0

rhs = zeros(Nx)
U238_cur = U238_0
for i in 1:Nt-1

    dt = t[i] - t[i+1]
    U238_cur = U238_0*(exp(-COAST.lambda_38*(t[1]-t[i]+dt/2))) # Crank-Nicolson is staggered in time, hence dt/2
    D_diffn[1] = D0*exp(-Ea/(R*T[i]))
    A = D_diffn[1]*dt/(dr^2)
    B = D_diffn[1]*dt/(2*dr)
    lhs_center = [1.0 + 3*A; fill(1.0 + A, Nx-2);1.0]
    lhs_lo = [-(A/2).+ B./r[2:Nx-1];0.0]
    lhs_hi = [-3*A; -A/2 .- B./r[2:Nx-1]]
    lhs = LinearAlgebra.Tridiagonal(lhs_lo,lhs_center,lhs_hi)
    rhs[1] = (1.0-3*A)*Pb[1] + 3*A*Pb[2] + U238_cur[1]*COAST.lambda_38*dt
    for j in 2:(Nx-1)
        rhs[j] = (A/2 - B/r[j])*Pb[j-1] + (1.0 - A)*Pb[j] + (A/2 + B/r[j])*Pb[j+1] + U238_cur[j]*COAST.lambda_38*dt
    end
    rhs[end] = Pb_rad

    Pb[:] = lhs\rhs
    Pb_solns[:,i+1] = Pb

end

return Pb_solns[:,end]
end