"""
    Mattsson2017(version::Symbol)

Coefficients of the upwind SBP operators given in
- Mattsson (2017)
  Diagonal-norm upwind SBP operators.
  Journal of Computational Physics 335, pp. 283-310.

You can choose between the different versions `:central`, `:plus`, and `:minus`.
"""
struct Mattsson2017 <: SourceOfCoefficients
  kind::Symbol

  function Mattsson2017(kind::Symbol)
    if (kind !== :plus) && (kind !== :minus) && (kind !== :central)
      throw(ArgumentError("The only choices are :plus, :minus, and :central, not :$kind."))
    end
    new(kind)
  end
end

function Base.show(io::IO, source::Mattsson2017)
  if get(io, :compact, false)
    summary(io, source)
  else
    print(io,
        "Mattsson (2017) \n",
        "  Diagonal-norm upwind SBP operators. \n",
        "  Journal of Computational Physics 335, pp. 283-310. \n",
        "  (upwind coefficients ", source.kind, ")")
  end
end

function first_derivative_coefficients(source::Mattsson2017, order::Int,
                                       T=Float64, mode=FastMode())
    if order == 2
        left_boundary_plus = (
            DerivativeCoefficientRow{T,1,3}(SVector(T(-3),
                                                    T(5),
                                                    T(-2), )),
            DerivativeCoefficientRow{T,1,4}(SVector(T(-1//5),
                                                    T(-1),
                                                    T(8//5),
                                                    T(-2//5), )),
        )
        right_boundary_plus = (
            DerivativeCoefficientRow{T,1,2}(SVector(T(1),
                                                    T(-1), )),
            DerivativeCoefficientRow{T,1,2}(SVector(T(1),
                                                    T(-1), )),
        )
        upper_coef_plus = SVector( T(2),
                                   T(-1//2), )
        central_coef_plus = T(-3//2)
        lower_coef_plus = SVector{0,T}()
        left_weights = SVector( T(1//4),
                                T(5//4), )
        right_weights = left_weights
        left_boundary_derivatives = Tuple{}()
        right_boundary_derivatives = left_boundary_derivatives

        left_boundary_minus = (
            DerivativeCoefficientRow{T,1,2}(SVector(T(-1),
                                                    T(1), )),
            DerivativeCoefficientRow{T,1,2}(SVector(T(-1),
                                                    T(1), )),
        )
        right_boundary_minus = (
            DerivativeCoefficientRow{T,1,3}(SVector(T(3),
                                                    T(-5),
                                                    T(2), )),
            DerivativeCoefficientRow{T,1,4}(SVector(T(1//5),
                                                    T(1),
                                                    T(-8//5),
                                                    T(2//5), )),
        )
        upper_coef_minus     = .- lower_coef_plus
        central_coef_minus   = .- central_coef_plus
        lower_coef_minus     = .- upper_coef_plus

        left_boundary_central  = (left_boundary_plus  .+ left_boundary_minus)  ./ 2
        right_boundary_central = (right_boundary_plus .+ right_boundary_minus) ./ 2
        upper_coef_central     = widening_plus(upper_coef_plus, upper_coef_minus) / 2
        central_coef_central   = (central_coef_plus   + central_coef_minus) / 2
        lower_coef_central     = widening_plus(lower_coef_plus, lower_coef_minus) / 2

        if source.kind === :plus
          left_boundary  = left_boundary_plus
          right_boundary = right_boundary_plus
          upper_coef     = upper_coef_plus
          central_coef   = central_coef_plus
          lower_coef     = lower_coef_plus
        elseif source.kind === :minus
          left_boundary  = left_boundary_minus
          right_boundary = right_boundary_minus
          upper_coef     = upper_coef_minus
          central_coef   = central_coef_minus
          lower_coef     = lower_coef_minus
        elseif source.kind === :central
          left_boundary  = left_boundary_central
          right_boundary = right_boundary_central
          upper_coef     = upper_coef_central
          central_coef   = central_coef_central
          lower_coef     = lower_coef_central
        end
        DerivativeCoefficients(left_boundary, right_boundary,
                                left_boundary_derivatives, right_boundary_derivatives,
                                lower_coef, central_coef, upper_coef,
                                left_weights, right_weights, mode, 1, order, source)
    elseif order == 3
      left_boundary_plus = (
          DerivativeCoefficientRow{T,1,3}(SVector(T(-7//5),
                                                  T(9//5),
                                                  T(-2//5), )),
          DerivativeCoefficientRow{T,1,4}(SVector(T(-5//13),
                                                  T(-5//13),
                                                  T(12//13),
                                                  T(-2//13), )),
      )
      right_boundary_plus = (
          DerivativeCoefficientRow{T,1,2}(SVector(T(1),
                                                  T(-1), )),
          DerivativeCoefficientRow{T,1,3}(SVector(T(9//13),
                                                  T(-5//13),
                                                  T(-4//13), )),
      )
      upper_coef_plus = SVector( T(1),
                                 T(-1//6), )
      central_coef_plus = T(-1//2)
      lower_coef_plus = SVector( T(-1//3), )
      left_weights = SVector( T(5//12),
                              T(13//12), )
      right_weights = left_weights
      left_boundary_derivatives = Tuple{}()
      right_boundary_derivatives = left_boundary_derivatives

      left_boundary_minus = (
          DerivativeCoefficientRow{T,1,2}(SVector(T(-1),
                                                  T(1), )),
          DerivativeCoefficientRow{T,1,3}(SVector(T(-9//13),
                                                  T(5//13),
                                                  T(4//13), )),
      )
      right_boundary_minus = (
          DerivativeCoefficientRow{T,1,3}(SVector(T(7//5),
                                                  T(-9//5),
                                                  T(2//5), )),
          DerivativeCoefficientRow{T,1,4}(SVector(T(5//13),
                                                  T(5//13),
                                                  T(-12//13),
                                                  T(2//13), )),
      )
      upper_coef_minus     = .- lower_coef_plus
      central_coef_minus   = .- central_coef_plus
      lower_coef_minus     = .- upper_coef_plus

      left_boundary_central  = (left_boundary_plus  .+ left_boundary_minus)  ./ 2
      right_boundary_central = (right_boundary_plus .+ right_boundary_minus) ./ 2
      upper_coef_central     = widening_plus(upper_coef_plus, upper_coef_minus) / 2
      central_coef_central   = (central_coef_plus   + central_coef_minus) / 2
      lower_coef_central     = widening_plus(lower_coef_plus, lower_coef_minus) / 2

      if source.kind === :plus
        left_boundary  = left_boundary_plus
        right_boundary = right_boundary_plus
        upper_coef     = upper_coef_plus
        central_coef   = central_coef_plus
        lower_coef     = lower_coef_plus
      elseif source.kind === :minus
        left_boundary  = left_boundary_minus
        right_boundary = right_boundary_minus
        upper_coef     = upper_coef_minus
        central_coef   = central_coef_minus
        lower_coef     = lower_coef_minus
      elseif source.kind === :central
        left_boundary  = left_boundary_central
        right_boundary = right_boundary_central
        upper_coef     = upper_coef_central
        central_coef   = central_coef_central
        lower_coef     = lower_coef_central
      end
      DerivativeCoefficients(left_boundary, right_boundary,
                              left_boundary_derivatives, right_boundary_derivatives,
                              lower_coef, central_coef, upper_coef,
                              left_weights, right_weights, mode, 1, order, source)
    elseif order == 4
      left_boundary_plus = (
          DerivativeCoefficientRow{T,1,4}(SVector(T(-75//49),
                                                  T(205//98),
                                                  T(-29//49),
                                                  T(3//98), )),
          DerivativeCoefficientRow{T,1,5}(SVector(T(-169//366),
                                                  T(-11//61),
                                                  T(99//122),
                                                  T(-43//183),
                                                  T(4//61), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(11//123),
                                                  T(-39//82),
                                                  T(-29//41),
                                                  T(389//246),
                                                  T(-24//41),
                                                  T(4//41), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(9//298),
                                                  T(-11//149),
                                                  T(-65//298),
                                                  T(-117//149),
                                                  T(216//149),
                                                  T(-72//149),
                                                  T(12//149), )),
      )
      right_boundary_plus = (
          DerivativeCoefficientRow{T,1,4}(SVector(T(69//49),
                                                  T(-169//98),
                                                  T(11//49),
                                                  T(9//98), )),
          DerivativeCoefficientRow{T,1,4}(SVector(T(205//366),
                                                  T(-11//61),
                                                  T(-39//122),
                                                  T(-11//183), )),
          DerivativeCoefficientRow{T,1,4}(SVector(T(-29//123),
                                                  T(99//82),
                                                  T(-29//41),
                                                  T(-65//246), )),
          DerivativeCoefficientRow{T,1,5}(SVector(T(3//298),
                                                  T(-43//149),
                                                  T(389//298),
                                                  T(-117//149),
                                                  T(-36//149), )),
      )
      upper_coef_plus = SVector( T(3//2),
                                 T(-1//2),
                                 T(1//12), )
      central_coef_plus = T(-5//6)
      lower_coef_plus = SVector( T(-1//4), )
      left_weights = SVector( T(49//144),
                              T(61//48),
                              T(41//48),
                              T(149//144), )
      right_weights = left_weights
      left_boundary_derivatives = Tuple{}()
      right_boundary_derivatives = left_boundary_derivatives

      left_boundary_minus = (
          DerivativeCoefficientRow{T,1,4}(SVector(T(-69//49),
                                                  T(169//98),
                                                  T(-11//49),
                                                  T(-9//98), )),
          DerivativeCoefficientRow{T,1,4}(SVector(T(-205//366),
                                                  T(11//61),
                                                  T(39//122),
                                                  T(11//183), )),
          DerivativeCoefficientRow{T,1,4}(SVector(T(29//123),
                                                  T(-99//82),
                                                  T(29//41),
                                                  T(65//246), )),
          DerivativeCoefficientRow{T,1,5}(SVector(T(-3//298),
                                                  T(43//149),
                                                  T(-389//298),
                                                  T(117//149),
                                                  T(36//149), )),
      )
      right_boundary_minus = (
          DerivativeCoefficientRow{T,1,4}(SVector(T(75//49),
                                                  T(-205//98),
                                                  T(29//49),
                                                  T(-3//98), )),
          DerivativeCoefficientRow{T,1,5}(SVector(T(169//366),
                                                  T(11//61),
                                                  T(-99//122),
                                                  T(43//183),
                                                  T(-4//61), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(-11//123),
                                                  T(39//82),
                                                  T(29//41),
                                                  T(-389//246),
                                                  T(24//41),
                                                  T(-4//41), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(-9//298),
                                                  T(11//149),
                                                  T(65//298),
                                                  T(117//149),
                                                  T(-216//149),
                                                  T(72//149),
                                                  T(-12//149), )),
      )
      upper_coef_minus     = .- lower_coef_plus
      central_coef_minus   = .- central_coef_plus
      lower_coef_minus     = .- upper_coef_plus

      left_boundary_central  = (left_boundary_plus  .+ left_boundary_minus)  ./ 2
      right_boundary_central = (right_boundary_plus .+ right_boundary_minus) ./ 2
      upper_coef_central     = widening_plus(upper_coef_plus, upper_coef_minus) / 2
      central_coef_central   = (central_coef_plus   + central_coef_minus) / 2
      lower_coef_central     = widening_plus(lower_coef_plus, lower_coef_minus) / 2

      if source.kind === :plus
        left_boundary  = left_boundary_plus
        right_boundary = right_boundary_plus
        upper_coef     = upper_coef_plus
        central_coef   = central_coef_plus
        lower_coef     = lower_coef_plus
      elseif source.kind === :minus
        left_boundary  = left_boundary_minus
        right_boundary = right_boundary_minus
        upper_coef     = upper_coef_minus
        central_coef   = central_coef_minus
        lower_coef     = lower_coef_minus
      elseif source.kind === :central
        left_boundary  = left_boundary_central
        right_boundary = right_boundary_central
        upper_coef     = upper_coef_central
        central_coef   = central_coef_central
        lower_coef     = lower_coef_central
      end
      DerivativeCoefficients(left_boundary, right_boundary,
                              left_boundary_derivatives, right_boundary_derivatives,
                              lower_coef, central_coef, upper_coef,
                              left_weights, right_weights, mode, 1, order, source)
    elseif order == 5
      left_boundary_plus = (
          DerivativeCoefficientRow{T,1,4}(SVector(T(-366//251),
                                                  T(941//502),
                                                  T(-94//251),
                                                  T(-21//502), )),
          DerivativeCoefficientRow{T,1,5}(SVector(T(-869//1794),
                                                  T(-22//299),
                                                  T(375//598),
                                                  T(-86//897),
                                                  T(8//299), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(58//633),
                                                  T(-255//422),
                                                  T(-58//211),
                                                  T(1309//1266),
                                                  T(-60//211),
                                                  T(8//211), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(45//1478),
                                                  T(-22//739),
                                                  T(-661//1478),
                                                  T(-234//739),
                                                  T(720//739),
                                                  T(-180//739),
                                                  T(24//739), )),
      )
      right_boundary_plus = (
          DerivativeCoefficientRow{T,1,4}(SVector(T(354//251),
                                                  T(-869//502),
                                                  T(58//251),
                                                  T(45//502), )),
          DerivativeCoefficientRow{T,1,4}(SVector(T(941//1794),
                                                  T(-22//299),
                                                  T(-255//598),
                                                  T(-22//897), )),
          DerivativeCoefficientRow{T,1,5}(SVector(T(-94//633),
                                                  T(375//422),
                                                  T(-58//211),
                                                  T(-661//1266),
                                                  T(12//211), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(-21//1478),
                                                  T(-86//739),
                                                  T(1309//1478),
                                                  T(-234//739),
                                                  T(-360//739),
                                                  T(36//739), )),
      )
      upper_coef_plus = SVector( T(1),
                                 T(-1//4),
                                 T(1//30), )
      central_coef_plus = T(-1//3)
      lower_coef_plus = SVector( T(-1//2),
                                 T(1//20), )
      left_weights = SVector( T(251//720),
                              T(299//240),
                              T(211//240),
                              T(739//720), )
      right_weights = left_weights
      left_boundary_derivatives = Tuple{}()
      right_boundary_derivatives = left_boundary_derivatives

      left_boundary_minus = (
          DerivativeCoefficientRow{T,1,4}(SVector(T(-354//251),
                                                  T(869//502),
                                                  T(-58//251),
                                                  T(-45//502), )),
          DerivativeCoefficientRow{T,1,4}(SVector(T(-941//1794),
                                                  T(22//299),
                                                  T(255//598),
                                                  T(22//897), )),
          DerivativeCoefficientRow{T,1,5}(SVector(T(94//633),
                                                  T(-375//422),
                                                  T(58//211),
                                                  T(661//1266),
                                                  T(-12//211), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(21//1478),
                                                  T(86//739),
                                                  T(-1309//1478),
                                                  T(234//739),
                                                  T(360//739),
                                                  T(-36//739), )),
      )
      right_boundary_minus = (
          DerivativeCoefficientRow{T,1,4}(SVector(T(366//251),
                                                  T(-941//502),
                                                  T(94//251),
                                                  T(21//502), )),
          DerivativeCoefficientRow{T,1,5}(SVector(T(869//1794),
                                                  T(22//299),
                                                  T(-375//598),
                                                  T(86//897),
                                                  T(-8//299), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(-58//633),
                                                  T(255//422),
                                                  T(58//211),
                                                  T(-1309//1266),
                                                  T(60//211),
                                                  T(-8//211), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(-45//1478),
                                                  T(22//739),
                                                  T(661//1478),
                                                  T(234//739),
                                                  T(-720//739),
                                                  T(180//739),
                                                  T(-24//739), )),
      )
      upper_coef_minus     = .- lower_coef_plus
      central_coef_minus   = .- central_coef_plus
      lower_coef_minus     = .- upper_coef_plus

      left_boundary_central  = (left_boundary_plus  .+ left_boundary_minus)  ./ 2
      right_boundary_central = (right_boundary_plus .+ right_boundary_minus) ./ 2
      upper_coef_central     = widening_plus(upper_coef_plus, upper_coef_minus) / 2
      central_coef_central   = (central_coef_plus   + central_coef_minus) / 2
      lower_coef_central     = widening_plus(lower_coef_plus, lower_coef_minus) / 2

      if source.kind === :plus
        left_boundary  = left_boundary_plus
        right_boundary = right_boundary_plus
        upper_coef     = upper_coef_plus
        central_coef   = central_coef_plus
        lower_coef     = lower_coef_plus
      elseif source.kind === :minus
        left_boundary  = left_boundary_minus
        right_boundary = right_boundary_minus
        upper_coef     = upper_coef_minus
        central_coef   = central_coef_minus
        lower_coef     = lower_coef_minus
      elseif source.kind === :central
        left_boundary  = left_boundary_central
        right_boundary = right_boundary_central
        upper_coef     = upper_coef_central
        central_coef   = central_coef_central
        lower_coef     = lower_coef_central
      end
      DerivativeCoefficients(left_boundary, right_boundary,
                              left_boundary_derivatives, right_boundary_derivatives,
                              lower_coef, central_coef, upper_coef,
                              left_weights, right_weights, mode, 1, order, source)
    elseif order == 6
      left_boundary_plus = (
          DerivativeCoefficientRow{T,1,6}(SVector(T(-58148100//36496453),
                                                  T(1146190567//547446795),
                                                  T(-14369571//52137790),
                                                  T(-55265831//182482265),
                                                  T(26269819//1094893590),
                                                  T(9858004//182482265), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(-1116490567//2422752675),
                                                  T(-954612//32303369),
                                                  T(190538869//484550535),
                                                  T(102705469//969101070),
                                                  T(4964892//161516845),
                                                  T(-191689861//4845505350), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(9869571//102452500),
                                                  T(-135385429//215150250),
                                                  T(-2198412//7171675),
                                                  T(45137333//35858375),
                                                  T(-253641811//430300500),
                                                  T(70665929//358583750),
                                                  T(-72//2675), )),
          DerivativeCoefficientRow{T,1,8}(SVector(T(66965831//723199750),
                                                  T(-208765789//867839700),
                                                  T(-17623253//72319975),
                                                  T(-657684//2066285),
                                                  T(410905829//433919850),
                                                  T(-477953317//1446399500),
                                                  T(576//5395),
                                                  T(-72//5395), )),
          DerivativeCoefficientRow{T,1,9}(SVector(T(-49219819//3153258150),
                                                  T(3519588//105108605),
                                                  T(26422771//630651630),
                                                  T(-141938309//315325815),
                                                  T(-12476988//21021721),
                                                  T(2217185207//1576629075),
                                                  T(-4320//7841),
                                                  T(1152//7841),
                                                  T(-144//7841), )),
          DerivativeCoefficientRow{T,1,10}(SVector(T(-9498004//587634985),
                                                  T(142906261//3525809910),
                                                  T(-3137129//587634985),
                                                  T(-29884283//1175269970),
                                                  T(-630168407//1762904955),
                                                  T(-9609300//16789571),
                                                  T(57600//43837),
                                                  T(-21600//43837),
                                                  T(5760//43837),
                                                  T(-720//43837), )),
      )
      right_boundary_plus = (
          DerivativeCoefficientRow{T,1,6}(SVector(T(57671100//36496453),
                                                  T(-1116490567//547446795),
                                                  T(9869571//52137790),
                                                  T(66965831//182482265),
                                                  T(-49219819//1094893590),
                                                  T(-9498004//182482265), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(1146190567//2422752675),
                                                  T(-954612//32303369),
                                                  T(-135385429//484550535),
                                                  T(-208765789//969101070),
                                                  T(3519588//161516845),
                                                  T(142906261//4845505350), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(-14369571//102452500),
                                                  T(190538869//215150250),
                                                  T(-2198412//7171675),
                                                  T(-17623253//35858375),
                                                  T(26422771//430300500),
                                                  T(-3137129//358583750), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(-55265831//723199750),
                                                  T(102705469//867839700),
                                                  T(45137333//72319975),
                                                  T(-657684//2066285),
                                                  T(-141938309//433919850),
                                                  T(-2298791//111261500), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(26269819//3153258150),
                                                  T(4964892//105108605),
                                                  T(-253641811//630651630),
                                                  T(410905829//315325815),
                                                  T(-12476988//21021721),
                                                  T(-630168407//1576629075),
                                                  T(288//7841), )),
          DerivativeCoefficientRow{T,1,8}(SVector(T(9858004//587634985),
                                                  T(-191689861//3525809910),
                                                  T(70665929//587634985),
                                                  T(-477953317//1175269970),
                                                  T(2217185207//1762904955),
                                                  T(-9609300//16789571),
                                                  T(-17280//43837),
                                                  T(1440//43837), )),
      )
      upper_coef_plus = SVector( T(4//3),
                                 T(-1//2),
                                 T(2//15),
                                 T(-1//60), )
      central_coef_plus = T(-7//12)
      lower_coef_plus = SVector( T(-2//5),
                                 T(1//30), )
      left_weights = SVector( T(13613//43200),
                              T(12049//8640),
                              T(535//864),
                              T(1079//864),
                              T(7841//8640),
                              T(43837//43200), )
      right_weights = left_weights
      left_boundary_derivatives = Tuple{}()
      right_boundary_derivatives = left_boundary_derivatives

      left_boundary_minus = (
          DerivativeCoefficientRow{T,1,6}(SVector(T(-57671100//36496453),
                                                  T(1116490567//547446795),
                                                  T(-9869571//52137790),
                                                  T(-66965831//182482265),
                                                  T(49219819//1094893590),
                                                  T(9498004//182482265), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(-1146190567//2422752675),
                                                  T(954612//32303369),
                                                  T(135385429//484550535),
                                                  T(208765789//969101070),
                                                  T(-3519588//161516845),
                                                  T(-142906261//4845505350), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(14369571//102452500),
                                                  T(-190538869//215150250),
                                                  T(2198412//7171675),
                                                  T(17623253//35858375),
                                                  T(-26422771//430300500),
                                                  T(3137129//358583750), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(55265831//723199750),
                                                  T(-102705469//867839700),
                                                  T(-45137333//72319975),
                                                  T(657684//2066285),
                                                  T(141938309//433919850),
                                                  T(2298791//111261500), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(-26269819//3153258150),
                                                  T(-4964892//105108605),
                                                  T(253641811//630651630),
                                                  T(-410905829//315325815),
                                                  T(12476988//21021721),
                                                  T(630168407//1576629075),
                                                  T(-288//7841), )),
          DerivativeCoefficientRow{T,1,8}(SVector(T(-9858004//587634985),
                                                  T(191689861//3525809910),
                                                  T(-70665929//587634985),
                                                  T(477953317//1175269970),
                                                  T(-2217185207//1762904955),
                                                  T(9609300//16789571),
                                                  T(17280//43837),
                                                  T(-1440//43837), )),
      )
      right_boundary_minus = (
          DerivativeCoefficientRow{T,1,6}(SVector(T(58148100//36496453),
                                                  T(-1146190567//547446795),
                                                  T(14369571//52137790),
                                                  T(55265831//182482265),
                                                  T(-26269819//1094893590),
                                                  T(-9858004//182482265), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(1116490567//2422752675),
                                                  T(954612//32303369),
                                                  T(-190538869//484550535),
                                                  T(-102705469//969101070),
                                                  T(-4964892//161516845),
                                                  T(191689861//4845505350), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(-9869571//102452500),
                                                  T(135385429//215150250),
                                                  T(2198412//7171675),
                                                  T(-45137333//35858375),
                                                  T(253641811//430300500),
                                                  T(-70665929//358583750),
                                                  T(72//2675), )),
          DerivativeCoefficientRow{T,1,8}(SVector(T(-66965831//723199750),
                                                  T(208765789//867839700),
                                                  T(17623253//72319975),
                                                  T(657684//2066285),
                                                  T(-410905829//433919850),
                                                  T(477953317//1446399500),
                                                  T(-576//5395),
                                                  T(72//5395), )),
          DerivativeCoefficientRow{T,1,9}(SVector(T(49219819//3153258150),
                                                  T(-3519588//105108605),
                                                  T(-26422771//630651630),
                                                  T(141938309//315325815),
                                                  T(12476988//21021721),
                                                  T(-2217185207//1576629075),
                                                  T(4320//7841),
                                                  T(-1152//7841),
                                                  T(144//7841), )),
          DerivativeCoefficientRow{T,1,10}(SVector(T(9498004//587634985),
                                                  T(-142906261//3525809910),
                                                  T(3137129//587634985),
                                                  T(29884283//1175269970),
                                                  T(630168407//1762904955),
                                                  T(9609300//16789571),
                                                  T(-57600//43837),
                                                  T(21600//43837),
                                                  T(-5760//43837),
                                                  T(720//43837), )),
      )
      upper_coef_minus     = .- lower_coef_plus
      central_coef_minus   = .- central_coef_plus
      lower_coef_minus     = .- upper_coef_plus

      left_boundary_central  = (left_boundary_plus  .+ left_boundary_minus)  ./ 2
      right_boundary_central = (right_boundary_plus .+ right_boundary_minus) ./ 2
      upper_coef_central     = widening_plus(upper_coef_plus, upper_coef_minus) / 2
      central_coef_central   = (central_coef_plus   + central_coef_minus) / 2
      lower_coef_central     = widening_plus(lower_coef_plus, lower_coef_minus) / 2

      if source.kind === :plus
        left_boundary  = left_boundary_plus
        right_boundary = right_boundary_plus
        upper_coef     = upper_coef_plus
        central_coef   = central_coef_plus
        lower_coef     = lower_coef_plus
      elseif source.kind === :minus
        left_boundary  = left_boundary_minus
        right_boundary = right_boundary_minus
        upper_coef     = upper_coef_minus
        central_coef   = central_coef_minus
        lower_coef     = lower_coef_minus
      elseif source.kind === :central
        left_boundary  = left_boundary_central
        right_boundary = right_boundary_central
        upper_coef     = upper_coef_central
        central_coef   = central_coef_central
        lower_coef     = lower_coef_central
      end
      DerivativeCoefficients(left_boundary, right_boundary,
                              left_boundary_derivatives, right_boundary_derivatives,
                              lower_coef, central_coef, upper_coef,
                              left_weights, right_weights, mode, 1, order, source)
    elseif order == 7
      left_boundary_plus = (
          DerivativeCoefficientRow{T,1,6}(SVector(T(-81216540//51172247),
                                                  T(1587945773//767583705),
                                                  T(-17337249//73103210),
                                                  T(-84398989//255861235),
                                                  T(48781961//1535167410),
                                                  T(13716476//255861235), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(-1570125773//3386062785),
                                                  T(-2863836//225737519),
                                                  T(240029831//677212557),
                                                  T(202934303//1354425114),
                                                  T(1418484//225737519),
                                                  T(-231357719//6772125570), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(14637249//144536540),
                                                  T(-206937767//303526734),
                                                  T(-6595236//50587789),
                                                  T(49602727//50587789),
                                                  T(-218919665//607053468),
                                                  T(51815011//505877890),
                                                  T(-216//18869), )),
          DerivativeCoefficientRow{T,1,8}(SVector(T(91418989//1008619010),
                                                  T(-266570495//1210342812),
                                                  T(-33094279//100861901),
                                                  T(-1973052//14408843),
                                                  T(440626231//605171406),
                                                  T(-365711063//2017238020),
                                                  T(2016//37621),
                                                  T(-216//37621), )),
          DerivativeCoefficientRow{T,1,9}(SVector(T(-62551961//4426143330),
                                                  T(9588//385217),
                                                  T(82588241//885228666),
                                                  T(-279245719//442614333),
                                                  T(-37430964//147538111),
                                                  T(2312302333//2213071665),
                                                  T(-18144//55031),
                                                  T(4032//55031),
                                                  T(-432//55031), )),
          DerivativeCoefficientRow{T,1,10}(SVector(T(-13500476//822302915),
                                                  T(202087559//4933817490),
                                                  T(-11297731//822302915),
                                                  T(61008503//1644605830),
                                                  T(-1360092253//2466908745),
                                                  T(-5765580//23494369),
                                                  T(60480//61343),
                                                  T(-18144//61343),
                                                  T(4032//61343),
                                                  T(-432//61343), )),
      )
      right_boundary_plus = (
          DerivativeCoefficientRow{T,1,6}(SVector(T(80930340//51172247),
                                                  T(-1570125773//767583705),
                                                  T(14637249//73103210),
                                                  T(91418989//255861235),
                                                  T(-62551961//1535167410),
                                                  T(-13500476//255861235), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(1587945773//3386062785),
                                                  T(-2863836//225737519),
                                                  T(-206937767//677212557),
                                                  T(-266570495//1354425114),
                                                  T(9588//589393),
                                                  T(202087559//6772125570), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(-17337249//144536540),
                                                  T(240029831//303526734),
                                                  T(-6595236//50587789),
                                                  T(-33094279//50587789),
                                                  T(82588241//607053468),
                                                  T(-11297731//505877890), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(-84398989//1008619010),
                                                  T(202934303//1210342812),
                                                  T(49602727//100861901),
                                                  T(-1973052//14408843),
                                                  T(-279245719//605171406),
                                                  T(61008503//2017238020),
                                                  T(-288//37621), )),
          DerivativeCoefficientRow{T,1,8}(SVector(T(48781961//4426143330),
                                                  T(1418484//147538111),
                                                  T(-218919665//885228666),
                                                  T(440626231//442614333),
                                                  T(-37430964//147538111),
                                                  T(-1360092253//2213071665),
                                                  T(6048//55031),
                                                  T(-576//55031), )),
          DerivativeCoefficientRow{T,1,9}(SVector(T(13716476//822302915),
                                                  T(-231357719//4933817490),
                                                  T(51815011//822302915),
                                                  T(-365711063//1644605830),
                                                  T(2312302333//2466908745),
                                                  T(-5765580//23494369),
                                                  T(-36288//61343),
                                                  T(6048//61343),
                                                  T(-576//61343), )),
      )
      upper_coef_plus = SVector( T(1),
                                 T(-3//10),
                                 T(1//15),
                                 T(-1//140), )
      central_coef_plus = T(-1//4)
      lower_coef_plus = SVector( T(-3//5),
                                 T(1//10),
                                 T(-1//105), )
      left_weights = SVector( T(19087//60480),
                              T(84199//60480),
                              T(18869//30240),
                              T(37621//30240),
                              T(55031//60480),
                              T(61343//60480), )
      right_weights = left_weights
      left_boundary_derivatives = Tuple{}()
      right_boundary_derivatives = left_boundary_derivatives

      left_boundary_minus = (
          DerivativeCoefficientRow{T,1,6}(SVector(T(-80930340//51172247),
                                                  T(1570125773//767583705),
                                                  T(-14637249//73103210),
                                                  T(-91418989//255861235),
                                                  T(62551961//1535167410),
                                                  T(13500476//255861235), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(-1587945773//3386062785),
                                                  T(2863836//225737519),
                                                  T(206937767//677212557),
                                                  T(266570495//1354425114),
                                                  T(-9588//589393),
                                                  T(-202087559//6772125570), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(17337249//144536540),
                                                  T(-240029831//303526734),
                                                  T(6595236//50587789),
                                                  T(33094279//50587789),
                                                  T(-82588241//607053468),
                                                  T(11297731//505877890), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(84398989//1008619010),
                                                  T(-202934303//1210342812),
                                                  T(-49602727//100861901),
                                                  T(1973052//14408843),
                                                  T(279245719//605171406),
                                                  T(-61008503//2017238020),
                                                  T(288//37621), )),
          DerivativeCoefficientRow{T,1,8}(SVector(T(-48781961//4426143330),
                                                  T(-1418484//147538111),
                                                  T(218919665//885228666),
                                                  T(-440626231//442614333),
                                                  T(37430964//147538111),
                                                  T(1360092253//2213071665),
                                                  T(-6048//55031),
                                                  T(576//55031), )),
          DerivativeCoefficientRow{T,1,9}(SVector(T(-13716476//822302915),
                                                  T(231357719//4933817490),
                                                  T(-51815011//822302915),
                                                  T(365711063//1644605830),
                                                  T(-2312302333//2466908745),
                                                  T(5765580//23494369),
                                                  T(36288//61343),
                                                  T(-6048//61343),
                                                  T(576//61343), )),
      )
      right_boundary_minus = (
          DerivativeCoefficientRow{T,1,6}(SVector(T(81216540//51172247),
                                                  T(-1587945773//767583705),
                                                  T(17337249//73103210),
                                                  T(84398989//255861235),
                                                  T(-48781961//1535167410),
                                                  T(-13716476//255861235), )),
          DerivativeCoefficientRow{T,1,6}(SVector(T(1570125773//3386062785),
                                                  T(2863836//225737519),
                                                  T(-240029831//677212557),
                                                  T(-202934303//1354425114),
                                                  T(-1418484//225737519),
                                                  T(231357719//6772125570), )),
          DerivativeCoefficientRow{T,1,7}(SVector(T(-14637249//144536540),
                                                  T(206937767//303526734),
                                                  T(6595236//50587789),
                                                  T(-49602727//50587789),
                                                  T(218919665//607053468),
                                                  T(-51815011//505877890),
                                                  T(216//18869), )),
          DerivativeCoefficientRow{T,1,8}(SVector(T(-91418989//1008619010),
                                                  T(266570495//1210342812),
                                                  T(33094279//100861901),
                                                  T(1973052//14408843),
                                                  T(-440626231//605171406),
                                                  T(365711063//2017238020),
                                                  T(-2016//37621),
                                                  T(216//37621), )),
          DerivativeCoefficientRow{T,1,9}(SVector(T(62551961//4426143330),
                                                  T(-9588//385217),
                                                  T(-82588241//885228666),
                                                  T(279245719//442614333),
                                                  T(37430964//147538111),
                                                  T(-2312302333//2213071665),
                                                  T(18144//55031),
                                                  T(-4032//55031),
                                                  T(432//55031), )),
          DerivativeCoefficientRow{T,1,10}(SVector(T(13500476//822302915),
                                                  T(-202087559//4933817490),
                                                  T(11297731//822302915),
                                                  T(-61008503//1644605830),
                                                  T(1360092253//2466908745),
                                                  T(5765580//23494369),
                                                  T(-60480//61343),
                                                  T(18144//61343),
                                                  T(-4032//61343),
                                                  T(432//61343), )),
      )
      upper_coef_minus     = .- lower_coef_plus
      central_coef_minus   = .- central_coef_plus
      lower_coef_minus     = .- upper_coef_plus

      left_boundary_central  = (left_boundary_plus  .+ left_boundary_minus)  ./ 2
      right_boundary_central = (right_boundary_plus .+ right_boundary_minus) ./ 2
      upper_coef_central     = widening_plus(upper_coef_plus, upper_coef_minus) / 2
      central_coef_central   = (central_coef_plus   + central_coef_minus) / 2
      lower_coef_central     = widening_plus(lower_coef_plus, lower_coef_minus) / 2

      if source.kind === :plus
        left_boundary  = left_boundary_plus
        right_boundary = right_boundary_plus
        upper_coef     = upper_coef_plus
        central_coef   = central_coef_plus
        lower_coef     = lower_coef_plus
      elseif source.kind === :minus
        left_boundary  = left_boundary_minus
        right_boundary = right_boundary_minus
        upper_coef     = upper_coef_minus
        central_coef   = central_coef_minus
        lower_coef     = lower_coef_minus
      elseif source.kind === :central
        left_boundary  = left_boundary_central
        right_boundary = right_boundary_central
        upper_coef     = upper_coef_central
        central_coef   = central_coef_central
        lower_coef     = lower_coef_central
      end
      DerivativeCoefficients(left_boundary, right_boundary,
                              left_boundary_derivatives, right_boundary_derivatives,
                              lower_coef, central_coef, upper_coef,
                              left_weights, right_weights, mode, 1, order, source)
    # elseif order == 6
    #   left_boundary_plus = (
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,7}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,8}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,9}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,10}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #   )
    #   right_boundary_plus = (
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,7}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,8}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #   )
    #   upper_coef_plus = SVector( T(),
    #                              T(),
    #                              T(), )
    #   central_coef_plus = T()
    #   lower_coef_plus = SVector( T(),
    #                              T(),
    #                              T(), )
    #   left_weights = SVector( T(),
    #                           T(),
    #                           T(),
    #                           T(),
    #                           T(),
    #                           T(), )
    #   right_weights = left_weights
    #   left_boundary_derivatives = Tuple{}()
    #   right_boundary_derivatives = left_boundary_derivatives

    #   left_boundary_minus = (
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,7}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,8}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #   )
    #   right_boundary_minus = (
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,6}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,7}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,8}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,9}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #       DerivativeCoefficientRow{T,1,10}(SVector(T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(),
    #                                               T(), )),
    #   )
    #   upper_coef_minus     = .- lower_coef_plus
    #   central_coef_minus   = .- central_coef_plus
    #   lower_coef_minus     = .- upper_coef_plus

    #   left_boundary_central  = (left_boundary_plus  .+ left_boundary_minus)  ./ 2
    #   right_boundary_central = (right_boundary_plus .+ right_boundary_minus) ./ 2
    #   upper_coef_central     = widening_plus(upper_coef_plus, upper_coef_minus) / 2
    #   central_coef_central   = (central_coef_plus   + central_coef_minus) / 2
    #   lower_coef_central     = widening_plus(lower_coef_plus, lower_coef_minus) / 2

    #   if source.kind === :plus
    #     left_boundary  = left_boundary_plus
    #     right_boundary = right_boundary_plus
    #     upper_coef     = upper_coef_plus
    #     central_coef   = central_coef_plus
    #     lower_coef     = lower_coef_plus
    #   elseif source.kind === :minus
    #     left_boundary  = left_boundary_minus
    #     right_boundary = right_boundary_minus
    #     upper_coef     = upper_coef_minus
    #     central_coef   = central_coef_minus
    #     lower_coef     = lower_coef_minus
    #   elseif source.kind === :central
    #     left_boundary  = left_boundary_central
    #     right_boundary = right_boundary_central
    #     upper_coef     = upper_coef_central
    #     central_coef   = central_coef_central
    #     lower_coef     = lower_coef_central
    #   end
    #   DerivativeCoefficients(left_boundary, right_boundary,
    #                           left_boundary_derivatives, right_boundary_derivatives,
    #                           lower_coef, central_coef, upper_coef,
    #                           left_weights, right_weights, mode, 1, order, source)
  else
        throw(ArgumentError("Order $order not implemented/derived."))
    end
end

# function second_derivative_coefficients(source::Mattsson2017, order::Int, T=Float64, mode=FastMode())
#     if order == 2
#         left_boundary = (
#             # d1
#             DerivativeCoefficientRow{T,1,3}(SVector(T(1),
#                                                     T(-2),
#                                                     T(1) )),
#         )
#         right_boundary = left_boundary
#         upper_coef = SVector(T(1))
#         central_coef = T(-2)
#         lower_coef = upper_coef
#         left_weights = SVector(T(1//2))
#         right_weights = left_weights
#         left_boundary_derivatives = (
#             DerivativeCoefficientRow{T,1,3}(SVector(T(-3//2),
#                                                     T(2),
#                                                     T(-1//2) )),
#         )
#         right_boundary_derivatives = (-left_boundary_derivatives[1], )

#         DerivativeCoefficients(left_boundary, right_boundary,
#                                 left_boundary_derivatives, right_boundary_derivatives,
#                                 lower_coef, central_coef, upper_coef,
#                                 left_weights, right_weights, mode, 2, order, source)
#     else
#         throw(ArgumentError("Order $order not implemented/derived."))
#     end
# end


function test_jump_soc(solver)
    @testset "Second-order Cone Programming" begin
        @testset "SOC1" begin
            m = Model(solver=solver)
            @variable(m, x)
            @variable(m, y)
            @variable(m, t >= 0)
            @objective(m, Min, t)
            @constraint(m, x + y >= 1)
            @constraint(m, [t,x,y] in MOI.SecondOrderCone(3))

            JuMP.solve(m)

            @test JuMP.isattached(m)
            @test JuMP.hasvariableresult(m)
            @test JuMP.terminationstatus(m) == MOI.Success
            @test JuMP.primalstatus(m) == MOI.FeasiblePoint

            @test JuMP.objectivevalue(m) ≈ sqrt(1/2) atol=1e-6

            @test JuMP.resultvalue.([x,y,t]) ≈ [0.5,0.5,sqrt(1/2)] atol=1e-3

        end

        @testset "RotatedSOC1" begin
            m = Model(solver=solver)

            @variable(m, x[1:5] >= 0)
            @variable(m, 0 <= u <= 5)
            @variable(m, v)
            @variable(m, t1 == 1)
            @variable(m, t2 == 1)

            @objective(m, Max, v)

            @constraint(m, [t1/sqrt(2),t2/sqrt(2),x...] in MOI.RotatedSecondOrderCone(7))
            @constraint(m, [x[1]/sqrt(2), u/sqrt(2), v] in MOI.RotatedSecondOrderCone(3))

            JuMP.solve(m)

            @test JuMP.isattached(m)
            @test JuMP.hasvariableresult(m)
            @test JuMP.terminationstatus(m) == MOI.Success
            @test JuMP.primalstatus(m) == MOI.FeasiblePoint

            @test JuMP.resultvalue.(x) ≈ [1,0,0,0,0] atol=1e-2
            @test JuMP.resultvalue(u) ≈ 5 atol=1e-4
            @test JuMP.resultvalue(v) ≈ sqrt(5) atol=1e-6
        end
    end
end


#=
Created on Saturday 3 August 2019
Last update: -

@author: Michiel Stock
michielfmstock@gmail.com

Vec trick: multiplying vectors with Kronecker systems.
=#

import Base.ReshapedArray

vecmulR!(X,N,V,M) = X .= N * (V * transpose(M))
vecmulL!(X,N,V,M) = X .= (N * V) * transpose(M)
vecmul!(X,N,V,M) = X .= N * V * transpose(M)

vectrick_reshape(v::AbstractVector, d::Int, b::Int) = reshape(v, d, b)
function vectrick_reshape(v::ReshapedArray{<:Any, 1}, d::Int, b::Int)
    return size(v.parent) == (d, b) ? v.parent : reshape(v, d, b)
end

"""
    mul!(x::AbstractVector, K::AbstractKroneckerProduct, v::AbstractVector)

Calculates the vector-matrix multiplication `K * v` and stores the result in
`x`, overwriting its existing value.
"""
function mul!(x::AbstractVector, K::AbstractKroneckerProduct, v::AbstractVector)
    M, N = getmatrices(K)
    a, b = size(M)
    c, d = size(N)
    e = length(v)
    f = length(x)
    f == a * c || throw(DimensionMismatch(
        "Dimension missmatch between kronecker system and result placeholder"))
    e == b * d || throw(DimensionMismatch(
        "Dimension missmatch between kronecker system and vector"))
    V = vectrick_reshape(v, d, b)
    X = vectrick_reshape(x, c, a)
    if b * c * (a + d) < a * d * (b + c)
        mul!(X, N, V * transpose(M))
    else
        mul!(X, N * V, transpose(M))
    end
    return x
end

function Base.:*(K::AbstractKroneckerProduct, v::AbstractVector)
    return mul!(Vector{promote_type(eltype(v), eltype(K))}(undef, first(size(K))), K, v)
end

reshape_cols(x, sizes...) = reshape(x, sizes...)
reshape_rows(x, sizes...) = transpose(reshape(collect(transpose(x)), reverse(sizes)...))

kron_id_a(a, x) = reshape_cols(a * reshape_cols(x, size(a)[2], :), :, size(x)[2])
kron_a_id(a, x) = reshape_rows(a * reshape_rows(x, size(a)[2], :), :, size(x)[2])

function kron_a_b(A, B, x)
    a, b = size(A)
    c, d = size(B)
    if b * c * (a + d) < a * d * (b + c)
        return kron_a_id(A, kron_id_a(B, x))
    else
        return kron_id_a(B, kron_a_id(A, x))
    end
end

function Base.:*(A::AbstractKroneckerProduct, B::StridedMatrix)
    size(A, 2) != size(B, 1) && throw(DimensionMismatch("size(A, 2) != size(B, 1)"))
    return kron_a_b(getmatrices(A)..., B)
end

function Base.:*(A::KroneckerProduct{<:Any, <:Eye, <:AbstractMatrix}, B::StridedMatrix)
    size(A, 2) != size(B, 1) && throw(DimensionMismatch("size(A, 2) != size(B, 1)"))
    return kron_id_a(A.B, B)
end

function Base.:*(A::KroneckerProduct{<:Any, <:AbstractMatrix, <:Eye}, B::StridedMatrix)
    size(A, 2) != size(B, 1) && throw(DimensionMismatch("size(A, 2) != size(B, 1)"))
    return kron_a_id(A.A, B)
end

function Base.sum(K::AbstractKroneckerProduct; dims::Union{Nothing,Int}=nothing)
    A, B = getmatrices(K)
    if dims == nothing
        s = zero(eltype(K))
        sumB = sum(B)
        return sum(sumB * A)
    else
        return kronecker(sum(A, dims=dims), sum(B, dims=dims))
    end
end


using Test
using LinearAlgebra
using QBase
using Base.Iterators
using Suppressor
using CVChannel

@testset "./src/see-saw_optimization.jl" begin

id_kraus_ops = [[1 0;0 1]]
bit_flip_kraus_ops = [[1 0;0 1]*sqrt(0.7), [0 1;1 0]*sqrt(0.3)]

x_states = [[0.5 0.5;0.5 0.5], [0.5 -0.5;-0.5 0.5]]
z_states = [[1 0;0 0],[0 0;0 1]]
trine_states = trine_qubit_states()

@testset "fixedStateCV" begin
    @testset "identity chanel" begin
        z_cv, z_povm = fixedStateCV(z_states, id_kraus_ops)

        @test z_cv ≈ 2 atol=1e-6
        @test is_povm(z_povm, atol=1e-6)
        @test isapprox(z_povm, z_states, atol=1e-6)

        trine_cv, trine_povm = fixedStateCV(trine_states, id_kraus_ops)

        @test trine_cv ≈ 2 atol=1e-6
        @test is_povm(trine_povm, atol=1e-6)
        @test isapprox(trine_povm, 2/3*trine_states, atol=1e-6)
    end

    @testset "bit-flip channel" begin
        x_cv, x_povm = fixedStateCV(x_states, bit_flip_kraus_ops)

        @test x_cv ≈ 2 atol=1e-6
        @test is_povm(x_povm, atol=1e-6)
        @test isapprox(x_povm, x_states, atol=1e-6)

        z_cv, z_povm = fixedStateCV(z_states, bit_flip_kraus_ops)

        @test z_cv ≈ 1.4 atol=1e-6
        @test is_povm(z_povm)
        @test isapprox(z_povm, z_states, atol=1e-6)
    end
end

@testset "fixedMeasurementCV" begin
    @testset "identity channel" begin
        z_cv, opt_z_states = fixedMeasurementCV(z_states, id_kraus_ops)

        @test z_cv ≈ 2 atol=1e-6
        @test all(ρ -> is_density_matrix(ρ, atol=1e-6), opt_z_states)
        @test isapprox(opt_z_states, z_states, atol=1e-6)

        trine_povm = 2/3 * trine_states
        trine_cv, opt_trine_states = fixedMeasurementCV(trine_povm, id_kraus_ops)

        @test trine_cv ≈ 2 atol=1e-6
        @test all(ρ -> is_density_matrix(ρ, atol=1e-6), opt_trine_states)
        @test isapprox(opt_trine_states, trine_states, atol=1e-6)
    end

    @testset "bit-flip channel" begin
        x_cv, opt_x_states = fixedMeasurementCV(x_states, bit_flip_kraus_ops)

        @test x_cv ≈ 2 atol=1e-6
        @test all(ρ -> is_density_matrix(ρ), opt_x_states)
        @test isapprox(opt_x_states, x_states, atol=1e-6)

        z_cv, opt_z_states = fixedMeasurementCV(z_states, bit_flip_kraus_ops)

        @test z_cv ≈ 1.4 atol=1e-6
        @test all(ρ -> is_density_matrix(ρ, atol=1e-6), opt_z_states)
        @test isapprox(opt_z_states, z_states, atol=1e-6)
    end
end

@testset "seesawCV" begin
    d = 3
    anti_sym_choi = wernerState(d, 0) * d
    anti_sym_kraus_ops = map(i -> reshape(anti_sym_choi[:,i], (d,d)), 1:d^2)
    par_anti_sym_kraus_ops = collect(flatten(
        map(k1 -> map(k2 -> kron(k1,k2), anti_sym_kraus_ops), anti_sym_kraus_ops)
    ))

    # verify kraus operators are trace-preserving
    @test sum(k -> k' * k, anti_sym_kraus_ops) ≈ I
    @test sum(k -> k' * k, par_anti_sym_kraus_ops) ≈ I
    @test choi(anti_sym_kraus_ops) ≈ anti_sym_choi

    @testset "singular qutrit anti-symmetric Werner-Holevo channel" begin
        init_states = [
            [1/2 0 0;0 1/3 0;0 0 1/6],[1/6 0 0;0 1/2 0;0 0 1/3],[1/3 0 0;0 1/6 0;0 0 1/2]
        ]

        num_steps = 3
        max_cv_tuple, cvs, opt_ensembles, opt_povms = seesawCV(
            init_states, anti_sym_kraus_ops, num_steps
        )

        @test max_cv_tuple[1] ≈ 1.5 atol=1e-6
        @test all(ρ -> is_density_matrix(ρ), max_cv_tuple[2])
        @test is_povm(max_cv_tuple[3])
        @test length(max_cv_tuple) == 3

        @test length(cvs) == 2 * num_steps
        @test max(cvs...) ≈ 1.5 atol=1e-6
        @test min(cvs...) ≈ 1.25 atol=1e-5

        @test length(opt_ensembles) == num_steps
        @test all(states -> all(ρ -> is_density_matrix(ρ, atol=1e-6), states), opt_ensembles)

        @test length(opt_povms) == num_steps
        @test all(Π -> is_povm(Π, atol=1e-6), opt_povms)
    end

    @testset "parallel qutrit anti-symmetric Werner-Holevo channel" begin
        z_basis_states = computational_basis_states(9)
        max_mix_state = Matrix{Float64}(I, (9,9))/9
        init_states = map(ρ -> ρ/16 + 15/16 * max_mix_state, z_basis_states)

        num_steps = 2
        max_cv_tuple, cvs, opt_ensembles, opt_povms = seesawCV(
            init_states, par_anti_sym_kraus_ops, num_steps
        )

        @test max_cv_tuple[1] ≈ 2.25 atol=1e-5
        @test all(ρ -> is_density_matrix(ρ, atol=1e-5), max_cv_tuple[2])
        @test is_povm(max_cv_tuple[3], atol=1e-5)
        @test length(max_cv_tuple) == 3

        @test length(cvs) == 2 * num_steps
        @test max(cvs...) ≈ 2.25 atol=1e-5
        @test min(cvs...) ≈ 1.078 atol=1e-3

        @test length(opt_ensembles) == num_steps
        @test all(states -> all(ρ -> is_density_matrix(ρ, atol=1e-5), states), opt_ensembles)

        @test length(opt_povms) == num_steps
        @test all(Π -> is_povm(Π, atol=1e-5), opt_povms)
    end

    @testset "verbose printout" begin
        init_states = [
            [1/2 0 0;0 1/3 0;0 0 1/6],[1/6 0 0;0 1/2 0;0 0 1/3],[1/3 0 0;0 1/6 0;0 0 1/2]
        ]

        num_steps = 1
        empty_printout = @capture_out seesawCV(
            init_states, anti_sym_kraus_ops, num_steps
        )
        @test empty_printout == ""

        printout = @capture_out seesawCV(
            init_states, anti_sym_kraus_ops, num_steps, verbose=true
        )

        regex_match = r"^i = 1\nfixed_state_cv = \d\.\d+\nfixed_povm_cv = \d\.\d+\nmax_cv = \d\.\d+\n$"
        @test occursin(regex_match, printout)
    end
end

end


using FourierCore.Grid

@testset "Constructing the grid" begin
    grid = FourierGrid(4, Circle())
    k⃗ = grid.wavenumbers[]
    @test k⃗[1] == 0.0
    @test k⃗[2] == 1.0
    @test k⃗[3] == 0.0
    @test k⃗[4] == -1.0
    @test length(k⃗) == 4
    @test grid[1][1] == 0.0

    grid = FourierGrid(4, Circle(1π))
    k⃗ = grid.wavenumbers[]
    @test k⃗[1] == 0.0
    @test k⃗[2] == 2.0
    @test k⃗[3] == 0.0
    @test k⃗[4] == -2.0
    @test length(k⃗) == 4

    grid = FourierGrid((8, 8), Circle()^2)
    (; nodes, wavenumbers) = grid
    @test length(nodes) == 2
    @test length(wavenumbers) == 2
    @test all(wavenumbers[1][:] .== wavenumbers[2][:])
    @test all(LinearAlgebra.size(wavenumbers[1]) .== (8, 1))
    @test all(LinearAlgebra.size(wavenumbers[2]) .== (1, 8))

    grid = FourierGrid((3, 5, 7), Circle()^3)
    (; nodes, wavenumbers) = grid
    @test length(nodes) == 3
    @test length(wavenumbers) == 3
    @test all(LinearAlgebra.size(wavenumbers[1]) .== (3, 1, 1))
    @test all(LinearAlgebra.size(wavenumbers[2]) .== (1, 5, 1))
    @test all(LinearAlgebra.size(wavenumbers[3]) .== (1, 1, 7))

end


"""
    IndependentMOKernel(k::Kernel)

Kernel for multiple independent outputs with kernel `k` each.

# Definition

For inputs ``x, x'`` and output dimensions ``p, p'``, the kernel ``\\widetilde{k}``
for independent outputs with kernel ``k`` each is defined as
```math
\\widetilde{k}\\big((x, p), (x', p')\\big) = \\begin{cases}
    k(x, x') & \\text{if } p = p', \\\\
    0 & \\text{otherwise}.
\\end{cases}
```
Mathematically, it is equivalent to a matrix-valued kernel defined as
```math
\\widetilde{K}(x, x') = \\mathrm{diag}\\big(k(x, x'), \\ldots, k(x, x')\\big) \\in \\mathbb{R}^{m \\times m},
```
where ``m`` is the number of outputs.
"""
struct IndependentMOKernel{Tkernel<:Kernel} <: MOKernel
    kernel::Tkernel
end

function (κ::IndependentMOKernel)((x, px)::Tuple{Any,Int}, (y, py)::Tuple{Any,Int})
    return κ.kernel(x, y) * (px == py)
end

_mo_output_covariance(k::IndependentMOKernel, out_dim) = Eye{Bool}(out_dim)

function kernelmatrix(
    k::IndependentMOKernel, x::MOI, y::MOI
) where {MOI<:IsotopicMOInputsUnion}
    x.out_dim == y.out_dim ||
        throw(DimensionMismatch("`x` and `y` must have the same `out_dim`"))
    Kfeatures = kernelmatrix(k.kernel, x.x, y.x)
    Koutputs = _mo_output_covariance(k, x.out_dim)
    return _kernelmatrix_kron_helper(MOI, Kfeatures, Koutputs)
end

if VERSION >= v"1.6"
    function kernelmatrix!(
        K::AbstractMatrix, k::IndependentMOKernel, x::MOI, y::MOI
    ) where {MOI<:IsotopicMOInputsUnion}
        x.out_dim == y.out_dim ||
            throw(DimensionMismatch("`x` and `y` must have the same `out_dim`"))
        Kfeatures = kernelmatrix(k.kernel, x.x, y.x)
        Koutputs = _mo_output_covariance(k, x.out_dim)
        return _kernelmatrix_kron_helper!(K, MOI, Kfeatures, Koutputs)
    end
end

function Base.show(io::IO, k::IndependentMOKernel)
    return print(io, string("Independent Multi-Output Kernel\n\t", string(k.kernel)))
end


import Base.sqrt
immutable Sqrt <: SingleArg
	x
end
sqrt(a)=Sqrt(a)
sqrt(eq::Equation)=Equation(simplify(sqrt(eq.lhs)),simplify(sqrt(eq.rhs)))
function print(io::IO,s::Sqrt)
	print(io,"√(")
	print(io,s.x)
	print(io,')')
end
function simplify(sq::Sqrt)
	sq=Sqrt(simplify(sq.x))
	if isa(sq.x,Number)
		if isreal(sq.x)&&sq.x<0
			return sqrt(complex(sq.x))
		else
			return sqrt(sq.x)
		end
	elseif isa(sq.x,Expression)
		ap=terms(sq.x)
		if length(ap)==1
			facs=ap[1]
			nfacs=length(facs)
			if iseven(nfacs)
				for p in permutations(facs)
					if p[1:Int(nfacs/2)]==p[Int(nfacs/2+1):nfacs]
						return simplify(expression(p[1:Int(nfacs/2)]))
					end
				end
			end
		end
	elseif isa(sq.x,Pow)
		return Pow(sq.x.x,sq.x.y/2)
	end
	return sq
end
function matches(eq::Equation,t::Type{Sqrt})
	lhs=deepcopy(eq.lhs)
	rhs=deepcopy(eq.rhs)
	m=Equation[]
	push!(m,Equation(Sqrt(lhs),Sqrt(rhs)))
	push!(m,Equation(Sqrt(lhs),-Sqrt(rhs)))
	return simplify!(m)
end
function matches(s::Sqrt,ex::Expression)
	ex=simplify(ex)
	if isa(ex,Sqrt)
		return matches(s.x,ex.x)
	end
	[]
end
matches(::Term,::Sqrt)=[]
matches(::N, ::Sqrt)=[]
function unsqrt!(term::Term)
	inds=indsin(term,Sqrt)
	li=length(inds)
	if li>1
		for i in 1:li-1
			for j in 2:li
				if i!=j && term[inds[i]].x==term[inds[j]].x
					push!(term,term[inds[i]].x)
					deleteat!(term,[inds[i],inds[j]])
					return term
				end
			end
		end
	end
	return term
end


using Test, PolynomialBases

if !haskey(ENV, "JULIA_PKGEVAL") # sympy is not install on https://github.com/JuliaComputing/NewPkgEval.jl
  import SymPy
  x = SymPy.symbols("x")

  @test 0 == SymPy.simplify( hermite(x, 0) - 1 )
  @test 0 == SymPy.simplify( hermite(x, 1) - ( 2x ) )
  @test 0 == SymPy.simplify( hermite(x, 2) - ( 4x^2 - 2 ) )
  @test 0 == SymPy.simplify( hermite(x, 3) - ( 8x^3 - 12x ) )
  @test 0 == SymPy.simplify( hermite(x, 4) - ( 16x^4 - 48x^2 + 12 ) )

  @test_skip hermite(x, 4)
end
@inferred hermite(10., 4)


"""
This is the IRNN model, a recurrent net with relu activations whose
recurrent weights are initialized with the identity matrix.  From: Le,
Q. V., Jaitly, N., & Hinton, G. E. (2015). A Simple Way to Initialize
Recurrent Networks of Rectified Linear Units. arXiv preprint
arXiv:1504.00941.
"""

irnn(;n=1,std=0.01) = quote
    x1 = input()
    w1 = par($n,0; init=Gaussian(0,$std))
    x2 = dot(w1,x1)
    w6 = par($n,0; init=Identity())
    x3 = dot(w6,x6)
    x4 = add(x2,x3)
    w4 = par(0; init=Constant(0))
    x5 = add(w4,x4)
    x6 = relu(x5)
end

# IRNN(n; std=0.01)=Net(Mmul(n; init=randn!, initp=(0,std)),
#                       (Mmul(n; init=eye!), 5),
#                       Add2(), Bias(), Relu())

# eye!(a)=copy!(a, eye(eltype(a), size(a)...)) #


import LinearAlgebra: checksquare, BlasInt
import Printf

## Todo
# 1. Add method for partial sweep cut
# 2. Add a "SymmetricMatrix" wrapper to avoid
#    constant symmetry checks.
# 3. Avoid creating the Laplacian matrix directly

"""
`_symeigs_smallest_arpack`
---
Compute eigenvalues and vectors using direct calls to the ARPACK wrappers
to get type-stability. This function works for symmetric matrices.

This function works on Float32 and Float64-valued sparse matrices.
It returns the smallest set of eigenvalues and vectors.

It only works on matrices with more than 21 rows and columns.
(Use a dense eigensolver for smaller problems.)

Functions
---------
- `(evals,evecs) = _symeigs_smallest_arpack(A::SparseMatrixCSC{V,Int},
                        nev::Int,tol::V,maxiter::Int, v0::Vector{V})`
Inputs
------
- `A`: the sparse matrix, must be symmetric
- `nev`: the number of eigenvectors requested
- `tol`: the relative tolerance of the eigenvalue computation
- `maxiter`: the maximum number of restarts
- `v0`: the initial vector for the Lanczos process

Example
-------
This is an internal function.
"""
function _symeigs_smallest_arpack(
            A::SparseMatrixCSC{V,Int},nev::Int,tol::V,maxiter::Int,
            v0::Vector{V}) where V

    n::Int = checksquare(A) # get the size
    @assert n >= 21

    # setup options
    mode = 1
    sym = true
    iscmplx = false
    bmat = String("I")
    ncv = min(max(2*nev,20),n-1)

    whichstr = String("SA")
    ritzvec = true
    sigma = 0.

    #TOL = Array{V}(undef,1)
    #TOL[1] = tol
    TOL = Ref(tol)
    lworkl = ncv*(ncv + 8)
    v = Array{V}(undef, n, ncv)
    workd = Array{V}(undef, 3*n)
    workl = Array{V}(undef, lworkl)
    resid = Array{V}(undef, n)

    resid[:] = v0[:]

    info = zeros(BlasInt, 1)
    info[1] = 1

    iparam = zeros(BlasInt, 11)
    ipntr = zeros(BlasInt, 11)
    ido = zeros(BlasInt, 1)

    iparam[1] = BlasInt(1)
    iparam[3] = BlasInt(maxiter)
    iparam[7] = BlasInt(mode)

    # this is a helpful indexing vector
    zernm1 = 0:(n-1)

    while true
        # This is the reverse communication step that ARPACK does
        # we need to extract the desired vector and multiply it by A
        # unless the code says to stop
        Arpack.saupd(
            ido, bmat, n, whichstr, nev, TOL, resid, ncv, v, n,
            iparam, ipntr, workd, workl, lworkl, info)

        load_idx = ipntr[1] .+ zernm1
        store_idx = ipntr[2] .+ zernm1

        x = workd[load_idx]

        if ido[1] == 1
            workd[store_idx] = A*x
        elseif ido[1] == 99
            break
        else
            error("unexpected ARPACK behavior")
        end
    end

    # Once the process terminates, we need to extract the
    # eigenvectors.

    # calls to eupd
    howmny = String("A")
    select = Array{BlasInt}(undef, ncv)

    d = Array{V}(undef, nev)
    sigmar = ones(V,1)*sigma
    ldv = n
    Arpack.seupd(ritzvec, howmny, select, d, v, ldv, sigmar,
        bmat, n, whichstr, nev, TOL, resid, ncv, v, ldv,
        iparam, ipntr, workd, workl, lworkl, info)
    if info[1] != 0
        error("unexpected ARPACK exception")
    end

    # Now we want to return them in sorted order (smallest first)
    p = sortperm(d)

    d = d[p]
    vectors = v[1:n,p]

    return (d,vectors,iparam[5])
end

"""
`fiedler_vector`
----------------

Compute the Fiedler vector associated with the normalized Laplacian
of the graph with adjacency matrix A.

This function works with Float32 and Float64-valued sparse
matrices and vectors.

It requires a symmetric input matrix representing
the adjacency matrix of an undirected graph. If the input
is a disconnected network then the result may not

The return vector is signed so that the number of positive
entries is at least the number of negative entries. This will
always give a unique, deterministic output
in the case of a non-repeated second eigenvalue.

Functions
---------
- `(v,lam2) = fiedler_vector(A::SparseMatrixCSC{V,Int})`
- `(v,lam2) = fiedler_vector(A::MatrixNetwork{V,Int})`

Inputs
------
- `A`: the adjacency matrix
- Optional Keywoard Inputs
  - `checksym`: ensure the matrix is symmetric
  - `tol`: the residual tolerance in the eigenvalue, eigenvector estimate.
    (This is an absolute error)
  - `maxiter`: the maximum iteration for ARPACK
  - `nev`: the number of eigenvectors estimated by ARPACK
  - `dense`: the threshold for a dense (LAPACK) computation
    instead of a sparse (ARPACK) computation. If the size of
    the matrix is less than `dense` then maxiter and nev are
    ignored and LAPACK computes all the eigenvalues.

Returns
-------
- `v`: The Fiedler vector as an array
- `lam2`: the eigenvalue itself

Example
-------
~~~
# construct an n-node path graph
n = 25
A = sparse(1:n-1,2:n,1.,n,n)
A = A + A'
(v,lam2) = fiedler_vector(A) # returns a cosine-like fiedler vector
# using UnicodePlots; lineplot(v)
~~~
"""
function fiedler_vector(A::SparseMatrixCSC{V,Int};
    tol=1e-12,maxiter=300,dense=96,nev=2,checksym=true) where V
    n = checksquare(A)
    if checksym
        if !issymmetric(A)
            throw(ArgumentError("The input matrix must be symmetric."))
        end
    end

    d = vec(sum(A,dims=1))
    d = sqrt.(d)

    if n == 1
        X = zeros(V,1,2)
        lam2 = 0.
    elseif n <= dense
        ai,aj,av = findnz(A)
        L = sparse(ai,aj,-av./((d[ai].*d[aj])),n,n) # applied sqrt above
        L = Matrix(L) + 2I
        F = eigen!(Symmetric(L))
        lam2 = F.values[2]-1.
        X = F.vectors
    else
        ai,aj,av = findnz(A)
        L = sparse(ai,aj,-av./((d[ai].*d[aj])),n,n) # applied sqrt above
        L = L + sparse(2.0I,n,n)

        (lams,X,nconv) = _symeigs_smallest_arpack(L,nev,tol,maxiter,d)
        lam2 = lams[2]-1.
    end
    x1err = norm(X[:,1]*sign(X[1,1]) - d/norm(d))
    if x1err >= sqrt(tol)
        s = @sprintf("""
        the null-space vector associated with the normalized Laplacian
        was computed inaccurately (diff=%.3e); the Fiedler vector is
        probably wrong or the graph is disconnected""",x1err)
        @warn s
    end

    x = vec(X[:,2])
    if n > 1
        x = x./d # applied sqrt above
    end

    # flip the sign if the number of pos. entries is less than the num of neg. entries
    nneg = sum(x .< 0.)
    if n-nneg < nneg
      x = -x;
    end

    return (x,lam2)
end

fiedler_vector(A::MatrixNetwork{T};kwargs...) where {T} =
    fiedler_vector(sparse_transpose(A);kwargs...) # sparse_transpose converts directly

"""
RankedArray
-----------

This is a data-type that functions as a union between
a sparse and dense array. It allows us to check if
an element is not in the array (i.e. it's too long)
by adding a getindex function to the array type.

It's called a RankedArray because the idea is that
the elements of the array are the ranks of the
nodes in a sorted vector.

This should not be used externally.

Example
-------
v = rand(10)
r = RankedArray(sortperm(v))
haskey(r,11) # returns false
haskey(r,1) # returns true
haskey(r,0) # returns false
"""
mutable struct RankedArray{S}
    data::S
end

import Base: getindex, haskey

haskey(A::RankedArray{S}, x::Int) where {S} = x >= 1 && x <= length(A.data)
getindex(A::RankedArray{S}, i) where {S} = A.data[i]

"""
SweepcutProfile
---------------

This type is the result of a sweepcut operation. It stores
a number of vectors associated with a sweep cut including
the cut and volume associated with each.

Methods
-------
- `bestset(P::SweepcutProfile)` return the best set identified
  in the sweepcut. This is usually what you want.

Example
-------
See `sweepcut`

See also
--------
* `spectral_cut`

"""
struct SweepcutProfile{V,F}
    p::Vector{Int}
    conductance::Vector{F}
    cut::Vector{V}
    volume::Vector{V}
    total_volume::V
    total_nodes::Int

    function SweepcutProfile{V,F}(p::Vector{Int},nnodes::Int,totalvol::V) where {V,F}
        n = length(p)
        new{V,F}(p,Array{F}(undef,n-1),Array{V}(undef,n-1),Array{V}(undef,n-1),totalvol,nnodes)
    end
end

"""
`sweepcut`
--------

A sweepcut is an operation that takes an order to
the vertices of a graph and evaluates the cut and
volume of every partition induced by a prefix of
that ordering. That is, if the order of vertices
is

    v1, v2, v3, ...

the the sweep cut evaluates

    cut({v1}), vol({v1})
    cut({v1,v2}), vol({v1,v2})
    cut({v1,v2,v3}), vol({v1,v2,v3})
    ...

where cut is the total edge weight that connects
vertices in S to the graph. And vol is the
total edge weight connecting

Functions
---------
- `profile = sweepcut(A,x::Vector{T})`
  A standard sweepcut call that will get a sweepcut
  for a vector where x is sorted to get the order
  of the vertices (decreasing order).
  This is useful if you have a vector that should
  indicate good cuts in the graph, such as
  the Fiedler vector.

- `profile = sweepcut(A,p,r,totalvol,maxvol)`
  the in-depth call that all others are converted into.
  We strongly recommend against calling this yourself
  unless you understand the sweepcut code.

Inputs
------
- `A`: the sparse matrix representing the symmetric graph
- `x`: A vector scoring each vertex. This will be sorted and
       turned into one of the other inputs.
- `p`: a partial permutation vector over the vertices of the graph
        This vector needs to list every vertex at most once.
        It could be shorter and need not list every vertex.
- `r`: A general data structure that gives
        the rank of an item in the permutation vector
        p should be sorted in decreasing order so that
        i < j means that r[p[i]] < r[p[j]]
- `totalvol`: the total volume of the graph
- `maxvol`: the maximum volume of any set considered

Returns
-------
A `SweepcutProfile` type with all the information computed
in the sweepcut. Most likely, you want the result `bestset`
as indicated below.

Example
-------
~~~~
A = load_matrix_network("minnesota")
v = fiedler_vector(A)[1] # get the
p = sweepcut(A,v)
S = bestset(p) # get the bestset from the profile
T = spectral_cut(A).set # should give you the same set
# using UnicodePlots; lineplot(p.conductance) # show the conductance
~~~~
"""
function sweepcut(A::SparseMatrixCSC{V,Int}, p::Vector{Int}, r, totalvol::V, maxvol::T) where {V,T}

    n = checksquare(A)
    nlast = length(p)

    if n < nlast
        throw(DimensionMismatch(
            "the permutation vector is too long and should have fewer entries than the graph"))
    end

    F = typeof(one(V)/one(V)) # find the conductance type
    output = SweepcutProfile{V,F}(p,n,totalvol)

    cut = zero(V)
    vol = zero(V)
    colptr = A.colptr
    rowval = rowvals(A)
    nzval = A.nzval

    for (i,v) in enumerate(p)
        deltain = zero(V) # V might be pos. only... so delay subtraction
        deg = zero(V)
        rankv = getindex(r,v)
        for nzi in colptr[v]:(colptr[v+1] - 1)
            nbr = rowval[nzi]
            deg += nzval[nzi]
            if haskey(r,nbr) # our neighbor is ranked
                if getindex(r,nbr) <= rankv # nbr is ranked lower, decrease cut
                    deltain += v == nbr ? nzval[nzi] : 2*nzval[nzi]
                end
            end
        end
        cut += deg
        cut -= deltain
        vol += deg

        # don't assign final values because they are unhelpful
        if i==nlast
            break
        end

        cond = cut/min(vol,totalvol-vol)
        output.conductance[i] = cond
        output.cut[i] = cut
        output.volume[i] = vol
    end

    if nlast == size(A,1)
        @assert abs(cut) <= 1e-12*totalvol
    end
    return output
end

function sweepcut(A::SparseMatrixCSC{V,Int}, x::Vector{T}, vol::V) where {V,T}
    p = sortperm(x,rev=true)
    ranks = Array{Int}(undef, length(x))
    for (i,v) in enumerate(p)
        ranks[v] = i
    end
    r = RankedArray(ranks)
    return sweepcut(A, p, r, vol, Inf)
end

sweepcut(A::SparseMatrixCSC{V,Int}, x::Vector{T}) where {V,T} =
    sweepcut(A, x, sum(A))

function bestset(prof::SweepcutProfile{V,F}) where {V,F}
    nnodes = 0
    if !isempty(prof.conductance)
        bsetind = argmin(prof.conductance)
        bsetvol = prof.volume[bsetind]
        nnodes = bsetind
        if bsetvol > prof.total_volume - bsetvol
            # we want the complement set
            nnodes = prof.total_nodes - bsetind
        end
    end

    bset = Vector{Int}(undef,nnodes)
    if isempty(prof.conductance)
    elseif bsetvol > prof.total_volume - bsetvol
        # ick, we need the complement
        bset[:] = collect(setdiff(BitSet(Int(1):Int(prof.total_nodes)),prof.p[1:bsetind]))
    else
        # easy
        bset[:] = prof.p[1:bsetind]
    end
    return bset
end

"""
`SpectralCut`
-------------
The return type from the `spectral_cut`

Fields
------
- `set`: the small side of the spectral cut
- `A`: the network of the largest strong component of the network
- `lam2`: the eigenvalue of the normalized Laplacian
- `x`: the Fiedler vector for spectral partitioning
- `sweepcut_profile`: the sweepcut profile output
- `comps`: the output from the strong_components function
    check comps.number to get the number of components
- `largest_component`: the index of the largest strong component

The most useful outputs are `set` and `lam2`; the others are provided
for experts who wish to use some of the diagonstics provided.
"""
struct SpectralCut{V,F}
    set::Vector{Int}
    A::SparseMatrixCSC{V,Int}
    lam2::F
    x::Vector{Float64}
    sweepcut_profile::SweepcutProfile{V,F}
    comps::MatrixNetworks.Strong_components_output
    largest_component::Int
end

function show(io::IO,obj::SpectralCut{V,F}) where {V,F}
    println(io,"Spectral cut on adjacency matrix with $(size(obj.A,1)) nodes and $(nnz(obj.A)) non-zeros")
    if obj.comps.number > 1
        println(io, "    formed from the largest connected component of the input matrix")
    end
    println(io,"  conductance = $(minimum(obj.sweepcut_profile.conductance))")
    println(io,"  size = $(length(obj.set))")
end

"""
`spectral_cut`
--------------
`spectral_cut` will produce a spectral cut partition of the graph into
two pieces.

Special cases
* if your graph is disconnected, then we will partition it into the
largest connected component (chosen arbitrary if there are multiple)
and produce a cut of just the largest connected component.
* if your graph is a single node, we will partition it into the empty
cut.

Output
------
The output has type SpectralCut
We always return the smaller side of the cut in terms of total volume.

Inputs
------
- `A`: The sparse matrix or martrix network that you want to
  find the spectral cut for.
- `checksym`: Should we check symmetry?
  Don't set this to false unless you have checked symmetry
  in another call. *This may go away in future interfaces*
- `ccwarn`: Turn off the warning for disconnected graphs.
  This useful in larger subroutines where this is handled
  through another mechanism.
"""
function spectral_cut(A::SparseMatrixCSC{V,Int},checksym::Bool,ccwarn::Bool) where V
    n = checksquare(A)
    if checksym
        if !issymmetric(A)
            throw(ArgumentError("The input matrix must be symmetric."))
        end
    end

    # test for non-negativity
    if !all(nonzeros(A) .>= 0)
        throw(ArgumentError("The input matrix must be non-negative"))
    end

    # need to test for components
    G = MatrixNetwork(n,A.colptr,A.rowval,A.nzval)
    cc = scomponents(G)
    lccind = argmax(cc.sizes)
    B = A
    if cc.number > 1
        if ccwarn
            @warn "The input matrix had $(cc.number) components, running on largest..."
        end
        f = cc.map .== lccind
        B = A[f,f] # using logical indexing is faster than find for large sets (2015/11/14)
        # n=10^5;A=sprand(n,n,25/n);f=rand(n).>0.01;s=find(f);@time sum(A);@time A[f,f]; @time A[s,s];
        subset = findall(f)
    end

    # run the partition
    x,lam2 = fiedler_vector(B;checksym=false)

    totalvol = sum(B)
    sweep = sweepcut(B,x,totalvol)
    bset = bestset(sweep)

    if cc.number > 1
        # map the set back to the original index set
        bset = subset[bset]
    end

    return SpectralCut(bset,B,lam2,x,sweep,cc,lccind)
end

function spectral_cut(A::SparseMatrixCSC{V,Int}) where V
    return spectral_cut(A,true,true)
end

function spectral_cut(A::MatrixNetwork{V}) where V
    return spectral_cut(sparse_transpose(A),true,true)
end