export moments

abstract type PolynomialSet end

function JuMP.in_set_string(print_mode, set::PolynomialSet)
    return string(JuMP._math_symbol(print_mode, :in), ' ', set)
end

struct ZeroPoly <: PolynomialSet end
struct NonNegPoly <: PolynomialSet end
struct PosDefPolyMatrix <: PolynomialSet end

function JuMP.function_string(::Type{JuMP.REPLMode},
                              p::MultivariatePolynomials.APL)
    return sprint(show, MIME"text/plain"(), p)
end
function JuMP.function_string(::Type{JuMP.IJuliaMode},
                              p::MultivariatePolynomials.APL)
    # `show` prints `$$` around what `_show` prints.
    return sprint(MultivariatePolynomials._show, MIME"text/latex"(), p)
end

### Shapes for polynomial/moments primal-dual pair ###

# Inspired from `JuMP.dual_shape` docstring example
struct PolynomialShape{BT <: MB.AbstractPolynomialBasis} <: JuMP.AbstractShape
    basis::BT
end
function JuMP.reshape_vector(x::Vector, shape::PolynomialShape)
    return polynomial(x, shape.basis)
end
struct MomentsShape{BT <: MB.AbstractPolynomialBasis} <: JuMP.AbstractShape
    basis::BT
end
function JuMP.reshape_vector(x::Vector, shape::MomentsShape)
    return measure(x, shape.basis)
end
JuMP.dual_shape(shape::PolynomialShape) = MomentsShape(shape.basis)
JuMP.dual_shape(shape::MomentsShape) = PolynomialShape(shape.basis)

JuMP.reshape_set(::ZeroPolynomialSet, ::PolynomialShape) = ZeroPoly()
function JuMP.moi_set(::ZeroPoly, basis::MB.AbstractPolynomialBasis;
                      domain::AbstractSemialgebraicSet=FullSpace()
                     )
    return ZeroPolynomialSet(domain, basis)
end

"""
    moments(cref::JuMP.ConstraintRef)

Return the [`MomentsAttribute`](@ref) of `cref`.
"""
function MultivariateMoments.moments(cref::JuMP.ConstraintRef)
    return MOI.get(cref.model, MomentsAttribute(), cref)
end

"""
    bridges(F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet})

Return a list of bridges that may be needed to bridge `F`-in-`S` constraints but
not the bridges that may be needed by constraints added by the bridges.
"""
bridges(::Type{<:MOI.AbstractFunction}, ::Type{<:MOI.AbstractSet}) = []

"""
    bridges(S::Type{<:MOI.AbstractSet})

Return a list of bridges that may be needed to bridge constrained variables
in `S` but not the bridges that may be needed by constraints added by the
bridges.
"""
bridges(S::Type{<:MOI.AbstractSet}) = bridges(MOI.VectorOfVariables, S)

function bridges(::Type{<:MOI.AbstractVectorFunction},
                 ::Type{<:ZeroPolynomialSet{FullSpace}})
    return [ZeroPolynomialBridge]
end

function bridges(::Type{<:MOI.AbstractVectorFunction},
                 ::Type{<:ZeroPolynomialSet{<:AbstractAlgebraicSet}})
    return [ZeroPolynomialInAlgebraicSetBridge]
end

function bridges(::Type{<:MOI.AbstractVectorFunction},
                 ::Type{<:PlusMinusSet})
    return [PlusMinusBridge]
end

"""
    bridgeable(c::JuMP.AbstractConstraint, F::Type{<:MOI.AbstractFunction},
               S::Type{<:MOI.AbstractSet})

Wrap the constraint `c` in `JuMP.BridgeableConstraint`s that may be needed to
bridge `F`-in-`S` constraints.
"""
function bridgeable end

function _concrete(bridge_type::Type{<:MOIB.Variable.AbstractBridge},
                   S::Type{<:MOI.AbstractSet})
    MOIB.Variable.concrete_bridge_type(bridge_type, S)
end
function _concrete(bridge_type::Type{<:MOIB.Constraint.AbstractBridge},
                   S::Type{<:MOI.AbstractSet})
    MOIB.Constraint.concrete_bridge_type(bridge_type, MOI.VectorOfVariables, S)
end
function bridgeable(c::JuMP.AbstractConstraint,
                    S::Type{<:MOI.AbstractSet})
    bridge_types = bridges(S)
    for bridge_type in bridge_types
        c = BridgeableConstraint(c, bridge_type)
        concrete_bridge_type = _concrete(bridge_type{Float64}, S)
        for (ST,) in MOIB.added_constrained_variable_types(concrete_bridge_type)
            c = bridgeable(c, ST)
        end
        for (FT, ST) in MOIB.added_constraint_types(concrete_bridge_type)
            c = bridgeable(c, FT, ST)
        end
    end
    return c
end

function bridgeable(c::JuMP.AbstractConstraint,
                    F::Type{<:MOI.AbstractFunction},
                    S::Type{<:MOI.AbstractSet})
    bridge_types = bridges(F, S)
    for bridge_type in bridge_types
        c = BridgeableConstraint(c, bridge_type)
        concrete_bridge_type = MOIB.Constraint.concrete_bridge_type(
            bridge_type{Float64}, F, S)
        for (ST,) in MOIB.added_constrained_variable_types(concrete_bridge_type)
            c = bridgeable(c, ST)
        end
        for (FT, ST) in MOIB.added_constraint_types(concrete_bridge_type)
            c = bridgeable(c, FT, ST)
        end
    end
    return c
end

### @constraint/@SDconstraint macros ###

non_constant(a::Vector{<:Number}) = convert(Vector{AffExpr}, a)
non_constant(a::Vector{<:JuMP.AbstractJuMPScalar}) = a
non_constant_coefficients(p) = non_constant(coefficients(p))

## ZeroPoly
function JuMP.build_constraint(_error::Function, p::AbstractPolynomialLike,
                               s::ZeroPoly;
                               domain::AbstractSemialgebraicSet=FullSpace(),
                               basis_type = MB.MonomialBasis,
                               kws...)
    coefs = non_constant_coefficients(p)
    basis = MB.basis_covering_monomials(basis_type, monomials(p))
    if domain isa BasicSemialgebraicSet
        # p(x) = 0 for all x in a basic semialgebraic set. We replace it by
        # p(x) ≤ 0 and p(x) ≥ 0 for all x in the basic semialgebraic set.
        # We need to determine the cone two use for `NonNegPoly` which is stored
        # in the `ext` field of the `model` so we need the `model` hence we need
        # to go to `JuMP.add_constraint`.

        # We need to recreate the full list of keyword arguments. `kws` is a
        # `Iterators.Pairs, `kws.data` is a `NamedTuple` and `kws.itr` is a
        # `Tuple`.
        all_kws = Iterators.Pairs(merge((domain=domain,), kws.data),
                                  (:domain, kws.itr...))
        return Constraint(_error, p, s, all_kws)
    else
        set = JuMP.moi_set(s, basis; domain=domain, kws...)
        constraint = JuMP.VectorConstraint(coefs, set, PolynomialShape(basis))
        return bridgeable(constraint, JuMP.moi_function_type(typeof(coefs)),
                          typeof(set))
    end
end
function JuMP.build_constraint(_error::Function, p::AbstractPolynomialLike,
                               s::MOI.EqualTo; kws...)
    return JuMP.build_constraint(_error, p-s.value, ZeroPoly(); kws...)
end

## NonNegPoly and PosDefPolyMatrix
# The `model` is not given in `JuMP.build_constraint` so we create a custom
# `Constraint` object and transform the `set` in `JuMP.add_constraint`.
struct Constraint{FT, PT, ST<:PolynomialSet, KWT<:Iterators.Pairs} <: JuMP.AbstractConstraint
    _error::FT
    polynomial_or_matrix::PT
    set::ST
    kws::KWT
end
function JuMP.build_constraint(_error::Function, polynomial_or_matrix,
                               set::Union{NonNegPoly, PosDefPolyMatrix};
                               kws...)
    return Constraint(_error, polynomial_or_matrix, set, kws)
end

# FIXME the domain will not appear in the printing, it should be a field of
#       `ZeroPoly` which is `FullSpace` by default
JuMP.reshape_set(::PlusMinusSet, ::PolynomialShape) = ZeroPoly()
function JuMP.add_constraint(model::JuMP.Model,
                             constraint::Constraint{<:Any, <:Any, ZeroPoly},
                             name::String = "")
    cone = getdefault(model, NonNegPoly())
    coefs = non_constant_coefficients(constraint.polynomial_or_matrix)
    basis = MB.MonomialBasis(monomials(constraint.polynomial_or_matrix))
    set = PlusMinusSet(JuMP.moi_set(cone, basis; constraint.kws...))
    new_constraint = JuMP.VectorConstraint(coefs, set, PolynomialShape(basis))
    bridgeable_con = bridgeable(
        new_constraint, JuMP.moi_function_type(typeof(coefs)), typeof(set))
    return JuMP.add_constraint(model, bridgeable_con, name)
end

function JuMP.add_constraint(model::JuMP.Model, constraint::Constraint,
                             name::String = "")
    set = getdefault(model, constraint.set)
    new_constraint = JuMP.build_constraint(constraint._error,
                                           constraint.polynomial_or_matrix, set;
                                           constraint.kws...)
    return JuMP.add_constraint(model, new_constraint, name)
end

# NonNegPoly
function JuMP.build_constraint(_error::Function, p::AbstractPolynomialLike,
                               s::MOI.GreaterThan; kws...)
    return JuMP.build_constraint(_error, p-s.lower, NonNegPoly(); kws...)
end
function JuMP.build_constraint(_error::Function, p::AbstractPolynomialLike,
                               s::MOI.LessThan; kws...)
    return JuMP.build_constraint(_error, s.upper-p, NonNegPoly(); kws...)
end

# PosDefPolyMatrix
# there is already a method for AbstractMatrix in PSDCone in JuMP so we need a
# more specific here to avoid ambiguity
function JuMP.build_constraint(_error::Function,
                               p::AbstractMatrix{<:AbstractPolynomialLike},
                               s::PSDCone; kws...)
    return JuMP.build_constraint(_error, p, PosDefPolyMatrix(); kws...)
end


T = Float64

# options
max_con_viol = 1.0e-8
verbose=false

opts_ilqr = iLQRSolverOptions{T}(verbose=verbose,live_plotting=:off)

opts_al = AugmentedLagrangianSolverOptions{T}(verbose=false,
    opts_uncon=opts_ilqr,
    cost_tolerance=1.0e-4,
    cost_tolerance_intermediate=1.0e-3,
    constraint_tolerance=max_con_viol,
    penalty_scaling=50.,
    penalty_initial=1.)

opts_pn = ProjectedNewtonSolverOptions{T}(verbose=verbose,
    feasibility_tolerance=max_con_viol)

opts_altro = ALTROSolverOptions{T}(verbose=verbose,
    opts_al=opts_al,
    opts_pn=opts_pn,
    projected_newton=true,
    projected_newton_tolerance=1.0e-3);

opts_ipopt = DIRCOLSolverOptions{T}(verbose=verbose,
    nlp=Ipopt.Optimizer(),
    feasibility_tolerance=max_con_viol)

# AL-iLQR
prob = copy(Problems.cartpole_static)
U0 = deepcopy(controls(prob))
alilqr = AugmentedLagrangianSolver(prob, opts_al)
solve!(alilqr)
max_violation(alilqr)

@btime begin
    initial_controls!($alilqr, $U0)
    solve!($alilqr)
end

# ALTRO
altro = ALTROSolver(prob, opts_altro)
initial_controls!(altro, U0)
solve!(altro)
max_violation(altro)

@btime begin
    initial_controls!($altro, $U0)
    solve!($altro)
end

# Ipopt
prob_ipopt = copy(Problems.cartpole_static)
prob_ipopt = TO.change_integration(prob_ipopt, HermiteSimpson)
rollout!(prob_ipopt)
ipopt = DIRCOLSolver(prob_ipopt, opts_ipopt)
ipopt.opts.verbose = false
solve!(ipopt)
max_violation(ipopt)

@btime solve!($ipopt)


# This file is a part of BAT.jl, licensed under the MIT License (MIT).

"""
    MHProposalDistTuning

Abstract super-type for Metropolis-Hastings tuning strategies for
proposal distributions.
"""
abstract type MHProposalDistTuning <: MCMCTuningAlgorithm end
export MHProposalDistTuning

"""
    MetropolisHastings

Metropolis-Hastings MCMC sampling algorithm.

Constructors:

```julia
MetropolisHastings(
    proposal::ProposalDistSpec
    weighting::AbstractMCMCWeightingScheme
    tuning::TN = MHProposalDistTuning
)
"""
@with_kw struct MetropolisHastings{
    Q<:ProposalDistSpec,
    WS<:AbstractMCMCWeightingScheme,
    TN<:MHProposalDistTuning,
} <: MCMCAlgorithm
    proposal::Q = MvTDistProposal()
    weighting::WS = RepetitionWeighting()
    tuning::TN = AdaptiveMHTuning()
end

export MetropolisHastings

bat_default(::Type{MCMCSampling}, ::Val{:trafo}, sampler::MetropolisHastings) = PriorToGaussian()

bat_default(::Type{MCMCSampling}, ::Val{:nsteps}, sampler::MetropolisHastings, trafo::AbstractDensityTransformTarget, nchains::Integer) = 10^5

get_mcmc_tuning(algorithm::MetropolisHastings) = algorithm.tuning

mcmc_compatible(::MetropolisHastings, ::AbstractProposalDist, ::NoVarBounds) = true

