### BasicContUnvParameter

# Guidelines for usage of inner constructors of continuous parameter types:
# 1) Function fields have higher priority than implicitly derived definitions via the pdf field
# 2) Target-related fields have higher priority than implicitly derived likelihood+prior fields
# 3) Upto-related fields have higher priority than implicitly derived Function tuples

### BasicContUnvParameter

mutable struct BasicContUnvParameter <: Parameter{Continuous, Univariate}
  key::Symbol
  index::Integer
  pdf::Union{ContinuousUnivariateDistribution, Void}
  prior::Union{ContinuousUnivariateDistribution, 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}
  diffmethods::Union{DiffMethods, Void}
  diffopts::Union{DiffOptions, Void}
  states::VariableStateVector
  state::ParameterState{Continuous, Univariate}

  function BasicContUnvParameter(
    key::Symbol,
    index::Integer,
    pdf::Union{ContinuousUnivariateDistribution, Void},
    prior::Union{ContinuousUnivariateDistribution, 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},
    diffmethods::Union{DiffMethods, Void},
    diffopts::Union{DiffOptions, Void},
    states::VariableStateVector,
    state::ParameterState{Continuous, Univariate}
  )
    args = (setpdf, setprior, ll, lp, lt, gll, glp, glt, tll, tlp, tlt, dtll, dtlp, dtlt, uptoglt, uptotlt, uptodtlt)
    fnames = fieldnames(BasicContUnvParameter)[5:21]

    # Check that all generic functions have correct signature
    for i in 1:17
      if isa(args[i], Function) &&
        isa(args[i], Function) &&
        !method_exists(args[i], (BasicContUnvParameterState, 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,
      diffmethods,
      diffopts,
      states,
      state
    )
  end
end

function BasicContUnvParameter!(
  parameter::BasicContUnvParameter,
  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)
        (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
          setfield!(parameter, distribution, args[i](_state, _states))
      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)
        _state::BasicContUnvParameterState -> args[i](_state, parameter.states)
      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)
        _state::BasicContUnvParameterState -> args[i](_state, parameter.states)
      else
        if (
            isa(parameter.prior, ContinuousUnivariateDistribution) &&
            method_exists(f, (typeof(parameter.prior), eltype(parameter.prior)))
          ) ||
          isa(args[2], Function)
          _state::BasicContUnvParameterState -> setfield!(_state, spfield, f(getfield(parameter, :prior), _state.value))
        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)
        _state::BasicContUnvParameterState -> args[i](_state, parameter.states)
      else
        if isa(args[i-2], Function) && isa(getfield(parameter, ppfield), Function)
          function (_state::BasicContUnvParameterState)
            getfield(parameter, plfield)(_state)
            getfield(parameter, ppfield)(_state)
            setfield!(_state, stfield, getfield(_state, slfield)+getfield(_state, spfield))
          end
        elseif (
            isa(parameter.pdf, ContinuousUnivariateDistribution) &&
            method_exists(f, (typeof(parameter.pdf), eltype(parameter.pdf)))
          ) ||
          isa(args[1], Function)
          _state::BasicContUnvParameterState -> setfield!(_state, stfield, f(getfield(parameter, :pdf), _state.value))
        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)
        _state::BasicContUnvParameterState -> args[i](_state, parameter.states)
      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)
        _state::BasicContUnvParameterState -> args[i](_state, parameter.states)
      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)
        _state::BasicContUnvParameterState -> args[i](_state, parameter.states)
      else
        if isa(args[i-2], Function) && isa(args[i-1], Function)
          function (_state::BasicContUnvParameterState)
            getfield(parameter, plfield)(_state)
            getfield(parameter, ppfield)(_state)
            setfield!(_state, stfield, getfield(_state, slfield)+getfield(_state, spfield))
          end
        else
          nothing
        end
      end
    )
  end

  # Define uptogradlogtarget!
  setfield!(
    parameter,
    :uptogradlogtarget!,
    if isa(args[15], Function)
      _state::BasicContUnvParameterState -> args[15](_state, parameter.states)
    else
      if isa(parameter.logtarget!, Function) && isa(parameter.gradlogtarget!, Function)
        function (state::BasicContUnvParameterState)
          parameter.logtarget!(state)
          parameter.gradlogtarget!(state)
        end
      else
        nothing
      end
    end
  )

  # Define uptotensorlogtarget!
  setfield!(
    parameter,
    :uptotensorlogtarget!,
    if isa(args[16], Function)
      _state::BasicContUnvParameterState -> args[16](_state, parameter.states)
    else
      if isa(parameter.logtarget!, Function) &&
        isa(parameter.gradlogtarget!, Function) &&
        isa(parameter.tensorlogtarget!, Function)
        function (state::BasicContUnvParameterState)
          parameter.logtarget!(state)
          parameter.gradlogtarget!(state)
          parameter.tensorlogtarget!(state)
        end
      else
        nothing
      end
    end
  )

  # Define uptodtensorlogtarget!
  setfield!(
    parameter,
    :uptodtensorlogtarget!,
    if isa(args[17], Function)
      _state::BasicContUnvParameterState -> args[17](_state, parameter.states)
    else
      if isa(parameter.logtarget!, Function) &&
        isa(parameter.gradlogtarget!, Function) &&
        isa(parameter.tensorlogtarget!, Function) &&
        isa(parameter.dtensorlogtarget!, Function)
        function (state::BasicContUnvParameterState)
          parameter.logtarget!(state)
          parameter.gradlogtarget!(state)
          parameter.tensorlogtarget!(state)
          parameter.dtensorlogtarget!(state)
        end
      else
        nothing
      end
    end
  )
end

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

