# The code in this file is a small port from scikit-learn's and numpy's
# library which is distributed under the 3-Clause BSD license.
# The rest of DecisionTree.jl is released under the MIT license.

# written by Poom Chiarawongse <eight1911@gmail.com>

module treeregressor
    include("../util.jl")

    import Random
    export fit

    mutable struct NodeMeta{S}
        l           :: NodeMeta{S}  # right child
        r           :: NodeMeta{S}  # left child
        label       :: Float64      # most likely label
        feature     :: Int          # feature used for splitting
        threshold   :: S            # threshold value
        is_leaf     :: Bool
        depth       :: Int
        region      :: UnitRange{Int} # a slice of the samples used to decide the split of the node
        features    :: Vector{Int}    # a list of features not known to be constant
        split_at    :: Int            # index of samples
        function NodeMeta{S}(
            features :: Vector{Int},
            region   :: UnitRange{Int},
            depth    :: Int) where S
        node = new{S}()
        node.depth = depth
        node.region = region
        node.features = features
        node.is_leaf = false
        node
    end
    end

    struct Tree{S}
        root   :: NodeMeta{S}
        labels :: Vector{Int}
    end

    # find an optimal split that satisfy the given constraints
    # (max_depth, min_samples_split, min_purity_increase)
    function _split!(
            X                   :: AbstractMatrix{S}, # the feature array
            Y                   :: AbstractVector{Float64}, # the label array
            W                   :: AbstractVector{U},
            node                :: NodeMeta{S}, # the node to split
            max_features        :: Int, # number of features to consider
            max_depth           :: Int, # the maximum depth of the resultant tree
            min_samples_leaf    :: Int, # the minimum number of samples each leaf needs to have
            min_samples_split   :: Int, # the minimum number of samples in needed for a split
            min_purity_increase :: Float64, # minimum purity needed for a split
            indX                :: AbstractVector{Int}, # an array of sample indices,
                                                # we split using samples in indX[node.region]
            # the two arrays below are given for optimization purposes
            Xf                  :: AbstractVector{S},
            Yf                  :: AbstractVector{Float64},
            Wf                  :: AbstractVector{U},
            rng                 :: Random.AbstractRNG) where {S, U}

        region = node.region
        n_samples = length(region)
        r_start = region.start - 1

        @inbounds @simd for i in 1:n_samples
            Yf[i] = Y[indX[i + r_start]]
            Wf[i] = W[indX[i + r_start]]
        end

        tssq = zero(U)
        tsum = zero(U)
        wsum = zero(U)
        @inbounds @simd for i in 1:n_samples
            tssq += Wf[i]*Yf[i]*Yf[i]
            tsum += Wf[i]*Yf[i]
            wsum += Wf[i]
        end

        node.label =  tsum / wsum
        if (min_samples_leaf * 2 >  n_samples
         || min_samples_split    >  n_samples
         || max_depth            <= node.depth
          # equivalent to old_purity > -1e-7
         || tsum * node.label    > -1e-7 * wsum + tssq)
            node.is_leaf = true
            return
        end

        features = node.features
        n_features = length(features)
        best_purity = typemin(U)
        best_feature = -1
        threshold_lo = X[1]
        threshold_hi = X[1]

        indf = 1
        # the number of new constants found during this split
        n_constant = 0
        # true if every feature is constant
        unsplittable = true
        # the number of non constant features we will see if
        # only sample n_features used features
        # is a hypergeometric random variable
        total_features = size(X, 2)

        # this is the total number of features that we expect to not
        # be one of the known constant features. since we know exactly
        # what the non constant features are, we can sample at 'non_constants_used'
        # non constant features instead of going through every feature randomly.
        non_constants_used = util.hypergeometric(n_features, total_features-n_features, max_features, rng)
        @inbounds while (unsplittable || indf <= non_constants_used) && indf <= n_features
            feature = let
                indr = rand(rng, indf:n_features)
                features[indf], features[indr] = features[indr], features[indf]
                features[indf]
            end

            rssq = tssq
            lssq = zero(U)
            rsum = tsum
            lsum = zero(U)

            @simd for i in 1:n_samples
                Xf[i] = X[indX[i + r_start], feature]
            end

            # sort Yf and indX by Xf
            util.q_bi_sort!(Xf, indX, 1, n_samples, r_start)
            @simd for i in 1:n_samples
                Yf[i] = Y[indX[i + r_start]]
                Wf[i] = W[indX[i + r_start]]
            end
            nl, nr = zero(U), wsum
            # lo and hi are the indices of
            # the least upper bound and the greatest lower bound
            # of the left and right nodes respectively
            hi = 0
            last_f = Xf[1]
            is_constant = true
            while hi < n_samples
                lo = hi + 1
                curr_f = Xf[lo]
                hi = (lo < n_samples && curr_f == Xf[lo+1]
                    ? searchsortedlast(Xf, curr_f, lo, n_samples, Base.Order.Forward)
                    : lo)

                (lo != 1) && (is_constant = false)
                # honor min_samples_leaf
                if lo-1 >= min_samples_leaf && n_samples - (lo-1) >= min_samples_leaf
                    unsplittable = false
                    purity = (rsum * rsum / nr) + (lsum * lsum / nl)
                    if purity > best_purity && !isapprox(purity, best_purity)
                        # will take average at the end, if possible
                        threshold_lo = last_f
                        threshold_hi = curr_f
                        best_purity  = purity
                        best_feature = feature
                    end
                end

                # update, lssq, rssq, lsum, rsum in the direction
                # that would require the smaller number of iterations
                if (hi << 1) < n_samples + lo # i.e., hi - lo < n_samples - hi
                    @simd for i in lo:hi
                        nr   -= Wf[i]
                        rsum -= Wf[i]*Yf[i]
                        rssq -= Wf[i]*Yf[i]*Yf[i]
                    end
                else
                    nr = rsum = rssq = zero(U)
                    @simd for i in (hi+1):n_samples
                        nr   += Wf[i]
                        rsum += Wf[i]*Yf[i]
                        rssq += Wf[i]*Yf[i]*Yf[i]
                    end
                end
                lsum = tsum - rsum
                lssq = tssq - rssq
                nl   = wsum - nr

                last_f = curr_f
            end

            # keep track of constant features to be used later.
            if is_constant
                n_constant += 1
                features[indf], features[n_constant] = features[n_constant], features[indf]
            end

            indf += 1
        end

        # no splits honor min_samples_leaf
        @inbounds if (unsplittable
                || best_purity - tsum * node.label < min_purity_increase * wsum)
            node.is_leaf = true
            return
        else
            # new_purity - old_purity < stop.min_purity_increase
            @simd for i in 1:n_samples
                Xf[i] = X[indX[i + r_start], best_feature]
            end

            try
                node.threshold = (threshold_lo + threshold_hi) / 2.0
            catch
                node.threshold = threshold_hi
            end
            # split the samples into two parts: ones that are greater than
            # the threshold and ones that are less than or equal to the threshold
            #                                 ---------------------
            # (so we partition at threshold_lo instead of node.threshold)
            node.split_at = util.partition!(indX, Xf, threshold_lo, region)
            node.feature = best_feature
            node.features = features[(n_constant+1):n_features]
        end

    end

    @inline function fork!(node :: NodeMeta{S}) where S
        ind = node.split_at
        region = node.region
        features = node.features
        # no need to copy because we will copy at the end
        node.l = NodeMeta{S}(features, region[    1:ind], node.depth + 1)
        node.r = NodeMeta{S}(features, region[ind+1:end], node.depth + 1)
    end

    function _fit(
            X                     :: AbstractMatrix{S},
            Y                     :: AbstractVector{Float64},
            W                     :: AbstractVector{U},
            max_features          :: Int,
            max_depth             :: Int,
            min_samples_leaf      :: Int,
            min_samples_split     :: Int,
            min_purity_increase   :: Float64,
            rng=Random.GLOBAL_RNG :: Random.AbstractRNG) where {S, U}

        n_samples, n_features = size(X)

        Yf  = Array{Float64}(undef, n_samples)
        Xf  = Array{S}(undef, n_samples)
        Wf  = Array{U}(undef, n_samples)

        indX = collect(1:n_samples)
        root = NodeMeta{S}(collect(1:n_features), 1:n_samples, 0)
        stack = NodeMeta{S}[root]

        @inbounds while length(stack) > 0
            node = pop!(stack)
            _split!(
                X, Y, W,
                node,
                max_features,
                max_depth,
                min_samples_leaf,
                min_samples_split,
                min_purity_increase,
                indX,
                Xf, Yf, Wf,
                rng)
            if !node.is_leaf
                fork!(node)
                push!(stack, node.r)
                push!(stack, node.l)
            end
        end
        return (root, indX)
    end

    function fit(;
            X                     :: AbstractMatrix{S},
            Y                     :: AbstractVector{Float64},
            W                     :: Union{Nothing, AbstractVector{U}},
            max_features          :: Int,
            max_depth             :: Int,
            min_samples_leaf      :: Int,
            min_samples_split     :: Int,
            min_purity_increase   :: Float64,
            rng=Random.GLOBAL_RNG :: Random.AbstractRNG) where {S, U}

        n_samples, n_features = size(X)
        if W == nothing
            W = fill(1.0, n_samples)
        end

        util.check_input(
            X,
            Y,
            W,
            max_features,
            max_depth,
            min_samples_leaf,
            min_samples_split,
            min_purity_increase)

        root, indX = _fit(
            X,
            Y,
            W,
            max_features,
            max_depth,
            min_samples_leaf,
            min_samples_split,
            min_purity_increase,
            rng)

        return Tree{S}(root, indX)

    end