mcmc_compatible(::MetropolisHastings, proposaldist::AbstractProposalDist, bounds::HyperRectBounds) =
    issymmetric(proposaldist) || all(x -> x == hard_bounds, bounds.bt)

mcmc_compatible(::MetropolisHastings, proposaldist::AbstractProposalDist, bounds::HierarchicalDensityBounds) =
    issymmetric(proposaldist)

mutable struct MHIterator{
    AL<:MetropolisHastings,
    D<:AbstractDensity,
    R<:AbstractRNG,
    PR<:RNGPartition,
    Q<:AbstractProposalDist,
    SV<:DensitySampleVector
} <: MCMCIterator
    algorithm::AL
    density::D
    rng::R
    rngpart_cycle::PR
    info::MCMCIteratorInfo
    proposaldist::Q
    samples::SV
    nsamples::Int64
    stepno::Int64
end

function MHIterator(
    rng::AbstractRNG,
    algorithm::MCMCAlgorithm,
    density::AbstractDensity,
    info::MCMCIteratorInfo,
    x_init::AbstractVector{P},
) where {P<:Real}
    stepno::Int64 = 0

    npar = totalndof(density)

    params_vec = Vector{P}(undef, npar)
    params_vec .= x_init
    !(params_vec in var_bounds(density)) && throw(ArgumentError("Parameter(s) out of bounds"))

    proposaldist = algorithm.proposal(P, npar)

    log_posterior_value = eval_logval(density, params_vec, strict = true)

    T = typeof(log_posterior_value)
    W = sample_weight_type(typeof(algorithm.weighting))

    sample_info = MCMCSampleID(info.id, info.cycle, 1, CURRENT_SAMPLE)
    current_sample = DensitySample(params_vec, log_posterior_value, one(W), sample_info, nothing)
    samples = DensitySampleVector{Vector{P},T,W,MCMCSampleID,Nothing}(undef, 0, npar)
    push!(samples, current_sample)

    nsamples::Int64 = 0

    rngpart_cycle = RNGPartition(rng, 0:(typemax(Int16) - 2))

    chain = MHIterator(
        algorithm,
        density,
        rng,
        rngpart_cycle,
        info,
        proposaldist,
        samples,
        nsamples,
        stepno
    )

    reset_rng_counters!(chain)

    chain
end

function MCMCIterator(
    rng::AbstractRNG,
    algorithm::MetropolisHastings,
    density::AbstractDensity,
    chainid::Integer,
    startpos::AbstractVector{<:Real}
)
    cycle = 0
    tuned = false
    converged = false
    info = MCMCIteratorInfo(chainid, cycle, tuned, converged)
    MHIterator(rng, algorithm, density, info, startpos)
end

@inline _current_sample_idx(chain::MHIterator) = firstindex(chain.samples)
@inline _proposed_sample_idx(chain::MHIterator) = lastindex(chain.samples)

getalgorithm(chain::MHIterator) = chain.algorithm

getdensity(chain::MHIterator) = chain.density

getrng(chain::MHIterator) = chain.rng

mcmc_info(chain::MHIterator) = chain.info

nsteps(chain::MHIterator) = chain.stepno

nsamples(chain::MHIterator) = chain.nsamples

current_sample(chain::MHIterator) = chain.samples[_current_sample_idx(chain)]

sample_type(chain::MHIterator) = eltype(chain.samples)

function reset_rng_counters!(chain::MHIterator)
    set_rng!(chain.rng, chain.rngpart_cycle, chain.info.cycle)
    rngpart_step = RNGPartition(chain.rng, 0:(typemax(Int32) - 2))
    set_rng!(chain.rng, rngpart_step, chain.stepno)
    nothing
end

function samples_available(chain::MHIterator)
    i = _current_sample_idx(chain::MHIterator)
    chain.samples.info.sampletype[i] == ACCEPTED_SAMPLE
end

function get_samples!(appendable, chain::MHIterator, nonzero_weights::Bool)::typeof(appendable)
    if samples_available(chain)
        samples = chain.samples

        for i in eachindex(samples)
            st = samples.info.sampletype[i]
            if (
                (st == ACCEPTED_SAMPLE || st == REJECTED_SAMPLE) &&
                (samples.weight[i] > 0 || !nonzero_weights)
            )
                push!(appendable, samples[i])
            end
        end
    end
    appendable
end

function next_cycle!(chain::MHIterator)
    _cleanup_samples(chain)

    chain.info = MCMCIteratorInfo(chain.info, cycle = chain.info.cycle + 1)
    chain.nsamples = 0
    chain.stepno = 0

    reset_rng_counters!(chain)

    resize!(chain.samples, 1)

    i = _proposed_sample_idx(chain)
    @assert chain.samples.info[i].sampletype == CURRENT_SAMPLE
    chain.samples.weight[i] = 1

    chain.samples.info[i] = MCMCSampleID(chain.info.id, chain.info.cycle, chain.stepno, CURRENT_SAMPLE)

    chain
end

function _cleanup_samples(chain::MHIterator)
    samples = chain.samples
    current = _current_sample_idx(chain)
    proposed = _proposed_sample_idx(chain)
    if (current != proposed) && samples.info.sampletype[proposed] == CURRENT_SAMPLE
        # Proposal was accepted in the last step
        @assert samples.info.sampletype[current] == ACCEPTED_SAMPLE
        samples.v[current] .= samples.v[proposed]
        samples.logd[current] = samples.logd[proposed]
        samples.weight[current] = samples.weight[proposed]
        samples.info[current] = samples.info[proposed]

        resize!(samples, 1)
    end
end

function mcmc_step!(chain::MHIterator)
    _cleanup_samples(chain)

    samples = chain.samples
    algorithm = getalgorithm(chain)

    if !mcmc_compatible(algorithm, chain.proposaldist, var_bounds(getdensity(chain)))
        error("Implementation of algorithm $algorithm does not support current parameter bounds with current proposal distribution")
    end

    chain.stepno += 1
    reset_rng_counters!(chain)

    rng = getrng(chain)
    density = getdensity(chain)

    proposaldist = chain.proposaldist

    # Grow samples vector by one:
    resize!(samples, size(samples, 1) + 1)
    samples.info[lastindex(samples)] = MCMCSampleID(chain.info.id, chain.info.cycle, chain.stepno, PROPOSED_SAMPLE)

    current = _current_sample_idx(chain)
    proposed = _proposed_sample_idx(chain)
    @assert current != proposed

    current_params = samples.v[current]
    proposed_params = samples.v[proposed]

    # Propose new variate:
    samples.weight[proposed] = 0
    proposal_rand!(rng, proposaldist, proposed_params, current_params)
    renormalize_variate!(proposed_params, density, proposed_params)

    current_log_posterior = samples.logd[current]
    T = typeof(current_log_posterior)

    # Evaluate prior and likelihood with proposed variate:
    proposed_log_posterior = eval_logval(density, proposed_params, strict = false)

    samples.logd[proposed] = proposed_log_posterior

    p_accept = if proposed_log_posterior > -Inf
        # log of ratio of forward/reverse transition probability
        log_tpr = if issymmetric(proposaldist)
            T(0)
        else
            log_tp_fwd = distribution_logpdf(proposaldist, proposed_params, current_params)
            log_tp_rev = distribution_logpdf(proposaldist, current_params, proposed_params)
            T(log_tp_fwd - log_tp_rev)
        end

        p_accept_unclamped = exp(proposed_log_posterior - current_log_posterior - log_tpr)
        T(clamp(p_accept_unclamped, 0, 1))
    else
        zero(T)
    end

    @assert p_accept >= 0
    accepted = rand(chain.rng, float(typeof(p_accept))) < p_accept

    if accepted
        samples.info.sampletype[current] = ACCEPTED_SAMPLE
        samples.info.sampletype[proposed] = CURRENT_SAMPLE
        chain.nsamples += 1
    else
        samples.info.sampletype[proposed] = REJECTED_SAMPLE
    end

    delta_w_current, w_proposed = _mh_weights(algorithm, p_accept, accepted)
    samples.weight[current] += delta_w_current
    samples.weight[proposed] = w_proposed

    nothing
end

function _mh_weights(
    algorithm::MetropolisHastings{Q,<:RepetitionWeighting},
    p_accept::Real,
    accepted::Bool
) where {Q,W}
    if accepted
        (0, 1)
    else
        (1, 0)
    end
end

function _mh_weights(
    algorithm::MetropolisHastings{Q,<:ARPWeighting},
    p_accept::Real,
    accepted::Bool
) where {Q,W}
    T = typeof(p_accept)
    if p_accept ≈ 1
        (zero(T), one(T))
    elseif p_accept ≈ 0
        (one(T), zero(T))
    else
        (T(1 - p_accept), p_accept)
    end
end

eff_acceptance_ratio(chain::MHIterator) = nsamples(chain) / nsteps(chain)


import ..Filter
import ..Wirreducible  #remove all polarization subdiagrams
import ..Girreducible  #remove all self-energy inseration
import ..NoHatree
import ..NoFock
import ..NoBubble  # true to remove all bubble subdiagram
import ..Proper  #ver4, ver3, and polarization diagrams may require to be irreducible along the transfer momentum/frequency

import ..DiagramType
import ..GreenDiag
import ..SigmaDiag
import ..PolarDiag
import ..Ver3Diag
import ..Ver4Diag

import ..Composite
import ..ChargeCharge
import ..SpinSpin
import ..UpUp
import ..UpDown
import ..Response

import ..Instant
import ..Dynamic
import ..D_Instant
import ..D_Dynamic
import ..AnalyticProperty

import ..symbol
import ..short

import ..Interaction
import ..GenericPara
import ..innerTauNum

import ..Diagram

import ..DiagramId
import ..Ver4Id
import ..Ver3Id
import ..GreenId
import ..SigmaId
import ..PolarId
import ..BareInteractionId
import ..BareGreenId

import ..TwoBodyChannel
import ..Alli
import ..PHr
import ..PHEr
import ..PPr
import ..AnyChan

import ..Permutation
import ..Di
import ..Ex
import ..DiEx

import ..uidreset
import ..toDataFrame
import ..mergeby