function BasicContUnvParameter(
  key::Symbol,
  ::Type{Val{:low}},
  index::Integer=0;
  pdf::Union{ContinuousUnivariateDistribution, Void}=nothing,
  prior::Union{ContinuousUnivariateDistribution, 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,
  diffmethods::Union{DiffMethods, Void}=nothing,
  diffopts::Union{DiffOptions, Void}=nothing,
  states::VariableStateVector=VariableState[],
  state::ParameterState{Continuous, Univariate}=BasicContUnvParameterState()
)
  parameter = BasicContUnvParameter(key, index, pdf, prior, fill(nothing, 17)..., diffmethods, diffopts, states, state)

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

  parameter
end

function BasicContUnvParameter(
  key::Symbol,
  ::Type{Val{:high}},
  index::Integer=0;
  pdf::Union{ContinuousUnivariateDistribution, Void}=nothing,
  prior::Union{ContinuousUnivariateDistribution, 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,
  nkeys::Integer=0,
  vfarg::Bool=false,
  diffopts::Union{DiffOptions, Void}=nothing,
  states::VariableStateVector=VariableState[],
  state::ParameterState{Continuous, Univariate}=BasicContUnvParameterState()
)
  inargs = (
    setpdf,
    setprior,
    loglikelihood,
    logprior,
    logtarget,
    gradloglikelihood,
    gradlogprior,
    gradlogtarget,
    tensorloglikelihood,
    tensorlogprior,
    tensorlogtarget,
    dtensorloglikelihood,
    dtensorlogprior,
    dtensorlogtarget,
    uptogradlogtarget,
    uptotensorlogtarget,
    uptodtensorlogtarget
  )

  fnames = fieldnames(BasicContUnvParameterState)[2:13]

  if vfarg
    if nkeys > 0
      if diffopts != nothing
        error("In the case of autodiff, if nkeys is not 0, then vfarg must be false")
      end
    else
      error("If vfarg=$vfarg, nkeys must be positive, got $nkeys")
    end
  else
    @assert nkeys >= 0 "If vfarg=$vfarg, nkeys must be non-negative, got $nkeys"
  end

  if diffopts != nothing
    if diffopts.mode != :forward
      error("Only :forward mode autodiff is available for univariate parameters, got $autodiff mode")
    end

    if diffopts.order != 1
      error("Derivative order must be 1, got order=$order")
    end
  end

  if diffopts != nothing
    for i in 3:5
      if isa(inargs[i], Function) && !isa(inargs[i+3], Function)
        diffopts.targets[i-2] = true
      end
    end
  end

  parameter = BasicContUnvParameter(key, index, pdf, prior, fill(nothing, 17)..., DiffMethods(), diffopts, states, state)

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

  if vfarg
    for i in 1:2
      if isa(inargs[i], Function)
        outargs[i] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
          inargs[i](Any[s.value for s in _states])
      end
    end

    for i in 3:14
      if isa(inargs[i], Function)
        outargs[i] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
          setfield!(_state, fnames[i-2], inargs[i](Any[s.value for s in _states]))
      end
    end

    if isa(inargs[15], Function)
      outargs[15] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
        (_state.logtarget, _state.gradlogtarget) = inargs[15](Any[s.value for s in _states])
    end

    if isa(inargs[16], Function)
      outargs[16] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
        (_state.logtarget, _state.gradlogtarget, _state.tensorlogtarget) = inargs[16](Any[s.value for s in _states])
    end

    if isa(inargs[17], Function)
      outargs[17] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
        (_state.logtarget, _state.gradlogtarget, _state.tensorlogtarget, _state.dtensorlogtarget) =
          inargs[17](Any[s.value for s in _states])
    end
  else
    if nkeys > 0
      for i in 1:2
        if isa(inargs[i], Function)
          outargs[i] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
            inargs[i](_state.value, Any[s.value for s in _states])
        end
      end

      for i in 3:14
        if isa(inargs[i], Function)
          outargs[i] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
            setfield!(_state, fnames[i-2], inargs[i](_state.value, Any[s.value for s in _states]))
        end
      end

      if isa(inargs[15], Function)
        outargs[15] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
          (_state.logtarget, _state.gradlogtarget) = inargs[15](_state.value, Any[s.value for s in _states])
      end

      if isa(inargs[16], Function)
        outargs[16] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
          (_state.logtarget, _state.gradlogtarget, _state.tensorlogtarget) =
            inargs[16](_state.value, Any[s.value for s in _states])
      end

      if isa(inargs[17], Function)
        outargs[17] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
          (_state.logtarget, _state.gradlogtarget, _state.tensorlogtarget, _state.dtensorlogtarget) =
            inargs[17](_state.value, Any[s.value for s in _states])
      end
    else
      for i in 1:2
        if isa(inargs[i], Function)
          outargs[i] = (_state::BasicContUnvParameterState, _states::VariableStateVector) -> inargs[i](_state.value)
        end
      end

      for i in 3:14
        if isa(inargs[i], Function)
          outargs[i] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
            setfield!(_state, fnames[i-2], inargs[i](_state.value))
        end
      end

      if isa(inargs[15], Function)
        outargs[15] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
          (_state.logtarget, _state.gradlogtarget) = inargs[15](_state.value)
      end

      if isa(inargs[16], Function)
        outargs[16] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
          (_state.logtarget, _state.gradlogtarget, _state.tensorlogtarget) = inargs[16](_state.value)
      end

      if isa(inargs[17], Function)
        outargs[17] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
          (_state.logtarget, _state.gradlogtarget, _state.tensorlogtarget, _state.dtensorlogtarget) =
            inargs[17](_state.value)
      end
    end
  end

  for (i, j, field, distribution, f) in ((4, 2, :logprior, :prior, logpdf), (5, 1, :logtarget, :pdf, logpdf))
    if !isa(inargs[i], Function) &&
      (
        (
          isa(getfield(parameter, distribution), ContinuousUnivariateDistribution) &&
          method_exists(f, (typeof(getfield(parameter, distribution)), eltype(getfield(parameter, distribution))))
        ) ||
        isa(inargs[j], Function)
      )
      outargs[i] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
        setfield!(_state, field, f(getfield(parameter, distribution), _state.value))
    end
  end

  if diffopts != nothing
    for (i, field) in ((3, :closurell), (4, :closurelp), (5, :closurelt))
      if isa(inargs[i], Function)
        setfield!(
          parameter.diffmethods,
          field,
          nkeys == 0 ? inargs[i] : _x::Real -> inargs[i](_x, Any[s.value for s in parameter.states])
        )
      end
    end

    for (i, returnname, diffresult, diffmethod) in (
      (6, :gradloglikelihood, :resultll, :closurell),
      (7, :gradlogprior, :resultlp, :closurelp),
      (8, :gradlogtarget, :resultlt, :closurelt)
    )
      if !isa(inargs[i], Function) && isa(inargs[i-3], Function)
        outargs[i] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
          setfield!(
            _state,
            returnname,
            forward_autodiff_derivative(
              getfield(_state.diffstate, diffresult), getfield(_state.diffmethods, diffmethod), _state.value
            )
          )
      end
    end

    if !isa(inargs[15], Function) && isa(inargs[5], Function)
      outargs[15] = (_state::BasicContUnvParameterState, _states::VariableStateVector) ->
        (_state.logtarget, _state.gradlogtarget) =
          forward_autodiff_upto_derivative(_state.diffstate.resultlt, _state.diffmethods.closurelt, _state.value)
    end
  end

  BasicContUnvParameter!(parameter, outargs...)

  parameter
end

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

variate_form(::Type{BasicContUnvParameter}) = Univariate
variate_form(::BasicContUnvParameter) = Univariate

default_state_type(::BasicContUnvParameter) = BasicContUnvParameterState

default_state(variable::BasicContUnvParameter, value::N, outopts::Dict) where {N<:Real} =
  BasicContUnvParameterState(
    value,
    (haskey(outopts, :diagnostics) && in(:accept, outopts[:diagnostics])) ? [:accept] : Symbol[],
    variable.diffmethods,
    variable.diffopts
  )

show(io::IO, ::Type{BasicContUnvParameter}) = print(io, "BasicContUnvParameter")


export iauApio13
"""
For a terrestrial observer, prepare star-independent astrometry
parameters for transformations between CIRS and observed
coordinates.  The caller supplies UTC, site coordinates, ambient air
conditions and observing wavelength.

This function is part of the International Astronomical Union's
SOFA (Standards of Fundamental Astronomy) software collection.

Status:  support function.

Given:
utc1   double      UTC as a 2-part...
utc2   double      ...quasi Julian Date (Notes 1,2)
dut1   double      UT1-UTC (seconds)
elong  double      longitude (radians, east +ve, Note 3)
phi    double      geodetic latitude (radians, Note 3)
hm     double      height above ellipsoid (m, geodetic Notes 4,6)
xp,yp  double      polar motion coordinates (radians, Note 5)
phpa   double      pressure at the observer (hPa = mB, Note 6)
tc     double      ambient temperature at the observer (deg C)
rh     double      relative humidity at the observer (range 0-1)
wl     double      wavelength (micrometers, Note 7)

Returned:
astrom iauASTROM*  star-independent astrometry parameters:
pmt    double       unchanged
eb     double[3]    unchanged
eh     double[3]    unchanged
em     double       unchanged
v      double[3]    unchanged
bm1    double       unchanged
bpn    double[3][3] unchanged
along  double       longitude + s' (radians)
xpl    double       polar motion xp wrt local meridian (radians)
ypl    double       polar motion yp wrt local meridian (radians)
sphi   double       sine of geodetic latitude
cphi   double       cosine of geodetic latitude
diurab double       magnitude of diurnal aberration vector
eral   double       "local" Earth rotation angle (radians)
refa   double       refraction constant A (radians)
refb   double       refraction constant B (radians)

Returned (function value):
        int         status: +1 = dubious year (Note 2)
                                0 = OK
                        -1 = unacceptable date

Notes:

1.  utc1+utc2 is quasi Julian Date (see Note 2), apportioned in any
convenient way between the two arguments, for example where utc1
is the Julian Day Number and utc2 is the fraction of a day.

However, JD cannot unambiguously represent UTC during a leap
second unless special measures are taken.  The convention in the
present function is that the JD day represents UTC days whether
the length is 86399, 86400 or 86401 SI seconds.

Applications should use the function iauDtf2d to convert from
calendar date and time of day into 2-part quasi Julian Date, as
it implements the leap-second-ambiguity convention just
described.

2.  The warning status "dubious year" flags UTCs that predate the
introduction of the time scale or that are too far in the future
to be trusted.  See iauDat for further details.

3)  UT1-UTC is tabulated in IERS bulletins.  It increases by exactly
one second at the end of each positive UTC leap second,
introduced in order to keep UT1-UTC within +/- 0.9s.  n.b. This
practice is under review, and in the future UT1-UTC may grow
essentially without limit.

4.  The geographical coordinates are with respect to the WGS84
reference ellipsoid.  TAKE CARE WITH THE LONGITUDE SIGN:  the
longitude required by the present function is east-positive
(i.e. right-handed), in accordance with geographical convention.

5.  The polar motion xp,yp can be obtained from IERS bulletins.  The
values are the coordinates (in radians) of the Celestial
Intermediate Pole with respect to the International Terrestrial
Reference System (see IERS Conventions 2003), measured along the
meridians 0 and 90 deg west respectively.  For many applications,
xp and yp can be set to zero.

Internally, the polar motion is stored in a form rotated onto
the local meridian.

6.  If hm, the height above the ellipsoid of the observing station
in meters, is not known but phpa, the pressure in hPa (=mB), is
available, an adequate estimate of hm can be obtained from the
expression

        hm = -29.3 * tsl * log ( phpa / 1013.25 );

where tsl is the approximate sea-level air temperature in K
(See Astrophysical Quantities, C.W.Allen, 3rd edition, section
52).  Similarly, if the pressure phpa is not known, it can be
estimated from the height of the observing station, hm, as
follows:

        phpa = 1013.25 * exp ( -hm / ( 29.3 * tsl ) );

Note, however, that the refraction is nearly proportional to the
pressure and that an accurate phpa value is important for
precise work.

7.  The argument wl specifies the observing wavelength in
micrometers.  The transition from optical to radio is assumed to
occur at 100 micrometers (about 3000 GHz).

8.  It is advisable to take great care with units, as even unlikely
values of the input parameters are accepted and processed in
accordance with the models used.

9.  In cases where the caller wishes to supply his own Earth
rotation information and refraction constants, the function
iauApc can be used instead of the present function.

10. This is one of several functions that inserts into the astrom
structure star-independent parameters needed for the chain of
astrometric transformations ICRS <-> GCRS <-> CIRS <-> observed.

The various functions support different classes of observer and
portions of the transformation chain:

        functions         observer        transformation

iauApcg iauApcg13    geocentric      ICRS <-> GCRS
iauApci iauApci13    terrestrial     ICRS <-> CIRS
iauApco iauApco13    terrestrial     ICRS <-> observed
iauApcs iauApcs13    space           ICRS <-> GCRS
iauAper iauAper13    terrestrial     update Earth rotation
iauApio iauApio13    terrestrial     CIRS <-> observed

Those with names ending in "13" use contemporary SOFA models to
compute the various ephemerides.  The others accept ephemerides
supplied by the caller.

The transformation from ICRS to GCRS covers space motion,
parallax, light deflection, and aberration.  From GCRS to CIRS
comprises frame bias and precession-nutation.  From CIRS to
observed takes account of Earth rotation, polar motion, diurnal
aberration and parallax (unless subsumed into the ICRS <-> GCRS
transformation), and atmospheric refraction.

11. The context structure astrom produced by this function is used
by iauAtioq and iauAtoiq.

Called:
iauUtctai    UTC to TAI
iauTaitt     TAI to TT
iauUtcut1    UTC to UT1
iauSp00      the TIO locator s', IERS 2000
iauEra00     Earth rotation angle, IAU 2000
iauRefco     refraction constants for given ambient conditions
iauApio      astrometry parameters, CIRS-observed

This revision:   2013 October 9

SOFA release 2018-01-30

Copyright (C) 2018 IAU SOFA Board.  See notes at end.
"""
function iauApio13(utc1::Real, utc2::Real, dut1::Real,
               elong::Real, phi::Real, hm::Real,
               xp::Real, yp::Real,
               phpa::Real, tc::Real,
               rh::Real, wl::Real)
    # Allocate return value
    ref_astrom = Ref{iauASTROM}(iauASTROM())

    status = ccall((:iauApio13, libsofa_c), Cint,
            (Cdouble, Cdouble, Cdouble, Cdouble,
            Cdouble, Cdouble, Cdouble,
            Cdouble, Cdouble, Cdouble, Cdouble, Cdouble,
            Ref{iauASTROM}),
            convert(Float64, utc1),
            convert(Float64, utc2),
            convert(Float64, dut1),
            convert(Float64, elong),
            convert(Float64, phi),
            convert(Float64, hm),
            convert(Float64, xp),
            convert(Float64, yp),
            convert(Float64, phpa),
            convert(Float64, tc),
            convert(Float64, rh),
            convert(Float64, wl),
            ref_astrom)

    return status, ref_astrom[]
end


const Deckinfo = Array{NamedTuple{(:name, :amount, :prints),Tuple{String,Int64,Array{NamedTuple{(:index, :set, :rarity),Tuple{Int64,Symbol,Int64}},1}}},1}

"""
    SimParameters

Parameters to adjust certain aspects of the simulation:
- `pitytimer_distribution`: Collection of samples determining the distribution of parameters
   used in the pack opening and pitytimers.  See `?PityTimerSamples`
- `wc_timer_track`: Vector representing the WC rarities associated with the timer tracks.  Each pack
   opened advances the position and then resets at the end.  0 is associated with no wildcard, 2 to 4
   correspond to uncommon through mythic.
- `icrs_per_pack`: A `Cycle` of vectors of 4-dimensional categorical distributions representing
   probabilistic ICR rarities associated with each pack, e.g. to simulate doing constructed events
   in between buying packs.  Each pack opened advances to the next vector of distributions, and then an
   ICR rarity is drawn from each distribution in the vector.
- `pack_vault_pct`: Vault percentage associated with opening a pack.  Defaults to 0% as of M19.
- `duplicate_vpct`: Vault percentage associated with an excess copy of a card of each rarity
- `noopen_cards`: Cards that don't show up in packs on account of being promos or banned
- `starter_cards`: Cards from NPE decks assumed to be in everyone's collection relatively quickly
- `bonus_packs`: One-time packs from, e.g. NPE, that don't count against the total packs
"""
@with_kw mutable struct SimParameters
    pitytimer_distribution::PityTimerSamples = pity_timer_samps
    wc_timer_track::Vector{Int} = [0,0,2,0,0,3,0,0,2,0,0,3,0,0,2,0,0,4,0,0,2,0,0,3,0,0,2,0,0,3]
    icrs_per_pack::Cycle{Vector{Categorical{Float64}}} = icrgen_qc(0.5,0,0)
    pack_vault_pct::Float64 = 0.0
    duplicate_vpct::NTuple{4,Float64} = (1/9, 1/3, 5/9, 1+1/9)
    noopen_cards::Vector{Int} = map(x -> first(x.prints).index, deckinfo(deckreader_mtga_format(joinpath(@__DIR__, "..", "data", "noopen_cards.txt"))))
    starter_cards::Vector{Int} = tenstarters
    fixed_starter::Bool = true
    bonus_packs::Dict{Symbol,Int} = Dict( :GRN => 4, :M19 => 5 )
    nextset::Function = next_set!
    wc_upgrade_rate::Vector{Float64} = [Inf,Inf,Inf,Inf]
    wc_upgrade_threshold::Vector{Int} = [20,20,100,0]
    welcome_bundle::Bool = false
    kaladesh_grant::Bool = false
    prevent_duplicates::Bool = false
    starting_wc_count::Vector{Int} = [8,4,2,1]
    welcome_bundle_set::Symbol = :M19
    fixed_pack_track::Cycle{Union{Missing, Symbol}} = Cycle([missing,:GRN,missing,:GRN,missing, :GRN, missing, missing, missing, missing])
    openable_schedule::Dict{Int,Vector{Symbol}} = Dict{Int,Vector{Symbol}}()
    track_collection_progress::Bool = false
    max_track_progress_packs::Int = 500
end

"""
    AccountState

Represents non-card aspects of an account: Vault percentage, wildcard counts,
bonus packs that don't count against output statistics (e.g. from NPE), pitytimer
for pack-based wildcards, and the position on the wildcard timer tracks
"""
@with_kw mutable struct AccountState
    vault_pct::Float64 = 0.0
    wc_count::Vector{Int} = [0,0,0,0]
    bonus_packs::Dict{Symbol,Int} = Dict( (s, 0) for s in sets)
    pitytimer::Vector{Int} = [0,0,0,0]
    wc_timer::Cycle{Int} = Cycle(SimParameters().wc_timer_track, 0)
    fixed_pack_track::Cycle{Union{Missing, Symbol}} = Cycle(SimParameters().fixed_pack_track.data, 0)
end

function AccountState(parameters::SimParameters)
    account = AccountState()
    account.wc_count = deepcopy(parameters.starting_wc_count)

    for set in keys(parameters.bonus_packs)
        account.bonus_packs[set] += parameters.bonus_packs[set]
    end

    account.wc_timer = Cycle(parameters.wc_timer_track, 0)
    account.fixed_pack_track = Cycle(parameters.fixed_pack_track.data,0)

    account
end

"""
    SimOutput

Summarizes simulation output in terms of:
- `packs_opened`: (Non-bonus) packs opened of each set
- `wcs_from_vault`: How many wildcards came from Vault
- `wcs_spent_on_decks`: How many wildcards were spent on decks of each rarity
- `card_sources`: An estimate of card sources for each deck. Array dimensions:
    1. Simulation rep
    2. Deck order
    3. Card rarity (1 = common, ..., 4 = mythic)
    4. Various statistics, corresponding to this 9 'columns' in this dimension:
        1. Total cards of this rarity in deck (non-stochastic)
        2. Amount needed of this rarity before starting this deck
        3. Amount needed of this rarity after finishing this deck (should be 0)
        4. Cards of this rarity in this deck crafted by wildcards during its construction
        5. Cards of this rarity in this deck opened from packs during its construction
        6. Cards of this rarity in this deck opened from ICRs during its construction
        7. As in 4, except during all decks so far
        8. As in 5, except during all decks so far
        9. As in 6, except during all decks so far
- `total_pack_wcs`: Running total across an entire iteration of wildcards for each rarity
- `total_packs`: Running total across an entire iteration of packs, free or not, for each rarity
"""
@with_kw mutable struct SimOutput
    packs_opened::Dict{Symbol,Array{Int,2}}
    bonus_packs_opened::Dict{Symbol,Array{Int,2}}
    wcs_from_vault::Array{Int,2}
    wcs_spent_on_decks::Array{Int,3}
    card_sources::Array{Float64, 4}
    total_pack_wcs::Array{Int,2}
    total_packs::Vector{Int}
    total_earned_packs::Vector{Int}
    ending_collection::Array{Int,2}
    collection_progress::Array{Union{Missing,Int},3}
end

function SimOutput(reps::Int, decks::Int, sets::Vector{Symbol}, collection::Vector{Int})::SimOutput
    SimOutput(
        packs_opened = Dict( (s, zeros(Int, (reps, decks))) for s in sets),
        bonus_packs_opened = Dict( (s, zeros(Int, (reps, decks))) for s in sets),
        wcs_from_vault = zeros(Int, (reps, 4)),
        wcs_spent_on_decks = zeros(Int, (reps, decks, 4)),
        card_sources = zeros(Float64, (reps, decks, 4, 9)),
        total_pack_wcs = zeros(Int, (reps, 4)),
        total_packs = zeros(Int, reps),
        total_earned_packs = zeros(Int, reps),
        ending_collection = zeros(Int, (reps, length(collection))),
        collection_progress = Array{Union{Missing,Int}}(missing, 0,0,0)
    )
end

function copy_into_output!(total::SimOutput, partial::SimOutput; startindex::Int)
    endindex = startindex + size(partial.card_sources,1) - 1
    range = startindex:endindex

    total.wcs_from_vault[range,:] = partial.wcs_from_vault
    total.wcs_spent_on_decks[range,:,:] = partial.wcs_spent_on_decks
    total.card_sources[range,:,:,:] = partial.card_sources
    total.total_pack_wcs[range,:] = partial.total_pack_wcs
    total.total_packs[range] = partial.total_packs
    total.total_earned_packs[range] = partial.total_earned_packs
    total.ending_collection[range,:] = partial.ending_collection

    if length(total.collection_progress) > 0 && length(partial.collection_progress) > 0
        total.collection_progress[range,:,:] = partial.collection_progress
    end

    for set in keys(total.packs_opened)
        total.packs_opened[set][range,:] = partial.packs_opened[set]
    end

    for set in keys(total.bonus_packs_opened)
        total.bonus_packs_opened[set][range,:] = partial.bonus_packs_opened[set]
    end

    endindex + 1
end


# MIT license
# Copyright (c) Microsoft Corporation. All rights reserved.
# See LICENSE in the project root for full license information.

intersectionat(α::Float64) = Intersection(α, [0.7071067811865475, 0.7071067811865475, 0.0] * α, [0.0, 0.0, 0.0], 0.0, 0.0, NullInterface())

const greenwavelength = Unitful.u"550nm"
const redwavelength = Unitful.u"650nm"
const bluewavelength = Unitful.u"450nm"
const roomtemperature = 20 * Unitful.u"°C"

transmissivethingrating(period, orders) = ThinGratingInterface(SVector(0.0, 1.0, 0.0), period, OpticSim.GlassCat.Air, OpticSim.GlassCat.Air, minorder = -orders, maxorder = orders, reflectance = [0.0 for _ in (-orders):orders], transmission = [0.2 for _ in (-orders):orders])
reflectivethingrating(period, orders) = ThinGratingInterface(SVector(0.0, 1.0, 0.0), period, OpticSim.GlassCat.Air, OpticSim.GlassCat.Air, minorder = -orders, maxorder = orders, transmission = [0.0 for _ in (-orders):orders], reflectance = [0.2 for _ in (-orders):orders])

function multiHOE()
    rect = Rectangle(5.0, 5.0, SVector(0.0, 0.0, 1.0), SVector(0.0, 0.0, 0.0))
    inbeam = SVector(0.0, 0.0, -1.0), CollimatedBeam

    int1 = HologramInterface(SVector(-5.0, 0.0, -20.0), ConvergingBeam, SVector(0.0, -1.0, -1.0), CollimatedBeam, 0.55, 100.0, OpticSim.GlassCat.Air, OpticSim.Examples.Examples_N_BK7, OpticSim.GlassCat.Air, OpticSim.GlassCat.Air, OpticSim.GlassCat.Air, 0.05, false)
    int2 = HologramInterface(SVector(5.0, 0.0, -20.0), ConvergingBeam, SVector(0.0, 1.0, -1.0), CollimatedBeam, 0.55, 100.0, OpticSim.GlassCat.Air, OpticSim.Examples.Examples_N_BK7, OpticSim.GlassCat.Air, OpticSim.GlassCat.Air, OpticSim.GlassCat.Air, 0.05, false)

    mint = MultiHologramInterface(int1, int2)
    obj = MultiHologramSurface(rect, mint)
    sys = CSGOpticalSystem(LensAssembly(obj), Rectangle(50.0, 50.0, SVector(0.0, 0.0, 1.0), SVector(0.0, 0.0, -20.0), interface = opaqueinterface()))
    return sys
end


function make_idmap(table; col_user = :userid, col_item = :itemid)
    table, user2uidx = reindex_id_column(table, col_user)
    table, item2iidx = reindex_id_column(table, col_item)
    iidx2item = Dict(iidx => itemid for (itemid, iidx) in item2iidx)
    return table, user2uidx, item2iidx, iidx2item
end

struct StopTrain <: Exception end

abstract type AbstractCallback end

function initialize!(
    cb::AbstractCallback,
    model::AbstractRecommender;
    col_user = :userid,
    col_item = :itemid,
)

    throw("initialize! not implemented.")
end

struct LogTrainLoss <: AbstractCallback end

initialize!(
    cb::LogTrainLoss,
    model::AbstractRecommender;
    col_user = :userid,
    col_item = :itemid,
) = nothing

function (cb::LogTrainLoss)(model::AbstractRecommender, train_loss, epoch, verbose = -1)
    if verbose >= 1 && (epoch % verbose == 0)
        @info "epoch=$epoch: train_loss=$train_loss"
    end
end

"""
    EvaluateValidData(valid_metric::MeanMetric, valid_table, early_stopping_rounds, name = "val_metric")

Callback to monitor the validation metrics during training, and raise `StopTrain` exception if early stopping is requred.

# Constructor arguments
- `valid_metric`: monotring metric. See [Evaluation metrics](@ref) for the available ones.
- `valid_table`: any `Tables.jl`-compatible object for validation dataset.
- `early_stopping_rounds`: If the validation metric does not improve more than this epochs, the early stopping is invoked. If set to be less than 1, no early stopping is applied.
- `name`: metrics name to show on logger.

# Example
Use in the matrix factorizaion training.
```julia
ndcg10 = MeanNDCG(10)
cb = EvaluateValidData(ndcg10, test_table, 1, "val_NDCG")

model = ImplicitMF(16, true, 0.01)
fit!(
    model,
    train_table,
    10,
    callbacks = [cb],
    col_item = :movieid,
    n_epochs = 20,
    n_negatives = 1,
    learning_rate = 0.01,
    verbose = 1,
)
```
"""
mutable struct EvaluateValidData <: AbstractCallback
    valid_metric::MeanMetric
    name::Union{AbstractString,Symbol}
    valid_table::Any
    early_stopping_rounds::Int
    best_epoch::Int
    best_val_metric::Float64
    topk::Int
    val_xs::Any
    val_ys::Any

    EvaluateValidData(
        valid_metric::MeanMetric,
        valid_table,
        early_stopping_rounds,
        name = "val_metric",
    ) = new(
        valid_metric,
        name,
        valid_table,
        early_stopping_rounds,
        1,
        0.0,
        -1,
        nothing,
        nothing,
    )
end

function initialize!(
    cb::EvaluateValidData,
    model::AbstractRecommender;
    col_user = :userid,
    col_item = :itemid,
)
    # TODO: loss as metric
    cb.topk = cb.valid_metric.base_metric.k
    cb.val_xs, cb.val_ys =
        make_u2i_dataset(cb.valid_table, col_user = col_user, col_item = col_item)
    preds = predict_u2i(model, cb.val_xs, cb.topk, drop_history = true)
    cb.best_val_metric = cb.valid_metric(preds, cb.val_ys)
end

function (cb::EvaluateValidData)(
    model::AbstractRecommender,
    train_loss,
    epoch,
    verbose = -1,
)
    preds = predict_u2i(model, cb.val_xs, cb.topk, drop_history = true)
    current_metric = cb.valid_metric(preds, cb.val_ys)

    if current_metric > cb.best_val_metric
        cb.best_epoch = epoch
        cb.best_val_metric = current_metric
    end

    if verbose >= 1 && (epoch % verbose == 0)
        @info "epoch=$epoch: $(cb.name)=$current_metric, best_$(cb.name)=$(cb.best_val_metric), best_epoch=$(cb.best_epoch)"
    end

    if cb.early_stopping_rounds >= 1 &&
       ((epoch - cb.best_epoch) >= cb.early_stopping_rounds)
        throw(StopTrain)
    end
end


using Golgi
using Test

@testset "Golgi.jl" begin
    # Write your tests here.
end

using Enzyme
using ChainRules
Enzyme.pullback(sin, 2.0)(1.0)
@time Enzyme.pullback(tanh, 2.0)(1.0)
ChainRules.rrule(sin, 2.0)[2](1.0)

f(x) = cos(sin(x))

Golgi.rrule_call(f, 2.0)
Golgi.rrule_call(sin, 2.0)

foo(x) = x
ChainRules.rrule(foo, 2.0)

using Enzyme

struct Foo
    x::Float64
end

function (f::Foo)(x::Float64)
    return f.x + x
end

Enzyme.pullback(Foo(1.0), 2.0)(1.0)

@generated function goo(xs...)
    quote
        return $(xs)
    end
end

@generated function foo(x)
    quote
        goo(x)
    end
end

using Zygote

Zygote.pullback(sin, 2.0)
goo(1, 2, 3.0)

function rrule(::Type{f}, args...) where {f <: Operation}
end

function rrule(::Type{typeof(apply!)}, r::AbstractRegister, op::Operation)
    apply!(r, op), function pullback(Δ)
        reverse_r, grad = Δ
        apply!(reverse_r, adjoint(op))
        # ...
        # NOTE: grad_op is of type ∇Operation
        # which is a struct contains same field
        # of Operation, but are gradients of the fields
        return NoTangent(), grad_r, grad_op
    end
end

function rrule(f::Type{F}, args...) where {F <: Operation}
    f(args...), function pullback(Δ::∇Operation)
        return unpack(Δ) # unpack the struct back to a Tuple in argument order
    end
end

# this only works when return value is an expectation or measure
# error when return value is not a Real
# how do we extract expectation loops from function body?
# canonicalize measure accumulation like for _ in 1:1000::UnitRange; measure end? but how
function frule((∇self, ∇r, ∇op), ::Type{typeof(apply!)}, r::AbstractRegister, op::Operation)
    apply!(r, op)
    # ∇r can be NoTangent() if compile target is a quantum device
    # how do we add/sub pi/2 in the IR?
end


using CrystalNets
cifs = joinpath(@__DIR__, "..", "test", "cif")

empty!(ARGS)
push!(ARGS, "-g", "3   1 2  0 0 0   1 2  0 0 1   1 2  0 1 0   1 2  1 0 0")
CrystalNets.julia_main()

empty!(ARGS)
push!(ARGS, joinpath(cifs, "ABW.cif"))
CrystalNets.julia_main()

empty!(ARGS)
push!(ARGS, joinpath(cifs, "RRO.cif"))
CrystalNets.julia_main()

empty!(ARGS)
push!(ARGS, joinpath(cifs, "Moganite.cif"))
CrystalNets.julia_main()

empty!(ARGS)
push!(ARGS, "-c", "mof", joinpath(cifs, "HKUST-1.cif"))
CrystalNets.julia_main()

empty!(ARGS)
push!(ARGS, "-c", "atom", joinpath(cifs, "Diamond.cif"))
CrystalNets.julia_main()

empty!(ARGS)
push!(ARGS, "-g", "3 1 2 0 0 0 1 3 0 0 0 1 4 0 0 0 1 5 0 0 0 2 6 0 0 0 2 7 0 0 0 2 8 0 0 0 3 9 0 0 0 3 10 0 0 0 3 11 0 0 0 4 12 0 0 0 4 13 0 0 0 4 14 0 0 0 5 15 0 0 0 5 16 0 0 0 5 17 0 0 0 6 9 0 0 0 6 18 0 0 0 6 19 0 0 0 7 12 0 0 0 7 20 0 0 0 7 21 0 0 0 8 15 0 0 0 8 22 0 0 0 8 23 0 0 0 9 24 0 0 0 9 25 0 0 0 10 13 0 0 0 10 26 0 0 0 10 27 0 0 0 11 16 0 0 0 11 28 0 0 0 11 29 0 0 0 12 30 0 0 0 12 31 0 0 0 13 32 0 0 0 13 33 0 0 0 14 17 0 0 0 14 34 0 0 0 14 35 0 0 0 15 36 0 0 0 15 37 0 0 0 16 38 0 0 0 16 39 0 0 0 17 40 0 0 0 17 41 0 0 0 18 20 0 0 0 18 42 0 0 0 18 43 0 0 0 18 44 0 0 0 19 22 0 0 0 19 45 0 0 0 19 46 0 0 0 20 47 0 0 0 20 48 0 0 0 20 49 0 0 0 21 23 0 0 0 21 50 0 0 0 21 51 0 0 0 22 52 0 0 0 22 53 0 0 0 23 54 0 0 0 23 55 0 0 0 24 26 0 0 0 24 44 0 0 0 24 56 0 0 0 25 28 0 0 0 25 46 0 0 0 25 57 0 0 0 25 58 0 0 0 26 59 0 0 0 26 60 0 0 0 27 29 0 0 0 27 61 0 0 0 27 62 0 0 0 28 63 0 0 0 28 64 0 0 0 28 65 0 0 0 29 66 0 0 0 29 67 0 0 0 30 32 0 0 0 30 49 0 0 0 30 68 0 0 0 31 34 0 0 0 31 51 0 0 0 31 69 0 0 0 31 70 0 0 0 32 59 0 0 0 32 71 0 0 0 33 35 0 0 0 33 62 0 0 0 33 72 0 0 0 34 73 0 0 0 34 74 0 0 0 34 75 0 0 0 35 76 0 0 0 35 77 0 0 0 36 38 0 0 0 36 53 0 0 0 36 78 0 0 0 37 40 0 0 0 37 55 0 0 0 37 79 0 0 0 38 64 0 0 0 38 80 0 0 0 39 41 0 0 0 39 67 0 0 0 39 81 0 0 0 39 82 0 0 0 40 73 0 0 0 40 83 0 0 0 41 77 0 0 0 41 84 0 0 0 41 85 0 0 0 42 74 -1 -1 0 42 86 0 0 0 42 87 0 0 0 43 45 0 0 0 43 88 0 0 0 43 89 0 0 0 44 90 0 0 0 44 91 0 0 0 45 92 0 0 0 45 93 0 0 0 46 94 0 0 0 46 95 0 0 0 47 63 -1 0 -1 47 86 0 0 0 47 96 0 0 0 48 50 0 0 0 48 89 0 0 0 48 97 0 0 0 49 91 0 0 0 49 98 0 0 0 50 99 0 0 0 50 100 0 0 0 51 101 0 0 0 51 102 0 0 0 52 54 0 0 0 52 93 0 0 0 52 103 0 0 0 53 95 0 0 0 53 104 0 0 0 54 100 0 0 0 54 105 0 0 0 55 102 0 0 0 55 106 0 0 0 56 58 0 0 0 56 107 0 0 0 56 108 0 0 0 57 85 -1 -1 0 57 109 0 0 0 57 110 0 0 0 58 111 0 0 0 58 112 0 0 0 59 91 0 0 0 59 113 0 0 0 60 61 0 0 0 60 108 0 0 0 60 114 0 0 0 61 115 0 0 0 61 116 0 0 0 62 117 0 0 0 62 118 0 0 0 63 97 1 0 1 63 110 0 0 0 64 95 0 0 0 64 119 0 0 0 65 66 0 0 0 65 96 1 0 1 65 112 0 0 0 66 116 0 0 0 66 120 0 0 0 67 118 0 0 0 67 121 0 0 0 68 70 0 0 0 68 120 -1 0 -1 68 122 0 0 0 69 82 -1 0 -1 69 123 0 0 0 69 124 0 0 0 70 125 0 0 0 70 126 0 0 0 71 72 0 0 0 71 122 0 0 0 71 127 0 0 0 72 128 0 0 0 72 129 0 0 0 73 102 0 0 0 73 130 0 0 0 74 88 1 1 0 74 124 0 0 0 75 76 0 0 0 75 87 1 1 0 75 126 0 0 0 76 107 1 1 0 76 129 0 0 0 77 118 0 0 0 77 131 0 0 0 78 79 0 0 0 78 132 0 0 0 78 133 0 0 0 79 134 0 0 0 79 135 0 0 0 80 81 0 0 0 80 99 1 0 1 80 133 0 0 0 81 123 1 0 1 81 136 0 0 0 82 125 1 0 1 82 137 0 0 0 83 84 0 0 0 83 92 1 1 0 83 135 0 0 0 84 109 1 1 0 84 136 0 0 0 85 111 1 1 0 85 137 0 0 0 86 138 0 0 0 86 139 0 0 0 87 90 0 0 0 87 132 -1 0 0 88 115 -1 -1 -1 88 130 -1 -1 0 89 117 -1 -1 -1 89 140 0 0 0 90 107 0 0 0 90 133 -1 0 0 91 141 0 0 0 92 94 0 0 0 92 120 -1 -1 -1 92 130 -1 -1 0 93 121 -1 -1 -1 93 140 0 0 0 94 109 0 0 0 94 122 0 -1 0 95 142 0 0 0 96 98 0 0 0 96 134 -1 0 0 97 119 -1 0 -1 97 128 -1 -1 -1 98 120 -1 0 -1 98 135 -1 0 0 99 101 0 0 0 99 107 0 0 -1 99 119 -1 0 -1 100 131 -1 -1 -1 100 140 0 0 0 101 108 0 0 -1 101 123 0 0 0 102 143 0 0 0 103 104 0 0 0 103 125 0 -1 0 103 144 0 0 0 104 126 0 -1 0 104 132 0 0 0 105 106 0 0 0 105 111 0 0 -1 105 144 0 0 0 106 112 0 0 -1 106 134 0 0 0 107 131 -1 -1 0 108 143 0 0 1 109 127 0 -1 0 110 138 1 0 1 110 145 0 0 0 111 131 -1 -1 0 112 143 0 0 1 113 114 0 0 0 113 127 0 0 0 113 136 -1 0 0 114 123 0 0 1 114 146 0 0 0 115 117 0 0 0 115 146 0 0 0 116 130 0 0 1 116 143 0 0 1 117 128 0 0 0 118 140 1 1 1 119 129 0 -1 0 120 121 0 0 0 121 125 1 0 1 122 142 0 1 0 124 138 1 1 0 124 146 0 0 -1 126 142 0 1 0 127 145 0 1 0 128 145 0 1 0 129 142 0 1 0 132 139 1 0 0 133 141 1 0 0 134 139 1 0 0 135 141 1 0 0 136 141 1 0 0 137 138 2 1 1 137 144 1 1 1 139 144 -1 0 0 145 146 0 -1 0")
CrystalNets.julia_main()

empty!(ARGS)
push!(ARGS, "-g", "3 1 2 0 0 0 1 3 0 0 0 1 4 0 0 0 1 5 0 0 0 2 6 0 0 0 2 7 0 0 0 2 8 0 0 0 3 7 0 0 0 3 9 0 0 0 3 10 0 0 0 4 5 0 0 0 4 9 0 0 0 4 11 0 0 0 5 12 0 0 0 6 7 0 0 0 6 13 0 0 0 6 14 0 0 0 7 15 0 0 0 8 14 0 0 0 8 16 0 0 0 8 17 0 0 0 9 18 0 0 0 9 19 0 0 0 10 15 0 0 0 10 19 0 0 0 10 20 0 0 0 11 12 0 0 0 11 21 0 0 0 11 22 0 0 0 12 22 0 0 0 13 23 0 0 0 13 24 0 0 0 13 25 0 0 0 14 25 0 0 0 14 26 0 0 0 15 20 0 0 0 15 27 0 0 0 16 17 0 0 0 16 26 0 0 0 16 28 0 0 0 17 29 0 0 0 17 30 0 0 0 18 19 0 0 0 18 31 0 0 0 18 32 0 0 0 19 33 0 0 0 20 34 0 0 0 20 35 0 0 0 21 36 0 0 0 21 37 0 0 0 21 38 0 0 0 22 37 0 0 0 22 39 0 0 0 23 40 0 0 0 23 41 0 0 0 23 42 0 0 0 24 25 0 0 0 24 42 0 0 0 24 43 0 0 0 25 44 0 0 0 26 44 0 0 0 26 45 0 0 0 27 34 0 0 0 27 46 0 0 0 27 47 0 0 0 28 30 0 0 0 28 48 0 0 0 28 49 0 0 0 29 50 0 0 0 29 51 0 0 0 29 52 0 0 0 30 52 0 0 0 30 53 0 0 0 31 54 0 0 0 31 55 0 0 0 31 56 0 0 0 32 33 0 0 0 32 56 0 0 0 32 57 0 0 0 33 57 0 0 0 33 58 0 0 0 34 59 0 0 0 34 60 0 0 0 35 60 0 0 0 35 61 0 0 0 35 62 0 0 0 36 38 0 0 0 36 63 0 0 0 36 64 0 0 0 37 65 0 0 0 37 66 0 0 0 38 66 0 0 0 38 67 0 0 0 39 65 0 0 0 39 68 0 0 0 39 69 0 0 0 40 42 0 0 0 40 70 0 0 0 40 71 0 0 0 41 71 0 0 0 41 72 0 0 0 41 73 0 0 0 42 74 0 0 0 43 74 0 0 0 43 75 0 0 0 43 76 0 0 0 44 45 0 0 0 44 77 0 0 0 45 78 0 0 0 46 47 0 0 0 46 79 0 0 0 47 59 0 0 0 47 80 0 0 0 48 49 0 0 0 48 81 0 0 0 48 82 0 0 0 49 53 0 0 0 49 83 0 0 0 50 84 0 0 0 50 85 0 0 0 50 86 0 0 0 51 52 0 0 0 51 85 0 0 0 51 87 0 0 0 52 88 0 0 0 53 88 0 0 0 53 89 0 0 0 54 55 0 0 0 54 90 0 0 0 54 91 0 0 0 55 92 0 0 0 55 93 0 0 0 56 93 0 0 0 56 94 0 0 0 57 95 0 0 0 57 96 0 0 0 58 96 0 0 0 58 97 0 0 0 58 98 0 0 0 59 99 0 0 0 59 100 0 0 0 60 61 0 0 0 60 100 0 0 0 61 101 0 0 0 61 102 0 0 0 62 102 0 0 0 62 103 0 0 0 62 104 0 0 0 63 105 0 0 0 63 106 0 0 0 63 107 0 0 0 64 67 0 0 0 64 107 0 0 0 64 108 0 0 0 65 68 0 0 0 65 109 0 0 0 66 109 0 0 0 66 110 0 0 0 67 108 0 0 0 67 111 0 0 0 68 75 0 0 0 68 112 0 0 0 69 84 0 0 0 69 112 0 0 0 69 113 0 0 0 70 114 0 0 0 70 115 0 0 0 70 116 0 0 0 71 115 0 0 0 71 117 0 0 0 72 73 0 0 0 72 117 0 0 0 72 118 0 0 0 73 90 0 0 0 73 119 0 0 0 74 120 0 0 0 74 121 0 0 0 75 76 0 0 0 75 121 0 0 0 76 112 0 0 0 76 122 0 0 0 77 78 0 0 0 77 123 0 0 0 77 124 0 0 0 78 124 0 0 0 79 80 0 0 0 79 125 0 0 0 80 125 0 0 0 80 126 0 0 0 81 106 0 1 0 81 107 0 1 0 81 127 0 0 0 82 83 0 0 0 82 127 0 0 0 82 128 0 0 0 83 101 0 0 0 83 129 0 0 0 84 113 0 0 0 84 130 0 0 0 85 86 0 0 0 85 131 0 0 0 86 130 0 0 0 86 132 0 0 0 87 131 0 0 0 87 133 0 0 0 87 134 0 0 0 88 89 0 0 0 88 135 0 0 0 89 136 0 0 0 90 92 0 0 0 90 118 0 0 0 91 118 0 0 0 91 137 0 0 0 91 138 0 0 0 92 119 0 0 0 92 139 0 0 0 93 94 0 0 0 93 140 0 0 0 94 141 0 0 0 94 142 0 0 0 95 143 0 0 0 95 144 0 0 0 95 145 0 0 0 96 145 0 0 0 96 146 0 0 0 97 98 0 0 0 97 146 0 0 0 97 147 0 0 0 98 148 0 0 0 98 149 0 0 0 99 100 0 0 0 99 150 0 0 0 99 151 0 0 0 100 152 0 0 0 101 128 0 0 0 101 129 0 0 0 102 129 0 0 0 102 153 0 0 0 103 104 0 0 0 103 154 0 0 0 104 153 0 0 0 104 155 0 0 0 105 106 0 0 0 105 138 0 0 0 105 156 0 0 0 106 157 0 0 0 107 127 0 -1 0 108 143 0 0 0 108 158 0 0 0 109 110 0 0 0 109 159 0 0 0 110 160 0 0 0 110 161 0 0 0 111 158 0 0 0 111 162 0 0 0 111 163 0 0 0 112 164 0 0 0 113 164 0 0 0 113 165 0 0 0 114 166 0 0 0 114 167 0 0 0 114 168 0 0 0 115 116 0 0 0 115 169 0 0 0 116 167 0 0 0 117 169 0 0 0 117 170 0 0 0 118 137 0 0 0 119 125 0 0 0 119 139 0 0 0 120 121 0 0 0 120 155 -1 -1 0 120 171 0 0 0 121 172 0 0 0 122 164 0 0 0 122 173 0 0 0 122 174 0 0 0 123 175 0 0 0 123 176 0 0 0 123 177 0 0 0 124 177 0 0 0 124 178 0 0 0 125 179 0 0 0 126 131 0 -1 -1 126 179 0 0 0 126 180 0 0 0 127 181 0 0 0 128 181 0 0 0 128 182 0 0 0 129 183 0 0 0 130 165 0 0 0 130 184 0 0 0 131 180 0 1 1 132 184 0 0 0 132 185 0 0 0 132 186 0 0 0 133 134 0 0 0 133 180 0 1 1 133 187 0 0 0 134 188 0 0 0 134 189 0 0 0 135 136 0 0 0 135 190 0 0 0 135 191 0 0 0 136 190 0 0 0 137 192 0 0 0 138 192 0 0 0 138 193 0 0 0 139 179 0 0 0 139 194 0 0 0 140 141 0 0 0 140 165 0 -1 -1 140 195 0 0 0 141 196 0 0 0 141 197 0 0 0 142 181 0 -1 0 142 196 0 0 0 142 198 0 0 0 143 199 0 0 0 143 200 0 0 0 144 145 0 0 0 144 199 0 0 0 145 201 0 0 0 146 201 0 0 0 146 202 0 0 0 147 148 0 0 0 147 186 0 -1 -1 147 203 0 0 0 148 204 0 0 0 148 205 0 0 0 149 205 0 0 0 149 206 0 0 0 149 207 0 0 0 150 152 0 0 0 150 208 0 0 0 150 209 0 0 0 151 204 0 0 0 151 209 0 0 0 151 210 0 0 0 152 208 0 0 0 152 211 0 0 0 153 183 0 0 0 153 212 0 0 0 154 155 0 0 0 154 207 0 0 0 155 207 0 0 0 156 157 0 0 0 156 193 0 0 0 156 213 0 0 0 157 214 0 0 0 157 215 0 0 0 158 200 0 0 0 158 216 0 0 0 159 160 0 0 0 159 217 0 0 0 159 218 0 0 0 160 208 0 0 1 160 211 0 0 1 161 166 1 1 1 161 211 0 0 1 161 219 0 0 0 162 163 0 0 0 162 190 0 -1 0 162 216 0 0 0 163 169 1 1 1 163 220 0 0 0 164 174 0 0 0 165 195 0 1 1 166 167 0 0 0 166 221 0 0 0 168 221 0 0 0 168 222 0 0 0 168 223 0 0 0 169 170 0 0 0 170 220 -1 -1 -1 170 224 0 0 0 171 172 0 0 0 171 206 -1 -1 0 171 207 -1 -1 0 172 213 0 0 0 172 225 0 0 0 173 226 0 0 0 173 227 0 0 0 173 228 0 0 0 174 227 0 0 0 175 176 0 0 0 175 223 0 0 0 175 229 0 0 0 176 202 -1 0 0 176 222 0 0 0 177 202 -1 0 0 177 230 0 0 0 178 187 0 0 0 178 214 0 1 0 178 230 0 0 0 179 231 0 0 0 180 231 0 0 0 181 182 0 0 0 182 198 0 1 0 183 212 0 0 0 183 232 0 0 0 184 185 0 0 0 184 195 0 1 1 185 203 0 1 1 186 203 0 1 1 186 204 0 1 1 187 188 0 0 0 187 230 0 0 0 188 214 0 1 0 188 233 0 0 0 189 218 0 1 0 189 233 0 0 0 189 234 0 0 0 190 220 0 1 0 191 209 0 1 1 191 220 0 1 0 191 224 1 2 1 192 193 0 0 0 193 225 0 0 0 194 228 0 -1 -1 194 231 0 0 0 194 235 0 0 0 195 197 0 0 0 196 198 0 0 0 196 236 0 0 0 197 236 0 0 0 197 237 0 0 0 199 200 0 0 0 200 238 0 0 0 201 202 0 0 0 201 230 1 0 0 204 210 0 0 0 205 206 0 0 0 205 210 0 0 0 206 225 1 1 0 208 218 0 0 -1 209 224 1 1 0 210 224 1 1 0 211 219 0 0 -1 212 223 1 1 0 212 229 1 1 0 213 215 0 0 0 213 225 0 0 0 214 215 0 0 0 215 233 0 -1 0 216 238 0 0 0 216 239 0 0 0 217 218 0 0 0 217 234 0 -1 0 219 221 1 1 1 219 236 0 1 1 221 237 -1 0 0 222 223 0 0 0 222 237 -1 0 0 226 229 0 0 0 226 232 -1 -1 0 226 240 0 0 0 227 228 0 0 0 228 240 0 0 0 229 232 -1 -1 0 231 235 0 0 0 232 239 0 1 0 233 234 0 0 0 235 238 -1 -1 -1 235 240 0 -1 -1 236 237 0 0 0 238 239 0 0 0 239 240 1 0 0")
CrystalNets.julia_main()

empty!(ARGS)
push!(ARGS, "--help")
CrystalNets.julia_main()

include(joinpath(@__DIR__, "..", "test", "runtests.jl"))


#!/usr/bin/env julia

const Tlist = [0.9, 0.8, 0.7]
const tw1list = map(Int64,[1e4,1e6,1e8])

const reps=8

function rectify_times!(array)
    L = length(array)
    for i in 2:L
        if array[i] == array[i-1]
            for j in i:L
                array[j] += 1
            end
        end
    end
end

function generate_times()
    tws = logspace(log10(1),log10(3e8+10),100)
    tws = map(x->floor(Int64,x), tws)
    tws = vcat(tws, tw1list) |> sort
    rectify_times!(tws)

    tws = unique(tws) |> sort
    ts = logspace(4,8,10)
    ts = map(x->floor(Int64,x), ts) |> sort

    allt = [tw + t for tw in tws, t in [0; ts]] |> vec |> unique |> sort

    writedlm("tiempos_log.dat", allt)
    writedlm("tiempos_lineal.dat",[])
end

if isdir("jobs")
    error("Cowardly refusing to use existing directory 'jobs'")
else
    mkdir("jobs")
end

function generate_jobs(;T=0.9)
    suffix = "quench_T_$(T)"
    mkdir("jobs/$suffix")
    for i in 1:reps
        jobid  = join(rand('a':'z',5))
        open("jobs/$(suffix)/$(jobid).job", "w") do f
            println(f, "/home/sergio/output/$(suffix)_replica_$(i)    Nombre del directorio")
            println(f, "400000001                                Timewindow (maximo numero pasos MC simulacion) ")
            println(f, "0                                        FlagJ                                          ")
            println(f, "0                                        FlagS                                          ")
            println(f, "666                                      Flag_h (now, flag_dummy: not used)             ")
            println(f, "0                                        Flag_continua                                  ")
            println(f, "$T                                      T1                                             ")
            println(f, "$T                                      T2                                             ")
            println(f, "1                                     tw1                                            ")
            println(f, "2                                    tw2                                            ")
            println(f, "0                                        Hfield                                         ")
            println(f, "1                                        TimeHon                                        ")
        end
    end
end

generate_times()

info("Generating jobs")
f = 0
for T in Tlist
    generate_jobs(T=T)
    f+=1
    print("$f*$reps files generated  \r")
end
println()


"""
The TinStandard type contains the state variables (swe), the inputs
(prec, tair) for one time step, the parameters (param) and the time step
length (tstep) for a standard temperature index model.
"""
type TinStandard <: Snow

  swe::Array{Float64,1}
  lw::Array{Float64,1}
  prec::Array{Float64,1}
  tair::Array{Float64,1}
  q_sim::Array{Float64,1}
  param::Array{Float64,1}
  frac::Array{Float64,1}
  tstep::Float64
  time::DateTime

end

"""
    TinStandard(tstep, time, frac)

Constructor for TinStandard with predefined state variables, parameters and inputs.
The time step (tstep) is given as a fraction of one day. Thus, for hourly input
data tstep should be set to 1/24. The fraction of elevation bands should sum
up to unity.
"""
function TinStandard(tstep::Float64, time::DateTime, frac::Array{Float64,1})

  nzones = length(frac)
  swe    = zeros(Float64, nzones)
  lw     = zeros(Float64, nzones)
  prec   = zeros(Float64, nzones)
  tair   = zeros(Float64, nzones)
  q_sim  = zeros(Float64, nzones)
  param  = zeros(Float64, 5)

  TinStandard(swe, lw, prec, tair, q_sim, param, frac, tstep, time)

end

"""
    TinStandard(tstep, time, param, frac)

Constructor for TinStandard with predefined state variables and inputs.
The time step (tstep) is given as a fraction of one day. Thus, for hourly input
data tstep should be set to 1/24. The fraction of elevation bands should sum
up to unity.
"""
function TinStandard(tstep::Float64, time::DateTime, param::Array{Float64,1}, frac::Array{Float64,1})

  nzones = length(frac)
  swe    = zeros(Float64, nzones)
  lw     = zeros(Float64, nzones)
  prec   = zeros(Float64, nzones)
  tair   = zeros(Float64, nzones)
  q_sim  = zeros(Float64, nzones)

  TinStandard(swe, lw, prec, tair, q_sim, param, frac, tstep, time)

end

"""
    init_states(mdata::TinStandard)

Initilize the state variables of the model.
"""
function init_states(mdata::TinStandard)

  for ireg in eachindex(mdata.swe)
    mdata.swe[ireg] = 0.
    mdata.lw[ireg] = 0.
  end

end

"""
    get_param_range(mdata::TinStandard)

Get allowed parameter ranges for the calibration of the model.
"""
function get_param_range(mdata::TinStandard)

  param_range_snow = [(-0.5, 0.5), (0.5, 3.0), (0.5, 4.0), (0.01, 0.10), (0.5, 2.0)]

end

"""
    get_states(mdata::TinStandard)

Get state variables for computing penelty during calibration.

"""
function get_states(mdata::TinStandard)

  return [mean(mdata.swe); mean(mdata.lw)]

end

"""
    assign_param(mdata::TinStandard, param::Array{Float64,1})

Assign parameter values to the TinBasic type.
"""
function assign_param(mdata::TinStandard, param::Array{Float64,1})

  for ireg in eachindex(mdata.param)
    mdata.param[ireg] = param[ireg]
  end

end

"""
    enkf_snow(mdata::Array{TinStandard}, obs_ens, q_sim)

Implementation of the ensemble Kalman filter for the standard temperature index
snow model.
"""
function enkf_snow(mdata::Array{TinStandard}, obs_ens, q_sim)

  nens = length(mdata)

  # Allocate arrays

  swe = zeros(Float64, length(mdata[1].swe), nens)
  lw = zeros(Float64, length(mdata[1].lw), nens)

  # Add states to arrays

  for iens = 1:nens

    swe[:, iens] = mdata[iens].swe
    lw[:, iens] = mdata[iens].lw

  end

  # Run ensemble kalman filter

  swe = enkf(swe, obs_ens, q_sim)
  lw = enkf(lw, obs_ens, q_sim)

  # Check limits of states

  swe[swe .< 0] = 0.

  lwmax = swe * mdata[1].param[4]

  lw[lw .< 0] = 0.
  lw[lw .> lwmax] = 0.

  # Add arrays to states

  for iens = 1:nens

    mdata[iens].swe = swe[:, iens]
    mdata[iens].lw = lw[:, iens]

  end

  nothing

end

"""
    run_timestep(mdata::TinStandard)

Propagate the model one time step and compute simulated snowpack dischage.
"""
function run_timestep(mdata::TinStandard)

    # Parameters

    tth     = mdata.param[1]
    ddf_min = mdata.param[2]
    ddf_max = mdata.param[3]
    whcap   = mdata.param[4]
    pcorr   = mdata.param[5]

    ddf = compute_ddf(mdata.time, ddf_min, ddf_max)

    for ireg in eachindex(mdata.swe)

        swe = mdata.swe[ireg]
        lw = mdata.lw[ireg]

        # Compute solid and liquid precipitation

        psolid, pliquid = split_prec(mdata.prec[ireg], mdata.tair[ireg], tth)

        # Apply snowfall correction factor

        psolid = pcorr * psolid

        # Compute frozen water in snowpack

        fw  = swe - lw

        # Constrain fw and lw

        if fw < 0.0
            fw = 0.0
        end

        if lw < 0.0
            lw = 0.0
        end

        # Compute melt rates

        melt = compute_melt(mdata.tair[ireg], fw, ddf, tth)

        # Compute refreezing rates

        refr = compute_refreeze(mdata.tair[ireg], lw, ddf, tth)

        # Massbalance for frozen water in snowpack

        dfw = psolid + refr - melt
        fw  = fw + dfw

        # Massbalance for liquid water in snowpack

        dlw = pliquid + melt - refr
        lw  = lw + dlw

        lwmax = fw * whcap / (1 - whcap)

        q_sim = lw - lwmax

        if q_sim < 0.
            q_sim = 0.
        end

        lw = lw - q_sim

        # Massbalance for snowpack

        swe = fw + lw

        # Assign final results

        mdata.swe[ireg] = swe
        mdata.lw[ireg] = lw
        mdata.q_sim[ireg] = q_sim

    end

    mdata.time += Dates.Hour(mdata.tstep)

    return nothing

end

# Compute snowmelt

function compute_melt(tair::Float64, fw::Float64, ddf::Float64, tth_melt::Float64)

    m_melt = 0.5

    t_m = (tair - tth_melt) / m_melt
    melt = ddf * m_melt * (t_m + log(1. + exp(-t_m)))

    melt = min(max(0.,fw), melt)

    return melt

end

# Compute refreeze

function compute_refreeze(tair::Float64, lw::Float64, ddf::Float64, tth_refr::Float64)

    m_refr = 0.5

    t_m = (tth_refr - tair) / m_refr
    refr = ddf * m_refr * (t_m + log(1. + exp(-t_m)))

    if refr > lw
        refr = lw
    end

    refr = min(lw,refr)

end


VERSION >= v"0.4.0-dev+6521" && __precompile__()

module Nemo

import Base: Array, abs, asin, asinh, atan, atanh, base, bin, checkbounds,
             conj, convert, cmp, contains, cos, cosh, dec, deepcopy,
             deepcopy_internal, deserialize, det, div, divrem, eye,
             gcd, gcdx, getindex, hash, hcat, hex, intersect, inv, invmod,
             isequal, isfinite, isless, isqrt, isreal, iszero, lcm,
             ldexp, length, log, lufact, lufact!, mod, ndigits, nextpow2, norm,
             nullspace, numerator, oct, one, parent, parse, precision, prevpow2,
             rand, rank, Rational, rem, reverse, serialize, setindex!, show,
             similar, sign, sin, sinh, size, sqrt, string, tan, tanh, trace,
             trailing_zeros, transpose, transpose!, truncate, typed_hvcat,
             typed_hcat, var, vcat, zero, zeros, +, -, *, ==, ^, &, |, $, <<,
             >>, ~, <=, >=, <, >, //, /, !=

if VERSION >= v"0.7.0-DEV.1144"
import Base: isone
end

import Base: floor, ceil, hypot, sqrt, log, log1p, expm1, sin, cos, sinpi,
             cospi, tan, cot, sinh, cosh, tanh, coth, atan, asin, acos, atanh,
             asinh, acosh, gamma, lgamma, sinpi, cospi, atan2

export elem_type, parent_type

export SetElem, GroupElem, RingElem, FieldElem, AccessorNotSetError

export PolyElem, SeriesElem, AbsSeriesElem, RelSeriesElem, ResElem, FracElem,
       MatElem, FinFieldElem, MPolyElem

export PolyRing, SeriesRing, AbsSeriesRing, ResRing, FracField, MatSpace,
       FinField, MPolyRing

export JuliaZZ, JuliaQQ, zz, qq, JuliaRealField, RDF

export PermutationGroup, ZZ, QQ, PadicField, FiniteField, RealField, ComplexField,
       CyclotomicField, MaximalRealSubfield, NumberField

export create_accessors, get_handle, package_handle, zeros,
       Array, sig_exists

export flint_cleanup, flint_set_num_threads

export error_dim_negative, ErrorConstrDimMismatch

export is_windows64

if VERSION >= v"0.6.0-dev.2024" # julia started exporting iszero (again?)
   import Base: iszero
end

if VERSION < v"0.6-"
   import Base: isprime, factor, parity, sub, call
end

if VERSION >= v"0.7.0-DEV.264" # julia started exporting sincos
   import Base: sincos
end

if VERSION >= v"0.7.0-DEV.1144"
    import Base: isone
end

include("AbstractTypes.jl")

###############################################################################
#
#   Set up environment / load libraries
#
###############################################################################

is_windows64() = (Sys.iswindows() ? true : false) && (Int == Int64)

const pkgdir = realpath(joinpath(dirname(@__FILE__), ".."))
const libdir = joinpath(pkgdir, "local", "lib")
if Sys.iswindows()
   const libgmp = joinpath(pkgdir, "local", "lib", "libgmp-16")
else
   const libgmp = joinpath(pkgdir, "local", "lib", "libgmp")
end
const libmpfr = joinpath(pkgdir, "local", "lib", "libmpfr")
const libflint = joinpath(pkgdir, "local", "lib", "libflint")
const libarb = joinpath(pkgdir, "local", "lib", "libarb")

function flint_abort()
  error("Problem in the Flint-Subsystem")
end

function __init__()

   if "HOSTNAME" in keys(ENV) && ENV["HOSTNAME"] == "juliabox"
       push!(Libdl.DL_LOAD_PATH, "/usr/local/lib")
   elseif Sys.islinux()
       push!(Libdl.DL_LOAD_PATH, libdir)
       Libdl.dlopen(libgmp)
       Libdl.dlopen(libmpfr)
       Libdl.dlopen(libflint)
       Libdl.dlopen(libarb)
   else
      push!(Libdl.DL_LOAD_PATH, libdir)
   end

   if !Sys.iswindows()
      ccall((:__gmp_set_memory_functions, libgmp), Void,
         (Ref{Void},Ref{Void},Ref{Void}),
         cglobal(:jl_gc_counted_malloc),
         cglobal(:jl_gc_counted_realloc_with_old_size),
         cglobal(:jl_gc_counted_free))

      ccall((:__flint_set_memory_functions, libflint), Void,
         (Ref{Void},Ref{Void},Ref{Void},Ref{Void}),
         cglobal(:jl_malloc),
         cglobal(:jl_calloc),
         cglobal(:jl_realloc),
         cglobal(:jl_free))
   end

   ccall((:flint_set_abort, libflint), Void,
      (Ref{Void},), cfunction(flint_abort, Void, ()))

   println("")
   println("Welcome to Nemo version 0.7.0")
   println("")
   println("Nemo comes with absolutely no warranty whatsoever")
   println("")
end

function flint_set_num_threads(a::Int)
   ccall((:flint_set_num_threads, libflint), Void, (Int,), a)
end

function flint_cleanup()
   ccall((:flint_cleanup, libflint), Void, ())
end

###############################################################################
#
#  Version information
#
################################################################################

function versioninfo()
  print("Nemo version 0.7.0 \n")
  nemorepo = dirname(dirname(@__FILE__))

  print("Nemo: ")
  prepo = Base.LibGit2.GitRepo(nemorepo)
  Base.LibGit2.with(LibGit2.head(prepo)) do phead
    print("commit: ")
    print(string(LibGit2.Oid(phead))[1:8])
    print(" date: ")
    commit = Base.LibGit2.get(Base.LibGit2.GitCommit, prepo, LibGit2.Oid(phead))
    print(Base.Dates.unix2datetime(Base.LibGit2.author(commit).time))
    print(")\n")
  end

  finalize(prepo)

  for deps in ["flint2", "arb", "antic"]
    if ispath(joinpath(nemorepo, "deps", deps))
      print("$deps: ")
      repo = joinpath(nemorepo, "deps", deps)

      prepo = Base.LibGit2.GitRepo(repo)

      Base.LibGit2.with(LibGit2.head(prepo)) do phead
        print("commit: ")
        print(string(LibGit2.Oid(phead))[1:8])
        print(" date: ")
        commit = Base.LibGit2.get(Base.LibGit2.GitCommit, prepo, LibGit2.Oid(phead))
        print(Base.Dates.unix2datetime(Base.LibGit2.author(commit).time))
        print(")\n")
      end
      finalize(prepo)
    end
  end

  return nothing
end

###############################################################################
#
#   Julia types
#
###############################################################################

include("julia/JuliaTypes.jl")

###############################################################################
#
#   Generic submodule
#
###############################################################################

include("Generic.jl")

import .Generic: add!, addeq!, addmul!, base_ring, canonical_unit, character,
                 characteristic, charpoly, charpoly_danilevsky!,
                 charpoly_danilevsky_ff!, charpoly_hessenberg!, chebyshev_t,
                 chebyshev_u, _check_dim, check_parent, coeff, cols, compose,
                 content, cycles, data, degree, denominator, derivative, det, det_clow,
                 det_df, det_fflu, det_popov, dim, discriminant, divexact,
                 divides, divrem, elem_type, elements, evaluate,
                 extended_weak_popov, extended_weak_popov_with_trafo, fflu!,
                 fflu, find_pivot_popov, fit!, gcd, gen, gens, gcdinv, gcdx,
                 gram, has_left_neighbor, has_bottom_neighbor, hash,
                 hessenberg!, hessenberg, hnf, hnf_cohen, hnf_cohen_with_trafo,
                 hnf_kb, hnf_kb_with_trafo, hnf_minors, hnf_minors_with_trafo,
                 hnf_with_trafo, hnf_via_popov, hnf_via_popov_with_trafo,
                 hooklength, identity_matrix, inskewdiag, integral,
                 interpolate, inv, inv!, invmod, isconstant, isdegree,
                 isdomain_type, isexact_type, isgen, ishessenberg, ismonomial,
                 isnegative, isone, isreverse, isrimhook, isrref, isterm,
                 isunit, lcm, lead, length, leglength, main_variable,
                 main_variable_extract, main_variable_insert, matrix,
                 matrix_repr, max_degrees, max_precision, minpoly, mod,
                 modulus, monomial_iszero, monomial_set!, monomial_to_newton!,
                 mul!, mul_classical, mul_karatsuba, mul_ks, mullow, mulmod,
                 needs_parentheses, newton_to_monomial!, normalise, nvars, numerator,
                 O, one, order, ordering, parent_type, parity, partitionseq,
                 polcoeff, pol_length, powmod, pow_multinomial, popov, powers,
                 precision, primpart, pseudodivrem, pseudorem, rand_ordering,
                 rank_profile_popov, remove, renormalize!, resultant,
                 resultant_ducos, resultant_euclidean, resultant_subresultant,
                 resultant_sylvester, resx, reverse, rows, rref, rref!,
                 setcoeff!, set_length!, setpermstyle, set_prec!, set_val!,
                 shift_left, shift_right, show_minus_one, similarity!, snf,
                 snf_kb, snf_kb_with_trafo, snf_with_trafo, solve,
                 solve_rational, solve_triu, subst, swap_rows, swap_rows!,
                 trail, truncate, typed_hcat, typed_hvcat, valuation, var,
                 vars, weak_popov, weak_popov_with_trafo, zero, zero!,
                 zero_matrix

export add!, addeq!, addmul!, base_ring, canonical_unit, character,
                 characteristic, charpoly, charpoly_danilevsky!,
                 charpoly_danilevsky_ff!, charpoly_hessenberg!, chebyshev_t,
                 chebyshev_u, _check_dim, check_parent, coeff, cols, compose,
                 content, cycles, data, degree, denominator, derivative, det, det_clow,
                 det_df, det_fflu, det_popov, dim, discriminant, divexact,
                 divides, divrem, elem_type, elements, evaluate,
                 extended_weak_popov, extended_weak_popov_with_trafo, fflu!,
                 fflu, find_pivot_popov, fit!, gcd, gen, gens, gcdinv, gcdx,
                 gram, has_left_neighbor, has_bottom_neighbor, hash,
                 hessenberg!, hessenberg, hnf, hnf_cohen, hnf_cohen_with_trafo,
                 hnf_kb, hnf_kb_with_trafo, hnf_minors, hnf_minors_with_trafo,
                 hnf_with_trafo, hnf_via_popov, hnf_via_popov_with_trafo,
                 hooklength, identity_matrix, inskewdiag, integral,
                 interpolate, inv, inv!, invmod, isconstant, isdegree,
                 isdomain_type, isexact, isexact_type, isgen, ishessenberg,
                 ismonomial, isnegative, isone, isreverse, isrimhook, isrref,
                 isterm, isunit, iszero, lcm, lead, leglength, length,
                 main_variable, main_variable_extract, main_variable_insert,
                 matrix, matrix_repr, max_degrees, max_precision, minpoly, mod,
                 modulus, monomial_iszero, monomial_set!, monomial_to_newton!,
                 mul!, mul_classical, mul_karatsuba, mul_ks, mullow, mulmod,
                 needs_parentheses, newton_to_monomial!, normalise, nvars, numerator,
                 O, one, order, ordering, parent_type, parity, partitionseq,
                 polcoeff, pol_length, powmod, pow_multinomial, popov, powers,
                 ppio, precision, primpart, pseudodivrem, pseudorem,
                 rand_ordering, rank_profile_popov, remove, renormalize!,
                 resultant, resultant_ducos, resultant_euclidean,
                 resultant_subresultant, resultant_sylvester, resx, reverse,
                 rows, rref, rref!, setcoeff!, set_length!, setpermstyle,
                 set_prec!, set_val!, shift_left, shift_right, show_minus_one,
                 similarity!, snf, snf_kb, snf_kb_with_trafo, snf_with_trafo,
                 solve, solve_rational, solve_triu, subst, swap_rows,
                 swap_rows!, trail, truncate, typed_hcat, typed_hvcat,
                 valuation, var, vars, weak_popov, weak_popov_with_trafo, zero,
                 zero!, zero_matrix

function exp(a::T) where T
   return Base.exp(a)
end

function PermGroup(n::Int, cached=true)
   Generic.PermGroup(n, cached)
end

function AllPerms(n::Int)
   Generic.AllPerms(n)
end

function perm(n::Int)
   Generic.perm(n)
end

function perm(a::Array{Int, 1})
   Generic.perm(a)
end

function Partition(part::Vector{Int}, check::Bool=true)
   Generic.Partition(part, check)
end

function Partitions(n::Int)
   Generic.Partitions(n)
end

function SkewDiagram(lambda::Generic.Partition, mu::Generic.Partition)
   Generic.SkewDiagram(lambda, mu)
end

function SkewDiagram(lambda::Vector{Int}, mu::Vector{Int})
   Generic.SkewDiagram(lambda, mu)
end

function YoungTableau(part::Generic.Partition, tab::Array{Int, 2})
   Generic.YoungTableau(part, tab)
end

function YoungTableau(part::Generic.Partition, fill::Vector{Int}=collect(1:part.n))
   Generic.YoungTableau(part, fill)
end

function YoungTableau(p::Vector{Int})
   Generic.YoungTableau(p)
end

function PowerSeriesRing(R::Ring, prec::Int, s::AbstractString; cached=true, model=:capped_relative)
   Generic.PowerSeriesRing(R, prec, s; cached=cached, model=model)
end

function LaurentSeriesRing(R::Ring, prec::Int, s::AbstractString; cached=true)
   Generic.LaurentSeriesRing(R, prec, s; cached=cached)
end

function LaurentSeriesRing(R::Field, prec::Int, s::AbstractString; cached=true)
   Generic.LaurentSeriesField(R, prec, s; cached=cached)
end

function LaurentSeriesField(R::Field, prec::Int, s::AbstractString; cached=true)
   Generic.LaurentSeriesField(R, prec, s; cached=cached)
end

function PolynomialRing(R::Ring, s::AbstractString; cached::Bool = true)
   Generic.PolynomialRing(R, s; cached=cached)
end

function PolynomialRing(R::Ring, s::Array{String, 1}; cached::Bool = true, ordering::Symbol = :lex)
   Generic.PolynomialRing(R, s; cached=cached, ordering=ordering)
end

function SparsePolynomialRing(R::Ring, s::String; cached::Bool = true)
   Generic.SparsePolynomialRing(R, s; cached=cached)
end

function MatrixSpace(R::Ring, r::Int, c::Int, cached::Bool = true)
   Generic.MatrixSpace(R, r, c, cached)
end

function FractionField(R::Ring; cached=true)
   Generic.FractionField(R; cached=cached)
end

function ResidueRing(R::Ring, a::Union{RingElement, Integer}; cached::Bool = true)
   Generic.ResidueRing(R, a; cached=cached)
end

export PowerSeriesRing, PolynomialRing, SparsePolynomialRing, MatrixSpace,
       FractionField, ResidueRing, Partition, PermGroup, YoungTableau,
       Partitions, SkewDiagram, AllPerms, perm, LaurentSeriesRing,
       LaurentSeriesField

export Generic

###############################################################################
#
#   Polynomial Ring S, x = R["x"] syntax
#
###############################################################################

getindex(R::Ring, s::String) = PolynomialRing(R, s)

getindex(R::Tuple{Ring, T}, s::String) where {T} = PolynomialRing(R[1], s)

###############################################################################
#
#   Matrix M = R[...] syntax
#
################################################################################

function typed_hvcat(R::Ring, dims, d...)
   T = elem_type(R)
   r = length(dims)
   c = dims[1]
   A = Array{T}(r, c)
   for i = 1:r
      dims[i] != c && throw(ArgumentError("row $i has mismatched number of columns (expected $c, got $(dims[i]))"))
      for j = 1:c
         A[i, j] = R(d[(i - 1)*c + j])
      end
   end
   S = matrix(R, A)
   return S
end

function typed_hcat(R::Ring, d...)
   T = elem_type(R)
   r = length(d)
   A = Array{T}(1, r)
   for i = 1:r
      A[1, i] = R(d[i])
   end
   S = matrix(R, A)
   return S
end

###############################################################################
#
#   Load error objects
#
###############################################################################

include("error.jl")

###############################################################################
#
#   Load Nemo Rings/Fields/etc
#
###############################################################################

include("flint/FlintTypes.jl")

include("antic/AnticTypes.jl")

include("arb/ArbTypes.jl")

#include("ambiguities.jl") # remove ambiguity warnings

include("Groups.jl")

include("flint/adhoc.jl")

###########################################################
#
#   Package handle creation
#
###########################################################

const package_handle = [1]

function get_handle()
   package_handle[1] += 1
   return package_handle[1] - 1
end

###############################################################################
#
#   Auxilliary data accessors
#
###############################################################################

mutable struct AccessorNotSetError <: Exception
end

function create_accessors(T, S, handle)
   get = function(a)
      if handle > length(a.auxilliary_data) ||
         !isassigned(a.auxilliary_data, handle)
        throw(AccessorNotSetError())
      end
      return a.auxilliary_data[handle]
   end
   set = function(a, b)
      if handle > length(a.auxilliary_data)
         resize!(a.auxilliary_data, handle)
      end
      a.auxilliary_data[handle] = b
   end
   return get, set
end

###############################################################################
#
#   Promote rule helpers
#
###############################################################################

if VERSION >= v"0.5.0-dev+3171"

function sig_exists(T::Type{Tuple{U, V, W}}, sig_table::Array{X, 1}) where {U, V, W, X}
   for s in sig_table
      if s === T
         return true
      end
   end
   return false
end

else

function sig_exists(T::Type{Tuple{U, V, W}}, sig_table::Array{X, 1}) where {U, V, W, X}
   return false
end

end # if VERSION

###############################################################################
#
#   Array creation functions
#
###############################################################################

Array(R::Ring, r::Int...) = Array{elem_type(R)}(r)

function zeros(R::Ring, r::Int...)
   T = elem_type(R)
   A = Array{T}(r)
   for i in eachindex(A)
      A[i] = R()
   end
   return A
end

###############################################################################
#
#   Set domain for PermutationGroup to Flint
#
###############################################################################

PermutationGroup = PermGroup

###############################################################################
#
#   Set domain for ZZ, QQ, PadicField, FiniteField to Flint
#
###############################################################################

ZZ = FlintZZ
QQ = FlintQQ
PadicField = FlintPadicField
FiniteField = FlintFiniteField

###############################################################################
#
#   Set domain for RR, CC to Arb
#
###############################################################################

RealField = ArbField
ComplexField = AcbField

###############################################################################
#
#   Set domain for NumberField to Antic
#
###############################################################################

NumberField = AnticNumberField
CyclotomicField = AnticCyclotomicField
MaximalRealSubfield = AnticMaximalRealSubfield

###############################################################################
#
#   Test code
#
###############################################################################

include("../benchmarks/runbenchmarks.jl")

function test_module(x, y)
   julia_exe = Base.julia_cmd()
   test_file = joinpath(pkgdir, "test/$x/")
   test_file = test_file * "$y-test.jl";
   test_function_name = "test_"

   if x in ["flint", "arb", "antic"]
     test_function_name *= y
   else x == "generic"
     if y == "RelSeries"
       test_function_name *= "gen_rel_series"
     elseif y == "AbsSeries"
       test_function_name *= "gen_abs_series"
     elseif y == "Matrix"
       test_function_name *= "gen_mat"
     elseif y == "Fraction"
       test_function_name *= "gen_frac"
     elseif y == "Residue"
       test_function_name *= "gen_res"
     else
       test_function_name *= "gen_$(lowercase(y))"
     end
   end

   cmd = "using Base.Test; using Nemo; include(\"$test_file\"); $test_function_name();"
   info("spawning ", `$julia_exe -e \"$cmd\"`)
   run(`$julia_exe -e $cmd`)
end

end # module


# *********************************************************************************
# REopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this list
# of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
# *********************************************************************************
using CPLEX

#=
add a time-of-export rate that is greater than retail rate for the month of January,
check to make sure that PV does NOT export unless the site load is met first for the month of January.
=#
@testset "Do not allow_simultaneous_export_import" begin
    model = Model(optimizer_with_attributes(CPLEX.Optimizer, "CPX_PARAM_SCRIND" => 0))
    data = JSON.parsefile("./scenarios/monthly_rate.json")

    # create wholesale_rate with compensation in January > retail rate
    jan_rate = data["ElectricTariff"]["monthly_energy_rates"][1]
    data["ElectricTariff"]["wholesale_rate"] =
        append!(repeat([jan_rate + 0.1], 31 * 24), repeat([0.0], 8760 - 31*24))
    data["ElectricTariff"]["monthly_demand_rates"] = repeat([0], 12)
    data["ElectricUtility"] = Dict("allow_simultaneous_export_import" => false)

    s = Scenario(data)
    inputs = REoptInputs(s)
    results = run_reopt(model, inputs)

    @test all(x == 0.0 for (i,x) in enumerate(results["ElectricUtility"]["year_one_to_load_series_kw"][1:744])
              if results["PV"]["year_one_to_grid_series_kw"][i] > 0)
end

@testset "Solar and Storage" begin
    model = Model(optimizer_with_attributes(CPLEX.Optimizer, "CPX_PARAM_SCRIND" => 0))
    results = run_reopt(model, "./scenarios/pv_storage.json")

    @test results["PV"]["size_kw"] ≈ 217 atol=1
    @test results["Financial"]["lcc_us_dollars"] ≈ 1.23887e7 rtol=1e-5
    @test results["Storage"]["size_kw"] ≈ 56 atol=1
    @test results["Storage"]["size_kwh"] ≈ 79 atol=1
end

@testset "Minimize Unserved Load" begin
    m = Model(optimizer_with_attributes(CPLEX.Optimizer, "CPX_PARAM_SCRIND" => 0))
    results = run_reopt(m, "./scenarios/outage.json")

    @test results["expected_outage_cost"] ≈ 0
    @test sum(results["unserved_load_per_outage"]) ≈ 0
    @test value(m[:binMGTechUsed]["Generator"]) == 1
    @test value(m[:binMGTechUsed]["PV"]) == 0
    @test value(m[:binMGStorageUsed]) == 1
    @test results["Financial"]["lcc_us_dollars"] ≈ 7.3681609e7 atol=5e4

    #=
    Scenario with $0/kWh VoLL, 12x169 hour outages, 1kW load/hour, and min_resil_timesteps = 168
    - should meet 168 kWh in each outage such that the total unserved load is 12 kWh
    =#
    m = Model(optimizer_with_attributes(CPLEX.Optimizer, "CPX_PARAM_SCRIND" => 0))
    results = run_reopt(m, "./scenarios/nogridcost_minresilhours.json")
    @test sum(results["unserved_load_per_outage"]) ≈ 12

    # testing dvUnserved load, which would output 100 kWh for this scenario before output fix
    m = Model(optimizer_with_attributes(CPLEX.Optimizer, "CPX_PARAM_SCRIND" => 0))
    results = run_reopt(m, "./scenarios/nogridcost_multiscenario.json")
    @test sum(results["unserved_load_per_outage"]) ≈ 60
end

@testset "Multiple Sites" begin
    m = Model(optimizer_with_attributes(CPLEX.Optimizer, "CPX_PARAM_SCRIND" => 0))
    ps = [
        REoptInputs("./scenarios/pv_storage.json"),
        REoptInputs("./scenarios/monthly_rate.json"),
    ];
    results = run_reopt(m, ps)
    @test results[3]["Financial"]["lcc_us_dollars"] + results[10]["Financial"]["lcc_us_dollars"] ≈ 1.23887e7 + 437169.0 rtol=1e-5
end

@testset "LinDistFlow" begin
    m = Model(optimizer_with_attributes(CPLEX.Optimizer, "CPX_PARAM_SCRIND" => 0))
    ps = [
        REoptInputs("./scenarios/pv_storage.json"),
        REoptInputs("./scenarios/monthly_rate.json"),
    ];
    # make dummy REoptInputs with fixed systems for other nodes in network?
    load_nodes = Dict(
        "3" => [],
        "10" => [],
    )
    ldf_inputs = LinDistFlow.Inputs(
        "data/car10lines.dss",
        "0",
        "data/car10linecodes.dss";
        Pload=load_nodes,
        Qload=load_nodes,
        Sbase=1e6,
        Vbase=12.5e3,
        v0 = 1.00,
        v_uplim = 1.05,
        v_lolim = 0.95,
        Ntimesteps = 8760
    );
    build_reopt!(m, ps)
    LinDistFlow.build_ldf!(m, ldf_inputs, ps)
    add_objective!(m, ps)
    optimize!(m)

    results = reopt_results(m, ps)
    @test results[10]["Financial"]["lcc_us_dollars"] + results[3]["Financial"]["lcc_us_dollars"] ≈ 1.23887e7 + 437169.0 rtol=1e-5
    P0 = value.(m[:Pⱼ]["0",:]).data * ldf_inputs.Sbase / 1e3;  # converting to kW
    TotalGridPurchases = value.(m[:dvGridPurchase_3]).data + value.(m[:dvGridPurchase_10]).data;
    @test maximum(TotalGridPurchases) ≈ maximum(P0) rtol = 1e-5  # lossless model
end

## equivalent REopt Lite API Post for test 2:
#   NOTE have to hack in API levelization_factor to get LCC within 5e-5 (Mosel tol)
# {"Scenario": {
#     "Site": {
#         "longitude": -118.1164613,
#         "latitude": 34.5794343,
#         "roof_squarefeet": 5000.0,
#         "land_acres": 1.0,
#     "PV": {
#         "macrs_bonus_pct": 0.4,
#         "installed_cost_us_dollars_per_kw": 2000.0,
#         "tilt": 34.579,
#         "degradation_pct": 0.005,
#         "macrs_option_years": 5,
#         "federal_itc_pct": 0.3,
#         "module_type": 0,
#         "array_type": 1,
#         "om_cost_us_dollars_per_kw": 16.0,
#         "macrs_itc_reduction": 0.5,
#         "azimuth": 180.0,
#         "federal_rebate_us_dollars_per_kw": 350.0,
#         "dc_ac_ratio": 1.1
#     },
#     "LoadProfile": {
#         "doe_reference_name": "RetailStore",
#         "annual_kwh": 10000000.0,
#         "city": "LosAngeles"
#     },
#     "Storage": {
#         "total_rebate_us_dollars_per_kw": 100.0,
#         "macrs_option_years": 5,
#         "can_grid_charge": true,
#         "macrs_bonus_pct": 0.4,
#         "macrs_itc_reduction": 0.5,
#         "total_itc_pct": 0,
#         "installed_cost_us_dollars_per_kw": 1000.0,
#         "installed_cost_us_dollars_per_kwh": 500.0,
#         "replace_cost_us_dollars_per_kw": 460.0,
#         "replace_cost_us_dollars_per_kwh": 230.0
#     },
#     "ElectricTariff": {
#         "urdb_label": "5ed6c1a15457a3367add15ae"
#     },
#     "Financial": {
#         "escalation_pct": 0.026,
#         "offtaker_discount_pct": 0.081,
#         "owner_discount_pct": 0.081,
#         "analysis_years": 20,
#         "offtaker_tax_pct": 0.4,
#         "owner_tax_pct": 0.4,
#         "om_cost_escalation_pct": 0.025
#     }
# }}}