end


"""
`module  JAC.CoulombIonization`
    ... a submodel of JAC that contains all methods for computing Coulomb ionization cross sections and alignment parameters
        for the excitation of target or projectile electrons by fast ion impact.
"""
module CoulombIonization

    using ..Basics, ..ManyElectron, ..Radial

    """
    `struct  CoulombIonization.Settings`  ... defines a type for the details and parameters of computing Coulomb-ionization lines.

        + multipoles              ::Array{EmMultipole}           ... Specifies the multipoles of the radiation field that are to be included.
        + gauges                  ::Array{UseGauge}              ... Specifies the gauges to be included into the computations.
        + energies                ::Array{Float64,1}             ... List of ... energies.
        + calcAlignment           ::Bool                         ... True, if alignment parameters to be calculated and false otherwise.
        + printBefore  ::Bool                         ... True, if all energies and lines are printed before their evaluation.
        + selectLines             ::Bool                         ... True, if lines are selected individually for the computations.
        + selectedLines           ::Array{Tuple{Int64,Int64},1}  ... List of lines, given by tupels (inital-level, final-level).
    """
    struct Settings
        multipoles                ::Array{EmMultipole}
        gauges                    ::Array{UseGauge}
        photonEnergies            ::Array{Float64,1}
        calcAlignment             ::Bool
        printBefore    ::Bool
        selectLines               ::Bool
        selectedLines             ::Array{Tuple{Int64,Int64},1}
    end

    """
    `CoulombIonization.Settings()`  ... constructor for the default values of Coulomb-excitation line computations.
    """
    function Settings()
        Settings(EmMultipole[], UseGauge[], Float64[], false, false, false, Tuple{Int64,Int64}[])
    end

    # `Base.show(io::IO, settings::CoulombIonization.Settings)`  ... prepares a proper printout of the variable settings::CoulombIonization.Settings.
    function Base.show(io::IO, settings::CoulombIonization.Settings)
        println(io, "multipoles:               $(settings.multipoles)  ")
        println(io, "gauges:                   $(settings.gauges)  ")
        println(io, "photonEnergies:           $(settings.photonEnergies)  ")
        println(io, "calcAlignment:            $(settings.calcAlignment)  ")
        println(io, "printBefore:   $(settings.printBefore)  ")
        println(io, "selectLines:              $(settings.selectLines)  ")
        println(io, "selectedLines:            $(settings.selectedLines)  ")
    end

    """
    `struct  CoulombIonization.Channel`
        ... defines a type for a Coulomb-excitation channel to help characterize a single magnetic substate. !!! This need to adapted !!!

        + multipole      ::EmMultipole          ... Multipole of the photon absorption.
        + gauge          ::EmGauge              ... Gauge for dealing with the (coupled) radiation field.
        + kappa          ::Int64                ... partial-wave of the free electron
        + symmetry       ::LevelSymmetry        ... total angular momentum and parity of the scattering state
        + phase          ::Float64              ... phase of the partial wave
        + amplitude      ::Complex{Float64}     ... Photoionization amplitude associated with the given channel.
   """
    struct  Channel
        multipole        ::EmMultipole
        gauge            ::EmGauge
        kappa            ::Int64
        symmetry         ::LevelSymmetry
        phase            ::Float64
        amplitude        ::Complex{Float64}
    end

    """
    `struct  Line`  ... defines a type for a Coulomb-excitation line that may include the definition of channels.

        + initialLevel   ::Level                  ... initial-(state) level
        + finalLevel     ::Level                  ... final-(state) level
        + crossSection   ::EmProperty             ... Cross section for this photoionization.
        + alignmentA2    ::EmProperty             ... Alignment A_2 parameter.
        + hasChannels    ::Bool                   ... Determines whether the individual (sub-) channels are defined in terms of their
                                                      magnetic substates, etc., or not.
        + channels       ::Array{CoulombIonization.Channel,1}  ... List of CoulombIonization.Channels of this line.
    """
    struct  Line
        initialLevel     ::Level
        finalLevel       ::Level
        crossSection     ::EmProperty
        alignmentA2      ::EmProperty
        hasChannels      ::Bool
        channels         ::Array{CoulombIonization.Channel,1}
    end

    # `Base.show(io::IO, line::CoulombIonization.Line)`  ... prepares a proper printout of the variable line::CoulombIonization.Line.
    function Base.show(io::IO, line::CoulombIonization.Line)
        println(io, "initialLevel:      $(line.initialLevel)  ")
        println(io, "finalLevel:        $(line.finalLevel)  ")
        println(io, "crossSection:      $(line.crossSection)  ")
    end

end # module


import Base: isfinite, isnan, precision, iszero, eps,
    typemin, typemax, floatmin, floatmax,
    sign_mask, exponent_mask, exponent_one, exponent_half,
    significand_mask, round, Int32, Int64,
    +, -, *, /, ^, ==, <, <=, >=, >, !=, inv,
    abs, sqrt, exp, log, log2, log10, sin, cos, tan, asin,
    acos, atan, sinh, cosh, tanh, asinh, acosh, atan,
    exponent,
    bitstring

primitive type BFloat16 <: AbstractFloat 16 end

# Floating point property queries
for f in (:sign_mask, :exponent_mask, :exponent_one,
            :exponent_half, :significand_mask)
    @eval $(f)(::Type{BFloat16}) = UInt16($(f)(Float32) >> 16)
end

iszero(x::BFloat16) = reinterpret(UInt16, x) & ~sign_mask(BFloat16) == 0x0000
isfinite(x::BFloat16) = (reinterpret(UInt16,x) & exponent_mask(BFloat16)) != exponent_mask(BFloat16)
isnan(x::BFloat16) = (reinterpret(UInt16,x) & ~sign_mask(BFloat16)) > exponent_mask(BFloat16)
precision(::Type{BFloat16}) = 8
eps(::Type{BFloat16}) = Base.bitcast(BFloat16, 0x3c00)

round(x::BFloat16, r::RoundingMode{:Up}) = BFloat16(ceil(Float32(x)))
round(x::BFloat16, r::RoundingMode{:Down}) = BFloat16(floor(Float32(x)))
round(x::BFloat16, r::RoundingMode{:Nearest}) = BFloat16(round(Float32(x)))

Int64(x::BFloat16) = Int64(Float32(x))
Int32(x::BFloat16) = Int32(Float32(x))

## floating point traits ##
"""
    InfB16
Positive infinity of type [`BFloat16`](@ref).
"""
const InfB16 = reinterpret(BFloat16, 0x7f80)

"""
    NaNB16
A not-a-number value of type [`BFloat16`](@ref).
"""
const NaNB16 = reinterpret(BFloat16, 0x7fc0)

# More floating point property queries
typemin(::Type{BFloat16}) = -InfB16
typemax(::Type{BFloat16}) = InfB16
floatmax(::Type{BFloat16}) = reinterpret(BFloat16, 0x7f7f)
floatmin(::Type{BFloat16}) = reinterpret(BFloat16, 0x0080)

# Truncation from Float32
Base.uinttype(::Type{BFloat16}) = UInt16
Base.trunc(::Type{BFloat16}, x::Float32) = reinterpret(BFloat16,
        (reinterpret(UInt32, x) >> 16) % UInt16
    )

# Conversion from Float32
function BFloat16(x::Float32)
    isnan(x) && return NaNB16
    # Round to nearest even (matches TensorFlow and our convention for
    # rounding to lower precision floating point types).
    h = reinterpret(UInt32, x)
    h += 0x7fff + ((h >> 16) & 1)
    return reinterpret(BFloat16, (h >> 16) % UInt16)
end

# Conversion from Float64
function BFloat16(x::Float64)
	BFloat16(Float32(x))
end

# Conversion from Integer
function BFloat16(x::Integer)
	convert(BFloat16, convert(Float32, x))
end

# Expansion to Float32
function Base.Float32(x::BFloat16)
    reinterpret(Float32, UInt32(reinterpret(UInt16, x)) << 16)
end

# Expansion to Float64
function Base.Float64(x::BFloat16)
    Float64(Float32(x))
end

# Truncation to integer types
Base.unsafe_trunc(T::Type{<:Integer}, x::BFloat16) = unsafe_trunc(T, Float32(x))

# Basic arithmetic
for f in (:+, :-, :*, :/, :^)
    @eval ($f)(x::BFloat16, y::BFloat16) = BFloat16($(f)(Float32(x), Float32(y)))
end
-(x::BFloat16) = reinterpret(BFloat16, reinterpret(UInt16, x) ⊻ sign_mask(BFloat16))
^(x::BFloat16, y::Integer) = BFloat16(^(Float32(x), y))

for F in (:abs, :sqrt, :exp, :log, :log2, :log10,
          :sin, :cos, :tan, :asin, :acos, :atan,
          :sinh, :cosh, :tanh, :asinh, :acosh, :atanh)
  @eval begin
    $F(x::BFloat16) = BFloat16($F(Float32(x)))
  end
end

const ZeroBFloat16 = BFloat16(0.0f0)
const OneBFloat16 = BFloat16(1.0f0)
Base.zero(::Type{BFloat16}) = ZeroBFloat16
Base.one(::Type{BFloat16}) = OneBFloat16

inv(x::BFloat16) = one(BFloat16) / x