"""
`module  JAC.HighHarmonic
    ... a submodel of JAC that contains all methods to set-up and process high-harmonic computations.
        It defines in particular a number of structs to deal with the observables, approaches, laser pulses and
        time-dependent fields as they frequently occur in the computation of high-harmonic spectra.
"""
module HighHarmonic

    using Printf, ..Basics, ..Defaults, ..Radial, ..ManyElectron, ..Nuclear, ..TableStrings

    """
    `abstract type HighHarmonic.AbstractTargetModel`
        ... defines an abstract and a number of other types to represent an atomic target (cloud) for HHG:

        + struct LocalizedTargetModel     ... to represent a point-like localized target
        + struct GaussianTargetModel      ... to represent a localized, Gaussian-type target (not yet)
        + struct PlanarTargetModel        ... to represent a planar target of finite extent (not yet)
        + struct UserTargetModel          ... to represent a user-defined 3-dimensional target (not yet)
    """
    abstract type  AbstractTargetModel  end

    """
    `struct  HighHarmonic.LocalizedTargetModel  <:  AbstractTargetModel`
        ... defines a struct for point-like localized target cloud.

        + center     ::CartesianVector{Float64}   ... Center of the (point-like) target.
    """
    struct  LocalizedTargetModel  <:  AbstractTargetModel
        center       ::CartesianVector{Float64}
    end

    """
    `struct  HighHarmonic.GaussianTargetModel  <:  AbstractTargetModel`
        ... defines a struct for localized, Gaussian target cloud.

        + center     ::CartesianVector{Float64}   ... Center of the (point-like) target.
        + NoPoints   ::Int64                      ... Number of points to be used for the target.
        + sigma      ::Float64                    ... Gaussian half-widths of the cloud.
    """
    struct  GaussianTargetModel  <:  AbstractTargetModel
        center       ::CartesianVector{Float64}
        NoPoints     ::Int64
        sigma        ::Float64
    end

    """
    `struct  HighHarmonic.TargetCloud`
        ... defines a struct to represent a -- point-like or extended -- target cloud for the computation of high-harmonic spectra

        + model      ::AbstractTargetModel                    ... Model of the target cloud, from which the target is generated.
        + points     ::Array{WeightedCartesian{Float64},1}    ... Cartesian points and weights that define the atomic target cloud.
    """
    struct  TargetCloud
        model        ::AbstractTargetModel
        points       ::Array{WeightedCartesian{Float64},1}
    end

    """
    `abstract type HighHarmonic.AbstractDeterctorModel`
        ... defines an abstract and a number of other types to represent an dectector screen for high-harmonic spectra:

      + struct LocalizedDetectorModel      ... to represent a point-like localized detector
      + struct SphericalDetectorModel      ... to represent a partial-sphere detector screen (not yet)
      + struct PlanarDetectorModel         ... to represent a planar detector screen of finite extent (not yet)
    """
    abstract type  AbstractDetectorModel  end

    """
    `struct  HighHarmonic.LocalizedDetectorModel  <:  AbstractDetectorModel`
        ... defines a struct for point-like localized detector.

        + center     ::CartesianVector{Float64}   ... Center of the (point-like) detector.
    """
    struct  LocalizedDetectorModel  <:  AbstractDetectorModel
        center       ::CartesianVector{Float64}
    end

    """
    `struct  HighHarmonic.SphericalDetectorModel  <:  AbstractDetectorModel`
        ... defines a struct for a partly-spherical detector screen.

        + center     ::CartesianVector{Float64}   ... Center of the detector.
        + NoPoints   ::Int64                      ... Number of points to be used for the detector screen.
        + theta      ::Float64                    ... polar (arc) angle of the screen.
    """
    struct  SphericalDetectorModel  <:  AbstractDetectorModel
        center       ::CartesianVector{Float64}
        NoPoints     ::Int64
        theta        ::Float64
    end

    """
    `struct  HighHarmonic.DetectorScreen`
        ... defines a struct to represent a -- point-like or extended -- detector screen for the computation of high-harmonic spectra.

        + model      ::AbstractDetectorModel                    ... Model of the detector, from which the detection screen is generated.
        + points     ::Array{WeightedCartesian{Float64},1}      ... Cartesian points and weights that define the detection screen.
    """
    struct  DetectorScreen
        model        ::AbstractDetectorModel
        points       ::Array{WeightedCartesian{Float64},1}
    end

    """
    `abstract type HighHarmonic.AbstractHhgObservable`
        ... defines an abstract and a number of singleton types for the observables that can be computed for HHG processes.

      + struct HhgSpectrum
        ... to compute a standard (single-color) high-harmonic spectrum (for a point-like target and point-like detector).

      + struct HhgPolarizedSpectrum
        ... to compute a bi-color high-harmonic spectrum (for a point-like target and point-like detector).

      + struct AttoSecondPulse     (not yet)
      + struct IntensityProfile    (not yet)
      + struct PhaseProfile        (not yet)

      + struct HhgSpectrum
    """
    abstract type  AbstractHhgObservable                              end
    struct         HhgSpectrum           <:  AbstractHhgObservable   end
    struct         HhgPolarizedSpectrum  <:  AbstractHhgObservable   end

    """
    `abstract type HighHarmonic.AbstractHhgApproach`
        ... defines an abstract and a number of singleton types for the computational approach/model that is applied in the
            computation of HHG spectra.

      + struct HhgHydrogenicSaddlePoint
        ... to apply hydrogenic orbitals for the computation of the time-dependent dipole amplitude and the saddle-point
            approximation for the dipole moments.

      + struct HhgHydrogenicFullVolkov
        ... to apply hydrogenic orbitals for the computation of the time-dependent dipole amplitude but the full
            Volkov phase for the dipole moments.
    """
    abstract type  AbstractHhgApproach                                 end
    struct         HhgHydrogenicSaddlePoint  <:  AbstractHhgApproach   end
    struct         HhgHydrogenicFullVolkov   <:  AbstractHhgApproach   end

    """
    `abstract type HighHarmonic.AbstractHhgPulse`
        ... defines an abstract and a number of other types to represent an incident laser pulse/field for HHG:

      + struct HhgLinearPlaneWaveLaser  ... to represent a long, linearly-polarized plane-wave laser field,
                                            propagating into z-direction.
      + struct HhgSineSquaredPulse      ... to represent a sin^2(...) pulse  (not yet)
    """
    abstract type  AbstractHhgPulse  end

    """
    `struct  HighHarmonic.HhgLinearPlaneWaveLaser  <:  AbstractHhgPulse`
        ... defines a struct for a long, plane-wave laser field of given frequency and intensity.

        + omega          ::Float64   ... Frequency of the incident laser field in [a.u.].
        + E0             ::Float64   ... Electric-field amplitude E0 in [a.u.].
        ...
        + intensity      ::Float64   ... Intensity [W/cm^2] of the incident laser field.
        + polarization   ::...       ... Polarization of the incident laser field.
        + beamProfile    ::...
        + envelope       ::...
        + length         ::...
        + centralLength  ::...
    """
    struct  HhgLinearPlaneWaveLaser  <:  AbstractHhgPulse
        omega            ::Float64
        E0               ::Float64
    end

    """
    `HighHarmonic.HhgLinearPlaneWaveLaser()`
        ... constructor for an (empty) instance of a linearly-polarized plane-wave pulse.
    """
    function HhgLinearPlaneWaveLaser()
        HhgLinearPlaneWaveLaser(0., 0.)
    end

    """
    `HighHarmonic.HhgLinearPlaneWaveLaser(omega; intensity::Float64=1.0e14)`
        ... constructor for an linearly-polarized plane-wave pulse of given frequency omega and intensity [W/cm^2].
    """
    function HhgLinearPlaneWaveLaser(omega; intensity::Float64=1.0e14)
        omega_au = Defaults.convertUnits("energy: to atomic", omega);   E0_au = intensity / 3.51e16
        HhgLinearPlaneWaveLaser(omega_au, E0_au)
    end

    # `Base.show(io::IO, pulse::HhgLinearPlaneWaveLaser)`  ... prepares a proper printout of the variable pulse::HhgLinearPlaneWaveLaser.
    function Base.show(io::IO, pulse::HhgLinearPlaneWaveLaser)
        println(io, "omega:              $(pulse.omega)  ")
        println(io, "E0:                 $(pulse.E0)  ")
    end

    """
    `struct  HighHarmonic.Computation`
        ... defines a type for the computation of high-harmonic spectra, atto-second pulses, intensity and phase profiles,
            and several others.

        + observable         ::AbstractHhgObservable    ... Specifies one of the observables to be calculated.
        + approach           ::AbstractHhgApproach      ... Specifies the approach/frame to be applied in the computations.
        + pulse              ::AbstractHhgPulse         ... Specifies the incident laser field.
        + timeMesh           ::Array{Float64,1}         ... time mesh to be used for representing the incident and generated fields.
        + nuclearModel       ::Nuclear.Model            ... Model, charge and parameters of the nucleus.
        + initialOrbital     ::Radial.Orbital           ... (Single-electron) orbital to specify the initial state of atom.
        + target             ::TargetCloud              ... Specifies the target for generating high harmonics.
        + detector           ::DetectorScreen           ... Specifies the detector for observing high harmonics.
    """
    struct  Computation
        observable           ::AbstractHhgObservable
        approach             ::AbstractHhgApproach
        pulse                ::AbstractHhgPulse
        timeMesh             ::Array{Float64,1}
        nuclearModel         ::Nuclear.Model
        initialOrbital       ::Radial.Orbital
        target               ::TargetCloud
        detector             ::DetectorScreen
    end

    # `Base.show(io::IO, computation::HighHarmonic.Computation)`  ... prepares a proper printout of the variable computation::HighHarmonic.Computation.
    function Base.show(io::IO, computation::HighHarmonic.Computation)
        println(io, "observable:           $(computation.observable)  ")
        println(io, "approach:             $(computation.approach)  ")
        println(io, "pulse:                $(computation.pulse)  ")
        println(io, "timeMesh:             $(computation.timeMesh)  ")
        println(io, "nuclearModel:         $(computation.nuclearModel)  ")
        println(io, "initialOrbital:       $(computation.initialOrbital)  ")
        println(io, "target:               $(computation.target)  ")
        println(io, "detector:             $(computation.detector)  ")
    end

    """
    `HighHarmonic.computeTimeMesh(NoCycles::Int64, NoPointsPerCycle::Int64)`
        ... to compute a time mesh for a photon field with given number of cycles (NoCycles) as well as time
            points per cycle; a timeMesh::Array{Float64,1} is returned.
    """
    function computeTimeMesh(NoCycles::Int64, NoPointsPerCycle::Int64)
        dt = 2pi / NoPointsPerCycle;    timeMesh = Float64[]
        for  k = 1:NoCycles*NoPointsPerCycle   push!( timeMesh, k*dt)   end

        println("Time mesh generated in the (time) interval [0., $(timeMesh[end])] with $(length(timeMesh)) points.")
        return( timeMesh )
    end

    """
    `HighHarmonic.computeTimeDipoleMoment(approach::HhgHydrogenicSaddlePoint, pulse::HhgLinearPlaneWaveLaser,
                                          timeMesh::Array{Float64,1}, orbital::Radial.Orbital)`
        ... to compute the time-dependent dipole moment D(t) for the given pulse, approximation and the orbital wave function.
            This dipole moment is calculated for all times as defined by timeMesh. A dipoleMoment::Array{Float64,1} is returned.
    """
    function computeTimeDipoleMoment(approach::HhgHydrogenicSaddlePoint, pulse::HhgLinearPlaneWaveLaser,
                                     timeMesh::Array{Float64,1}, orbital::Radial.Orbital)
        dipoleMoment = CartesianVector{ComplexF64}[]
        for  t in timeMesh
            push!( dipoleMoment, CartesianVector{ComplexF64}(0., 0., 0.) )
        end
        return( dipoleMoment )
    end

    """
    `HighHarmonic.computeElectricField(pulse::HhgLinearPlaneWaveLaser, time::Float64)`
        ... to compute the electric field for the given pulse at time; an eField::CartesianVector{Float64} is returned.
    """
    function computeElectricField(pulse::HhgLinearPlaneWaveLaser, time::Float64)
        return( CartesianVector{Float64}(0., 0., pulse.E0 * cos(pulse.omega*time) ) )
    end

    """
    `HighHarmonic.computeVectorPotential(pulse::HhgLinearPlaneWaveLaser, time::Float64)`
        ... to compute the electric field for the given pulse at time; a aField::CartesianVector{Float64} is returned.
    """
    function computeVectorPotential(pulse::HhgLinearPlaneWaveLaser, time::Float64)
        aField = CartesianVector{Float64}(0., 0., 0.)
        return( aField )
    end

    """
    `HighHarmonic.computeVolkovPhase(approach::HhgHydrogenicSaddlePoint, time::Float64, ip::Float64, aField::CartesianVector{Float64})`
        ... to compute the Volkov phase at time for the given approximation, ionization potential and vector potential (aField);
            a Volkov phase::Float64 is returned.
    """
    function computeVolkovPhase(approach::HhgHydrogenicSaddlePoint, time::Float64, ip::Float64, aField::CartesianVector{Float64})
        phase = 0.
        return( phase )
    end

    """
    `HighHarmonic.computeTimeDipoleAmplitude(approach::HhgHydrogenicSaddlePoint, time::Float64, aField::CartesianVector{Float64},
                                             orbital::Radial.Orbital)`
        ... to compute the dipole amplitude d(t) at time for the given approximation, vector potential (aField) and the
            orbital wave function; a dipole amplitude::CartesianVector{ComplexF64} is returned.
    """
    function computeTimeDipoleAmplitude(approach::HhgHydrogenicSaddlePoint, time::Float64, aField::Array{Float64,1},
                                        orbital::Radial.Orbital)
        dipoleAmplitude = CartesianVector{ComplexF64}(0., 0., 0.)
        return( dipoleAmplitude )
    end

    """
    `HighHarmonic.computeFrequencyDipoleMoment(timeMesh::Array{Float64,1}, timeDipole::Array{CartesianVector{ComplexF64},1})`
        ... to compute the frequency-dependent dipole moment D^~ (omega);
            an omegaMoment::Array{CartesianVector{ComplexF64},1} is returned.
    """
    function computeFrequencyDipoleMoment(timeMesh::Array{Float64,1}, timeDipole::Array{CartesianVector{ComplexF64},1})
        omegaMoment = CartesianVector{ComplexF64}[]
        for  t in timeMesh
           push!( omegaMoment, CartesianVector{ComplexF64}(0., 0., 0.) )
        end
        return( omegaMoment )
    end

    """
    `HighHarmonic.perform(comp::HighHarmonic.Computation; output::Bool=false)`
        ... to perform a computation of (one) selected observable in HHG, such a spectra, attosecond pulses of profiles.
            Different approaches, pulse structures of the incident light as well as different target clouds and detector
            screens can also be specified together with all physical parameters: frequency, intensity and polarization of
            the incident light; size andshape of target clouds; size and shape of detector screens; ....
    """
    function perform(comp::HighHarmonic.Computation; output::Bool=false)
        if  output    results = Dict{String, Any}()    else    results = nothing    end
        nModel = comp.nuclearModel

        if       comp.observable == HighHarmonic.HhgSpectrum()
            timeDipole  = HighHarmonic.computeTimeDipoleMoment(comp.approach, comp.pulse, comp.timeMesh, comp.initialOrbital)
            omegaDipole = HighHarmonic.computeFrequencyDipoleMoment(comp.timeMesh, timeDipole)
        elseif   comp.observable == HighHarmonic.HhgPolarizedSpectrum()
        else     error("Undefined observable for HHG computations.")
        end

        return( 0. )
    end

end # module


export crc, spec, CHECK, NoTables, Single, Multiple, ALL,
       CRC_3_ROHC, CRC_4_ITU, CRC_5_EPC, CRC_5_ITU, CRC_5_USB,
       CRC_6_CDMA2000_A, CRC_6_CDMA2000_B, CRC_6_DARC, CRC_6_ITU,
       CRC_7, CRC_7_ROHC, CRC_8, CRC_8_CDMA2000, CRC_8_DARC,
       CRC_8_DVB_S2, CRC_8_EBU, CRC_8_I_CODE, CRC_8_ITU, CRC_8_MAXIM,
       CRC_8_ROHC, CRC_8_WCDMA, CRC_10, CRC_10_CDMA2000, CRC_11,
       CRC_12_3GPP, CRC_12_CDMA2000, CRC_12_DECT, CRC_13_BBC,
       CRC_14_DARC, CRC_15, CRC_15_MPT1327, CRC_16_ARC,
       CRC_16_AUG_CCITT, CRC_16_BUYPASS, CRC_16_CCITT_FALSE,
       CRC_16_CDMA2000, CRC_16_DDS_110, CRC_16_DECT_R, CRC_16_DECT_X,
       CRC_16_DNP, CRC_16_EN_13757, CRC_16_GENIBUS, CRC_16_MAXIM,
       CRC_16_RIELLO, CRC_16_TELEDISK, CRC_16_USB, CRC_16_CRC_A,
       CRC_16_KERMIT, CRC_16_MODBUS, CRC_16_X_25, CRC_16_XMODEM,
       CRC_24, CRC_24_FLEXRAY_A, CRC_24_FLEXRAY_B, CRC_31_PHILIPS,
       CRC_32, CRC_32_BZIP2, CRC_32_C, CRC_32_D, CRC_32_MPEG_2,
       CRC_32_POSIX, CRC_32_Q, CRC_32_JAMCRC, CRC_32_XFER, CRC_40_GSM,
       CRC_64, CRC_64_WE, CRC_64_XZ, CRC_82_DARC

import Base.Cartesian: @nexprs
import Base: ==, isless, print

const U = Unsigned

struct UnalignedVector{T<:U}
    a::Vector{UInt8}
end
# unsafe
@inline Base.getindex(a::UnalignedVector{T}, i) where {T} = unsafe_load(Ptr{T}(pointer(a.a)), i)
@inline Base.length(a::UnalignedVector{T}) where {T} = length(a.a) ÷ sizeof(T)
@inline unaligned_reinterpret(::Type{T}, a::Vector{UInt8}) where {T<:U} = UnalignedVector{T}(a)
@inline unaligned_reinterpret(::Type{UInt8}, a::Vector{UInt8}) = a
MaybeUnalignedVector{T} = Union{UnalignedVector{T},Vector{T}}

# ---- CRC specifications from http://www.zlib.net/crc_v3.txt

CHECK = Vector{UInt8}(b"123456789")   # universal check vector

struct Spec{P<:U}
    width::Int    # polynomial degree
    poly::P       # generating poly with msb missing
    init::P       # initial remainder
    refin::Bool   # reflect input
    refout::Bool  # reflect output
    xorout::P     # xored with final remainder
    check::P      # checksum for CHECK
    function (::Type{Spec{P}})(width::Int, poly::P, init::P, refin::Bool, refout::Bool, xorout::P, check::P) where {P}
        @assert width <= 8 * sizeof(P)
        new{P}(width, poly, init, refin, refout, xorout, check)
    end
end

spec(poly::P, init::P, refin::Bool, refout::Bool, xorout::P, check::P) where {P<:U} =
    Spec{P}(8*sizeof(P), poly, init, refin, refout, xorout, check)

spec(width::Int, poly::P, init::P, refin::Bool, refout::Bool, xorout::P, check::P) where {P<:U} =
    Spec{P}(width, poly, init, refin, refout, xorout, check)

==(a::Spec, b::Spec) = a.width == b.width && a.poly == b.poly && a.init == b.init && a.refin == b.refin && a.refout == b.refout && a.xorout == b.xorout

isless(a::Spec, b::Spec) = a.width < b.width || (a.width == b.width && (a.poly < b.poly || a.poly == b.poly && (a.init < b.init || (a.init == b.init && !a.refin && (a.refin != b.refin || (a.refin == b.refin && !a.refout && (a.refout != b.refout || a.xorout < b.xorout)))))))

function print(io::IO, s::Spec{P}) where {P<:U}
    n = 1 + div(s.width - 1, 4)
    h = x -> "0x" * string(x, base = 16, pad = n)
    print(io, "width=$(s.width) poly=$(h(s.poly)) init=$(h(s.init)) refin=$(s.refin) refout=$(s.refout) xorout=$(h(s.xorout)) check=$(h(s.check))")
end

# http://reveng.sourceforge.net/crc-catalogue/1-15.htm
CRC_3_ROHC =         spec(3, 0x03, 0x07, true,  true,  0x00, 0x06)
CRC_4_ITU =          spec(4, 0x03, 0x00, true,  true,  0x00, 0x07)
CRC_5_EPC =          spec(5, 0x09, 0x09, false, false, 0x00, 0x00)
CRC_5_ITU =          spec(5, 0x15, 0x00, true,  true,  0x00, 0x07)
CRC_5_USB =          spec(5, 0x05, 0x1f, true,  true,  0x1f, 0x19)
CRC_6_CDMA2000_A =   spec(6, 0x27, 0x3f, false, false, 0x00, 0x0d)
CRC_6_CDMA2000_B =   spec(6, 0x07, 0x3f, false, false, 0x00, 0x3b)
CRC_6_DARC =         spec(6, 0x19, 0x00, true,  true,  0x00, 0x26)
CRC_6_ITU =          spec(6, 0x03, 0x00, true,  true,  0x00, 0x06)
CRC_7 =              spec(7, 0x09, 0x00, false, false, 0x00, 0x75)
CRC_7_ROHC =         spec(7, 0x4f, 0x7f, true,  true,  0x00, 0x53)
CRC_8 =              spec(0x07, 0x00, false, false, 0x00, 0xf4)
CRC_8_CDMA2000 =     spec(0x9b, 0xff, false, false, 0x00, 0xda)
CRC_8_DARC =         spec(0x39, 0x00, true,  true,  0x00, 0x15)
CRC_8_DVB_S2 =       spec(0xd5, 0x00, false, false, 0x00, 0xbc)
CRC_8_EBU =          spec(0x1d, 0xff, true,  true,  0x00, 0x97)
CRC_8_I_CODE =       spec(0x1d, 0xfd, false, false, 0x00, 0x7e)
CRC_8_ITU =          spec(0x07, 0x00, false, false, 0x55, 0xa1)
CRC_8_MAXIM =        spec(0x31, 0x00, true,  true,  0x00, 0xa1)
CRC_8_ROHC =         spec(0x07, 0xff, true,  true,  0x00, 0xd0)
CRC_8_WCDMA =        spec(0x9b, 0x00, true,  true,  0x00, 0x25)
CRC_10 =             spec(10, 0x0233, 0x0000, false, false, 0x0000, 0x0199)
CRC_10_CDMA2000 =    spec(10, 0x03d9, 0x03ff, false, false, 0x0000, 0x0233)
CRC_11 =             spec(11, 0x0385, 0x001a, false, false, 0x0000, 0x05a3)
CRC_12_3GPP =        spec(12, 0x080f, 0x0000, false, true,  0x0000, 0x0daf)
CRC_12_CDMA2000 =    spec(12, 0x0f13, 0x0fff, false, false, 0x0000, 0x0d4d)
CRC_12_DECT =        spec(12, 0x080f, 0x0000, false, false, 0x0000, 0x0f5b)
CRC_13_BBC =         spec(13, 0x1cf5, 0x0000, false, false, 0x0000, 0x04fa)
CRC_14_DARC =        spec(14, 0x0805, 0x0000, true,  true,  0x0000, 0x082d)
CRC_15 =             spec(15, 0x4599, 0x0000, false, false, 0x0000, 0x059e)
CRC_15_MPT1327 =     spec(15, 0x6815, 0x0000, false, false, 0x0001, 0x2566)

# http://reveng.sourceforge.net/crc-catalogue/16.htm#crc.cat.crc-16-ccitt-false
CRC_16_ARC =         spec(0x8005, 0x0000, true,  true,  0x0000, 0xbb3d)
CRC_16_AUG_CCITT =   spec(0x1021, 0x1d0f, false, false, 0x0000, 0xe5cc)
CRC_16_BUYPASS   =   spec(0x8005, 0x0000, false, false, 0x0000, 0xfee8)
CRC_16_CCITT_FALSE = spec(0x1021, 0xffff, false, false, 0x0000, 0x29b1)
CRC_16_CDMA2000 =    spec(0xc867, 0xffff, false, false, 0x0000, 0x4c06)
CRC_16_DDS_110 =     spec(0x8005, 0x800d, false, false, 0x0000, 0x9ecf)
CRC_16_DECT_R =      spec(0x0589, 0x0000, false, false, 0x0001, 0x007e)
CRC_16_DECT_X =      spec(0x0589, 0x0000, false, false, 0x0000, 0x007f)
CRC_16_DNP =         spec(0x3d65, 0x0000, true,  true,  0xffff, 0xea82)
CRC_16_EN_13757 =    spec(0x3d65, 0x0000, false, false, 0xffff, 0xc2b7)
CRC_16_GENIBUS =     spec(0x1021, 0xffff, false, false, 0xffff, 0xd64e)
CRC_16_MAXIM =       spec(0x8005, 0x0000, true,  true,  0xffff, 0x44c2)
CRC_16_RIELLO =      spec(0x8bb7, 0x0000, false, false, 0x0000, 0xd0db)
CRC_16_TELEDISK =    spec(0x1021, 0x89ec, true,  true,  0x0000, 0x26b1)
CRC_16_USB =         spec(0x8005, 0xffff, true,  true,  0xffff, 0xb4c8)
CRC_16_CRC_A =       spec(0x1021, 0xc6c6, true,  true,  0x0000, 0xbf05)
CRC_16_KERMIT =      spec(0x1021, 0x0000, true,  true,  0x0000, 0x2189)
CRC_16_MODBUS =      spec(0x8005, 0xffff, true,  true,  0x0000, 0x4b37)
CRC_16_X_25 =        spec(0x1021, 0xffff, true,  true,  0xffff, 0x906e)
CRC_16_XMODEM =      spec(0x1021, 0x0000, false, false, 0x0000, 0x31c3)

# http://reveng.sourceforge.net/crc-catalogue/17plus.htm
CRC_24 =             spec(24, 0x00864cfb, 0x00b704ce, false, false, 0x00000000, 0x0021cf02)
CRC_24_FLEXRAY_A =   spec(24, 0x005d6dcb, 0x00fedcba, false, false, 0x00000000, 0x007979bd)
CRC_24_FLEXRAY_B =   spec(24, 0x005d6dcb, 0x00abcdef, false, false, 0x00000000, 0x001f23b8)
CRC_31_PHILIPS =     spec(31, 0x04c11db7, 0x7fffffff, false, false, 0x7fffffff, 0x0ce9e46c)
CRC_32 =             spec(0x04c11db7, 0xffffffff, true,  true,  0xffffffff, 0xcbf43926)
CRC_32_BZIP2 =       spec(0x04c11db7, 0xffffffff, false, false, 0xffffffff, 0xfc891918)
CRC_32_C =           spec(0x1edc6f41, 0xffffffff, true,  true,  0xffffffff, 0xe3069283)
CRC_32_D =           spec(0xa833982b, 0xffffffff, true,  true,  0xffffffff, 0x87315576)
CRC_32_MPEG_2 =      spec(0x04c11db7, 0xffffffff, false, false, 0x00000000, 0x0376e6e7)
CRC_32_POSIX =       spec(0x04c11db7, 0x00000000, false, false, 0xffffffff, 0x765e7680)
CRC_32_Q =           spec(0x814141ab, 0x00000000, false, false, 0x00000000, 0x3010bf7f)
CRC_32_JAMCRC =      spec(0x04c11db7, 0xffffffff, true,  true,  0x00000000, 0x340bc6d9)
CRC_32_XFER =        spec(0x000000af, 0x00000000, false, false, 0x00000000, 0xbd0be338)
CRC_40_GSM =         spec(40, 0x0000000004820009, 0x0000000000000000, false, false, 0x000000ffffffffff, 0x000000d4164fc646)
CRC_64 =             spec(0x42f0e1eba9ea3693, 0x0000000000000000, false, false, 0x0000000000000000, 0x6c40df5f0b497347)
CRC_64_WE =          spec(0x42f0e1eba9ea3693, 0xffffffffffffffff, false, false, 0xffffffffffffffff, 0x62ec59e3f1a4f00a)
CRC_64_XZ =          spec(0x42f0e1eba9ea3693, 0xffffffffffffffff, true,  true,  0xffffffffffffffff, 0x995dc9bbdf1939fa)
CRC_82_DARC =        spec(82, 0x0308c0111011401440411, 0x000000000000000000000, true,  true,   0x000000000000000000000, 0x09ea83f625023801fd612)