# Floating point comparison
function ==(x::BFloat16, y::BFloat16)
    ix = reinterpret(UInt16, x)
    iy = reinterpret(UInt16, y)
    # NaNs (isnan(x) || isnan(y))
    if (ix|iy)&~sign_mask(BFloat16) > exponent_mask(BFloat16)
        return false
    end
    # Signed zeros
    if (ix|iy)&~sign_mask(BFloat16) == 0
        return true
    end
    return ix == iy
end

for op in (:<, :<=, :>, :>=, :!=)
    @eval ($op)(a::BFloat16, b::BFloat16) = ($op)(Float32(a), Float32(b))
end

Base.widen(::Type{BFloat16}) = Float32
Base.promote_rule(::Type{Float32}, ::Type{BFloat16}) = Float32
Base.promote_rule(::Type{Float64}, ::Type{BFloat16}) = Float64
for t in (Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128)
    @eval Base.promote_rule(::Type{BFloat16}, ::Type{$t}) = BFloat16
end

# Wide multiplication
Base.widemul(x::BFloat16, y::BFloat16) = Float32(x) * Float32(y)

# Showing
function Base.show(io::IO, x::BFloat16)
    hastypeinfo = BFloat16 === get(io, :typeinfo, Any)
    if isinf(x)
        print(io, x < 0 ? "-InfB16" : "InfB16")
    elseif isnan(x)
        print(io, "NaNB16")
    else
        hastypeinfo || print(io, "BFloat16(")
        show(IOContext(io, :typeinfo=>Float32), Float32(x))
        hastypeinfo || print(io, ")")
    end
end

# Random
import Random: rand, randn, randexp, AbstractRNG, Sampler
rand(rng::AbstractRNG, ::Sampler{BFloat16}) = convert(BFloat16, rand(rng))
randn(rng::AbstractRNG, ::Type{BFloat16}) = convert(BFloat16, randn(rng))
randexp(rng::AbstractRNG, ::Type{BFloat16}) = convert(BFloat16, randexp(rng))

# Exponent
exponent(x::BFloat16) = exponent(Float32(x))

# Bitstring
bitstring(x::BFloat16) = bitstring(reinterpret(UInt16, x))


### BasicContMuvParameter

type BasicContMuvParameter <: Parameter{Continuous, Multivariate}
  key::Symbol
  index::Integer
  pdf::Union{ContinuousMultivariateDistribution, Void}
  prior::Union{ContinuousMultivariateDistribution, Void}
  setpdf::Union{Function, Void}
  setprior::Union{Function, Void}
  loglikelihood!::Union{Function, Void}
  logprior!::Union{Function, Void}
  logtarget!::Union{Function, Void}
  gradloglikelihood!::Union{Function, Void}
  gradlogprior!::Union{Function, Void}
  gradlogtarget!::Union{Function, Void}
  tensorloglikelihood!::Union{Function, Void}
  tensorlogprior!::Union{Function, Void}
  tensorlogtarget!::Union{Function, Void}
  dtensorloglikelihood!::Union{Function, Void}
  dtensorlogprior!::Union{Function, Void}
  dtensorlogtarget!::Union{Function, Void}
  uptogradlogtarget!::Union{Function, Void}
  uptotensorlogtarget!::Union{Function, Void}
  uptodtensorlogtarget!::Union{Function, Void}
  states::VariableStateVector

  function BasicContMuvParameter(
    key::Symbol,
    index::Integer,
    pdf::Union{ContinuousMultivariateDistribution, Void},
    prior::Union{ContinuousMultivariateDistribution, Void},
    setpdf::Union{Function, Void},
    setprior::Union{Function, Void},
    ll::Union{Function, Void},
    lp::Union{Function, Void},
    lt::Union{Function, Void},
    gll::Union{Function, Void},
    glp::Union{Function, Void},
    glt::Union{Function, Void},
    tll::Union{Function, Void},
    tlp::Union{Function, Void},
    tlt::Union{Function, Void},
    dtll::Union{Function, Void},
    dtlp::Union{Function, Void},
    dtlt::Union{Function, Void},
    uptoglt::Union{Function, Void},
    uptotlt::Union{Function, Void},
    uptodtlt::Union{Function, Void},
    states::VariableStateVector
  )
    args = (setpdf, setprior, ll, lp, lt, gll, glp, glt, tll, tlp, tlt, dtll, dtlp, dtlt, uptoglt, uptotlt, uptodtlt)
    fnames = fieldnames(BasicContMuvParameter)[5:21]

    # Check that all generic functions have correct signature
    for i in 1:17
      if isa(args[i], Function) &&
        isgeneric(args[i]) &&
        !method_exists(args[i], (BasicContMuvParameterState, VariableStateVector))
        error("$(fnames[i]) has wrong signature")
      end
    end

    new(
      key,
      index,
      pdf,
      prior,
      setpdf,
      setprior,
      ll,
      lp,
      lt,
      gll,
      glp,
      glt,
      tll,
      tlp,
      tlt,
      dtll,
      dtlp,
      dtlt,
      uptoglt,
      uptotlt,
      uptodtlt,
      states
    )
  end
end

function BasicContMuvParameter!(
  parameter::BasicContMuvParameter,
  setpdf::Union{Function, Void},
  setprior::Union{Function, Void},
  ll::Union{Function, Void},
  lp::Union{Function, Void},
  lt::Union{Function, Void},
  gll::Union{Function, Void},
  glp::Union{Function, Void},
  glt::Union{Function, Void},
  tll::Union{Function, Void},
  tlp::Union{Function, Void},
  tlt::Union{Function, Void},
  dtll::Union{Function, Void},
  dtlp::Union{Function, Void},
  dtlt::Union{Function, Void},
  uptoglt::Union{Function, Void},
  uptotlt::Union{Function, Void},
  uptodtlt::Union{Function, Void}
)
  args = (setpdf, setprior, ll, lp, lt, gll, glp, glt, tll, tlp, tlt, dtll, dtlp, dtlt, uptoglt, uptotlt, uptodtlt)

  # Define setpdf (i = 1) and setprior (i = 2)
  for (i, setter, distribution) in ((1, :setpdf, :pdf), (2, :setprior, :prior))
    setfield!(
      parameter,
      setter,
      if isa(args[i], Function)
        eval(codegen_setfield(parameter, distribution, args[i]))
      else
        nothing
      end
    )
  end

  # Define loglikelihood! (i = 3) and gradloglikelihood! (i = 6)
  # plfield stands for parameter likelihood-related field respectively
  for (i, plfield) in ((3, :loglikelihood!), (6, :gradloglikelihood!))
    setfield!(
      parameter,
      plfield,
      if isa(args[i], Function)
        eval(codegen_closure(parameter, args[i]))
      else
        nothing
      end
    )
  end

  # Define logprior! (i = 4) and gradlogprior! (i = 7)
  # ppfield and spfield stand for parameter prior-related field and state prior-related field repsectively
  for (i , ppfield, spfield, f) in ((4, :logprior!, :logprior, logpdf), (7, :gradlogprior!, :gradlogprior, gradlogpdf))
    setfield!(
    parameter,
      ppfield,
      if isa(args[i], Function)
        eval(codegen_closure(parameter, args[i]))
      else
        if (
            isa(parameter.prior, ContinuousMultivariateDistribution) &&
            method_exists(f, (typeof(parameter.prior), Vector{eltype(parameter.prior)}))
          ) ||
          isa(args[2], Function)
          eval(codegen_target_closure_via_distribution(parameter, :prior, f, spfield))
        else
          nothing
        end
      end
    )
  end

  # Define logtarget! (i = 5) and gradlogtarget! (i = 8)
  # ptfield, plfield and ppfield stand for parameter target, likelihood and prior-related field respectively
  # stfield, slfield and spfield stand for state target, likelihood and prior-related field respectively
  for (i , ptfield, plfield, ppfield, stfield, slfield, spfield, f) in (
    (5, :logtarget!, :loglikelihood!, :logprior!, :logtarget, :loglikelihood, :logprior, logpdf),
    (8, :gradlogtarget!, :gradloglikelihood!, :gradlogprior!, :gradlogtarget, :gradloglikelihood, :gradlogprior, gradlogpdf)
  )
    setfield!(
      parameter,
      ptfield,
      if isa(args[i], Function)
        eval(codegen_closure(parameter, args[i]))
      else
        if isa(args[i-2], Function) && isa(getfield(parameter, ppfield), Function)
          eval(codegen_sumtarget_closure(parameter, plfield, ppfield, stfield, slfield, spfield))
        elseif (
            isa(parameter.pdf, ContinuousMultivariateDistribution) &&
            method_exists(f, (typeof(parameter.pdf), Vector{eltype(parameter.pdf)}))
          ) ||
          isa(args[1], Function)
          eval(codegen_target_closure_via_distribution(parameter, :pdf, f, stfield))
        else
          nothing
        end
      end
    )
  end

  # Define tensorloglikelihood! (i = 9) and dtensorloglikelihood! (i = 12)
  # plfield stands for parameter likelihood-related field respectively
  for (i, plfield) in ((9, :tensorloglikelihood!), (12, :dtensorloglikelihood!))
    setfield!(
      parameter,
      plfield,
      if isa(args[i], Function)
        eval(codegen_closure(parameter, args[i]))
      else
        nothing
      end
    )
  end

  # Define tensorlogprior! (i = 10) and dtensorlogprior! (i = 13)
  # ppfield stands for parameter prior-related field respectively
  for (i, ppfield) in ((10, :tensorlogprior!), (13, :dtensorlogprior!))
    setfield!(
      parameter,
      ppfield,
      if isa(args[i], Function)
        eval(codegen_closure(parameter, args[i]))
      else
        nothing
      end
    )
  end

  # Define tensorlogtarget! (i = 11) and dtensorlogtarget! (i = 14)
  for (i , ptfield, plfield, ppfield, stfield, slfield, spfield) in (
    (
      11,
      :tensorlogtarget!, :tensorloglikelihood!, :tensorlogprior!,
      :tensorlogtarget, :tensorloglikelihood, :tensorlogprior
    ),
    (
      14,
      :dtensorlogtarget!, :dtensorloglikelihood!, :dtensorlogprior!,
      :dtensorlogtarget, :dtensorloglikelihood, :dtensorlogprior
    )
  )
    setfield!(
      parameter,
      ptfield,
      if isa(args[i], Function)
        eval(codegen_closure(parameter, args[i]))
      else
        if isa(args[i-2], Function) && isa(args[i-1], Function)
          eval(codegen_sumtarget_closure(parameter, plfield, ppfield, stfield, slfield, spfield))
        else
          nothing
        end
      end
    )
  end

  # Define uptogradlogtarget!
  setfield!(
    parameter,
    :uptogradlogtarget!,
    if isa(args[15], Function)
      eval(codegen_closure(parameter, args[15]))
    else
      if isa(parameter.logtarget!, Function) && isa(parameter.gradlogtarget!, Function)
        eval(codegen_uptotarget_closures(parameter, [:logtarget!, :gradlogtarget!]))
      else
        nothing
      end
    end
  )

  # Define uptotensorlogtarget!
  setfield!(
    parameter,
    :uptotensorlogtarget!,
    if isa(args[16], Function)
      eval(codegen_closure(parameter, args[16]))
    else
      if isa(parameter.logtarget!, Function) &&
        isa(parameter.gradlogtarget!, Function) &&
        isa(parameter.tensorlogtarget!, Function)
        eval(codegen_uptotarget_closures(parameter, [:logtarget!, :gradlogtarget!, :tensorlogtarget!]))
      else
        nothing
      end
    end
  )

  # Define uptodtensorlogtarget!
  setfield!(
    parameter,
    :uptodtensorlogtarget!,
    if isa(args[17], Function)
      eval(codegen_closure(parameter, args[17]))
    else
      if isa(parameter.logtarget!, Function) &&
        isa(parameter.gradlogtarget!, Function) &&
        isa(parameter.tensorlogtarget!, Function) &&
        isa(parameter.dtensorlogtarget!, Function)
        eval(codegen_uptotarget_closures(parameter, [:logtarget!, :gradlogtarget!, :tensorlogtarget!, :dtensorlogtarget!]))
      else
        nothing
      end
    end
  )