const ALL = Dict{Symbol,Spec}(:CRC_3_ROHC=>CRC_3_ROHC,
                            :CRC_4_ITU=>CRC_4_ITU,
                            :CRC_5_EPC=>CRC_5_EPC,
                            :CRC_5_ITU=>CRC_5_ITU,
                            :CRC_5_USB=>CRC_5_USB,
                            :CRC_6_CDMA2000_A=>CRC_6_CDMA2000_A,
                            :CRC_6_CDMA2000_B=>CRC_6_CDMA2000_B,
                            :CRC_6_DARC=>CRC_6_DARC,
                            :CRC_6_ITU=>CRC_6_ITU, :CRC_7=>CRC_7,
                            :CRC_7_ROHC=>CRC_7_ROHC, :CRC_8=>CRC_8,
                            :CRC_8_CDMA2000=>CRC_8_CDMA2000,
                            :CRC_8_DARC=>CRC_8_DARC,
                            :CRC_8_DVB_S2=>CRC_8_DVB_S2,
                            :CRC_8_EBU=>CRC_8_EBU,
                            :CRC_8_I_CODE=>CRC_8_I_CODE,
                            :CRC_8_ITU=>CRC_8_ITU,
                            :CRC_8_MAXIM=>CRC_8_MAXIM,
                            :CRC_8_ROHC=>CRC_8_ROHC,
                            :CRC_8_WCDMA=>CRC_8_WCDMA,
                            :CRC_10=>CRC_10,
                            :CRC_10_CDMA2000=>CRC_10_CDMA2000,
                            :CRC_11=>CRC_11,
                            :CRC_12_3GPP=>CRC_12_3GPP,
                            :CRC_12_CDMA2000=>CRC_12_CDMA2000,
                            :CRC_12_DECT=>CRC_12_DECT,
                            :CRC_13_BBC=>CRC_13_BBC,
                            :CRC_14_DARC=>CRC_14_DARC,
                            :CRC_15=>CRC_15,
                            :CRC_15_MPT1327=>CRC_15_MPT1327,
                            :CRC_16_ARC=>CRC_16_ARC,
                            :CRC_16_AUG_CCITT=>CRC_16_AUG_CCITT,
                            :CRC_16_BUYPASS=>CRC_16_BUYPASS,
                            :CRC_16_CCITT_FALSE=>CRC_16_CCITT_FALSE,
                            :CRC_16_CDMA2000=>CRC_16_CDMA2000,
                            :CRC_16_DDS_110=>CRC_16_DDS_110,
                            :CRC_16_DECT_R=>CRC_16_DECT_R,
                            :CRC_16_DECT_X=>CRC_16_DECT_X,
                            :CRC_16_DNP=>CRC_16_DNP,
                            :CRC_16_EN_13757=>CRC_16_EN_13757,
                            :CRC_16_GENIBUS=>CRC_16_GENIBUS,
                            :CRC_16_MAXIM=>CRC_16_MAXIM,
                            :CRC_16_RIELLO=>CRC_16_RIELLO,
                            :CRC_16_TELEDISK=>CRC_16_TELEDISK,
                            :CRC_16_USB=>CRC_16_USB,
                            :CRC_16_CRC_A=>CRC_16_CRC_A,
                            :CRC_16_KERMIT=>CRC_16_KERMIT,
                            :CRC_16_MODBUS=>CRC_16_MODBUS,
                            :CRC_16_X_25=>CRC_16_X_25,
                            :CRC_16_XMODEM=>CRC_16_XMODEM,
                            :CRC_24=>CRC_24,
                            :CRC_24_FLEXRAY_A=>CRC_24_FLEXRAY_A,
                            :CRC_24_FLEXRAY_B=>CRC_24_FLEXRAY_B,
                            :CRC_31_PHILIPS=>CRC_31_PHILIPS,
                            :CRC_32=>CRC_32,
                            :CRC_32_BZIP2=>CRC_32_BZIP2,
                            :CRC_32_C=>CRC_32_C, :CRC_32_D=>CRC_32_D,
                            :CRC_32_MPEG_2=>CRC_32_MPEG_2,
                            :CRC_32_POSIX=>CRC_32_POSIX,
                            :CRC_32_Q=>CRC_32_Q,
                            :CRC_32_JAMCRC=>CRC_32_JAMCRC,
                            :CRC_32_XFER=>CRC_32_XFER,
                            :CRC_40_GSM=>CRC_40_GSM, :CRC_64=>CRC_64,
                            :CRC_64_WE=>CRC_64_WE,
                            :CRC_64_XZ=>CRC_64_XZ,
                            :CRC_82_DARC=>CRC_82_DARC)

# --- Utilities

# http://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte
function reflect_bits(n::UInt8)
    n = (n & 0xf0) >>> 4 | (n & 0x0f) << 4;
    n = (n & 0xcc) >>> 2 | (n & 0x33) << 2;
    (n & 0xaa) >>> 1 | (n & 0x55) << 1;
end

const REFLECT_8 = UInt8[reflect_bits(i) for i in 0x00:0xff]

reflect(u::UInt8) = REFLECT_8[u+1]

for (T,S) in ((UInt16, UInt8), (UInt32, UInt16), (UInt64, UInt32), (UInt128, UInt64))
    n = 8 * sizeof(S)
    mask::S = -one(S)
    @eval reflect(u::$T) = (convert($T, reflect(convert($S, u & $mask))) << $n) | reflect(convert($S, (u >>> $n) & $mask))
end

function reflect(size, u::T) where {T<:U}
    width = 8 * sizeof(T)
    @assert size <= width "cannot reflect a value larger than the representation"
    reflect(u) >>> (width - size)
end

function largest(T, TS...)
    for t in TS
        if sizeof(t) > sizeof(T)
            T = t
        end
    end
    T
end

function fastest(T, TS...)
    L = largest(T, TS...)
    if L == UInt32 && UInt32 != UInt
        UInt64  # round up for speed
    else
        L
    end
end

function pad(::Type{A}, width) where {A<:U}
    8 * sizeof(A) - width
end

# --- CRC calculations

# a calculation may be a "natural" long division (padded to the size
# of the accumulator) or "reverse the rest of the world".  this
# depends on whether the input input bytes are taken as given
# (Forwards) or reflected (Backwards).

abstract type Direction{A<:U} end

struct Backwards{A<:U}<:Direction{A}
    poly::A
    init::A
    function (::Type{Backwards{A}})(spec::Spec) where {A}
        poly = reflect(spec.width, convert(A, spec.poly))
        init = reflect(spec.width, convert(A, spec.init))
        new{A}(poly, init)
    end
end

struct Forwards{A<:U}<:Direction{A}
    pad_p::Int
    poly::A
    carry::A
    pad_8::Int
    init::A
    function (::Type{Forwards{A}})(spec::Spec) where {A}
        pad_p = pad(A, spec.width)
        poly = convert(A, spec.poly) << pad_p
        carry = one(A) << pad(A, 1)
        pad_8 = pad(A, 8)
        init = convert(A, spec.init) << pad_p
        new{A}(pad_p, poly, carry, pad_8, init)
    end
end

# a calculation may use no, one, or many tables...

abstract type Tables{A<:U} end

struct NoTables{A<:U}<:Tables{A}
    (::Type{NoTables{A}})(direcn::Direction{A}) where {A} = new{A}()
end

struct Multiple{A<:U}<:Tables{A}
    tables::Vector{Vector{A}}
    function (::Type{Multiple{A}})(direcn::Direction{A}) where {A}
        tables = Vector{A}[Array{A}(undef, 256) for _ in 1:sizeof(A)]
        fill_table(direcn, tables[1])
        for index in zero(UInt8):convert(UInt8, 255)
            remainder = tables[1][index + 1]
            for t in 2:sizeof(A)
                # chaining gives the contribution of a byte to the
                # remainder after being shifted through t other zero bytes
                # (the different bytes are then combined with xor).
                remainder = chain(direcn, tables[1], remainder)
                tables[t][index + 1] = remainder
            end
        end
        new{A}(tables)
    end
end

struct Single{A<:U}<:Tables{A}
    table::Vector{A}
    # when using multiple tables we need a related single table for the
    # "last few bytes"
    (::Type{Single{A}})(tables::Multiple{A}) where {A} = new{A}(tables.tables[1])
    (::Type{Single{A}})(direcn::Direction{A}) where {A} = new{A}(fill_table(direcn, Array{A}(undef, 256)))
end

# the basic lookup table is just the remainder for each value
function fill_table(direcn, table::Vector{A}) where {A<:U}
    for index in zero(UInt8):convert(UInt8, 255)
        table[index + 1] = extend(direcn, NoTables{A}(direcn), [index], zero(A))
    end
    table
end

function chain(direcn::Forwards{A}, table::Vector{A}, remainder::A) where {A<:U}
    xor(remainder << 8, table[1 + ((remainder >>> direcn.pad_8) & 0xff)])
end

function chain(direcn::Backwards{A}, table::Vector{A}, remainder::A) where {A<:U}
    xor(remainder >>> 8, table[1 + (remainder & 0xff)])
end

"""
    crc(spec[, tables = Multiple])

Create a crc function implementing `spec` with lookup table behaviour `tables`.
Available specs are listed in [`ALL`](@ref) and tables can be either
[`Multiple`](@ref), [`Single`](@ref) or [`NoTables`](@ref).

The returned function accepts `String`, `Vector{UInt8}` or
`IO` and calculates the CRC checksum. An optional boolean parameter
`append` can be used for chained application.

A faster CRC32*C* implementation is available in Stdlib: [`CRC32c`](@ref).

# Examples
```julia-repl
using CRC

# create our own crc function, just once
crc32 = crc(CRC_32)

# use the crc function created above, many times
for s in ["hello", "there"]
    println(s => crc32(s))
end
```
"""
function crc(spec::Spec{P}; tables::Type{T}=Multiple) where {P<:U, T<:Tables}
    A = fastest(P, tables == Multiple ? UInt : UInt8)
    direcn = spec.refin ? Backwards{A}(spec) : Forwards{A}(spec)
    remainder::A = direcn.init
    tables = tables{A}(direcn)
    function handler(data::Vector{UInt8}; append=false)
        remainder = append ? remainder : direcn.init
        remainder = extend(direcn, tables, data, remainder)
        finalize(spec, direcn, remainder)
    end
    function handler(io::IO; append=false, buflen=1000000)
        buffer = Array{UInt8}(undef, buflen)
        remainder = append ? remainder : direcn.init
        while (nb = readbytes!(io, buffer)) > 0
            remainder = extend(direcn, tables, buffer[1:nb], remainder)
        end
        finalize(spec, direcn, remainder)
    end
    function handler(data::AbstractString; append=false)
        handler(convert(Vector{UInt8}, codeunits(data)), append=append)
    end
    handler
end

# calculations assume that the remainder is either reflected or
# padded; these functions correct for this to return the final result.

function finalize(spec::Spec{P}, direcn::Forwards{A}, remainder::A) where {P<:U, A<:U}
    remainder = convert(P, remainder >> direcn.pad_p)
    remainder = spec.refout ? reflect(spec.width, remainder) : remainder
    xor(remainder, spec.xorout)
end

function finalize(spec::Spec{P}, direcn::Backwards{A}, remainder::A) where {P<:U, A<:U}
    remainder = spec.refout ? remainder : reflect(spec.width, remainder)
    xor(convert(P, remainder), spec.xorout)
end

# all the routines below calculate a new value of remainder for the
# data given (along with the algorithm type, available lookup tables,
# etc).

const UNROLL = 16  # only get small improvements past this

function extend(direcn::Forwards{A}, tables::NoTables, data::Vector{UInt8}, remainder::A) where {A<:U}
    for word::UInt8 in data
        remainder::A = xor(remainder, (convert(A, word) << direcn.pad_8))
        for _ in 1:8
            if remainder & direcn.carry == direcn.carry
                remainder = xor(remainder << 1, direcn.poly)
            else
                remainder <<= 1
            end
        end
    end
    remainder
end

function extend(direcn::Forwards{A}, tables::Single{A}, data::Vector{UInt8}, remainder::A) where {A<:U}
    for i in 1:length(data)
        @inbounds word::UInt8 = data[i]
        remainder::A = xor(remainder, convert(A, word) << direcn.pad_8)
        remainder = xor(remainder << 8, tables.table[1 + remainder >>> direcn.pad_8])
    end
    remainder
end

for A in (UInt16, UInt32, UInt64, UInt128)
    n_tables = sizeof(A)
    @eval begin
        function extend(direcn::Forwards{$A}, tables::Multiple{$A}, data::MaybeUnalignedVector{$A}, remainder::$A)
            word::$A, tmp::$A, remainder::$A = zero($A), zero($A), remainder
            i = 1
            while true
                @nexprs $UNROLL _ -> begin  # unroll inner loop
                    if i > length(data)
                        break
                    end
                    @inbounds word = data[i]
                    tmp, remainder = remainder, zero($A)
                    @nexprs $n_tables t -> begin  # unroll table
                        # access similar to reflected but we need to
                        # handle little-endian bytes which turn out
                        # wrong for this case
                        remainder = xor(remainder,
                          tables.tables[t][xor(word >>> (($n_tables - t)*8), tmp >>> ((t-1)*8)) & 0xff + 1])
                    end
                    i += 1
                end
            end
            remainder
        end
    end
end

function extend(direcn::Backwards{A}, tables::NoTables, data::Vector{UInt8}, remainder::A) where {A<:U}
    for word::UInt8 in data
        remainder::A = xor(remainder, convert(A, word))
        for _ in 1:8
            if remainder & one(A) == one(A)
                remainder = xor(remainder >>> 1, direcn.poly)
            else
                remainder >>>= 1
            end
        end
    end
    remainder
end

function extend(direcn::Backwards{A}, tables::Single{A}, data::Vector{UInt8}, remainder::A) where {A<:U}
    for i in 1:length(data)
        @inbounds word::UInt8 = data[i]
        remainder::A = xor(remainder, convert(A, word))
        remainder = xor(remainder >>> 8, tables.table[1 + remainder & 0xff])
    end
    remainder
end

# generate code for processing a bunch of bytes in a single machine
# word, using multiple tables.  stolen from libz.
for A in (UInt16, UInt32, UInt64, UInt128)
    n_tables = sizeof(A)
    @eval begin
        function extend(direcn::Backwards{$A}, tables::Multiple{$A}, data::MaybeUnalignedVector{$A}, remainder::$A)
            word::$A, tmp::$A, remainder::$A = zero($A), zero($A), remainder
            i = 1
            while true
                @nexprs $UNROLL _ -> begin  # unroll inner loop
                    if i > length(data)
                        break
                    end
                    @inbounds word = data[i]
                    tmp, remainder = xor(remainder, word), zero($A)
                    @nexprs $n_tables t -> begin  # unroll table access
                        remainder = xor(remainder,
                          tables.tables[t][(tmp >>> (($n_tables - t)*8)) & 0xff + 1])
                    end
                    i += 1
                end
            end
            remainder
        end
    end
end

# short-circuit "all UInt8" avoiding a self-recursive loop below
function extend(direcn::Direction{UInt8}, tables::Multiple{UInt8}, data::Vector{UInt8}, remainder::UInt8)
    extend(direcn, Single{UInt8}(tables), data, remainder)
end

function extend(direcn::Direction{A}, tables::Multiple{A}, data::Vector{UInt8}, remainder::A) where {A<:U}
    # this is "clever" - we alias the array of bytes to native machine
    # words and then process those, which typically (64 bits) loads 8
    # bytes at a time.
    tail = length(data) % sizeof(A)
    remainder = extend(direcn, tables, unaligned_reinterpret(A, data), remainder)
    # slurp up the final bytes that didn't fill a complete machine word.
    extend(direcn, Single{A}(tables), data[end-tail+1:end], remainder)
end


is_interval_constraint(con_ref::ConstraintRef) = constraint_object(con_ref).set isa MOI.Interval
is_interval_constraint(con_ref::NonlinearConstraintRef) = count(i -> i == :(<=), Meta.parse(string(con_ref)).args) == 2
is_equality_constraint(con_ref::ConstraintRef) = constraint_object(con_ref).set isa MOI.EqualTo
is_equality_constraint(con_ref::NonlinearConstraintRef) = Meta.parse(string(con_ref)).args[1] == :(==)
JuMP.name(con_ref::NonlinearConstraintRef) = ""

"""
    check_constraint!(m::Model, constr::Tuple)

Check constraints in a disjunction Tuple.

    check_constraint!(m::Model, constr_j, constr_list::Vector)

Check nested constraint and update `constr_list`.

    check_constraint!(m::Model, constr)

Check constraint in a Model.

    check_constraint!(m::Model, constr::Nothing)

Return nothing for an empty disjunct.
"""
function check_constraint!(m::Model, constr::Tuple)
    constr_list = []
    map(constr_j -> check_constraint!(m, constr_j, constr_list), constr)
    return Tuple(constr_list)
end
function check_constraint!(m::Model, constr_j::Tuple, constr_list::Vector)
    map(constr_jk -> check_constraint!(m, constr_jk, constr_list), constr_j)
end
function check_constraint!(m::Model, constr_j::AbstractArray{<:ConstraintRef}, constr_list::Vector)
    push!(constr_list, check_constraint!(m, constr_j))
end
function check_constraint!(m::Model, constr_j::ConstraintRef, constr_list::Vector)
    push!(constr_list, check_constraint!(m, constr_j))
end
function check_constraint!(m::Model, constr::ConstraintRef)
    @assert all(is_valid(m, constr)) "$constr is not a valid constraint."
    new_constr = split_constraint(m, constr)
    if isnothing(new_constr)
        new_constr = constr
    else
        constr_name = gen_constraint_name(constr)
        m[constr_name] = new_constr
        constr_str = split(string(constr),"}")[end]
        @warn "$constr_str uses the `MOI.Interval` or `MOI.EqualTo` set. Each instance of the interval set has been split into two constraints, one for each bound."
        delete_original_constraint!(m, constr)
    end
    return new_constr
end
function check_constraint!(m::Model, constr::AbstractArray{<:ConstraintRef})
    @assert all(is_valid.(m, constr)) "$constr is not a valid constraint."
    if !any(is_interval_constraint.(constr)) && !any(is_equality_constraint.(constr))
        new_constr = constr
    else
        idxs = get_indices(constr)
        constr_dict = Dict(union(
            [
                split_constraint(m, constr[idx...]) |>
                    i -> isnothing(i) ?
                        (idx...,"") => constr[idx...] :
                        [(idx...,"lb") => i[1], (idx...,"ub") => i[2]]
                for idx in idxs
            ]...
        ))
        new_constr = Containers.SparseAxisArray(constr_dict)
        constr_name = gen_constraint_name(constr)
        m[constr_name] = new_constr
        constr_str = split(string(constr),"}")[end]
        @warn "$constr_str uses the `MOI.Interval` or `MOI.EqualTo` set. Each instance of the interval set has been split into two constraints, one for each bound."
        delete_original_constraint!(m, constr)
    end
    return new_constr
end
check_constraint!(m::Model, constr::Nothing) = nothing

"""
    split_constraint(m::Model, constr::NonlinearConstraintRef)

Split a nonlinear constraint that is an Interval or EqualTo constraint.

    split_constraint(m::Model, constr::ConstraintRef, constr_name::String = name(constr))

Split a linear or quadratic constraint.

    split_constraint(m::Model, constr_obj::ScalarConstraint, lb_name::String, ub_name::String)

Split a constraint that is a MOI.EqualTo or MOI.Interval.

    split_constraint(m::Model, func::Union{AffExpr,QuadExpr}, lb::Float64, ub::Float64, lb_name::String, ub_name::String)

Create split constraint for linear or quadratic constraint.

    split_constraint(m::Model, constr::ConstraintRef, constr_func_expr::Expr, lb::Float64, ub::Float64)

Split a nonlinear constraint.

    split_constraint(args...)

Return nothing for an empty disjunct.
"""
function split_constraint(m::Model, constr::NonlinearConstraintRef)
    constr_expr = Meta.parse(string(constr))
    if constr_expr.args[1] == :(==) #replace == for lb <= expr <= ub and split
        lb, ub = 0, 0#rhs is always 0, but could get obtained from: constr_expr.args[3]
        constr_func_expr = copy(constr_expr.args[2])
        new_constraints = split_constraint(m, constr, constr_func_expr, lb, ub)
        return new_constraints
    elseif count(x -> x == :(<=), constr_expr.args) == 2 #split lb <= expr <= ub
        lb = constr_expr.args[1]
        ub = constr_expr.args[5]
        constr_func_expr = copy(constr_expr.args[3]) #get func part of constraint
        new_constraints = split_constraint(m, constr, constr_func_expr, lb, ub)
        return new_constraints
    else
        return nothing
    end
end
function split_constraint(m::Model, constr::ConstraintRef, constr_name::String = name(constr))
    if isempty(constr_name)
        constr_name = "[$constr]"
    end
    lb_name = name_split_constraint(constr_name, :lb)
    ub_name = name_split_constraint(constr_name, :ub)
    constr_obj = constraint_object(constr)
    new_constraints = split_constraint(m, constr_obj, lb_name, ub_name)

    return new_constraints
end
function split_constraint(m::Model, func::Union{AffExpr,QuadExpr}, lb::Float64, ub::Float64, lb_name::String, ub_name::String)
    return [
        @constraint(m, lb <= func, base_name = lb_name),
        @constraint(m, func <= ub, base_name = ub_name)
    ]
end
function split_constraint(m::Model, constr_obj::ScalarConstraint{T,<:MOI.EqualTo}, lb_name::String, ub_name::String) where T
    split_constraint(m, constr_obj.func, constr_obj.set.value, constr_obj.set.value, lb_name, ub_name)
end
function split_constraint(m::Model, constr_obj::ScalarConstraint{T,<:MOI.Interval}, lb_name::String, ub_name::String) where T
    split_constraint(m, constr_obj.func, constr_obj.set.lower, constr_obj.set.upper, lb_name, ub_name)
end
function split_constraint(m::Model, constr::ConstraintRef, constr_func_expr::Expr, lb::Float64, ub::Float64)
    replace_JuMPvars!(constr_func_expr, m) #replace Expr with JuMP vars
    #replace original constraint with lb <= func
    lb_constr = JuMP._NonlinearConstraint(
        JuMP._NonlinearExprData(m, constr_func_expr),
        lb,
        Inf
    )
    m.nlp_data.nlconstr[constr.index.value] = lb_constr
    #create new constraint for func <= ub
    constr_expr_ub = Expr(:call, :(<=), constr_func_expr, ub)
    ub_constr = add_nonlinear_constraint(m, constr_expr_ub)

    return [constr, ub_constr]
end
split_constraint(args...) = nothing

delete_original_constraint!(m::Model, constr::ConstraintRef) = delete(m,constr)
delete_original_constraint!(m::Model, constr::NonlinearConstraintRef) = nothing
delete_original_constraint!(m::Model, constr::AbstractArray{<:ConstraintRef}) = map(c -> delete_original_constraint!(m,c), constr)

"""
    parse_constraint(constr::ConstraintRef)

Extract constraint operator symbol (op), constraint function expression (LHS), and constraint RHS.
"""
function parse_constraint(constr::ConstraintRef)
    #check function has a single comparrison operator (<=, >=, ==)
    constr_str = replace(split(string(constr),": ")[end], " " => "", "²" => "^2") #remove name, blanks and power notation
    constr_expr = Meta.parse(constr_str).args
    op = constr_expr[1] #comparrison operator
    lhs = constr_expr[2] #LHS of the constraint
    rhs = constr_expr[3] #RHS of constraint
    return op, lhs, rhs
end

"""

"""
function replace_constraint(constr::NonlinearConstraintRef, sym_expr, op, rhs)
    #convert symbolic function to expression
    expr = Base.remove_linenums!(build_function(sym_expr)).args[2].args[1]
    #replace symbolic variables by their JuMP variables and math operators with their symbols
    m = constr.model
    replace_JuMPvars!(expr, m)
    replace_operators!(expr)
    # determine bounds of original constraint (if op is ==, both bounds are set to rhs)
    upper_b = (op == :(>=)) ? Inf : rhs
    lower_b = (op == :(<=)) ? -Inf : rhs
    # replace NL constraint currently in the model with the reformulated one
    m.nlp_data.nlconstr[constr.index.value] = JuMP._NonlinearConstraint(JuMP._NonlinearExprData(m, expr), lower_b, upper_b)
end
function replace_constraint(constr::ConstraintRef, sym_expr, op, rhs)
    #convert symbolic function to expression
    op = eval(op)
    expr = Base.remove_linenums!(build_function(op(sym_expr,rhs))).args[2].args[1]
    #replace symbolic variables by their JuMP variables and math operators with their symbols
    m = constr.model
    replace_JuMPvars!(expr, m)
    replace_operators!(expr)
    #add a nonlinear constraint with the perspective function and delete old constraint
    add_nonlinear_constraint(m, expr)
    delete(m, constr)
end


using DSGE
using Base.Test

# Can we actually test? Require that the FRED_API_KEY ENV is populated.
if haskey(ENV, "FRED_API_KEY")
    m = Model990()
    m.testing=true

    # Specify exact vintage and dates
    m <= Setting(:data_vintage, "151127")
    m <= Setting(:date_presample_start, quartertodate("2015-Q2"))
    m <= Setting(:date_mainsample_end, quartertodate("2015-Q3"))

    df = load_data(m; try_disk=false, verbose=:none)

    # Silly comparisons
    delete!(df, :date)
    colmeans = colwise(mean, df)
    @test_approx_eq_eps colmeans[1][1] 0.4435 1e-4
    @test_approx_eq_eps colmeans[2][1] -51.2707 1e-4
    @test_approx_eq_eps colmeans[3][1] 0.1582 1e-4
    @test_approx_eq_eps colmeans[4][1] 0.4271 1e-4
    @test_approx_eq_eps colmeans[5][1] 0.3979 1e-4
    @test_approx_eq_eps colmeans[6][1] 0.0325 1e-4

    colstds  = colwise(std, df)
    @test_approx_eq_eps colstds[1][1] 0.3205 1e-4
    @test_approx_eq_eps colstds[2][1] 0.1015 1e-4
    @test_approx_eq_eps colstds[3][1] 0.3692 1e-4
    @test_approx_eq_eps colstds[4][1] 0.1367 1e-4
    @test_approx_eq_eps colstds[5][1] 0.0937 1e-4
    @test_approx_eq_eps colstds[6][1] 0.0000 1e-4
end


#=##############################################################################################
Filename: solve_rotor_wake.jl
Author: Ryan Anderson
Contact: rymanderson@gmail.com
README: define an `Action` object to solve for the wake of a rotor system
=###############################################################################################

"""
    solve_rotor_wake(aircraft, parameters, freestream, environment, step_range, stepi, step_symbol) <: Action

# Arguments:

* `aircraft::Aircraft`: `Aircraft` system object
* `parameters<:Parameters` `Parameters` struct
* `freestream::Freestream`: `Freestream` object
* `environment::Environment` `Environment` object
* `step_range::AbstractArray`: array of times for which the simulation is run
* `stepi::Int`: index of the current step
* `step_symbol::String`: defines the step, e.g. `alpha` or `time`

`parameters <: Parameters` requires the following elements:

* `wake_function::Function` : a function describing the induced velocity behind each propeller as a function of global coordinates
* `us::Vector{Vector{Vector{Float64}}}` : each [i][j][k]th element is the axial induced velocity at ith step of the jth rotor at the kth radial section
* `vs::Vector{Vector{Vector{Float64}}}` : each [i][j][k]th element is the swirl induced velocity at ith step of the jth rotor at the kth radial section
* `wake_shape_functions::Vector{Function}` : [i]th element is a function f(Rtip, x) describing the radial distance from the rotor axis to the boundary of the wake of the ith rotor
* `axial_interpolations::Vector{Function}` : [i]th element is a function f(rs, us, r, Rtip) that returns the axial component of rotor-induced velocity at distance r from the rotor axis based on the calculated axial induced velocities output from CCBlade of the ith rotor
* `swirl_interpolations::Vector{Function}` : [i]th element is a function f(rs, us, r, Rtip) that returns the swirl component of rotor-induced velocity at distance r from the rotor axis based on the calculated axial induced velocities output from CCBlade of the ith rotor
* `axial_multipliers::Vector{Function}` : [i]th element is a function f(distance2plane, Rtip) that is multiplied by the axial induced velocity function of the ith rotor
* `swirl_multipliers::Vector{Function}` : [i]th element is a function f(distance2plane, Rtip) that is multiplied by the swirl induced velocity function of the ith rotor

"""
function solve_rotor_wake(aircraft, parameters, freestream, environment, step_range, stepi, step_symbol)

    us = parameters.us[stepi]
    vs = parameters.vs[stepi]
    wake_shape_functions = parameters.wake_shape_functions
    axial_interpolations = parameters.axial_interpolations
    swirl_interpolations = parameters.swirl_interpolations
    axial_multipliers = parameters.axial_multipliers
    swirl_multipliers = parameters.swirl_multipliers
    wakefunction = induced2wakefunction(aircraft.rotor_system, us, vs;
        wake_shape_functions,
        axial_interpolations,
        swirl_interpolations,
        axial_multipliers,
        swirl_multipliers
    )

    parameters.wake_function[stepi] = wakefunction

    return false