end

BasicContMuvParameter(key::Symbol, index::Integer=0; signature::Symbol=:high, args...) =
  BasicContMuvParameter(key, Val{signature}, index; args...)

function BasicContMuvParameter(
  key::Symbol,
  ::Type{Val{:low}},
  index::Integer=0;
  pdf::Union{ContinuousMultivariateDistribution, Void}=nothing,
  prior::Union{ContinuousMultivariateDistribution, Void}=nothing,
  setpdf::Union{Function, Void}=nothing,
  setprior::Union{Function, Void}=nothing,
  loglikelihood::Union{Function, Void}=nothing,
  logprior::Union{Function, Void}=nothing,
  logtarget::Union{Function, Void}=nothing,
  gradloglikelihood::Union{Function, Void}=nothing,
  gradlogprior::Union{Function, Void}=nothing,
  gradlogtarget::Union{Function, Void}=nothing,
  tensorloglikelihood::Union{Function, Void}=nothing,
  tensorlogprior::Union{Function, Void}=nothing,
  tensorlogtarget::Union{Function, Void}=nothing,
  dtensorloglikelihood::Union{Function, Void}=nothing,
  dtensorlogprior::Union{Function, Void}=nothing,
  dtensorlogtarget::Union{Function, Void}=nothing,
  uptogradlogtarget::Union{Function, Void}=nothing,
  uptotensorlogtarget::Union{Function, Void}=nothing,
  uptodtensorlogtarget::Union{Function, Void}=nothing,
  states::VariableStateVector=VariableState[]
)
  parameter = BasicContMuvParameter(key, index, pdf, prior, fill(nothing, 17)..., states)

  BasicContMuvParameter!(
    parameter,
    setpdf,
    setprior,
    loglikelihood,
    logprior,
    logtarget,
    gradloglikelihood,
    gradlogprior,
    gradlogtarget,
    tensorloglikelihood,
    tensorlogprior,
    tensorlogtarget,
    dtensorloglikelihood,
    dtensorlogprior,
    dtensorlogtarget,
    uptogradlogtarget,
    uptotensorlogtarget,
    uptodtensorlogtarget
  )

  parameter
end

function BasicContMuvParameter(
  key::Symbol,
  ::Type{Val{:high}},
  index::Integer=0;
  pdf::Union{ContinuousMultivariateDistribution, Void}=nothing,
  prior::Union{ContinuousMultivariateDistribution, Void}=nothing,
  setpdf::Union{Function, Void}=nothing,
  setprior::Union{Function, Void}=nothing,
  loglikelihood::Union{Function, Expr, Void}=nothing,
  logprior::Union{Function, Expr, Void}=nothing,
  logtarget::Union{Function, Expr, Void}=nothing,
  gradloglikelihood::Union{Function, Void}=nothing,
  gradlogprior::Union{Function, Void}=nothing,
  gradlogtarget::Union{Function, Void}=nothing,
  tensorloglikelihood::Union{Function, Void}=nothing,
  tensorlogprior::Union{Function, Void}=nothing,
  tensorlogtarget::Union{Function, Void}=nothing,
  dtensorloglikelihood::Union{Function, Void}=nothing,
  dtensorlogprior::Union{Function, Void}=nothing,
  dtensorlogtarget::Union{Function, Void}=nothing,
  uptogradlogtarget::Union{Function, Void}=nothing,
  uptotensorlogtarget::Union{Function, Void}=nothing,
  uptodtensorlogtarget::Union{Function, Void}=nothing,
  states::VariableStateVector=VariableState[],
  nkeys::Integer=0,
  vfarg::Bool=false,
  autodiff::Symbol=:none,
  order::Integer=1,
  chunksize::Integer=0,
  init::Vector=fill(Any[], 3)
)
  inargs = (
    setpdf,
    setprior,
    loglikelihood,
    logprior,
    logtarget,
    gradloglikelihood,
    gradlogprior,
    gradlogtarget,
    tensorloglikelihood,
    tensorlogprior,
    tensorlogtarget,
    dtensorloglikelihood,
    dtensorlogprior,
    dtensorlogtarget,
    uptogradlogtarget,
    uptotensorlogtarget,
    uptodtensorlogtarget
  )

  fnames = Array(Any, 17)
  fnames[1:2] = fill(Symbol[], 2)
  fnames[3:14] = [Symbol[f] for f in fieldnames(BasicContMuvParameterState)[2:13]]
  for i in 1:3
    fnames[14+i] = Symbol[fnames[j][1] for j in 5:3:(5+i*3)]
  end

  for i in 3:5
    if isa(inargs[i], Expr) && autodiff != :reverse
      error("The only case $(fnames[i][1]) can be an expression is when used in conjunction with reverse mode autodiff")
    end
  end

  if nkeys > 0
    if (autodiff == :forward || autodiff == :reverse) && vfarg
      error("In the case of autodiff, if nkeys is not 0, then vfarg must be false")
    end
  elseif nkeys < 0
    "nkeys must be non-negative, got $nkeys"
  end

  if !in(autodiff, (:none, :forward, :reverse))
    error("autodiff must be :nore or :forward or :reverse, got $autodiff")
  end

  if order < 0 || order > 2
    error("Derivative order must be 0, 1 or 2, got $order")
  elseif autodiff != :reverse && order == 0
    error("Zero order can be used only with reverse mode autodiff")
  end

  @assert chunksize >= 0 "chunksize must be non-negative, got $chunksize"

  initarg = Array(Any, 3)
  initlen = length(init)

  if initlen == 1 || initlen == 2
    if autodiff != :reverse
      @assert all(isempty, init) "init option is used only for reverse mode autodiff"
    end

    for i in 1:3
      initarg[i] = (inargs[i+2] != nothing) ? init : Any[]
    end
  elseif initlen == 3
    if autodiff != :reverse
      @assert all(isempty, init) "init option is used only for reverse mode autodiff"
    end

    initarg = init
  else
    error("init must be a vector of length 1, 2 or 3, got vector of length $initlen")
  end

  parameter = BasicContMuvParameter(key, index, pdf, prior, fill(nothing, 17)..., states)

  outargs = Union{Function, Void}[nothing for i in 1:17]

  for i in 1:17
    if isa(inargs[i], Function)
      outargs[i] = eval(
        codegen_lowlevel_variable_method(inargs[i], :BasicContMuvParameterState, true, fnames[i], nkeys, vfarg)
      )
    end
  end

  if autodiff == :forward
    fadclosure = Array(Union{Function, Void}, 3)
    for i in 3:5
      fadclosure[i-2] =
        if isa(inargs[i], Function)
          nkeys == 0 ? inargs[i] : eval(codegen_internal_autodiff_closure(parameter, inargs[i], nkeys))
        else
          nothing
        end
    end

    for i in 6:8
      if !isa(inargs[i], Function) && isa(inargs[i-3], Function)
        outargs[i] = eval(codegen_lowlevel_variable_method(
          eval(codegen_forward_autodiff_function(Val{:gradient}, fadclosure[i-5], chunksize)),
          :BasicContMuvParameterState,
          true,
          fnames[i],
          0
        ))
      end
    end

    if !isa(inargs[15], Function) && isa(inargs[5], Function)
      outargs[15] = eval(codegen_lowlevel_variable_method(
        eval(codegen_forward_autodiff_uptofunction(Val{:gradient}, fadclosure[3], chunksize)),
        :BasicContMuvParameterState,
        true,
        fnames[15],
        0
      ))
    end

    if order >= 2
      for i in 9:11
        if !isa(inargs[i], Function) && isa(inargs[i-6], Function)
          outargs[i] = eval(codegen_lowlevel_variable_method(
            eval(codegen_forward_autodiff_target(:hessian, fadclosure[i-8], chunksize)),
            :BasicContMuvParameterState,
            true,
            fnames[i],
            0
          ))
        end
      end

      if !isa(inargs[16], Function) && isa(inargs[5], Function)
        outargs[16] = eval(codegen_lowlevel_variable_method(
          eval(codegen_forward_autodiff_uptotarget(:hessian, fadclosure[3], chunksize)),
          :BasicContMuvParameterState,
          true,
          fnames[16],
          0
        ))
      end
    end
  elseif autodiff == :reverse
    local f::Function

    for i in 3:5
      if isa(inargs[i], Expr)
        if nkeys == 0
          f = eval(codegen_reverse_autodiff_function(inargs[i], :Vector, initarg[i-2][1], 0, false))
        else
          f = eval(codegen_reverse_autodiff_function(inargs[i], :Vector, initarg[i-2], 0, false))
          f = eval(codegen_internal_autodiff_closure(parameter, f, nkeys))
        end

        outargs[i] = eval(codegen_lowlevel_variable_method(f, :BasicContMuvParameterState, true, fnames[i], 0))
      end
    end

    for i in 6:8
      if !isa(inargs[i], Function)
        if isa(inargs[i-3], Function)
          if nkeys == 0
            f = ReverseDiffSource.rdiff(inargs[i-3], (initarg[i-5][1][2],), order=1, allorders=false)
          else
            f = ReverseDiffSource.rdiff(
              inargs[i-3], (initarg[i-5][1][2], initarg[i-5][2][2]), ignore=[initarg[i-5][2][1]], order=1, allorders=false
            )
            f = eval(codegen_internal_autodiff_closure(parameter, f, nkeys))
          end

          outargs[i] = eval(codegen_lowlevel_variable_method(f, :BasicContMuvParameterState, true, fnames[i], 0))
        elseif isa(inargs[i-3], Expr)
          if nkeys == 0
            f = eval(codegen_reverse_autodiff_function(inargs[i-3], :Vector, initarg[i-5][1], 1, false))
          else
            f = eval(codegen_reverse_autodiff_function(inargs[i-3], :Vector, initarg[i-5], 1, false))
            f = eval(codegen_internal_autodiff_closure(parameter, f, nkeys))
          end

          outargs[i] = eval(codegen_lowlevel_variable_method(f, :BasicContMuvParameterState, true, fnames[i], 0))
        end
      end
    end

    if !isa(inargs[15], Function)
      if isa(inargs[5], Function)
        if nkeys == 0
          f = ReverseDiffSource.rdiff(inargs[5], (initarg[3][1][2],), order=1, allorders=true)
        else
          f = ReverseDiffSource.rdiff(
            inargs[5], (initarg[3][1][2], initarg[3][2][2]), ignore=[initarg[3][2][1]], order=1, allorders=true
          )
          f = eval(codegen_internal_autodiff_closure(parameter, f, nkeys))
        end

        outargs[15] = eval(codegen_lowlevel_variable_method(f, :BasicContMuvParameterState, true, fnames[15], 0))
      elseif isa(inargs[5], Expr)
        if nkeys == 0
          f = eval(codegen_reverse_autodiff_function(inargs[5], :Vector, initarg[3][1], 1, true))
        else
          f = eval(codegen_reverse_autodiff_function(inargs[5], :Vector, initarg[3], 1, true))
          f = eval(codegen_internal_autodiff_closure(parameter, f, nkeys))
        end

        outargs[15] = eval(codegen_lowlevel_variable_method(f, :BasicContMuvParameterState, true, fnames[15], 0))
      end
    end

    if order >= 2
      for i in 9:11
        if !isa(inargs[i], Function)
          if isa(inargs[i-6], Function) || isa(inargs[i-6], Expr)
            if nkeys == 0
              f = eval(codegen_reverse_autodiff_target(:hessian, inargs[i-6], :Vector, initarg[i-8][1]))
            else
              f = eval(codegen_reverse_autodiff_target(:hessian, inargs[i-6], :Vector, initarg[i-8]))
              f = eval(codegen_internal_autodiff_closure(parameter, f, nkeys))
            end

            outargs[i] = eval(codegen_lowlevel_variable_method(f, :BasicContMuvParameterState, true, fnames[i], 0))
          end
        end
      end

      if !isa(inargs[16], Function)
        if isa(inargs[5], Function) || isa(inargs[5], Expr)
          if nkeys == 0
            f = eval(codegen_reverse_autodiff_uptotarget(:hessian, inargs[5], :Vector, initarg[3][1]))
          else
            f = eval(codegen_reverse_autodiff_uptotarget(:hessian, inargs[5], :Vector, initarg[3]))
            f = eval(codegen_internal_autodiff_closure(parameter, f, nkeys))
          end

          outargs[16] = eval(codegen_lowlevel_variable_method(f, :BasicContMuvParameterState, true, fnames[16], 0))
        end
      end
    end
  end

  BasicContMuvParameter!(parameter, outargs...)

  parameter
end

function codegen_internal_autodiff_closure(parameter::BasicContMuvParameter, f::Function, nkeys::Integer)
  fstatesarg = [Expr(:ref, :Any, [:($(parameter).states[$i].value) for i in 1:nkeys]...)]

  @gensym internal_forward_autodiff_closure

  quote
    function $internal_forward_autodiff_closure(_x::Vector)
      $(f)(_x, $(fstatesarg...))
    end
  end
end

value_support(::Type{BasicContMuvParameter}) = Continuous
value_support(::BasicContMuvParameter) = Continuous

variate_form(::Type{BasicContMuvParameter}) = Multivariate
variate_form(::BasicContMuvParameter) = Multivariate

default_state_type(::BasicContMuvParameter) = BasicContMuvParameterState

default_state{N<:Real}(variable::BasicContMuvParameter, value::Vector{N}, outopts::Dict) =
  BasicContMuvParameterState(
    value,
    [getfield(variable, fieldnames(BasicContMuvParameter)[i]) == nothing ? false : true for i in 10:18],
    (haskey(outopts, :diagnostics) && in(:accept, outopts[:diagnostics])) ? [:accept] : Symbol[]
  )

Base.show(io::IO, ::Type{BasicContMuvParameter}) = print(io, "BasicContMuvParameter")
Base.writemime(io::IO, ::MIME"text/plain", t::Type{BasicContMuvParameter}) = show(io, t)


const sense_alias = Dict(
"max"      => :Max,
"maximize" => :Max,
"maximise" => :Max,
"maximum"  => :Max,
"min"      => :Min,
"minimize" => :Min,
"minimise" => :Min,
"minimum"  => :Min
)

const subject_to_alias = ["subject to", "such that", "st", "s.t."]

# a list of section keywords in lower-case
const KEYWORDS = Dict(
    "max"      => Val{:obj},
    "maximize" => Val{:obj},
    "maximise" => Val{:obj},
    "maximum"  => Val{:obj},
    "min"      => Val{:obj},
    "minimize" => Val{:obj},
    "minimise" => Val{:obj},
    "minimum"  => Val{:obj},

    "subject to" => Val{:constraints},
    "such that"  => Val{:constraints},
    "st"         => Val{:constraints},
    "s.t."       => Val{:constraints},

    "bounds" => Val{:bounds},
    "bound"  => Val{:bounds},

    "gen"      => Val{:integer},
    "general"  => Val{:integer},
    "generals" => Val{:integer},

    "bin"      => Val{:binary},
    "binary"   => Val{:binary},
    "binaries" => Val{:binary},

    "end"      => Val{:quit}
)

const COMMENT_REG = r"(.*?)\\(.*)"
function stripcomment(line::String)
    if contains(line, "\\")
        m = match(COMMENT_REG, line)
        return strip(String(m[1]))
    else
        return strip(line)
    end
end

immutable TempSparseMatrix
    i::Vector{Int}
    j::Vector{Int}
    v::Vector{Float64}
end
TempSparseMatrix() = TempSparseMatrix(Int[], Int[], Float64[])
Base.sparse(m::TempSparseMatrix, nr::Int, nc::Int) = sparse(m.i, m.j, m.v, nr, nc)

newdatastore() = Dict(
    :A => TempSparseMatrix(),
    :collb => Float64[],
    :colub => Float64[],
    :c => Float64[],
    :rowlb => Float64[],
    :rowub => Float64[],
    :sense => :Min,
    :colcat => Symbol[],
    :sos => SOS[],
    :Q => TempSparseMatrix(),
    :modelname => "",
    :colnames => String[],
    :rownames => String[],
    :open_constraint => false
)
setsense!(T, data, line) = nothing
function setsense!(::Type{Val{:obj}}, data, line)
    data[:sense] = sense_alias[lowercase(line)]