end

"""
    solve_rotor_wake(aircraft, step_range)

Method returns initialized elements required for the `parameters <: Parameters` struct during simulation.

# Arguments:

* `aircraft::Aircraft` : system to be simulated
* `step_range::AbstractArray` : defines each step of the simulation

# Returns:

* `wake_function::Vector{Union{Function, Nothing}}` : [i]th element is a function describing the velocity field of rotor wakes as a function of global coordinates at the ith step
* `us::Vector{Vector{Vector{Float64}}}` : each [i][j][k]th element is the axial induced velocity at ith step of the jth rotor at the kth radial section
* `vs::Vector{Vector{Vector{Float64}}}` : each [i][j][k]th element is the swirl induced velocity at ith step of the jth rotor at the kth radial section
* `wake_shape_functions::Vector{Function}` : [i]th element is a function f(Rtip, x) describing the radial distance from the rotor axis to the boundary of the wake of the ith rotor
* `axial_interpolations::Vector{Function}` : [i]th element is a function f(rs, us, r, Rtip) that returns the axial component of rotor-induced velocity at distance r from the rotor axis based on the calculated axial induced velocities output from CCBlade of the ith rotor
* `swirl_interpolations::Vector{Function}` : [i]th element is a function f(rs, us, r, Rtip) that returns the swirl component of rotor-induced velocity at distance r from the rotor axis based on the calculated axial induced velocities output from CCBlade of the ith rotor
* `axial_multipliers::Vector{Function}` : [i]th element is a function f(distance2plane, Rtip) that is multiplied by the axial induced velocity function of the ith rotor
* `swirl_multipliers::Vector{Function}` : [i]th element is a function f(distance2plane, Rtip) that is multiplied by the swirl induced velocity function of the ith rotor

"""
function solve_rotor_wake(aircraft, step_range)

    wake_function = Function[(x) -> [0.0, 0.0, 0.0] for i in 1:length(step_range)]
    _, _, _, _, us, vs = solve_rotor(aircraft, step_range)

    wake_shape_functions = Function[(Rtip, x) -> Rtip for i in 1:length(aircraft.rotor_system.index)]
    axial_interpolations = Function[(rs, us, r, Rtip) -> FM.linear(rs, us, r) for i in 1:length(aircraft.rotor_system.index)]
    swirl_interpolations = Function[(rs, vs, r, Rtip) -> FM.linear(rs, vs, r) for i in 1:length(aircraft.rotor_system.index)]
    axial_multipliers = Function[(distance2plane, Rtip) -> 2 for i in 1:length(aircraft.rotor_system.index)]
    swirl_multipliers = Function[(distance2plane, Rtip) -> 1 for i in 1:length(aircraft.rotor_system.index)]

    return wake_function, us, vs, wake_shape_functions, axial_interpolations, swirl_interpolations, axial_multipliers, swirl_multipliers
end

# # prepare solutionkeys and solutioninits for this particular system and simulation
# solutionkeys = [
#     "thrust",
#     "torque",
#     "efficiency",
#     "u",
#     "v"
# ]


using POMDPs
using ContinuousPOMDPTreeSearchExperiments
using POMDPToolbox
using POMCP
using POMCPOW
using ProgressMeter
using PmapProgressMeter
using ParticleFilters
using JLD
using CPUTime
using VDPTag

N = 1000

@everywhere begin
    using VDPTag
    using POMDPs
    using ContinuousPOMDPTreeSearchExperiments
    using POMDPToolbox
    using POMCP
    using POMCPOW
    using ParticleFilters
    using CPUTime

    pomdp = VDPTagPOMDP()
    p_rng = MersenneTwister(4)
    solvers = Dict{String, Union{Solver,Policy}}(

        "pomcpow" => begin
            rollout_policy = ToNextML(mdp(pomdp))
            solver = POMCPOWSolver(tree_queries=10_000,
                                   criterion=MaxUCB(40.0),
                                   final_criterion=MaxTries(),
                                   max_depth=20,
                                   k_action=8.0,
                                   alpha_action=1/20,
                                   k_observation=4.0,
                                   alpha_observation=1/20,
                                   estimate_value=FORollout(rollout_policy),
                                   check_repeat_act=false,
                                   check_repeat_obs=false,
                                   rng=MersenneTwister(13)
                                  )

        end,

        "bt_100" => begin
            rng3 = copy(p_rng)
            rollout_policy = ToNextML(mdp(pomdp))
            tree_queries = 1000
            node_updater = ObsAdaptiveParticleFilter(pomdp, LowVarianceResampler(100), 0.05, rng3)
            solver = POMCPDPWSolver(tree_queries=tree_queries,
                                    c=40.0,
                                    max_depth=20,
                                    k_action=8.0,
                                    alpha_action=1/20,
                                    k_observation=4.0,
                                    alpha_observation=1/20,
                                    estimate_value=FORollout(rollout_policy),
                                    node_belief_updater=node_updater,
                                    default_action=TagAction(false, 0.0),
                                    rng=rng3
                                   )
        end,

        "dpw" => begin
            rng3 = copy(p_rng)
            rollout_policy = ToNextML(mdp(pomdp))
            tree_queries = 10_000
            solver = POMCPDPWSolver(tree_queries=tree_queries,
                                    c=40.0,
                                    max_depth=20,
                                    k_action=8.0,
                                    alpha_action=1/20,
                                    k_observation=4.0,
                                    alpha_observation=1/20,
                                    estimate_value=FORollout(rollout_policy),
                                    default_action=TagAction(false, 0.0),
                                    rng=rng3
                                   )
        end,
    )
end

solver_keys = collect(keys(solvers))
rewards = Dict{String, AbstractVector{Float64}}()
# state_hists = Dict{String, AbstractVector{AbstractVector{state_type(pomdp)}}}()
times = Dict{String, AbstractVector{Float64}}()
steps = Dict{String, AbstractVector{Int}}()

for (j, sk) in enumerate(solver_keys)
    s_rewards = SharedArray(Float64, N)
    # s_hists = SharedArray(AbstractVector{state_type(pomdp)}, N)
    s_times = SharedArray(Float64, N)
    s_steps = SharedArray(Float64, N)
    prog = Progress(N, desc="$sk ($j of $(length(solver_keys)))...")
    pmap(prog, 1:N) do i
        sim_rng = MersenneTwister(i)
        up_rng = MersenneTwister(i+100_000)
        policy = solve(solvers[sk], pomdp)
        up = ObsAdaptiveParticleFilter(deepcopy(pomdp), LowVarianceResampler(10_000), 0.05, up_rng)
        sim = HistoryRecorder(max_steps=40, rng=sim_rng)
        s_times[i] = @CPUelapsed hist = simulate(sim, deepcopy(pomdp), policy, up)
        s_rewards[i] = discounted_reward(hist)
        s_steps[i] = n_steps(hist)
    end
    rewards[sk] = sdata(s_rewards)
    @show mean(rewards[sk])
    # state_hists[sk] = sdata(s_hists)
    times[sk] = sdata(s_times)
    steps[sk] = sdata(s_steps)
    @show sum(times[sk])/sum(steps[sk])
end

for k in solver_keys
    println("$k mean: $(mean(rewards[k])) sem: $(std(rewards[k])/sqrt(N))")
    println("$k time per step: $(sum(times[k])/sum(steps[k]))")
end

filename = Pkg.dir("ContinuousPOMDPTreeSearchExperiments", "data", "compare_$(Dates.format(now(), "E_d_u_HH_MM")).jld")
println("saving to $filename...")
@save(filename, solver_keys, rewards, times, steps)
println("done.")


using Fermi.HartreeFock

import Base: show

export RCCSD

"""
    Fermi.CoupledCluster.RCCSDAlgorithm

Abstract type for RCCSD implementations.
"""
abstract type RCCSDAlgorithm end

"""
    Fermi.CoupledCluster.RCCSD

Wave function object for Restricted Coupled Cluster Singles and Doubles.

# High Level Interface
Run a RCCSD computation and return the RCCSD object:
```
julia> @energy rccsd
```
Equivalent to
```
julia> Fermi.CoupledCluster.RCCSD()
```
This function calls a constructor that runs a RCCSD computation based on the options found in `Fermi.Options`.

# Fields

| Name   |   Description     |
|--------|---------------------|
| `guessenergy` |   Energy recovered in the first iteration, normally RMP2 |
| `correlation` |   Computed RCCSD correlation energy |
| `energy`   |   Total wave function energy (Reference energy + Correlation energy)      |
| `e_conv`   | ΔE from the last iteration  |
| `t_conv`   |  Amplitudes RMS change from the last iteration|

# Relevant options

These options can be set with `@set <option> <value>`

| Option         | What it does                      | Type      | choices [default]     |
|----------------|-----------------------------------|-----------|-----------------------|
| `cc_alg`      | Picks RCCSD algorithm              | `Int`     | [1]                   |
| `cc_e_conv`   | Energy convergence criterion           | `Float64` | [10^-10]              |
| `cc_max_rms`    | Amplitudes RMS convergence criterion   | `Float64` | [10^-10]              |
| `cc_max_iter`   | Max number of CC iterations   | `Int` | [50]              |
| `cc_damp_ratio` | Fraction of old amplitudes to be kept   | `Float64` | 0.0--1.0 [0.0]              |
| `cc_diis` | Whether to use DIIS   | `Bool` | `false` [`true`]              |
| `diis_start` | Iteration number where DIIS starts   | `Int` | [3]              |
| `cc_diis_relax` | Interval between DIIS extrapolations   | `Int` | [3]              |
| `cc_ndiis` | Maximum number of stored vectors for DIIS   | `Int` | [3]              |
| `basis`       | What basis set to use             | `String`  | ["sto-3g"]            |
| `df`          | Whether to use density fitting    | `Bool`    | `true` [`false`]      |
| `rifit`       | What aux. basis set to use for RI | `String`  | ["auto"]              |
| `drop_occ`    | Number of occupied electrons to be dropped | `Int`  | [0]              |
| `drop_vir`    | Number of virtual electrons to be dropped | `Int`  | [0]              |
"""
struct RCCSD{T} <: AbstractCCWavefunction
    guessenergy::Float64
    correlation::T
    energy::Float64
    T1::AbstractArray{T,2}
    T2::AbstractArray{T,4}
    e_conv::T
    t_conv::T
end

"""
    Fermi.CoupledCluster.get_rccsd_alg

Returns a singleton type corresponding to a RCCSD implementation based on the options.
"""
function get_rccsd_alg(N::Int = Options.get("cc_alg"))
    try
        return get_rccsd_alg(Val(N))
    catch MethodError
        throw(FermiException("implementation number $N not available for RCCSD."))
    end
end

# For each implementation a singleton type must be create
struct RCCSDa <: RCCSDAlgorithm end
include("RCCSDa.jl")
# And a number is assigned to the implementation
get_rccsd_alg(x::Val{1}) = RCCSDa()

function RCCSD(x...)
    if !any(i-> i isa RCCSDAlgorithm, x)
        RCCSD(x..., get_rccsd_alg())
    else
        # Print the type of arguments given for a better feedback
        args = "("
        for a in x[1:end-1]
            args *= "$(typeof(a)), "
        end
        args = args[1:end-2]*")"
        throw(FermiException("invalid arguments for RCCSD method: $args"))
    end
end

## MISCELLANEOUS
# Pretty printing
function string_repr(X::RCCSD)
    out = ""
    out = out*" ⇒ Fermi Restricted CCSD Wave function\n"
    out = out*" ⋅ Correlation Energy:     $(X.correlation)\n"
    out = out*" ⋅ Total Energy:           $(X.energy)"
    return out
end

function show(io::IO, ::MIME"text/plain", X::RCCSD)
    print(io, string_repr(X))
end


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
# ==============================================================================
#
#    API for the orbit propagators in SatelliteToolbox.jl.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

export get_epoch, get_mean_elements, init_orbit_propagator, propagate!,
       propagate_to_epoch!, step!

"""
    init_orbit_propagator(T, args...; kwargs...)

Initialize the orbit propagator of type `T`. The arguments `args` and keywords
`kwargs` depends of the propagator type.

"""
init_orbit_propagator

"""
    get_epoch(orbp)

Return the epoch of the propagator `orbp` [JD].

"""
get_epoch

"""
    get_mean_elements(orbp)

Return the mean elements of the latest propagation performed by `orbp`. This is
an optinal function in the API. It will return `nothing` if the propagator does
not support it.

"""
get_mean_elements(orbp::OrbitPropagator) = nothing