end
function read(filename::String)
    data = newdatastore()
    open(filename, "r") do io
        section = :none
        while !eof(io)
            line = stripcomment(readline(io))
            if line == "" # skip blank lines
                continue
            end
            if haskey(KEYWORDS, lowercase(line)) # section has changed
                section = KEYWORDS[lowercase(line)]
                setsense!(section, data, line)
                continue
            end
            parsesection!(section, data, line)
        end
    end
    return sparse(data[:A], length(data[:rownames]), length(data[:colnames])),
        data[:collb],
        data[:colub],
        data[:c],
        data[:rowlb],
        data[:rowub],
        data[:sense],
        data[:colcat],
        data[:sos],
        sparse(data[:Q], length(data[:rownames]), length(data[:colnames])),
        data[:modelname],
        data[:colnames],
        data[:rownames]
end

parsesection!(::Type{Val{:quit}}, data, line) = error("Corrupted LP File. You have the lne $(line) after an end.")

function parsesection!(::Type{Val{:obj}}, data, line)
    # okay so line should be the start of the objective
    if contains(line, ":")
        # throw away name
        m = match(r"(.*?)\:(.*)", line)
        line = String(m[2])
    end
    tokens = tokenize(line)
    if length(tokens) == 0 # no objective
        return
    end
    # tokens should be in order (+/-) (numeric) (variable) ...
    while length(tokens) > 0
        variable = String(pop!(tokens))
        idx = getvariableindex!(data, variable) # catch in here for malformed variables
        coef_token = pop!(tokens)
        try
            coefficient = parse(Float64, coef_token)
        catch
            error("Unable to parse objective. Expected numeric coefficient. Got $(coef_token)")
        end
        if length(tokens) > 0
            _sign = pop!(tokens)
            if _sign == "-"
                coefficient *= -1
            elseif _sign == "+"
            else
                error("Unable to parse objective due to bad operator: $(_sign) $(line)")
            end
        end
        data[:c][idx] = coefficient
    end
end

const constraintsense = Dict(
    "<"  => :le,
    "<=" => :le,
    "="  => :eq,
    "==" => :eq,
    ">"  => :ge,
    ">=" => :ge,
)

function parseconstraintcoefficients!(data, line, tokens, rowidx)
    # tokens should be in order (+/-) (numeric) (variable) ...
    while length(tokens) > 0
        variable = String(pop!(tokens))
        idx = getvariableindex!(data, variable) # catch in here for malformed variables
        coef_token = pop!(tokens)
        try
            coefficient = parse(Float64, coef_token)
        catch
            error("Unable to parse constraint $(line). Expected numeric coefficient. Got $(coef_token)")
        end
        if length(tokens) > 0
            _sign = pop!(tokens)
            if _sign == "-"
                coefficient *= -1
            elseif _sign == "+"
            else
                error("Unable to parse objective due to bad operator: $(_sign) $(line)")
            end
        end
        push!(data[:A].i, rowidx)
        push!(data[:A].j, idx)
        push!(data[:A].v, coefficient)
    end
end

function parsesos!(data, line)
    tokens = tokenize(line)
    if length(tokens) < 3
        error(string("Malformed SOS constraint: ", line))
    end
    if tokens[2] == "S1::"
        order = 1
    elseif tokens[2] == "S2::"
        order = 2
    else
        error("SOS of type $(tokens[2]) not recognised")
    end
    names   = String[]
    weights = Float64[]
    for token in tokens[3:end]
        items = split(token, ":")
        if length(items) != 2
            error(string("Invalid sequence: ", token))
        end
        push!(names, String(items[1]))
        push!(weights, parsefloat(String(items[2])))
    end
    indices = Int[]
    for name in names
        idx = findfirst(data[:colnames], name)
        if idx == 0
            push!(data[:colnames], name)
            push!(indices, length(data[:colnames]))
        else
            push!(indices, idx)
        end
    end
    push!(data[:sos], (order, indices, weights))
end

function parsesection!(::Type{Val{:constraints}}, data, line)
    if match(r" S([0-9]):: ", line) != nothing
        # it's an SOS constraint
        parsesos!(data, line)
        return
    end
    if data[:open_constraint] == false
        push!(data[:rownames], "R$(length(data[:rownames]) + 1)")
        push!(data[:rowlb], -Inf)
        push!(data[:rowub], Inf)
    end
    if contains(line, ":")
        if data[:open_constraint] == true
            error("Malformed constraint $(line). Is the previous one valid?")
        end
        # throw away name
        m = match(r"(.*?)\:(.*)", line)
        data[:rownames][end] = String(m[1])
        line = String(m[2])
    end
    data[:open_constraint] = true

    tokens = tokenize(line)
    if length(tokens) == 0 # no entries
        return
    elseif length(tokens) >= 2 && haskey(constraintsense, tokens[end-1])# test if constraint ends this line
        rhs = parsefloat(pop!(tokens))
        sym = pop!(tokens)
        if constraintsense[sym] == :le
            data[:rowub][end] = rhs
        elseif constraintsense[sym] == :ge
            data[:rowlb][end] = rhs
        elseif constraintsense[sym] == :eq
            data[:rowlb][end] = rhs
            data[:rowub][end] = rhs
        end
        data[:open_constraint] = false # finished
    end
    parseconstraintcoefficients!(data, line, tokens, length(data[:rownames]))
end

function getvariableindex!(data, v)
    i = findfirst(data[:colnames], v)
    if i == 0
        if !verifyname(v)
            error("Invalid variable name $v")
        end
        addnewvariable!(data, v)
        return length(data[:colnames])
    end
    return i
end

function addnewvariable!(data, name)
    push!(data[:collb], -Inf)
    push!(data[:colub],  Inf)
    push!(data[:c], 0)
    push!(data[:colcat], :Cont)
    push!(data[:colnames], name)
end

function parsevariabletype!(data, line, cat)
    items = tokenize(line)
    for v in items
        i = getvariableindex!(data, v)
        data[:colcat][i] = cat
    end
end
parsesection!(::Type{Val{:integer}}, data, line) = parsevariabletype!(data, line, :Int)
parsesection!(::Type{Val{:binary}}, data, line)  = parsevariabletype!(data, line, :Bin)

function parsefloat(val::String)
    if lowercase(val) == "-inf" || lowercase(val) == "-infinity"
        return -Inf
    elseif lowercase(val) == "+inf" || lowercase(val) == "+infinity"
        return Inf
    else
        return parse(Float64, val)
    end
end
function tokenize(line)
    items = String.(split(line, " "))
    items[items .!= ""]
end
bounderror(line) = error("Unable to parse bound: $(line)")
function parsesection!(::Type{Val{:bounds}}, data, line)
    items = tokenize(line)
    v = ""
    lb = -Inf
    ub = Inf
    if length(items) == 5 # ranged bound
        v = items[3]
        if (items[2] == "<=" || items[2] == "<") &&  (items[4] == "<=" || items[4] == "<") # le
            lb = parsefloat(items[1])
            ub = parsefloat(items[5])
        elseif (items[2] == ">=" || items[2] == ">") &&  (items[4] == ">=" || items[4] == ">") # ge
            lb = parsefloat(items[5])
            ub = parsefloat(items[1])
        else
            bounderror(line)
        end
    elseif length(items) == 3 # one sided
        v = items[1]
        if items[2] == "<=" || items[2] == "<" # le
            ub = parsefloat(items[3])
        elseif items[2] == ">=" || items[2] == ">" # ge
            lb = parsefloat(items[3])
        elseif items[2] == "==" || items[2] == "=" # eq
            lb = ub = parsefloat(items[3])
        else
            bounderror(line)
        end
    elseif length(items) == 2 # free
        if items[2] != "free"
            bounderror(line)
        end
        v = items[1]
    else
        bounderror(line)
    end
    i = getvariableindex!(data, v)
    data[:collb][i] = lb
    data[:colub][i] = ub

end


# This needs actual performance testing.
@generated function Chunk(::Val{N}) where N
    if 4N <= VectorizationBase.REGISTER_SIZE
        P = N
    else
        p1 = round(Int, 4N / VectorizationBase.REGISTER_SIZE, RoundUp)
        P = round(Int, N / p1, RoundUp)
    end
    ForwardDiff.Chunk{P}()
end
@generated ValP1(::Val{N}) where N = Val{N+1}()
@generated ValM1(::Val{N}) where N = Val{N-1}()

mutable struct GradientDiffResult{V,P,R} <: DiffResults.DiffResult{1,V,Tuple{ConstantFixedSizeVector{P,V,R,R}}}
    grad::ConstantFixedSizeVector{P,V,R,R}
    value::V
    function GradientDiffResult{V,P,R}(::UndefInitializer) where {V, P, R}
        new{V,P,R}()
    end
    @generated function GradientDiffResult{V,P}(::UndefInitializer) where {V, P}
        R = PaddedMatrices.calc_padding(P, V)
        :(GradientDiffResult{$V,$P,$R}(undef))
    end
    function GradientDiffResult(g::ConstantFixedSizeVector{P,V,R,R}, v::V = zero(V)) where {P,V,R}
        GradientDiffResult{P,V,R}(g, v)
    end
end
mutable struct HessianDiffResult{V,P,R,L} <: DiffResults.DiffResult{2,V,Tuple{ConstantFixedSizeVector{P,V,R,R},ConstantFixedSizeMatrix{P,P,V,R,L}}}
    grad::ConstantFixedSizeVector{P,V,R,R}
    hess::ConstantFixedSizeMatrix{P,P,V,R,L}
    value::V
    function HessianDiffResult{V,P,R,L}(::UndefInitializer) where {V,P,R,L}
        new{V,P,R,L}()
    end
    @generated function HessianDiffResult{V,P}(::UndefInitializer) where {V, P}
        N,R,L = PaddedMatrices.calc_NPL((P,P), V)
        :(HessianDiffResult{$V,$P,$R,$L}(undef))
    end
    function GradientDiffResult(g::ConstantFixedSizeVector{P,V,R,R}, H::ConstantFixedSizeMatrix{P,P,V,R,L}, v::V = zero(V)) where {P,V,R,L}
        GradientDiffResult{P,V,R,R}(g, H, v)
    end
end
const GradResult{V,P,R} = Union{GradientDiffResult{V,P,R},HessianDiffResult{V,P,R}}
@inline Base.pointer(result::GradResult{V}) where {V} = Base.unsafe_convert(Ptr{V}, pointer_from_objref(result))
@inline DiffResults.derivative(result::GradResult{V,P,R}) where {V,P,R} = PtrVector{P,V,R}(pointer(result))
@inline DiffResults.derivative(result::GradResult{V,P,R}, ::Type{Val{1}}) where {V,P,R} = PtrVector{P,V,R}(pointer(result))
@inline DiffResults.derivative(result::HessianDiffResult{V,P,R}, ::Type{Val{2}}) where {V,P,R} = PtrVector{P,V,R}(pointer(result) + sizeof(V)*R )
@inline DiffResults.gradient(result::GradResult{V,P,R}) where {V,P,R} = PtrVector{P,V,R}(pointer(result))
@inline DiffResults.hessian(result::HessianDiffResult{V,P,R})  where {V,P,R}= PtrMatrix{P,P,V,R}(pointer(result) + sizeof(V)*R )

@inline DiffResults.value!(r::GradResult, x::Number) = (r.value = x; return r)
# @inline DiffResults.value!(r::GradResult, x::AbstractArray) = (copyto!(value(r), x); return r)
@inline DiffResults.value!(f::F, r::GradResult, x::Number) where {F} = (r.value = f(x); return r)
# @inline DiffResults.value!(f, r::GradResult, x::AbstractArray) = (map!(f, value(r), x); return r)

function DiffResults.derivative!(r::GradResult, x::AbstractArray)
    copyto!(DiffResults.gradient(r), x)
    return r
end
function DiffResults.derivative!(r::GradResult, x::AbstractArray, ::Type{Val{1}})
    copyto!(DiffResults.gradient(r), x)
    return r
end
function DiffResults.derivative!(r::HessianDiffResult, x::AbstractArray, ::Type{Val{2}})
    copyto!(DiffResults.hessian(r), x)
    return r
end
function DiffResults.derivative!(r::GradResult{P,V,R}, x::ConstantFixedSizeVector{P,V,R}) where {P,V,R}
    r.grad = x
    return r
end
function DiffResults.derivative!(r::GradResult{P,V,R}, x::ConstantFixedSizeVector{P,V,R}, ::Type{Val{1}}) where {P,V,R}
    r.grad = x
    return r
end
function DiffResults.derivative!(r::HessianDiffResult{P,V,R,L}, x::ConstantFixedSizeMatrix{P,P,V,R,L}, ::Type{Val{2}}) where {P,V,R,L}
    r.hess = x
    return r
end
# function DiffResults.derivative!(f, r::GradResult, x::Number)
#     r.grad = f(x)
#     return r
# end
# function DiffResults.derivative!(f, r::GradResult, x::AbstractArray)
#     map!(f, r.grad, x)
#     return r
# end

function GradientDiffResult(x::SizedVector{P,T}) where {T,P}
    GradientDiffResult{T,P}(undef)
end
function HessianDiffResult(x::SizedVector{P,T}) where {T,P}
    HessianDiffResult{T,P}(undef)
    # HessianDiffResult(zero(T), similar(x), FixedSizeMatrix{P,P,T}(undef))
end
# function HessianDiffResult(x::MVector{P,T}) where {T,P}
#     HessianDiffResult{T,P}(undef)
#     # HessianDiffResult(zero(T), similar(x), MMatrix{P,P,T}(undef))
# end

abstract type AbstractAutoDiffDifferentiable{P,T} <: AbstractDifferentiableObject{P,T} end
abstract type AbstractOnceDifferentiableObject{P,T} <: AbstractAutoDiffDifferentiable{P,T} end
abstract type AbstractTwiceDifferentiableObject{P,T} <: AbstractAutoDiffDifferentiable{P,T} end
abstract type Configuration{P,V,F} end

struct GradientConfiguration{P,V,F,T,ND,DG,R} <: Configuration{P,V,F}
    f::F
    result::GradientDiffResult{V,P,R}
    gconfig::ForwardDiff.GradientConfig{T,V,ND,DG}
end

struct HessianConfiguration{P,V,F,T,T2,ND,DJ,DG,DG2, R_V, R_D, LSQM} <: Configuration{P,V,F}
    f::F
    result::HessianDiffResult{V,P,R_V,LSQM}
    inner_result::GradientDiffResult{ForwardDiff.Dual{T,V,ND},P,R_D}
    jacobian_config::ForwardDiff.JacobianConfig{T,V,ND,DJ}
    gradient_config::ForwardDiff.GradientConfig{T,ForwardDiff.Dual{T,V,ND},ND,DG}
    gconfig::ForwardDiff.GradientConfig{T2,V,ND,DG2}
end

struct TwiceDifferentiable{P,T,F,C<:Configuration{P,T,F}, SV_V <: SizedVector{P,T}} <: AbstractTwiceDifferentiableObject{P,T}
    x_f::SV_V # x used to evaluate f (stored in F)
    x_df::SV_V # x used to evaluate df (stored in DF)
    x_h::SV_V #??
    config::C
end
struct OnceDifferentiable{P,T,F,C<:GradientConfiguration{P,T,F}, SV_V <: SizedVector{P,T}} <: AbstractOnceDifferentiableObject{P,T}
    x_f::SV_V# x used to evaluate f (stored in F)
    x_df::SV_V # x used to evaluate df (stored in DF)
    config::C
end

function GradientConfiguration(f::F, x::SizedVector{P,T}) where {F,T,P}
    result = GradientDiffResult(x)
    chunk = Chunk(Val{P}())
    tag = ForwardDiff.Tag(f, T)
    gconfig = ForwardDiff.GradientConfig(f, x, chunk, tag)

    GradientConfiguration(f, result, gconfig)
end
extract_tag(::T) where {T} = T
function HessianConfiguration(f::F, x::SizedVector{P,T}) where {F,T,P}
    result = HessianDiffResult(x)
    chunk = Chunk(Val{P}())
    # tag = ForwardDiff.Tag(f, T)

    # here we construct a JacobianConfig so that yduals is a PtrVector to DiffResults.gradient(inner_result)
    tag_instance = ForwardDiff.Tag(f, T)
    tag_type = extract_tag(tag_instance)
    # @show tag
    seeds = ForwardDiff.construct_seeds(ForwardDiff.Partials{P,T})
    # inner_result = GradientDiffResult(jacobian_config.duals[2])
    inner_result = GradientDiffResult{ForwardDiff.Dual{tag_type,T,P},P}(undef)
    duals = (DiffResults.gradient(inner_result), similar(x, ForwardDiff.Dual{tag_type,T,P}))
    jacobian_config = ForwardDiff.JacobianConfig{tag_type,T,P,typeof(duals)}(seeds, duals)

    # jacobian_config = ForwardDiff.JacobianConfig((f,ForwardDiff.gradient), DiffResults.gradient(result), x, chunk, tag)
    # jacobian_config = ForwardDiff.JacobianConfig((f,ForwardDiff.gradient), DiffResults.gradient(result), x, chunk, tag)
    gradient_config = ForwardDiff.GradientConfig(f, jacobian_config.duals[2], chunk, tag_instance)

    gconfig = ForwardDiff.GradientConfig(f, x, chunk, tag_instance)

    HessianConfiguration(f, result, inner_result, jacobian_config, gradient_config, gconfig)
end

TwiceDifferentiable(f::F, ::Val{P}) where {F,P} = TwiceDifferentiable(f, FixedSizeVector{P,Float64}(undef))
function TwiceDifferentiable(f::F, x::SV_V) where {F,T,P,SV_V <: SizedVector{P,T}}
    TwiceDifferentiable(x, similar(x), similar(x), HessianConfiguration(f, x))
end
function TwiceDifferentiable(x_f::SV_V,x_df::SV_V,x_h::SV_V,config::C) where {T,F,C<:Configuration{T,F},P,SV_V <: SizedVector{P,T}}
    TwiceDifferentiable{P,T,F,C}(x_f, x_df, x_h, config)
end

function OnceDifferentiable(f, x_f::SizedVector{P,T}) where {T,P}
    OnceDifferentiable(x_f, similar(x_f), GradientConfiguration(f, x_f))
end
function OnceDifferentiable(x_f::SV_V, config::C) where {T,F,C<:GradientConfiguration{T,F},P,SV_V <: SizedVector{P,T}}
    OnceDifferentiable{P,T,F,C}(x_f, similar(x_f), config)
end

# ProfileDifferentiable(f::F, ::Val{N}) where {F,N} = ProfileDifferentiable(f, Vector{Float64}(undef, N), Val{N}())
# function ProfileDifferentiable(f::F, x::AbstractArray{T}, ::Val{N}) where {F,T,N}
#     x_f = Vector{T}(undef, N-1)
#     ProfileDifferentiable(x_f, similar(x_f), similar(x_f), Configuration(f, x_f, ValM1(Val{N}())), x, Ref(N), Ref{T}(),::Val{N})
# end
# function ProfileDifferentiable(x_f::Vector{T},x_df::Vector{T},x_h::Vector{T}, config::C, x,::A i::RefValue{Int}, v::RefValue{T},::Val{N}) where {T,A<:AbstractArray{T},C,N}
#     ProfileDifferentiable{N,T,A,C}(x_f, x_df, x_h, config, x, i, v)
# end