"""
    propagate!(orbp::OrbitPropagator{T}, t::Number) where T
    propagate!(orbp::OrbitPropagator{T}, t::AbstractVector) where T

If `t` is a number, then propagate `orbp` by `t` [s] from the orbit epoch.
Otherwise, if `t` is an array, then propagate the orbit in `orbp` using the time
instants defined in the vector `t` [s].

In both cases, the orbit propagator algorithm is the one related to the
structure `orbp`.

# Returns

* The position vector represented in inertial frame in each time instant [m].
* The velocity vector represented in inertial frame in each time instant [m].

If `t` is an array, then those values will be an array containing the
information related to each epoch in `t`.

"""
propagate!

function propagate!(orbp::OrbitPropagator{T}, t::AbstractVector) where T
    # Output.
    num_elems  = length(t)
    result_orb = Vector{KeplerianElements{T}}(undef, num_elems)
    result_r   = Vector{SVector{3,T}}(undef, num_elems)
    result_v   = Vector{SVector{3,T}}(undef, num_elems)

    @inbounds for k = 1:num_elems
        # Propagate the orbit.
        r_i_k, v_i_k = propagate!(orbp, t[k])
        result_r[k]  = r_i_k
        result_v[k]  = v_i_k
    end

    return result_r, result_v
end

"""
    propagate_to_epoch!(orbp::OrbitPropagator{T}, JD::Number) where T
    propagate_to_epoch!(orbp::OrbitPropagator{T}, JD::AbstractVector) where T

If `t` is a number, then propagate `orbp` until the epoch `JD` [Julian Day].
Otherwise, if `JD` is an array, then propagate the orbit in `orbp` using the
epochs defined in the vector `t` [Julian Day].

In both cases, the orbit propagator algorithm is the one related to the
structure `orbp`.

# Returns

* The position vector represented in inertial frame in each time instant [m].
* The velocity vector represented in inertial frame in each time instant [m].

If `JD` is an array, then those values will be an array containing the
information related to each epoch in `JD`.

"""
propagate_to_epoch!(orbp::OrbitPropagator,
                    JD::Union{Number, AbstractVector}) where T =
    propagate!(orbp, (JD .- get_epoch(orbp))*86400)

"""
    step!(orbp::OrbitPropagator{T}, Δt::Number)

Propagate the orbit in `orbp` by `Δt` [s] using the algorithm of `orbp`. The new
parameters will be written in `orbp`.

# Returns

* The position vector represented in the inertial frame after the step [m].
* The velocity vector represented in the inertial frame after the step [m].

"""
step!

################################################################################
#                              Iterator interface
################################################################################

# There functions allow broadcast when using the orbit propagators.
Base.iterate(orbp::OrbitPropagator) = (orbp, nothing)
Base.iterate(orbp::OrbitPropagator, ::Nothing) = nothing
Base.length(orbp::OrbitPropagator) = 1
Base.eltype(orbp::T) where T<:OrbitPropagator = T


push!(LOAD_PATH,joinpath("..","src"))
using Multilane
using JLD

# NOTE: assumes 4 lanes, desired lane is lane 4
desired_lane_reward = 10.

lambdas = Float64[0.1, 0.3, 0.5, 1., 3., 5., 10., 30., 50., 100.]

nb_lanes = 4 # XXX assumption

rmodels = Multilane.NoCrashRewardModel[Multilane.NoCrashRewardModel(desired_lane_reward*lambda,desired_lane_reward,1.5,nb_lanes) for lambda in lambdas]

jldopen(joinpath(".","rmodels.jld"),"w") do file
  addrequire(file,Multilane)
  write(file,"rmodels",rmodels)
end


module UnitCellModelling

using gmsh
using Parameters
using StaticArrays
# using

include("ucmBase.jl")
include("gshapes.jl")
include("geometric_modelling.jl")
include("meshing.jl")

"""
    make_unit_cell_model() -> Dict

It performs mainly three tasks
 1. Geometry creation
 2. Mesh generation
 3. Exporting geometry and mesh data

Returns a julia dictionary with the following key-value pairs

- `"mesh_data"` => `Dict{String, Any}` , where possible key-value pairs of dictionary are
    - `"all_node_tags"` => `Vector{Int}`
    - `"all_node_coordinates"` => `Matrix{Float64}`
    - `"matrix_element_connectivity"` => `Matrix{Int}`
    - `"inclusions_element_connectivity"` => `Matrix{Int}`
    - `"total_nodes"` => Int
    - `"num_2D_elements"` => Int
    - `"num_3D_elements"` => Int

"""
function make_unit_cell_model(
    unit_cell::AbstractUnitCell;
    small_parameter::Float64 = 1e-06,
    geom_export_paths::Tuple{Vararg{String}} = (),  # joinpath(homedir(), "unit_cell.step"),
    extr_dir::String = "XY->Z",
    extr_dir_num_ele::Vector{Int64} = Int64[],
    extr_dir_cum_heights::Vector{Float64} = Float64[],
    extr_dir_recombine_ele::Bool = true,
    mesh_periodicity::Bool = true,
    element_types::Tuple{Vararg{Symbol}} = (:DEFAULT,),
    min_ele_size_factor::Float64 = 1.0,   # FIXME make default to take factor, instead of absolute values
    max_ele_size_factor::Float64 = 2.0,
    mesh_opt_algorithm::String = "Netgen",
    show_mesh_stats::Bool = true,
    show_rve::Bool = true,
)::Dict{String,Any}
    unit_cell_dim::Int = dimension(unit_cell)
    small_uc_side_length = minimum(side_lengths(unit_cell))
    global ϵ = small_parameter

    #
    gmsh.initialize()
    gmsh.option.set_number("General.Verbosity", 0)
    gmsh.option.set_number("General.Terminal", 0)
    gmsh.option.set_number("General.NoPopup", 0)
    gmsh.option.set_number("Geometry.OCCBoundsUseStl", 1)
    gmsh.model.add("unit_cell")
    #
    # ---------------------
    #   Geometry creation
    # ---------------------
    ucp_geom_tags = geometric_model(
        unit_cell,
        extr_dir,
        extr_dir_num_ele,
        extr_dir_cum_heights,
        extr_dir_recombine_ele
    )  # unit cell phase tags
    matrix_pg_tag = gmsh.model.add_physical_group(unit_cell_dim, ucp_geom_tags[1:1])
    inclusions_pg_tags = gmsh.model.add_physical_group(unit_cell_dim, ucp_geom_tags[2:end])
    gmsh.model.set_physical_name(unit_cell_dim, matrix_pg_tag, "Matrix")
    gmsh.model.set_physical_name(unit_cell_dim, inclusions_pg_tags, "Inclusions")
    #
    # ---------------------
    #       Meshing
    # ---------------------
    generate_mesh(
        unit_cell,
        mesh_periodicity,
        element_types,
        extr_dir_num_ele,
        min_ele_size_factor*small_uc_side_length,
        max_ele_size_factor*small_uc_side_length,
        mesh_opt_algorithm,
        show_mesh_stats
    )
    #
    # ---------------------
    #       EXPort
    # ---------------------

    # exporting UC model to the specified fomats
    if !isempty(geom_export_paths)
        for a_export_path in geom_export_paths
            gmsh.write(a_export_path)
        end
    end

    #
    model_data::Dict{String,Any} = Dict{String,Any}()
    model_data["mesh_data"] = get_mesh_data(unit_cell_dim, ucp_geom_tags)  # Dict{String, Any}
    #
    if show_rve
        gmsh.fltk.run()
    end
    #
    gmsh.finalize()
    #
    return model_data
end  # of function

export make_unit_cell_model

end  # of UnitCellModelling module


function solve(model::AbstractModel,
               probtype::AbstractDynamicExperiment,
               alg::AbstractODEAlgorithm=DP5();
               kwargs...)
    prob = build_problem(model, probtype; kwargs...)
    sol = solve(prob, alg; kwargs...)
    return sol
end

function solve(model::AbstractModel,
               probtype::AbstractStochasticExperiment,
               alg::AbstractSDEAlgorithm=SRIW1();
               kwargs...)
    prob = build_problem(model, probtype; kwargs...)
    sol = solve(prob, alg; kwargs...)
    return sol
end

function solve(prob::AbstractNLOProblem, alg; kwargs...)
    sol = DiffEqBase.solve(prob.prob, alg; kwargs...)
    return DynamicNLOSolution(sol, prob)
end
function solve(prob::DynamicIkedaProblem, alg; kwargs...)
    sol = DiffEqBase.solve(prob.prob, alg; callback=prob.ikeda_callback, kwargs...)
    DynamicNLOSolution(sol, prob)
end


#Graphics functions
show(io::IO, w::AFWindow) = print(io, "ArrayFire Window")
show(io::IO, w::vcpp"af::Window") = print(io, "ArrayFire Window")

export AFWindow, window, setTitle, setPlot, setImage, setSurfacePlot

window() = AFWindow(af_window())
window(str::AbstractString) = AFWindow(af_window(str))
window(width::Integer, height::Integer, str::AbstractString) = AFWindow(af_window(width, height, str))

setTitle(w::AFWindow, str::AbstractString) = af_setTitle(w, str)
setImage(w::AFWindow, img::AFAbstractArray, str = "") = af_setImage(w, img/255.0, str)
setPlot(w::AFWindow, x::AFAbstractArray, y::AFAbstractArray, title = "") = af_setPlot(w, x, y, title)
setSurfacePlot(w::AFWindow, x::AFAbstractArray) = af_setSurface(w, x)
show(w::AFWindow) = af_show(w)


using AdversarialDriving
using POMDPs, POMDPPolicies, POMDPSimulators
using AutomotiveVisualization

sut_agent = BlinkerVehicleAgent(get_ped_vehicle(id=1, s=5., v=15.), TIDM(ped_TIDM_template, noisy_observations = true))
adv_ped = NoisyPedestrianAgent(get_pedestrian(id=2, s=7., v=2.0), AdversarialPedestrian())
mdp = AdversarialDrivingMDP(sut_agent, [adv_ped], ped_roadway, 0.2)

# Render a single frame
s = rand(initialstate(mdp))
c = render([mdp.roadway,
                crosswalk,
                VelocityArrow(entity = s[1], color = colorant"black"),
                VelocityArrow(entity = s[2], color = colorant"black"),
                FancyCar(car = s[2], color = colorant"blue"),
                FancyPedestrian(ped = s[1])],
                surface=AutomotiveVisualization.CairoPDFSurface(IOBuffer(), DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT))
write("pedestrian_crosswalk.pdf", c)

null_action = Disturbance[PedestrianControl()]
noisy_action = Disturbance[PedestrianControl(noise = Noise((-10.,0.), -2))]

# Nominal Behavior
hist = POMDPSimulators.simulate(HistoryRecorder(), mdp, FunctionPolicy((s) -> null_action))
scenes_to_gif([s.s for s in hist], mdp.roadway, "ped_crosswalk_nominal.gif", others = [crosswalk])

# Behavior with noise
hist = POMDPSimulators.simulate(HistoryRecorder(), mdp, FunctionPolicy((s) -> noisy_action))
scenes_to_gif([s.s for s in hist], mdp.roadway, "ped_crosswalk_failure.gif", others = [crosswalk])


"""

Computing GMD contributions for the parts of the solute

"""

push!(LOAD_PATH,"../../../")

using M3GTools
using DelimitedFiles
using LaTeXStrings

function smooth(data)
  # smooth data
  n = length(data)
  y = copy(data)
  nav = 10
  for i in nav+1:n-nav
    av = 0.
    for j in i-nav:i+nav
      av = av + data[j]
    end
    y[i] = av / (2*nav)
  end
  for i in 1:nav
    y[i] = data[i]
    y[n-i] = data[n-i]
  end
  return y
end

println(" Loading Plots... ")
using Plots
ENV["GKSwstype"]="nul" # This supresses the need of a display while plotting

# Data for my simulation (we only need the topoloy file here, might be PSF or GRO
topology = "./structure.psf"

# Read GMD file corresponding to solute contributions
println(" Reading GMD solute contribution file... ")
gmd_solute = readdlm("./gmd-GMD_ATOM_SOLUTE_CONTRIB.dat",comments=true,comment_char='#')

# The distance is the first column of the gmd file, and the total gmd is the second column:
d = gmd_solute[:,1]
gmd_total = gmd_solute[:,2]

gmd_total = smooth(gmd_total)

# Define the solute selection used for computation of the GMD here:
gmd_backbone = M3GTools.gmdget(topology,gmd_solute,data="protein",get="protein and (not sidechain)")
gmd_aliphatic = M3GTools.gmdget(topology,gmd_solute,data="protein",get="protein and sidechain and aliphatic")
gmd_aromatic = M3GTools.gmdget(topology,gmd_solute,data="protein",get="protein and sidechain and aromatic")
gmd_polar = M3GTools.gmdget(topology,gmd_solute,data="protein",get="protein and sidechain and polar")

gmd_backbone = smooth(gmd_backbone)
gmd_aliphatic = smooth(gmd_aliphatic)
gmd_aromatic = smooth(gmd_aromatic)
gmd_polar = smooth(gmd_polar)

# Plot the results
plot(d,gmd_total,label="Total",xlim=[0,8],xlabel=L"\textrm{Distance / \AA}",ylabel=L"\textrm{MDDF}",linewidth=2)
plot!(d,gmd_backbone,label="Backbone",linewidth=2)
plot!(d,gmd_aliphatic,label="Aliphatic",linewidth=2)
plot!(d,gmd_aromatic,label="Aromatic",linewidth=2)
plot!(d,gmd_polar,label="Polar",linewidth=2)
plot!(fontsize=16)
savefig("./gmd_total.png")
println(" Created plot: ./gmd_total.png")