@inline DiffResults.value!(obj::AbstractAutoDiffDifferentiable, x::Real) = DiffResults.value!(obj.config.result, x)

@inline value(obj::AbstractAutoDiffDifferentiable) = obj.config.result.value
@inline gradient(obj::AbstractAutoDiffDifferentiable) = DiffResults.gradient(obj.config.result)
# @inline gradient(obj::AbstractAutoDiffDifferentiable, i::Integer) = DiffResults.gradient(obj.config.result)[i]
@inline hessian(obj::TwiceDifferentiable) = DiffResults.hessian(obj.config.result)

@inline f(obj::AbstractAutoDiffDifferentiable, x) = obj.config.f(x)

# """
# For DynamicHMC support. Copies data.
# """
# @inline function (obj::OnceDifferentiable)(x)
#     fdf(obj, x)
#     DiffResults.ImmutableDiffResult(-obj.config.result.value, (-1 .* obj.config.result.grad,))
# end
# @inline function (obj::TwiceDifferentiable)(x)
#     fdf(obj, x)
#     DiffResults.ImmutableDiffResult(-obj.config.result.value, (-1 .* obj.config.result.grad,))
# end

@inline function df(obj::AbstractAutoDiffDifferentiable, x)
    ForwardDiff.gradient!(gradient(obj), obj.config.f, x, obj.config.gconfig, Val{false}())
end

@inline function fdf(obj::AbstractAutoDiffDifferentiable, x)
    # obj.config.result.grad = gradient(obj)
    ForwardDiff.gradient!(obj.config.result, obj.config.f, x, obj.config.gconfig, Val{false}())
    # DiffResults.value(obj.config.result)
    obj.config.result.value
end
@inline function scale_fdf(obj::AbstractAutoDiffDifferentiable, x, scale_target)
    # obj.config.result.grad = gradient(obj)
    scale = scale_gradient!(obj.config.result, obj.config.f, x, scale_target, obj.config.gconfig, Val{false}())
    # DiffResults.value(obj.config.result)
    obj.config.result.value, scale
end
@inline function scaled_fdf(obj::AbstractAutoDiffDifferentiable, x, scale)
    # obj.config.result.grad = gradient(obj)
    scaled_gradient!(obj.config.result, obj.config.f, x, scale, obj.config.gconfig, Val{false}())
    # DiffResults.value(obj.config.result)
    obj.config.result.value
end

# function Configuration(f::F, x::AbstractArray{V}, chunk::Chunk = Chunk(x), tag = Tag(f, V)) where {F,V}
#     result = DiffResults.HessianResult(x)
#     jacobian_config = ForwardDiff.JacobianConfig((f,gradient), DiffResults.gradient(result), x, chunk, tag)
#     gradient_config = ForwardDiff.GradientConfig(f, jacobian_config.duals[2], chunk, tag)
#     inner_result = DiffResults.DiffResult(zero(eltype(jacobian_config.duals[2])), jacobian_config.duals[2])
#     gconfig = ForwardDiff.GradientConfig(f, x, chunk, tag)
#     Configuration(f, result, inner_result, jacobian_config, gradient_config, gconfig)
# end

@inline function (c::HessianConfiguration)(y, z)
    # c.inner_result.grad = y #Already true?
    # @show typeof(y), typeof(z)
    ForwardDiff.gradient!(c.inner_result, c.f, z, c.gradient_config, Val{false}())
    DiffResults.value!(c.result, ForwardDiff.value(DiffResults.value(c.inner_result)))
    # copyto!(y, DiffResults.gradient(c.inner_result))
    y
end
@inline function hessian!(c::HessianConfiguration, x::AbstractArray)
    ForwardDiff.jacobian!(DiffResults.hessian(c.result), c, DiffResults.gradient(c.result), x, c.jacobian_config, Val{false}())
    # DiffResults.hessian(c.result)
    DiffResults.hessian(c.result)
end

"""
Force (re-)evaluation of the objective value at `x`.

Returns `f(x)` and stores the value in `obj.F`
"""
@inline function value!!(obj::AbstractAutoDiffDifferentiable, x)
    # obj.f_calls .+= 1
    # copyto!(obj.x_f, x)
    DiffResults.value!(obj, f(obj, x) )
    value(obj)
end
# """
# Evaluates the objective value at `x`.
#
# Returns `f(x)`, but does *not* store the value in `obj.F`
# """
# @inline function value(obj::AbstractAutoDiffDifferentiable, x)
#     if x != obj.x_f
#         # obj.f_calls .+= 1
#         value!!(obj, x)
#     end
#     value(obj)
# end
"""
Evaluates the objective value at `x`.

Returns `f(x)` and stores the value in `obj.F`
"""
@inline function value!(obj::AbstractAutoDiffDifferentiable, x)
    # if x != obj.x_f
        value!!(obj, x)
    # end
    value(obj)
end

"""
Evaluates the gradient value at `x`

This does *not* update `obj.DF`.
"""
@inline function gradient(obj::AbstractAutoDiffDifferentiable, x)
    DF = gradient(obj)
    gradient!!(obj, x)
    DF
end
"""
Evaluates the gradient value at `x`.

Stores the value in `obj.DF`.
"""
@inline function gradient!(obj::AbstractAutoDiffDifferentiable, x)
    # if x != obj.x_df
        gradient!!(obj, x)
    # end
    gradient(obj)
end
"""
Force (re-)evaluation of the gradient value at `x`.

Stores the value in `obj.DF`.
"""
@inline function gradient!!(obj::AbstractAutoDiffDifferentiable, x)
    # obj.df_calls .+= 1
    copyto!(obj.x_df, x)
    df(obj, x)
end

@inline function value_gradient!(obj::AbstractAutoDiffDifferentiable, x)
    # if x != obj.x_f && x != obj.x_df
        value_gradient!!(obj, x)
    # elseif x != obj.x_f
    #     value!!(obj, x)
    # elseif x != obj.x_df
    #     gradient!!(obj, x)
    # end
    value(obj)
end
@inline function value_gradient!!(obj::AbstractAutoDiffDifferentiable, x)
    # obj.f_calls .+= 1
    # obj.df_calls .+= 1
    # copyto!(obj.x_f, x)
    # copyto!(obj.x_df, x)
    DiffResults.value!(obj, fdf(obj, x))
end

@inline function hessian!(obj::AbstractAutoDiffDifferentiable, x)
    # if x != obj.x_h
        hessian!!(obj, x)
    # end
    nothing
end
@inline function hessian!!(obj::AbstractAutoDiffDifferentiable, x)
    # obj.h_calls .+= 1
    # copyto!(obj.x_h, x)
    hessian!(obj.config, x)
end

# function ForwardDiff.vector_mode_dual_eval(f::F, x::FixedSizeVector, cfg::Union{JacobianConfig,GradientConfig}) where F
#     xdual = cfg.duals
#     seed!(xdual, x, cfg.seeds)
#     return f(xdual)
# end

@inline function ForwardDiff.seed!(duals::AbstractMutableFixedSizeVector{P,ForwardDiff.Dual{T,V,N}}, x,
                                    seed::ForwardDiff.Partials{N,V}) where {T,V,N,P}
    @inbounds for i in 1:N
        duals[i] = ForwardDiff.Dual{T,V,N}(x[i], seed)
    end
    return duals
end

@inline function ForwardDiff.seed!(duals::AbstractMutableFixedSizeVector{P,ForwardDiff.Dual{T,V,N}}, x,
                                    seeds::NTuple{N,ForwardDiff.Partials{N,V}}) where {T,V,N,P}
    @inbounds for i in 1:N
        duals[i] = ForwardDiff.Dual{T,V,N}(x[i], seeds[i])
    end
    return duals
end

@inline function ForwardDiff.seed!(duals::AbstractMutableFixedSizeVector{P,ForwardDiff.Dual{T,V,N}}, x, index,
                                    seed::ForwardDiff.Partials{N,V} = zero(ForwardDiff.Partials{N,V})) where {T,V,N,P}
    offset = index - 1
    @inbounds for i in 1:N
        j = i + offset
        duals[j] = ForwardDiff.Dual{T,V,N}(x[j], seed)
    end
    return duals
end

@inline function ForwardDiff.seed!(duals::AbstractMutableFixedSizeVector{P,ForwardDiff.Dual{T,V,N}}, x, index,
                                    seeds::NTuple{N,ForwardDiff.Partials{N,V}}, chunksize = N) where {T,V,N,P}
    offset = index - 1
    @inbounds for i in 1:chunksize
        j = i + offset
        duals[j] = ForwardDiff.Dual{T,V,N}(x[j], seeds[i])
    end
    return duals
end
function ForwardDiff.extract_jacobian!(::Type{T}, result::AbstractMutableFixedSizeMatrix{M,N}, ydual::AbstractArray, n) where {M,N,T}
    # out_reshaped = reshape(result, length(ydual), n)
    # @inbounds for col in 1:size(out_reshaped, 2), row in 1:size(out_reshaped, 1)
    #     out_reshaped[row, col] = partials(T, ydual[row], col)
    # end
    @inbounds for col in 1:N, row in 1:M
        # result[row, col] = ForwardDiff.partials(T, ydual[row], col)
        result[row, col] = ForwardDiff.partials(T, ydual[col], row)
    end
    return result
end