# License for this file: MIT (expat)
# Copyright 2017-2018, DLR Institute of System Dynamics and Control
#
# This file is part of module
#   Modia3D.DLR_Visualization (Modia3D/renderer/DLR_Visualization/_module.jl)
#

@enum ShapeType SimVisBox=1 SimVisSphere=2 SimVisCylinder=3 SimVisCone=4 SimVisCapsule=5 SimVisCoordSys=6 SimVisSpring=7 SimVisGearWheel=8 SimVisPipe=9 SimVisGrid=10 SimVisBeam=11

const mvecSize = MVector{3,Cdouble}(0.0, 0.0, 0.0)
const DefaultMaterial = Shapes.VisualMaterial()
const emptyShaderName=" "

@inline function visualizeShape(simVis::SimVis_Renderer,
                                rabs::SVector{3,Float64},
                                Rabs::SMatrix{3,3,Float64,9},
                                shape::ShapeType,
                                id::Ptr{Nothing},
                                material::Shapes.VisualMaterial,
                                Lx::Float64, Ly::Float64, Lz::Float64;
                                extras::MVector{3,Float64}=Basics.ZeroMVector)
    mvecSize[1] = Lx
    mvecSize[2] = Ly
    mvecSize[3] = Lz

    SimVis_setBaseObject(simVis, id, Cint(0), Cint(shape), rabs, Rabs, mvecSize,
        material.color, Cint(material.wireframe), Cint(material.reflectslight),
        material.shininess, extras, material.transparency, Cint(0), Cint(material.shadowMask))
end

# add rotation so that `axis` becomes x-axis
function rotateAxis2x(axis, R_abs)
    if axis == 2
        Rabs = @SMatrix[0 1 0; 0 0 1; 1 0 0] * R_abs
    elseif axis == 3
        Rabs = @SMatrix[0 0 1; 1 0 0; 0 1 0] * R_abs
    else
        Rabs = R_abs
    end
    return Rabs
end

# add rotation so that `axis` becomes y-axis
function rotateAxis2y(axis, R_abs)
    if axis == 3
        Rabs = @SMatrix[0 1 0; 0 0 1; 1 0 0] * R_abs
    elseif axis == 1
        Rabs = @SMatrix[0 0 1; 1 0 0; 0 1 0] * R_abs
    else
        Rabs = R_abs
    end
    return Rabs
end

# add rotation so that `axis` becomes z-axis
function rotateAxis2z(axis, R_abs)
    if axis == 1
        Rabs = @SMatrix[0 1 0; 0 0 1; 1 0 0] * R_abs
    elseif axis == 2
        Rabs = @SMatrix[0 0 1; 1 0 0; 0 1 0] * R_abs
    else
        Rabs = R_abs
    end
    return Rabs
end

function visualizeObject(obj::Composition.Object3D, id::Ptr{Nothing}, simVis::SimVis_Renderer)
    shapeKind = obj.shapeKind

    if shapeKind == Modia3D.SphereKind
        sphere::Modia3D.Sphere = obj.shape
        visualizeShape(simVis, obj.r_abs, obj.R_abs, SimVisSphere, id, obj.visualMaterial,
                       sphere.diameter, sphere.diameter, sphere.diameter)

    elseif shapeKind == Modia3D.EllipsoidKind
        ellipsoid::Modia3D.Ellipsoid = obj.shape
        visualizeShape(simVis, obj.r_abs, obj.R_abs, SimVisSphere, id, obj.visualMaterial,
                       ellipsoid.lengthX, ellipsoid.lengthY, ellipsoid.lengthZ)

    elseif shapeKind == Modia3D.BoxKind
        box::Modia3D.Box = obj.shape
        visualizeShape(simVis, obj.r_abs, obj.R_abs, SimVisBox, id, obj.visualMaterial,
                       box.lengthX, box.lengthY, box.lengthZ)

    elseif shapeKind == Modia3D.CylinderKind
        cylinder::Modia3D.Cylinder = obj.shape
        if cylinder.innerDiameter == 0.0
            visualizeShape(simVis, obj.r_abs, rotateAxis2z(cylinder.axis, obj.R_abs), SimVisCylinder, id, obj.visualMaterial,
                           cylinder.diameter, cylinder.diameter, cylinder.length)
        else
            visualizeShape(simVis, obj.r_abs, rotateAxis2z(cylinder.axis, obj.R_abs), SimVisPipe, id, obj.visualMaterial,
                           cylinder.diameter, cylinder.diameter, cylinder.length; extras=@MVector[0.0, cylinder.innerDiameter/cylinder.diameter, 1.0])
        end

    elseif shapeKind == Modia3D.ConeKind
        cone::Modia3D.Cone = obj.shape
        if cone.axis == 1
            dr = @MVector[cone.length/4, 0.0, 0.0]
        elseif cone.axis == 2
            dr = @MVector[0.0, cone.length/4, 0.0]
        else
            dr = @MVector[0.0, 0.0, cone.length/4]
        end
        r_abs = obj.r_abs + obj.R_abs' * dr
        visualizeShape(simVis, r_abs, rotateAxis2z(cone.axis, obj.R_abs), SimVisCone, id, obj.visualMaterial,
                       cone.diameter, cone.diameter, cone.length; extras=@MVector[cone.topDiameter/cone.diameter, 0.0, 0.0])

    elseif shapeKind == Modia3D.CapsuleKind
        capsule::Modia3D.Capsule = obj.shape
        visualizeShape(simVis, obj.r_abs, rotateAxis2z(capsule.axis, obj.R_abs), SimVisCapsule, id, obj.visualMaterial,
                       capsule.diameter, capsule.diameter, capsule.length)

    elseif shapeKind == Modia3D.BeamKind
        beam::Modia3D.Beam = obj.shape
        visualizeShape(simVis, obj.r_abs, rotateAxis2x(beam.axis, obj.R_abs), SimVisBeam, id, obj.visualMaterial,
                       beam.length, beam.width, beam.thickness)

    elseif shapeKind == Modia3D.CoordinateSystemKind
        coordinateSystem::Modia3D.CoordinateSystem = obj.shape
        visualizeShape(simVis, obj.r_abs, obj.R_abs, SimVisCoordSys, id, DefaultMaterial,
                       coordinateSystem.length, coordinateSystem.length, coordinateSystem.length)

    elseif shapeKind == Modia3D.GridKind
        grid::Modia3D.Grid = obj.shape
        visualizeShape(simVis, obj.r_abs, rotateAxis2z(grid.axis, obj.R_abs), SimVisGrid, id, DefaultMaterial,
                       grid.length, grid.width, grid.length; extras=@MVector[grid.distance, grid.lineWidth, 0.0])

    elseif shapeKind == Modia3D.SpringKind
        spring::Modia3D.Spring = obj.shape
        visualizeShape(simVis, obj.r_abs, rotateAxis2z(spring.axis, obj.R_abs), SimVisSpring, id, obj.visualMaterial,
                       spring.length, spring.diameter, spring.diameter; extras=@MVector[spring.windings, spring.wireDiameter/2, 0.0])

    elseif shapeKind == Modia3D.GearWheelKind
        gearWheel::Modia3D.GearWheel = obj.shape
        visualizeShape(simVis, obj.r_abs, rotateAxis2z(gearWheel.axis, obj.R_abs), SimVisGearWheel, id, obj.visualMaterial,
                       gearWheel.diameter, gearWheel.diameter, gearWheel.length; extras=@MVector[gearWheel.innerDiameter/gearWheel.diameter, gearWheel.teeth, gearWheel.angle*180/pi])

    elseif shapeKind == Modia3D.FileMeshKind
        fileMesh::Modia3D.FileMesh = obj.shape
        SimVis_setFileObject(simVis, id, Cint(0), obj.r_abs, obj.R_abs,
                             fileMesh.scaleFactor, Cint(obj.visualMaterial.reflectslight), obj.visualMaterial.shininess, obj.visualMaterial.transparency, Cint(obj.visualMaterial.wireframe), Cint(0),
                             fileMesh.filename, Cint(fileMesh.smoothNormals), fileMesh.useMaterialColor, MVector{3,Cint}(obj.visualMaterial.color),
                             Cint(obj.visualMaterial.shadowMask), emptyShaderName)

    elseif shapeKind == Modia3D.TextKind
        textShape::Modia3D.TextShape = obj.shape
        SimVis_setTextObject(simVis, id, Cint(textShape.axisAlignment), textShape.text, 0.0, Cint(0), obj.r_abs, obj.R_abs,
                             textShape.font.charSize, textShape.font.fontFileName, MVector{3,Cint}(textShape.font.color), textShape.font.transparency,
                             textShape.offset, Cint(textShape.alignment), Cint(0))

    end
end


module titus
using Random

mersenne = MersenneTwister(12346)
# [(char, weight)]
sparkle_chars = ['*', '*', '*', '~', '~', '~', '~', '~', '(',  ')']

# he's a dog, so food emoji right?
food_emoji = ["🍏", "🍎", "🍐", "🍊", "🍋", "🍌", "🍉", "🍇", "🍓", "🍈", "🍒", "🍑", "🥭", "🍍",
							"🥥", "🥝", "🍅", "🍆", "🥑", "🥦", "🥬", "🥒", "🌽", "🥕", "🧄", "🧅", "🥔", "🍠",
							"🥐", "🥯", "🍞", "🥖", "🥨", "🧀", "🥚", "🍳", "🧈", "🥞", "🧇", "🥓", "🥩", "🍗",
							"🍖", "🦴", "🌭", "🍔", "🍟", "🍕", "🥪", "🥙", "🧆", "🌮", "🌯", "🥗", "🥘", "🥫",
							"🍝", "🍜", "🍲", "🍛", "🍣", "🍱", "🥟", "🦪", "🍤", "🍙", "🍚", "🍘", "🍥", "🥠",
							"🥮", "🍢", "🍡", "🍧", "🍨", "🍦", "🥧", "🧁", "🍰", "🎂", "🍮", "🍭", "🍬", "🍫",
							"🍿", "🍩", "🍪", "🌰", "🥜", "🍯", "🥛", "🍼", "☕", "🍵", "🧃", "🥤", "🍶", "🍺",
							"🍻", "🥂", "🍷", "🥃", "🍸", "🍹", "🧉", "🍾", "🧊", "🥄", "🍴", "🥣", "🥡", "🥢",
							"🧂"]

function random_bool()
	n = rand(1)[1]
	n > 0.5
end

function randomize_upper(s)
	k = ""
	for char in s
		if random_bool()
			k *= uppercase(char)
		else
			k *= char
		end
	end
	return k
end

function rand_between(lower, upper)
	n = floor(rand(1)[1] * upper)
  while n > upper || n < lower
		n = floor(rand(1)[1] * upper)
	end
	return n
end

function get_random(arr)
	rand(mersenne, arr, 1)[1]
end

function random_emoji(str)
	period = r"\."
	s = str
	while !isnothing(match(period, s))
		replacement = get_random(food_emoji)
		s = replace(s, "."=>replacement, count=1)
	end
	s
end

function sparklez()
	sparkle_length = rand_between(5, 11)
	sparkle = ""
	while sparkle_length > 0
		sparkle *= get_random(sparkle_chars)
		sparkle_length -= 1
	end
	sparkle
end

println(map(x->sparklez()*random_emoji(randomize_upper(x))*sparklez(), ARGS))

end # module


#################################
################################# MSE tables
#################################
#
all_best_mse = OrderedCollections.OrderedDict()
#
all_best_mse["ARM_SGP"] = OrderedCollections.OrderedDict()
all_best_mse["ARM_SGP"]["qt_mean"] = 0.2099693151607237
all_best_mse["ARM_SGP"]["updraft_area"] = 335.45875367066867
all_best_mse["ARM_SGP"]["updraft_w"] = 140.92677349078969
all_best_mse["ARM_SGP"]["updraft_qt"] = 28.70836894541848
all_best_mse["ARM_SGP"]["updraft_thetal"] = 172.01631026298847
all_best_mse["ARM_SGP"]["u_mean"] = 1.3235797273549681e-5
all_best_mse["ARM_SGP"]["tke_mean"] = 1313.6493467020196
all_best_mse["ARM_SGP"]["temperature_mean"] = 0.00010624950402975059
all_best_mse["ARM_SGP"]["ql_mean"] = 180.68590465680364
all_best_mse["ARM_SGP"]["qi_mean"] = "NA"
all_best_mse["ARM_SGP"]["thetal_mean"] = 9.770061871604239e-5
all_best_mse["ARM_SGP"]["Hvar_mean"] = 9311.054396714339
all_best_mse["ARM_SGP"]["QTvar_mean"] = 5802.412820315867
#
all_best_mse["Bomex"] = OrderedCollections.OrderedDict()
all_best_mse["Bomex"]["qt_mean"] = 0.08776796077692245
all_best_mse["Bomex"]["updraft_area"] = 130.01639628691083
all_best_mse["Bomex"]["updraft_w"] = 16.057586429511503
all_best_mse["Bomex"]["updraft_qt"] = 6.890128846374632
all_best_mse["Bomex"]["updraft_thetal"] = 69.78417329537506
all_best_mse["Bomex"]["v_mean"] = 67.12281585199479
all_best_mse["Bomex"]["u_mean"] = 0.3201493689027362
all_best_mse["Bomex"]["tke_mean"] = 73.89824775455659
all_best_mse["Bomex"]["temperature_mean"] = 3.252413195141947e-5
all_best_mse["Bomex"]["ql_mean"] = 6.313648778401822
all_best_mse["Bomex"]["qi_mean"] = "NA"
all_best_mse["Bomex"]["thetal_mean"] = 3.2858173111125033e-5
all_best_mse["Bomex"]["Hvar_mean"] = 6752.843931363311
all_best_mse["Bomex"]["QTvar_mean"] = 2345.9671498346493
#
all_best_mse["DryBubble"] = OrderedCollections.OrderedDict()
all_best_mse["DryBubble"]["updraft_area"] = 0.0
all_best_mse["DryBubble"]["updraft_w"] = 0.0
all_best_mse["DryBubble"]["updraft_thetal"] = 0.0
all_best_mse["DryBubble"]["u_mean"] = 0.0
all_best_mse["DryBubble"]["tke_mean"] = 0.0
all_best_mse["DryBubble"]["temperature_mean"] = 0.0
all_best_mse["DryBubble"]["thetal_mean"] = 0.0
all_best_mse["DryBubble"]["Hvar_mean"] = 0.0
#
all_best_mse["DYCOMS_RF01"] = OrderedCollections.OrderedDict()
all_best_mse["DYCOMS_RF01"]["qt_mean"] = 0.023198692272030544
all_best_mse["DYCOMS_RF01"]["ql_mean"] = 10.347286087600702
all_best_mse["DYCOMS_RF01"]["updraft_area"] = 30.41218602111556
all_best_mse["DYCOMS_RF01"]["updraft_w"] = 4.1304092648269375
all_best_mse["DYCOMS_RF01"]["updraft_qt"] = 2.1537875302070426
all_best_mse["DYCOMS_RF01"]["updraft_thetal"] = 46.18817055108242
all_best_mse["DYCOMS_RF01"]["v_mean"] = 0.002982833065277946
all_best_mse["DYCOMS_RF01"]["u_mean"] = 0.07894988376751147
all_best_mse["DYCOMS_RF01"]["tke_mean"] = 21.327285272822852
all_best_mse["DYCOMS_RF01"]["temperature_mean"] = 7.606763938668142e-5
all_best_mse["DYCOMS_RF01"]["thetal_mean"] = 7.644917953938347e-5
all_best_mse["DYCOMS_RF01"]["Hvar_mean"] = 1288.1645081619606
all_best_mse["DYCOMS_RF01"]["QTvar_mean"] = 518.9286165737209
#
all_best_mse["GABLS"] = OrderedCollections.OrderedDict()
all_best_mse["GABLS"]["updraft_thetal"] = 0.0
all_best_mse["GABLS"]["v_mean"] = 0.0
all_best_mse["GABLS"]["u_mean"] = 0.0
all_best_mse["GABLS"]["tke_mean"] = 0.0
all_best_mse["GABLS"]["temperature_mean"] = 0.0
all_best_mse["GABLS"]["thetal_mean"] = 0.0
all_best_mse["GABLS"]["Hvar_mean"] = 0.0
all_best_mse["GABLS"]["QTvar_mean"] = 0.0
all_best_mse["GABLS"]["qt_mean"] = 0.0
#
all_best_mse["life_cycle_Tan2018"] = OrderedCollections.OrderedDict()
all_best_mse["life_cycle_Tan2018"]["qt_mean"] = 0.0
all_best_mse["life_cycle_Tan2018"]["ql_mean"] = 0.0
all_best_mse["life_cycle_Tan2018"]["updraft_area"] = 0.0
all_best_mse["life_cycle_Tan2018"]["updraft_w"] = 0.0
all_best_mse["life_cycle_Tan2018"]["updraft_qt"] = 0.0
all_best_mse["life_cycle_Tan2018"]["updraft_thetal"] = 0.0
all_best_mse["life_cycle_Tan2018"]["v_mean"] = 0.0
all_best_mse["life_cycle_Tan2018"]["u_mean"] = 0.0
all_best_mse["life_cycle_Tan2018"]["tke_mean"] = 0.0
all_best_mse["life_cycle_Tan2018"]["temperature_mean"] = 0.0
all_best_mse["life_cycle_Tan2018"]["thetal_mean"] = 0.0
all_best_mse["life_cycle_Tan2018"]["Hvar_mean"] = 0.0
all_best_mse["life_cycle_Tan2018"]["QTvar_mean"] = 0.0
#
all_best_mse["Nieuwstadt"] = OrderedCollections.OrderedDict()
all_best_mse["Nieuwstadt"]["updraft_area"] = 98.61903956765104
all_best_mse["Nieuwstadt"]["updraft_w"] = 12.620181519729721
all_best_mse["Nieuwstadt"]["updraft_thetal"] = 117.60674820900034
all_best_mse["Nieuwstadt"]["u_mean"] = 13.627556919779142
all_best_mse["Nieuwstadt"]["tke_mean"] = 312.92521835084375
all_best_mse["Nieuwstadt"]["temperature_mean"] = 9.606319451874596e-6
all_best_mse["Nieuwstadt"]["thetal_mean"] = 9.918979982485869e-6
all_best_mse["Nieuwstadt"]["Hvar_mean"] = 1280.538121964536
#
all_best_mse["Rico"] = OrderedCollections.OrderedDict()
all_best_mse["Rico"]["qt_mean"] = 1.461216267731136
all_best_mse["Rico"]["updraft_area"] = 476.1668575277046
all_best_mse["Rico"]["updraft_w"] = 103.62523342741915
all_best_mse["Rico"]["updraft_qt"] = 12.084264582170741
all_best_mse["Rico"]["updraft_thetal"] = 133.77973866180497
all_best_mse["Rico"]["v_mean"] = 0.661591213386313
all_best_mse["Rico"]["u_mean"] = 0.5942324640298271
all_best_mse["Rico"]["tke_mean"] = 90.06304177194619
all_best_mse["Rico"]["temperature_mean"] = 0.0006561578714960826
all_best_mse["Rico"]["ql_mean"] = 69.79123138670636
all_best_mse["Rico"]["qi_mean"] = "NA"
all_best_mse["Rico"]["qr_mean"] = 765.9753444576647
all_best_mse["Rico"]["thetal_mean"] = 0.0006459491204637934
all_best_mse["Rico"]["Hvar_mean"] = 20043.952395704495
all_best_mse["Rico"]["QTvar_mean"] = 3977.214921455993
#
all_best_mse["Soares"] = OrderedCollections.OrderedDict()
all_best_mse["Soares"]["qt_mean"] = 0.1257904686397003
all_best_mse["Soares"]["updraft_area"] = 98.21922321246183
all_best_mse["Soares"]["updraft_w"] = 11.423147273156957
all_best_mse["Soares"]["updraft_qt"] = 23.615121270329094
all_best_mse["Soares"]["updraft_thetal"] = 65.7219396386948
all_best_mse["Soares"]["u_mean"] = 94.2923650437216
all_best_mse["Soares"]["tke_mean"] = 241.22739244519957
all_best_mse["Soares"]["temperature_mean"] = 1.0913731129957992e-5
all_best_mse["Soares"]["thetal_mean"] = 1.0343924750695743e-5
all_best_mse["Soares"]["Hvar_mean"] = 1118.8459567581533
#
all_best_mse["TRMM_LBA"] = OrderedCollections.OrderedDict()
all_best_mse["TRMM_LBA"]["qt_mean"] = 4.370141509071347
all_best_mse["TRMM_LBA"]["updraft_area"] = 7588.792583634186
all_best_mse["TRMM_LBA"]["updraft_w"] = 30830.63975659635
all_best_mse["TRMM_LBA"]["updraft_qt"] = 263.1587569415405
all_best_mse["TRMM_LBA"]["updraft_thetal"] = 1961.8451637527064
all_best_mse["TRMM_LBA"]["v_mean"] = 285.9190262131904
all_best_mse["TRMM_LBA"]["u_mean"] = 113.61185296026021
all_best_mse["TRMM_LBA"]["tke_mean"] = 25299.781769411315
all_best_mse["TRMM_LBA"]["temperature_mean"] = 0.001282306577221344
all_best_mse["TRMM_LBA"]["ql_mean"] = 10591.308689650668
all_best_mse["TRMM_LBA"]["qi_mean"] = "NA"
all_best_mse["TRMM_LBA"]["qr_mean"] = "NA"
all_best_mse["TRMM_LBA"]["qs_mean"] = "NA"
all_best_mse["TRMM_LBA"]["thetal_mean"] = 0.021142841574681895
all_best_mse["TRMM_LBA"]["Hvar_mean"] = 4196.816226656944
all_best_mse["TRMM_LBA"]["QTvar_mean"] = 2772.6720734920077
#
all_best_mse["LES_driven_SCM"] = OrderedCollections.OrderedDict()
all_best_mse["LES_driven_SCM"]["qt_mean"] = 3.5617162347368296
all_best_mse["LES_driven_SCM"]["v_mean"] = 1.3028760898968443
all_best_mse["LES_driven_SCM"]["u_mean"] = 0.44955547910863786
all_best_mse["LES_driven_SCM"]["temperature_mean"] = 0.001277413538359007
all_best_mse["LES_driven_SCM"]["ql_mean"] = 55700.59768552013
all_best_mse["LES_driven_SCM"]["thetal_mean"] = 0.0015156834284607626
#
#################################
#################################
#################################


# turn a openAI finetune-transformer-lm npy to bson

using JSON
using ZipFile

using Flux: loadparams!

using BytePairEncoding

"""
    openAInpy2bson(path;
                   raw=false,
                   saveto="./",
                   startsym="_start_",
                   delisym="_delimiter_",
                   clfsym="_classify_",
                   unksym="<unk>")

turn openai released gpt format(npy) into BSON file. Set `raw` to `true` to remain the origin data format in bson.
"""
function openAInpy2bson(path;
                        raw=false,
                        saveto="./",
                        startsym="_start_",
                        delisym="_delimiter_",
                        clfsym="_classify_",
                        unksym="<unk>")
  if iszip(path)
    filename = basename(path)[1:end-4]
    data = ZipFile.Reader(path)
  else
    filename = basename(isdirpath(path) ? path[1:end-1] : path)
    data = path
  end

  tokenizer = gpt_tokenizer
  weights, bpe, raw_vocab = readnpzfolder(data)

  iszip(path) && close(data)

  if raw
    bsonname = normpath(joinpath(saveto, filename * ".npbson"))
    BSON.@save bsonname weights bpe raw_vocab tokenizer
  else
    bsonname = normpath(joinpath(saveto, filename * ".bson"))
    vocab = build_vocab(raw_vocab;
                        startsym=startsym,
                        delisym=delisym,
                        clfsym=clfsym,
                        unksym=unksym,
                        )
    gpt_model = load_gpt_from_npbson(weights, length(vocab))
    BSON.@save bsonname gpt_model bpe vocab tokenizer
  end
end

readnpz(path) = error("readnpz require NPZ.jl installed. run `Pkg.add(\"NPZ\"); using NPZ`")

@init @require NPZ="15e1cf62-19b3-5cfa-8e77-841668bca605" begin
  import .NPZ

  function readnpz(dir)
    shapes = JSON.parsefile(joinpath(dir, "params_shapes.json"))
    offsets = accumulate(+, prod.(shapes))
    shapes = map(s -> length(s) > 1 ? (s[end], s[end-1]) : s, shapes)
    params = cat([npzread(joinpath(dir, "params_$(i).npy")) for i = 0:9]..., dims=1)
    params = [collect(reshape(selectdim(params, 1, a+1:b), s...)) for (a, b, s) in zip([0;offsets[1:end-1]], offsets, shapes)]
    params
  end

  function readnpz(z::ZipFile.Reader)
    shapes = JSON.parse(findfile(z, "params_shapes.json"))
    offsets = accumulate(+, prod.(shapes))
    shapes = map(s -> length(s) > 1 ? (s[end], s[end-1]) : s, shapes)
    params = cat([NPZ.npzreadarray(findfile(z, "params_$(i).npy")) for i = 0:9]..., dims=1)
    params = [collect(reshape(selectdim(params, 1, a+1:b), s...)) for (a, b, s) in zip([0;offsets[1:end-1]], offsets, shapes)]
    params
  end
end

function readnpzfolder(dir)
  emp = JSON.parsefile(joinpath(dir, "encoder_bpe_40000.json"))
  vocab = map(first, sort!(collect(emp), by=(x)->x.second))
  bpe = Bpe(joinpath(dir, "vocab_40000.bpe"))
  weights = readnpz(dir)

  weights, bpe, vocab
end

function readnpzfolder(z::ZipFile.Reader)
  emp = JSON.parse(findfile(z, "encoder_bpe_40000.json"))
  vocab = map(first, sort!(collect(emp), by=(x)->x.second))
  bpe = mktemp(
    (fn, f)-> begin
      zf = findfile(z, "vocab_40000.bpe")
      buffer = Vector{UInt8}(undef, zf.uncompressedsize)
      write(f, read!(zf, buffer))
      close(f)
      Bpe(fn)
    end
  )
  weights = readnpz(z)
  weights, bpe, vocab
end

function build_vocab(raw_vocab;
                     startsym="_start_",
                     delisym="_delimiter_",
                     clfsym="_classify_",
                     unksym="<unk>")
    vocab = copy(raw_vocab)
    push!(vocab, startsym)
    push!(vocab, delisym)
    push!(vocab, clfsym)

    vocab = Vocabulary(vocab, unksym)
end

load_gpt_from_npbson(path::AbstractString) = (@assert isnpbson(path); load_gpt_from_npbson(BSON.load(path)))
load_gpt_from_npbson(bson) = load_gpt_from_npbson(bson[:weights], length(bson[:raw_vocab])+3)
function load_gpt_from_npbson(weights, vocab_size; n_special = 3)
    embed = Embed(768, vocab_size)
    pe = PositionEmbedding(768, 512; trainable=true)

    gpt = Gpt(768, 12, 768*4, 12; act=gelu, pdrop=0.1, attn_pdrop=0.1)

    # pm_name = (:pe, :embed, (
    #     (:mh.iqproj.W, :mh.ikproj.W, :mh.ivproj.W),
    #     (:mh.iqproj.b, :mh.ikproj.b, :mh.ivproj.b),
    #     :mh.oproj.W, :mh.oproj.b,
    #     :LN1.α, :LN1.β,
    #     :pw.din.W, :pw.din.b,
    #     :pw.dout.W, :pw.dout.b,
    #     :LN2.α, :LN2.β),
    #            :xN,
    #            )

    loadparams!(embed, [hcat(weights[2],
                             randn(768, n_special) .* 0.02)])
    loadparams!(pe, [weights[1]])
    for i = 1:12
        mhW = weights[12(i-1) + 3]
        mhb = weights[12(i-1) + 4]
        loadparams!(gpt.ts[i].mh.iqproj,[selectdim(mhW, 1, 1:768),
                                         selectdim(mhb, 1, 1:768)])
        loadparams!(gpt.ts[i].mh.ikproj,[selectdim(mhW, 1, 768+1:2*768),
                                         selectdim(mhb, 1, 768+1:2*768)])
        loadparams!(gpt.ts[i].mh.ivproj,[selectdim(mhW, 1, 2*768+1:3*768),
                                         selectdim(mhb, 1, 2*768+1:3*768)])
        loadparams!(gpt.ts[i].mh.oproj,[weights[12(i-1) + 5],
                                        weights[12(i-1) + 6]])
        loadparams!(gpt.ts[i].mhn,[weights[12(i-1) + 7],
                                   weights[12(i-1) + 8]])
        loadparams!(gpt.ts[i].pw.din,[weights[12(i-1) + 9],
                                      weights[12(i-1) + 10]])
        loadparams!(gpt.ts[i].pw.dout,[weights[12(i-1) + 11],
                                       weights[12(i-1) + 12]])
        loadparams!(gpt.ts[i].pwn,[weights[12(i-1) + 13],
                                   weights[12(i-1) + 14]])
    end

    ce = CompositeEmbedding(tok=embed, pe=pe)
    TransformerModel(ce, gpt)
end


#///////////////////////////////////////
#// File Name: forward_backward_simulation.jl
#// Author: Haruki Nishimura (hnishimura@stanford.edu)
#// Date Created: 2021/01/29
#// Description: Forward-backward simulation model for Risk Sensitive SAC
#///////////////////////////////////////

using CUDA
using LinearAlgebra
using PyCall
using Random
using RobotOS
import StatsFuns: logsumexp

# Simulation Parameter
struct SimulationParameter <: Parameter
    dtc::Float64                   # Time Interval for Continuous-time Dynamics
    dto::Float64                   # Time Interval for Discrete-time Dynamics
    prediction_steps::Int64        # Prediction steps for ado robots
    num_samples::Int64             # Number of samples (per ado) for ado simulation
    cost_param::CostParameter
end

function SimulationParameter(traj_scene_loader::TrajectronSceneLoader,
                             traj_predictor::TrajectronPredictor,
                             dtc::Float64, cost_param::CostParameter)
    dto = traj_scene_loader.dto;
    prediction_steps = traj_predictor.param.prediction_steps;
    num_samples = traj_predictor.param.num_samples;
    return SimulationParameter(dtc, dto, prediction_steps, num_samples,
                               cost_param)
end

function SimulationParameter(traj_scene_loader::TrajectronSceneLoader,
                             oracle_predictor::OraclePredictor,
                             dtc::Float64, cost_param::CostParameter)
    dto = traj_scene_loader.dto;
    prediction_steps = oracle_predictor.param.prediction_steps;
    num_samples = 1;
    return SimulationParameter(dtc, dto, prediction_steps, num_samples,
                               cost_param)
end

function SimulationParameter(gaussian_predictor::GaussianPredictor,
                             dtc::Float64, cost_param::CostParameter)
    dto = gaussian_predictor.dto;
    prediction_steps = gaussian_predictor.param.prediction_steps;
    num_samples = gaussian_predictor.param.num_samples;
    return SimulationParameter(dtc, dto, prediction_steps, num_samples,
                               cost_param)
end

# Simulated Cost Result
mutable struct SimulationCostResult
    inst_cnt_cost_array_gpu::CuArray{Float32, 2} # instantaneous control costs: (num_controls, total_timesteps-1)
    inst_pos_cost_array_gpu::CuArray{Float32, 2} # instantaneous position costs: (num_controls, total_timesteps - 1)
    inst_col_cost_array_gpu::CuArray{Float32, 3} # instantaneous collision costs: (num_samples*num_controls, total_timesteps-1, num_ado_agents)

    term_pos_cost_array_gpu::CuArray{Float32, 2} # terminal position costs: (num_controls, 1)
    term_col_cost_array_gpu::CuArray{Float32, 3} # terminal collision costs: (num_samples*num_controls, 1, num_ado_agents)
end

# Simulated Cost Gradient Result
mutable struct SimulationCostGradResult
    inst_pos_cost_grad_array_gpu::CuArray{Float32, 2} # instantaneous position cost gradients: (total_timesteps-1, 4)
    inst_col_cost_grad_array_gpu::CuArray{Float32, 4} # instantaneous collision cost gradients: (num_samples, total_timesteps-1, num_ado_agents, 4)

    term_pos_cost_grad_array_gpu::CuArray{Float32, 2} # terminal position cost gradients: (1, 4) array
    term_col_cost_grad_array_gpu::CuArray{Float32, 4} # terminal collision cost gradients: (num_samples, 1, num_ado_agents, 4)
end

# Simulation Result
mutable struct SimulationResult
    u_nominal_array::Vector{Vector{Float64}} # best nominal control schedule to be used
    u_nominal_idx::Int64 # best nominal control idx
    e_state_array::Vector{RobotState} # simulation of ego robot states
    measurement_schedule::Vector{Time} # measurement schedule
    prediction_dict::Dict{String, Array{Float64, 3}} # predicted ado states (lower time resolution)
    ap_array_gpu::CuArray{Float32, 4} # predicted ado states (higher time resolution, concatenated)
    ado_ids_array::Vector{String} # correspondence between 1st dim of expanded_ap_array and ado agent id
    sampled_total_costs::Vector{Float64} # sampled total costs
    risk_val::Float64 # risk value (i.e. our objective)
    e_costate_array::Array{Float64, 3} # (4, total_timesteps, num_samples) array
    e_costate_array_constraint::Union{Nothing, Array{Float64, 3}} # nothing or (4, constraint_timesteps, num_samples) array
    sampled_total_costs_constraint::Union{Nothing, Vector{Float64}} # nothing or sampled total costs for constraint computation
    risk_constraint::Union{Nothing, Float64} # nothing or risk value for constraint computation
end

# Helper Functions
# # convert array of u_array to 3-dimensional u_array
function process_u_arrays(u_arrays::Vector{Vector{Vector{Float64}}})
    # u_arrays: array of length num_controls of arrays of length total_timesteps-1 of arrays of lenght 2
    num_controls = length(u_arrays)
    total_timesteps = length(u_arrays[1]) + 1
    @assert all([length(u_array) == total_timesteps - 1 for u_array in u_arrays])

    u_array = Array{Float64, 3}(undef, num_controls, total_timesteps - 1, 2)
    for ii = 1:num_controls
        @inbounds u_array[ii, :, :] = permutedims(hcat(u_arrays[ii]...), [2, 1]);
    end
    return u_array
end

# # Forward Simulation
function simulate_forward(e_init::RobotState,
                          u_array::Vector{Vector{Float64}},
                          sim_param::SimulationParameter)
    e_state_array = Vector{RobotState}(undef, length(u_array) + 1);
    e_state_array[1] = e_init;
    for ii in eachindex(u_array)
        @inbounds e_state_array[ii + 1] = transition(e_state_array[ii],
                                                     u_array[ii],
                                                     sim_param.dtc)
    end
    return e_state_array
end
# # Forward Simulation (CUDA version)
function simulate_forward(e_init::RobotState,
                          u_array_gpu::CuArray{Float32, 3}, # multiple nominal control version
                          sim_param::SimulationParameter)
    # ex_init: 4-length vector of initial ego state.
    # u_array_gpu: (num_controls, total_timesteps-1, 2) array of acceleration inputs
    out_vel = similar(u_array_gpu)
    dtc = Float32(sim_param.dtc);
    # compute velocity (Euler integration)
    CUDA.accumulate!(+, out_vel, u_array_gpu.*sim_param.dtc, dims=2);
    out_vel = cat(cu(zeros(size(u_array_gpu, 1), 1, 2)), out_vel, dims=2);
    ev_init = get_velocity(e_init);
    out_vel[:, :, 1] .+= Float32(ev_init[1]);
    out_vel[:, :, 2] .+= Float32(ev_init[2]);

    # compute position (Euler integration)
    out_pos = similar(u_array_gpu)
    CUDA.accumulate!(+, out_pos, out_vel[:, 1:end-1, :].*dtc, dims=2);
    out_pos = cat(cu(zeros(size(u_array_gpu, 1), 1, 2)), out_pos, dims=2);
    ep_init = get_position(e_init);
    out_pos[:, :, 1] .+= Float32(ep_init[1]);
    out_pos[:, :, 2] .+= Float32(ep_init[2]);

    # (num_controls, total_timesteps, 4) array
    return cat(out_pos, out_vel, dims=3)
end

# # Measurement Schedule Handler
function get_measurement_schedule(w_init::WorldState, horizon::Float64,
                                  sim_param::SimulationParameter)
    measurement_schedule =
        Vector{Time}(undef, Int64(round(horizon/sim_param.dto, digits=6)));
    # If no measurement has been taken, assume the initial measurement time of
    # Time(a*dto) where a is the minimum integer so that a*dto > w_init.t
    if w_init.t_last_m == nothing
        a = 0;
        while a*sim_param.dto <= to_sec(w_init.t)
            a += 1;
        end
        measurement_schedule[1] = Time(a*sim_param.dto);
    else
        measurement_schedule[1] =
            w_init.t_last_m + Duration(sim_param.dto);
    end
    for ii = 2:length(measurement_schedule)
        measurement_schedule[ii] =
            measurement_schedule[ii - 1] + Duration(sim_param.dto);
    end
    return measurement_schedule
end
function get_measurement_schedule(w_init::WorldState,
                                  sim_param::SimulationParameter)
    measurement_schedule = Vector{Time}(undef, sim_param.prediction_steps);
    # If no measurement has been taken, assume the initial measurement time of
    # Time(a*dto) where a is the minimum integer so that a*dto > w_init.t
    if w_init.t_last_m == nothing
        a = 0;
        while a*sim_param.dto <= to_sec(w_init.t)
            a += 1;
        end
        measurement_schedule[1] = Time(a*sim_param.dto);
    else
        measurement_schedule[1] =
            w_init.t_last_m + Duration(sim_param.dto);
    end
    for ii = 2:length(measurement_schedule)
        measurement_schedule[ii] =
            measurement_schedule[ii - 1] + Duration(sim_param.dto);
    end
    return measurement_schedule
end

function get_target_pos_array(ex_array_gpu::AbstractArray{Float32, 3},
                              w_init::WorldState,
                              target_trajectory::Trajectory2D,
                              sim_param::SimulationParameter)

    # target_pos_aray_gpu : (total_timesteps, 2)
    # ex_array_gpu : (num_controls, total_timesteps, 4)

    t_array = [w_init.t]
    for ii = 1:size(ex_array_gpu, 2) - 1
        push!(t_array, t_array[end] + Duration(sim_param.dtc))
    end

    target_pos_array = Array(transpose(hcat(map(t -> get_position(target_trajectory, t), t_array)...)));
    return target_pos_array # (total_timesteps, 2) array
end

function process_ap_dict(ex_array_gpu::CuArray{Float32, 3},
                         w_init::WorldState,
                         measurement_schedule::Vector{Time},
                         prediction_dict::Dict{String, Array{Float64, 3}},
                         sim_param::SimulationParameter)

    # prediction_dict : each value is (num_samples*num_controls, prediction_steps, 2) array
    # ap_array : (num_samples*num_controls, prediction_steps + 1, num_ado_agents, 2) array
    # time_idx_ap_array : (total_timesteps) array of time indices for referencing ap_array
    # control_idx_ex_array : (num_samples*num_controls) array of nominal control indices for referencing ex_array
    # ado_ids_array : (num_ado_agents) array of ado ids

    # ratio between dto and dtc
    expansion_factor = Int64(sim_param.dto/sim_param.dtc);
    # number of nominal control candidates
    num_controls = size(ex_array_gpu, 1);

    # total simulation timesteps
    total_timesteps = size(ex_array_gpu, 2);
    # timesteps between current and first measurement time
    init_timesteps =
        Int64(to_nsec(measurement_schedule[1] - w_init.t)/
              (sim_param.dtc*1e9));
    # timesteps between final measurement time and final e_state time
    remaining_timesteps = total_timesteps -
                          (init_timesteps + expansion_factor*(sim_param.prediction_steps - 1));

    # time_idx_ap_array
    time_idx_ap_array = Vector{Int64}(undef, total_timesteps);
    time_idx_ap_array[1:init_timesteps] .= 1;
    time_idx_ap_array[init_timesteps+1:end-remaining_timesteps] = repeat(2:1:sim_param.prediction_steps,
                                                                         inner=expansion_factor);
    time_idx_ap_array[end-remaining_timesteps+1:end] .= sim_param.prediction_steps + 1;

    # control_idx_ex_array
    control_idx_ex_array = repeat(1:1:num_controls, inner=sim_param.num_samples)

    # ado ids
    ado_ids_array = collect(keys(prediction_dict));

    if length(ado_ids_array) == 0
        # if no pedestrian exists, return (num_samples*num_controls, prediction_steps + 1, 0, 2) array
        ap_array = Array{Float64, 4}(undef, sim_param.num_samples*num_controls, sim_param.prediction_steps + 1, 0, 2);
        return ap_array, time_idx_ap_array, control_idx_ex_array, ado_ids_array
    end

    ap_init_array = permutedims(cat([w_init.ap_dict[id] for id in ado_ids_array]..., dims=4),
                                    [2, 3, 4, 1]); # (1, 1, num_ado_agents, 2) array of initial ado positions
    ap_init_array = repeat(ap_init_array, inner=(sim_param.num_samples*num_controls, 1, 1, 1));

    ap_pred_array = permutedims(cat([prediction_dict[id] for id in ado_ids_array]..., dims=4),
                                      [1, 2, 4, 3]); # (num_samples*num_controls, prediction_steps, num_ado_agents, 2) array
    ap_array = cat(ap_init_array, ap_pred_array, dims=2) # (num_samples*num_controls, prediction_steps + 1, num_ado_agents, 2)

    return ap_array, time_idx_ap_array, control_idx_ex_array, ado_ids_array
end

# # Compute costs based on the forward simulation & predicted ado positions
function compute_costs(ex_array_gpu::CuArray{Float32, 3},
                       u_array_gpu::CuArray{Float32, 3},
                       ap_array_gpu::CuArray{Float32, 4},
                       time_idx_ap_array_gpu::CuArray{Int32, 1},
                       control_idx_ex_array_gpu::CuArray{Int32, 1},
                       target_pos_array_gpu::CuArray{Float32, 2},
                       cost_param::CostParameter)
    if name(CuDevice(0)) == "GeForce RTX 2080 Ti"
        # Instataneous costs
        inst_cnt_cost_array_gpu = instant_control_cost(u_array_gpu, cost_param, threads=(16, 64));
        inst_pos_cost_array_gpu = instant_position_cost(ex_array_gpu, target_pos_array_gpu, cost_param, threads=(16, 64));
        inst_col_cost_array_gpu = instant_collision_cost(ex_array_gpu, ap_array_gpu,
                                                         time_idx_ap_array_gpu, control_idx_ex_array_gpu,
                                                         cost_param, threads=(64, 4, 4));
        # Terminal costs
        term_pos_cost_array_gpu = terminal_position_cost(ex_array_gpu, target_pos_array_gpu, cost_param, threads=(1024, 1));
        term_col_cost_array_gpu = terminal_collision_cost(ex_array_gpu, ap_array_gpu,
                                                          time_idx_ap_array_gpu, control_idx_ex_array_gpu,
                                                          cost_param, threads=(128, 1, 8));
    else
        # Instataneous costs
        inst_cnt_cost_array_gpu = instant_control_cost(u_array_gpu, cost_param);
        inst_pos_cost_array_gpu = instant_position_cost(ex_array_gpu, target_pos_array_gpu, cost_param);
        inst_col_cost_array_gpu = instant_collision_cost(ex_array_gpu, ap_array_gpu,
                                                         time_idx_ap_array_gpu, control_idx_ex_array_gpu,
                                                         cost_param);
        # Terminal costs
        term_pos_cost_array_gpu = terminal_position_cost(ex_array_gpu, target_pos_array_gpu, cost_param);
        term_col_cost_array_gpu = terminal_collision_cost(ex_array_gpu, ap_array_gpu,
                                                          time_idx_ap_array_gpu, control_idx_ex_array_gpu,
                                                          cost_param);
    end

    return SimulationCostResult(inst_cnt_cost_array_gpu, inst_pos_cost_array_gpu,
                                inst_col_cost_array_gpu, term_pos_cost_array_gpu,
                                term_col_cost_array_gpu)
end

# # Integrate all the costs over horizon, returning sampled total costs and the risk value
function integrate_costs(cost_result::SimulationCostResult,
                         sim_param::SimulationParameter,
                         constraint_time::Union{Nothing, Float64}=nothing)
    if !isnothing(constraint_time)
        c_idx = Int64(round(constraint_time/sim_param.dtc, digits=5))
        @assert c_idx < size(cost_result.inst_pos_cost_array_gpu, 2);
    else
        c_idx = size(cost_result.inst_cnt_cost_array_gpu, 2);
    end
    # control cost
    cnt_cost_per_control = sum(cost_result.inst_cnt_cost_array_gpu[:, 1:c_idx], dims=2).*Float32(sim_param.dtc);

    # position cost
    pos_cost_per_control = sum(cost_result.inst_pos_cost_array_gpu[:, 1:c_idx], dims=2).*Float32(sim_param.dtc);
    if isnothing(constraint_time)
        pos_cost_per_control += cost_result.term_pos_cost_array_gpu;
    end

    # for collision cost, sum over all timesteps and ado agents but not over samples
    col_cost_per_sample_control = sum(cost_result.inst_col_cost_array_gpu[:, 1:c_idx, :], dims=(2, 3)).*Float32(sim_param.dtc);
    if isnothing(constraint_time)
        col_cost_per_sample_control += sum(cost_result.term_col_cost_array_gpu, dims=3);
    end
    # reshape collision cost into (num_controls, num_samples) array
    col_cost_per_control_sample = permutedims(reshape(col_cost_per_sample_control, sim_param.num_samples, :),
                                              [2, 1]);
    @assert isa(col_cost_per_control_sample, CuArray{Float32, 2});
    num_controls = size(col_cost_per_control_sample, 1);

    # (num_controls, num_samples) array of Float64
    total_cost_per_control_sample = Float64.(collect(col_cost_per_control_sample .+ (cnt_cost_per_control + pos_cost_per_control)));

    # compute risk value per control
    risk_per_control = Vector{Float64}(undef, num_controls);
    for ii = 1:num_controls
        if !(sim_param.cost_param.σ_risk == 0.0)
            risk_value = 1/sim_param.cost_param.σ_risk*
                         logsumexp(sim_param.cost_param.σ_risk.*total_cost_per_control_sample[ii, :] .- log(sim_param.num_samples));
        else
            risk_value = sum(total_cost_per_control_sample[ii, :])./sim_param.num_samples
        end
        @inbounds risk_per_control[ii] = risk_value
    end
    return total_cost_per_control_sample, risk_per_control
end

# Choose best nominal control (i.e. with lowest risk value) for backward simulation
function choose_best_nominal_control(total_cost_per_control_sample, risk_per_control, u_array_gpu)
    minimum_risk, best_control_idx = findmin(risk_per_control);
    sampled_total_costs = total_cost_per_control_sample[best_control_idx, :];

    best_u_array_tmp = Float64.(collect(u_array_gpu[best_control_idx, :, :]));
    best_u_array = [best_u_array_tmp[ii, :] for ii = 1:size(best_u_array_tmp, 1)];

    return sampled_total_costs, minimum_risk, best_control_idx, best_u_array
end

# # Compute cost gradients based on the forward simulation & predicted ado positions
function compute_cost_gradients(best_ex_array_gpu::CuArray{Float32, 2},
                                best_u_array_gpu::CuArray{Float32, 2},
                                best_ap_array_gpu::CuArray{Float32, 4},
                                time_idx_ap_array_gpu::CuArray{Int32, 1},
                                target_pos_array_gpu::CuArray{Float32, 2},
                                cost_param::CostParameter)
    if name(CuDevice(0)) == "GeForce RTX 2080 Ti"
        # Instantaneous costs
        inst_pos_cost_grad_array_gpu = instant_position_cost_gradient(best_ex_array_gpu, target_pos_array_gpu,
                                                                      cost_param, threads=256);
        inst_col_cost_grad_array_gpu = instant_collision_cost_gradient(best_ex_array_gpu, best_ap_array_gpu,
                                                                       time_idx_ap_array_gpu, cost_param,
                                                                       threads=(64, 4, 4));

        # Terminal costs
        term_pos_cost_grad_array_gpu = terminal_position_cost_gradient(best_ex_array_gpu, target_pos_array_gpu,
                                                                       cost_param);
        term_col_cost_grad_array_gpu = terminal_collision_cost_gradient(best_ex_array_gpu, best_ap_array_gpu,
                                                                        time_idx_ap_array_gpu, cost_param, threads=(128, 1, 8));
    else
        # Instantaneous costs
        inst_pos_cost_grad_array_gpu = instant_position_cost_gradient(best_ex_array_gpu, target_pos_array_gpu,
                                                                      cost_param);
        inst_col_cost_grad_array_gpu = instant_collision_cost_gradient(best_ex_array_gpu, best_ap_array_gpu,
                                                                       time_idx_ap_array_gpu, cost_param);

        # Terminal costs
        term_pos_cost_grad_array_gpu = terminal_position_cost_gradient(best_ex_array_gpu, target_pos_array_gpu,
                                                                       cost_param);
        term_col_cost_grad_array_gpu = terminal_collision_cost_gradient(best_ex_array_gpu, best_ap_array_gpu,
                                                                        time_idx_ap_array_gpu, cost_param);
    end
    return SimulationCostGradResult(inst_pos_cost_grad_array_gpu, inst_col_cost_grad_array_gpu,
                                    term_pos_cost_grad_array_gpu, term_col_cost_grad_array_gpu)
end

# # sum cost gradients per timestep and sample, reshaping them to (4, total_timesteps, num_samples) array
function sum_cost_gradients(cost_grad_result::SimulationCostGradResult)
    num_samples = size(cost_grad_result.inst_col_cost_grad_array_gpu, 1);
    total_timesteps = size(cost_grad_result.inst_col_cost_grad_array_gpu, 2) + 1;

    cost_grad_array = Array{Float64, 3}(undef, 4, total_timesteps, num_samples);

    # sum over all ado agents
    inst_col_cost_grads_gpu = sum(cost_grad_result.inst_col_cost_grad_array_gpu, dims=3); # (num_samples, total_timesteps-1, 1, 4)
    term_col_cost_grads_gpu = sum(cost_grad_result.term_col_cost_grad_array_gpu, dims=3); # (num_samples, 1, 1, 4)

    cost_grad_array[:, 1:end-1, :] = collect(dropdims(permutedims(inst_col_cost_grads_gpu, [4, 3, 2, 1]), dims=2)); # (4, total_timesteps-1, num_samples)
    cost_grad_array[:, 1:end-1, :] .+= collect(permutedims(cost_grad_result.inst_pos_cost_grad_array_gpu, [2, 1])); # (4, total_timesteps-1, num_samples)
    cost_grad_array[:, end:end, :] = collect(dropdims(permutedims(term_col_cost_grads_gpu, [4, 3, 2, 1]), dims=2)); # (4, 1, num_samples)
    cost_grad_array[:, end:end, :] .+= collect(permutedims(cost_grad_result.term_pos_cost_grad_array_gpu, [2, 1])); # (4, 1, num_samples)

    return cost_grad_array
end

# # backward simulation of the costates.
function simulate_backward(best_ex_array::Array{Float64, 2}, # (total_timesteps, 4) array
                           best_u_array::Vector{Vector{Float64}},
                           cost_grad_array::Array{Float64, 3},
                           sim_param::SimulationParameter)
    e_costate_array = similar(cost_grad_array); # (4, total_timesteps, num_samples) array
    # boundary conditions
    e_costate_array[:, end, :] = cost_grad_array[:, end, :];

    for jj = Iterators.reverse(1:size(e_costate_array, 2) - 1)
        # backward euler integration
        @inbounds Jacobian =
            transition_jacobian(best_ex_array[jj + 1, :],
                                best_u_array[jj]) # Note the index jj here
        @inbounds costate_vel =
            -cost_grad_array[:, jj, :] .- # Note the index jj here
            transpose(Jacobian)*e_costate_array[:, jj + 1, :];
        @inbounds e_costate_array[:, jj, :] =
            e_costate_array[:, jj + 1, :] .+
            costate_vel*(-sim_param.dtc);
    end
    return e_costate_array
end

# Main Simulation Function (forward-backward simulation)
# Ado positions and predictions must be updated separately.
function simulate(w_init::WorldState,
                  u_arrays::Vector{Vector{Vector{Float64}}},
                  target_trajectory::Trajectory2D,
                  prediction_dict::Dict{String, Array{Float64, 3}},
                  sim_param::SimulationParameter,
                  constraint_time::Union{Nothing, Float64}=nothing)
    # process u_arrays
    u_array = process_u_arrays(u_arrays);
    u_array_gpu = cu(u_array);

    # ego state simulation
    ex_array_gpu = simulate_forward(w_init.e_state, u_array_gpu, sim_param);

    # get measurement schedule
    measurement_schedule = get_measurement_schedule(w_init, sim_param);

    # process prediction_dict for further computation
    # note that each value in prediction_dict has to be (num_samples*num_controls, prediction_steps, 2) array
    ap_array, time_idx_ap_array, control_idx_ex_array, ado_ids_array =
        process_ap_dict(ex_array_gpu, w_init, measurement_schedule, prediction_dict, sim_param);
    ap_array_gpu = cu(ap_array)
    time_idx_ap_array_gpu = CuArray{Int32}(time_idx_ap_array);
    control_idx_ex_array_gpu = CuArray{Int32}(control_idx_ex_array);

    # get target_pos array
    target_pos_array = get_target_pos_array(ex_array_gpu, w_init, target_trajectory, sim_param)
    target_pos_array_gpu = cu(target_pos_array);

    # compute costs
    cost_result = compute_costs(ex_array_gpu, u_array_gpu, ap_array_gpu,
                                time_idx_ap_array_gpu, control_idx_ex_array_gpu,
                                target_pos_array_gpu, sim_param.cost_param);

    # integrate costs
    total_cost_per_control_sample, risk_per_control = integrate_costs(cost_result, sim_param);

    # choose best nominal control
    sampled_total_costs, minimum_risk, best_control_idx, best_u_array =
        choose_best_nominal_control(total_cost_per_control_sample, risk_per_control, u_array_gpu);
    best_ex_array_gpu = ex_array_gpu[best_control_idx, :, :];
    best_ex_array = Float64.(collect(best_ex_array_gpu));
    best_u_array_gpu = u_array_gpu[best_control_idx, :, :];
    best_ap_array_sample_idx =
        sim_param.num_samples*(best_control_idx-1)+1:sim_param.num_samples*best_control_idx
    best_ap_array_gpu = ap_array_gpu[best_ap_array_sample_idx, :, :, :];
    # compute cost gradients using the best nominal control
    cost_grad_result = compute_cost_gradients(best_ex_array_gpu, best_u_array_gpu,
                                              best_ap_array_gpu, time_idx_ap_array_gpu,
                                              target_pos_array_gpu,
                                              sim_param.cost_param);

    # get cost gradient array for costate computation
    cost_grad_array = sum_cost_gradients(cost_grad_result);

    # backward simulation of ego costate
    e_costate_array = simulate_backward(best_ex_array, best_u_array, cost_grad_array, sim_param);

    if !isnothing(constraint_time)
        # compute indices to slice best_ex_array, best_u_array, cost_grad_array for constraint enforcement
        c_idx = Int64(round(constraint_time/sim_param.dtc, digits=5)) + 1;
        @assert c_idx < size(best_ex_array, 1)
        # compute adjoint for constraint enforcement
        cost_grad_array[:, c_idx, :] = zeros(size(cost_grad_array[:, c_idx, :])); # TODO: maybe we do not want to mutate cost_grad_array.
        e_costate_array_constraint = simulate_backward(best_ex_array[1:c_idx, :],
                                                       best_u_array[1:c_idx],
                                                       cost_grad_array[:, 1:c_idx, :],
                                                       sim_param);
        total_cost_per_control_sample_constraint, risk_per_control_constraint =
            integrate_costs(cost_result, sim_param, constraint_time);
        sampled_total_costs_constraint =
            total_cost_per_control_sample_constraint[best_control_idx, :];
        risk_constraint = risk_per_control_constraint[best_control_idx];
    else
        e_costate_array_constraint = nothing;
        sampled_total_costs_constraint = nothing;
        risk_constraint = nothing;
    end

    sim_result = SimulationResult(best_u_array,
                                  best_control_idx,
                                  simulate_forward(w_init.e_state, best_u_array, sim_param),
                                  measurement_schedule, prediction_dict,
                                  best_ap_array_gpu,
                                  ado_ids_array, sampled_total_costs, minimum_risk,
                                  e_costate_array,
                                  e_costate_array_constraint,
                                  sampled_total_costs_constraint,
                                  risk_constraint)
    return sim_result, best_u_array
end

#=
# Forward (re-)simulation and evaluation of risk (for potential line search which is not implemented yet)
function evaluate_risk(w_init::WorldState,
                       u_array::Vector{Vector{Float64}},
                       target_trajectory::Trajectory2D,
                       ap_array_gpu::CuArray{Float32, 4},
                       sim_param::SimulationParameter)
    # ego state simulation
    e_state_array = simulate_forward(w_init.e_state, u_array, sim_param);
    # export to gpu
    ep_array_gpu = cu(hcat(get_position.(e_state_array)...));
    # compute costs
    cost_result = compute_costs(e_state_array, ep_array_gpu, u_array, ap_array_gpu,
                                target_trajectory, sim_param.cost_param);
    ~, risk_val = integrate_costs(cost_result, sim_param);
    return risk_val
end
=#


module ITensorPySCF

using PyCall
using ITensors

export pyscf, fci, gto, scf, ao2mo

const pyscf = PyNULL()
const fci = PyNULL()
const gto = PyNULL()
const scf = PyNULL()
const ao2mo = PyNULL()

function __init__()
  copy!(pyscf, pyimport_conda("pyscf", "pyscf"))
  copy!(fci, pyimport_conda("pyscf.fci", "pycsf"))
  copy!(gto, pyimport_conda("pyscf.gto", "pycsf"))
  copy!(scf, pyimport_conda("pyscf.scf", "pycsf"))
  copy!(ao2mo, pyimport_conda("pyscf.ao2mo", "pycsf"))
end

end


@enum ResourceItemType begin
  i_material
  i_card_exp
  i_act_item
  i_diamond
  i_diamond_shard
  i_ap_item
  i_ap_base
  i_ap_supply
  i_ap_gameplay
  i_exp_player
  i_lim_ticket_10
  i_epgs
  i_ticket
  i_ticket_try
  i_ticket_recruit
  i_ticket_10
  i_ticket_inst_fin
  i_ticket_rand_prsv
  i_gold
  i_voucher_pick
  i_voucher_rand_m
  i_voucher_rand_c
  i_et_stage
  i_rep_coin
  i_crs_shop_coin
  i_crs_rune_coin
  i_social_pt
  i_hgg_shd
  i_lmtgs_coin
  i_lgg_shd
  i_activity_coin
  i_unknown
end
@enum ResourceClassType c_material c_normal c_consume c_none c_unknown

"""
"""
struct ResourceType
  id::String
  name::String
  item_type::ResourceItemType
  class_type::ResourceClassType
  abbrev::Union{String, Nothing}
  usage::String
  desc::String
  obtaining::Union{String, Nothing}
  rarity::Int8
  sort_id::Integer
end

function Base.show(io::IO, rt::ResourceType)
  print(io, "ResourceType($(rt.id), $(rt.name), $(rt.abbrev), $(rt.rarity))")
end

"""
  Used primarily in PlayerData, but must be pre-defined for GameData
"""
struct Resource
  val::Unsigned
  type::ResourceType

  Resource(val, type) = new(Base.convert(Unsigned, val), type)
end

function Base.show(io::IO, r::Resource)
  print(io, "Resource($(r.val), $(r.type.abbrev))")
end

"""
"""
struct FactoryRecipe
  product::Resource
  inputs::Array{Resource, 3}
  time::Dates.AbstractTime
  capacity::Int8
end

function Base.show(io::IO, fr::FactoryRecipe)
  print(io, "FactoryRecipe()")
end

"""
"""
struct WorkshopRecipe
  product::Resource
  inputs::Array{Resource, 3}
  lmd::Resource
  morale::Int8
end

function Base.show(io::IO, wr::WorkshopRecipe)
  print(io, "WorkshopRecipe()")
end

@enum PromotionPhase e0=0 e1=1 e2=2

@enum AtkType no_atk=0 melee=1 ranged=2
@enum DmgType no_dmg=0 phys_dmg=1 arts_dmg=2 true_dmg=3

"""
"""
struct OperatorBase
  id::String
  name::String
  rank::Int8

  function OperatorBase(id::String, name::String, rank::Integer)
    @assert 1 <= rank <= 6 "Rank must be between 1 and 6"
    return new(id, name, Int8(rank))
  end
end

"""
"""
struct EnemyFlags
  infected_creature::Bool
  possessed::Bool
  sarkaz::Bool
  drone::Bool
end

@enum EnemyRank ordinary=1 elite=2 leader=3
@enum EnemyGrade e=1 d=2 c=3 b=4 a=5 ap=6

"""
"""
struct EnemyBase
  id::String
  name::String
  species::String
  flavour_text::String
  rank::EnemyRank
  atk_type::AtkType
  dmg_type::DmgType
  hp::EnemyGrade
  atk::EnemyGrade
  def::EnemyGrade
  res::EnemyGrade
  special::String
  base_stats::Any # EnemyStats
end

function Base.show(io::IO, m::EnemyBase)
  print(io, "EnemyBase()")
end

"""
"""
struct Resistances
  silence::Bool
  stun::Bool
  sleep::Bool
end

"""
"""
struct EnemyStats
  id::Integer
  type::EnemyBase
  level::Integer
  hp::Integer
  regen::Float64
  atk::Integer
  atk_int::Float64
  atk_radius::Float64
  def::Integer
  res::Float64
  mov::Float64
  weight::Int8
  resistances::Resistances
end

function Base.show(io::IO, m::EnemyStats)
  print(io, "EnemyStats()")
end

"""
"""
struct Tile
  id::Integer
  name::String
  ascii_art::Char
end

function Base.show(io::IO, t::Tile)
  print(io, t.ascii_art)
end

"""
"""
struct Map
  tiles::Vector{Tile}
end

function Base.show(io::IO, m::Map)
  print(io, "Map()")
end

@enum DropRarity unknown=0 guranteed=1 common=2 uncommon=3 rare=4 very_rare=5 three_stars=10

"""
"""
struct Stage
  id::String
  # meta
  sanity_cost::Resource
  plan_cost::Resource
  rec_level::Integer
  # combat
  hp_seals::Integer
  enemy_count::Integer
  deployment_limit::Integer
  initial_cp::Integer
  max_cp::Integer
  enemies::Dict{EnemyStats, Integer}
  map::Map
  # rewards
  exp::Integer
  trust::Integer
  lmd::Resource
  first_drops::Dict{ResourceType, DropRarity}
  regular_drops::Dict{ResourceType, DropRarity}
  special_drops::Dict{ResourceType, DropRarity}
  extra_drops::Vector{ResourceType}
end

function Base.show(io::IO, s::Stage)
  print(io, "Stage()")
end

"""
"""
struct OperatorLevel
  level::Integer
  promotion::PromotionPhase
  xp::Integer
end

function OperatorLevel(
    level::Integer = 0,
    promotion::PromotionPhase = e0,
    xp::Integer = 0)
  return OperatorLevel(level, promotion, xp)
end

"""
"""
struct OperatorSkills
  rank::Integer
  mastery_1::Integer
  mastery_2::Integer
  mastery_3::Integer
end

function OperatorSkills(; rank::Integer = 1,
    m1::Integer = 0, m2::Integer = 0, m3::Integer = 0)
  @assert 1 <= rank <= 7 "skill rank must be between 1 and 7"
  for m in [m1, m2, m3]
    @assert 0 <= m <= 3 "skill mastery must be between 0 and 3"
  end
  return OperatorSkills(rank, m1, m2, m3)
end

"""
"""
struct Operator
  base::OperatorBase
  level::OperatorLevel
  skills::OperatorSkills
  trust::Integer
  potential::Integer
end

function Operator(
    base::OperatorBase,
    level::OperatorLevel = OperatorLevel(0, e0, 0),
    skills::OperatorSkills = OperatorSkills(),
    trust::Integer = 0,
    potential::Integer = 0)
  return Operator(base, level, skills, trust, potential)
end

"""
"""
struct StageReport
  id::String
  stars::Int8
  time::Dates.AbstractTime
  exp::Integer
  trust::Integer
  lmd::Resource
  drops::Dict{ResourceType, Integer}
end

function Base.show(io::IO, sr::StageReport)
  print(io, "StageReport()")
end

"""
"""
struct RecruitReport
  id::Integer
end

function Base.show(io::IO, rr::RecruitReport)
  print(io, "RecruitReport()")
end


using Colors, ColorTypes, FixedPointNumbers
abstract type Effect end
abstract type ConfigurableEffect <: Effect end
function update(e::Effect)
    throw(NullException())
end
struct EffectConfig{T<:AbstractFloat}
    primary_color::HSL{T}
    secondary_color::HSL{T}
    scaling::T
    speed::T
    special::Dict{String, Any}
end

function EffectConfig(primary_color::HSL, secondary_color::HSL, scaling::Number, speed::Number, special::Dict{String, Any})
    target = eltype(primary_color)
    return EffectConfig(primary_color, convert(HSL{target}, secondary_color), convert(target, scaling), convert(target, speed), special)
end

const file_regex = r".*?\.jl"six
for effectfile in readdir("./src/effects")
    if match(file_regex, effectfile) != nothing
        include("./effects/$effectfile")
    end
end

const effect_types = Dict{String, Any}(
    "0"=>NBarsEffect{Float64},
    "1"=>WalkingPulseEffect{Float64},
    "2"=>HeatWaveEffect
)


using Revise
using Flipping
##
Directory_path = "/home/beatriz/mainen.flipping.5ht@gmail.com/Flipping/";
Exp_type = "Photometry";
Exp_name = "GCaMP_close_pokes"
training_days = Date(2017,5,15):Day(1):Date(2017,5,26)
issues_days = Date(2016,12,31):Day(1):Date(2017,1,1)
bad_days = vcat(training_days, issues_days)
Camera_path = "/Users/dariosarra/Google Drive/Flipping/run_task_photo/DatcreGCaMPAutomaticBarr/Cam/"
Behavior_path = "/Users/dariosarra/Google Drive/Flipping/run_task_photo/raw_data"
Mice_suffix = "DG";
DataIndex = ProcessPhotometry.create_photometry_DataIndex(Directory_path, Exp_type,Exp_name, Mice_suffix;bad_days = bad_days)
names(DataIndex)
#
dformat = Dates.DateFormat("yyyymmdd")
mask = DataIndex[:Day].<=Date("20190717",dformat)
# mask = DataIndex[:MouseID].== "DG2"
DataIndex = DataIndex[mask,:]
# names(DataIndex)
##
pokes, cam_dict = save_bhv_photo(DataIndex)

DataIndex[.!ispath.(DataIndex[:Log_Path]),:Log_Path]
size(DataIndex,1)
union(column(pokes,:MouseID))
union(column(pokes,:Session))
union(DataIndex[:Session])
process_pokes(DataIndex[56,:Bhv_Path])[:MouseID]

Flipping.session_info(DataIndex[56,:Bhv_Path])


"""
`module  JAC.Atomic`  ... a submodel of JAC that contains all methods to set-up and process (simple) atomic and SCF computations as well as
                          atomic cascade computations; it is using JAC, JAC.Radial, JAC.ManyElectron, JAC.Nuclear, JAC.Einstein,
                          JAC.Hfs, JAC.IsotopeShift, JAC.PlasmaShift, JAC.LandeZeeman, JAC.PhotoEmission, JAC.PhotoIonization, JAC.AutoIonization.
"""
module Atomic

    using Interact
    using JAC.Basics, JAC.Radial, JAC.ManyElectron, JAC.Nuclear
    using JAC.Einstein, JAC.Hfs, JAC.IsotopeShift, JAC.PlasmaShift, JAC.LandeZeeman, JAC.AlphaVariation, JAC.FormFactor, JAC.DecayYield,
          JAC.GreenFunction,  JAC.MultipolePolarizibility,
          JAC.PhotoEmission, JAC.PhotoIonization, JAC.PhotoExcitation, JAC.PhotoRecombination,
          JAC.AutoIonization, JAC.Dielectronic, JAC.ImpactExcitation, JAC.CoulombExcitation, JAC.CoulombIonization,
          JAC.PhotoExcitationFluores, JAC.PhotoExcitationAutoion, JAC.RayleighCompton, JAC.MultiPhotonDeExcitation,
          JAC.PhotoIonizationFluores, JAC.PhotoIonizationAutoion, JAC.ImpactExcitationAutoion, JAC.RadiativeAuger,
          JAC.MultiPhotonIonization, JAC.MultiPhotonDoubleIon, JAC.InternalConversion
          ##x , JAC.ElectricDipoleMoment

    """
    `struct  Atomic.CasStep`  ... a type for defining an individual step of a (relativistic) complete active space computation for a specified
                                  set of levels. An instance of this struct provides all information to generate the atomic basis and to perform
                                  the associated SCF and multiplet computations for a selected No. of levels.

        + name             ::String                    ... to assign a name to the given SCF step.
        + NoElectrons      ::Int64                     ... Number of electrons.
        + refConfigs       ::Array{Configuration,1}    ... List of references configurations, at least 1.
        + Sfrom            ::Array{Shell,1}            ... Single-excitations from shells   [sh_1, sh_2, ...]
        + Sto              ::Array{Shell,1}            ... Single-excitations to shells  [sh_1, sh_2, ...]
        + Dfrom            ::Array{Shell,1}            ... Double-excitations from shells   [sh_1, sh_2, ...]
        + Dto              ::Array{Shell,1}            ... Double-excitations to shells  [sh_1, sh_2, ...]
        + Tfrom            ::Array{Shell,1}            ... Triple-excitations from shells   [sh_1, sh_2, ...]
        + Tto              ::Array{Shell,1}            ... Triple-excitations to shells  [sh_1, sh_2, ...]
        + Qfrom            ::Array{Shell,1}            ... Quadrupole-excitations from shells   [sh_1, sh_2, ...]
        + Qto              ::Array{Shell,1}            ... Quadrupole-excitations to shells  [sh_1, sh_2, ...]
        + restrictions     ::Array{String,1}           ... List of Strings to define 'restrictions' to applied to the CSF basis.
        + NoIterations     ::Int64                     ... Number of SCf iterations to be applied in this step of computations.
        + frozenSubshells  ::Array{Subshell,1}         ... List of subshells that are kept 'frozen' in this step.
        + failureHandling  ::Array{String,1}           ... List of Strings to define subsequent steps in case of failure.
    """
    struct  CasStep
        name               ::String
        NoElectrons        ::Int64
        refConfigs         ::Array{Configuration,1}
        Sfrom              ::Array{Shell,1}
        Sto                ::Array{Shell,1}
        Dfrom              ::Array{Shell,1}
        Dto                ::Array{Shell,1}
        Tfrom              ::Array{Shell,1}
        Tto                ::Array{Shell,1}
        Qfrom              ::Array{Shell,1}
        Qto                ::Array{Shell,1}
        restrictions       ::Array{String,1}
        NoIterations       ::Int64
        frozenShells       ::Array{Subshell,1}
        failureHandling    ::Array{String,1}
    end

    """
    `JAC.Atomic.CasStep(name::String, NoElectrons::Int64)`  ... constructor for an 'initial' instance of a variable::Atomic.CasStep with just
                                                                a given name and No. of Electrons.
    """
    function CasStep(name::String, NoElectrons::Int64)
        CasStep(name, NoElectrons, Configuration[], Shell[], Shell[], Shell[], Shell[], Shell[], Shell[], Shell[], Shell[],
                String[], 0, Subshell[], String[])
    end

    """
    `JAC.Atomic.CasStep("interactive"; refine::Atomic.CasStep=..)`  ... constructor to generate a new instance of Atomic.CasStep
                                       interactively by replying to some detailed dialog. This constructor enables one to add, modify or
                                       refine an individual step in the complete active space computations. **Not yet implemented !**
    """
    function CasStep(sa::String; refine::Atomic.CasStep = Atomic.CasStep())
        sa != "interactive"   &&    error("Unsupported keystring = $sa.")

        #= print("Enter the nuclear charge Z:  ");    Zx = Base.parse(Float64, readline(STDIN) )

        yes = yesno("The ...; modify ? [N|y]")
        while  yes
            print("Enter a charge distribution model {point, Fermi, uniform}:  ");    modelx = strip(readline(STDIN))
            !(modelx in [""])   &&   println("Unsupported charge distribution model $modelx ... redo:")
        end =#
    end

    # `Base.show(io::IO, step::Atomic.CasStep)`  ... prepares a proper printout of the (individual step of computations) step::Atomic.CasStep.
    function Base.show(io::IO, step::Atomic.CasStep)
        sa = Base.string(step);                print(io, sa, "\n")
        sa = "Reference configurations: ";     print(io, sa, "\n")
        if  length(step.refConfigs) > 0
           for  conf  in step.refConfigs          print(io, conf )  end;   print(io, "\n")
        end
        if  length(step.Sfrom) > 0
           sa = "Singles from:              { ";      for  sh in step.Sfrom    sa = sa * string(sh) * ", "  end
           sa = sa[1:end-2] *  " }   ... to { ";      for  sh in step.Sto      sa = sa * string(sh) * ", "  end;
           sa = sa[1:end-2] *  " }";            print(io, sa, "\n")
        end
        if  length(step.Dfrom) > 0
           sa = "Doubles from:              { ";      for  sh in step.Dfrom    sa = sa * string(sh) * ", "  end
           sa = sa[1:end-2] *  " }   ... to { ";      for  sh in step.Dto      sa = sa * string(sh) * ", "  end;
           sa = sa[1:end-2] *  " }";            print(io, sa, "\n")
        end
        if  length(step.Tfrom) > 0
           sa = "Triples from:              { ";      for  sh in step.Tfrom    sa = sa * string(sh) * ", "  end
           sa = sa[1:end-2] *  " }   ... to { ";      for  sh in step.Tto      sa = sa * string(sh) * ", "  end;
           sa = sa[1:end-2] *  " }";            print(io, sa, "\n")
        end
        if  length(step.Qfrom) > 0
           sa = "Quadruples from:           { ";      for  sh in step.Qfrom    sa = sa * string(sh) * ", "  end
           sa = sa[1:end-2] *  " }   ... to { ";      for  sh in step.Qto      sa = sa * string(sh) * ", "  end;
           sa = sa[1:end-2] *  " }";            print(io, sa, "\n")
        end
    end

    # `Base.string(step::Atomic.CasStep)`  ... provides a String notation for the variable step::Atomic.CasStep.
    function Base.string(step::Atomic.CasStep)
        sa = "Step: Computational model for $(step.name) with $(step.NoElectrons) electrons and with (step.NoCoreShells) "
        sa = sa * "core shells:"
        return( sa )
    end

    """
    `struct  Atomic.CasComputation`  ... a struct for defining a series of complete active space computations for a specified set of levels.
                                         This type provides all information to generate the atomic basis and to perform the corresponding
                                         SCF and multiplet computations for a selected No. of levels.

        + name             ::String                     ... to assign a name to the given model.
        + previousStep     ::Int64                      ... 0, if no previous steps was yet done in this series.
        + steps            ::Array{Atomic.CasStep,1}    ... List of SCF steps that are to be done in this model computation.
    """
    struct  CasComputation
        name               ::String
        previousStep       ::Int64
        steps              ::Array{Atomic.CasStep,1}
    end

    """
    `JAC.Atomic.CasComputation()`  ... constructor for an 'empty' instance of the a variable::Atomic.CasComputation
    """
    function CasComputation()
        CasComputation("", 0, Array{CasStep,1}[])
    end

    """
    `JAC.Atomic.CasComputation("interactive"; refine::Atomic.CasComputation=..)`  ... constructor to generate a new instance of
                               Atomic.CasComputation interactively by replying to some detailed dialog. This constructor enables one to add,
                               modify or refine the model for a particular complete active space computations. **Not yet implemented !**
    """
    function CasComputation(sa::String; refine::Atomic.CasComputation = Atomic.CasComputation())
        sa != "interactive"   &&    error("Unsupported keystring = $sa.")

        #= print("Enter the nuclear charge Z:  ");    Zx = Base.parse(Float64, readline(STDIN) )

        yes = yesno("The ...; modify ? [N|y]")
        while  yes
            print("Enter a charge distribution model {point, Fermi, uniform}:  ");    modelx = strip(readline(STDIN))
            !(modelx in [""])   &&   println("Unsupported charge distribution model $modelx ... redo:")
        end =#
    end

    # `Base.show(io::IO, model::CasComputation)`  ... prepares a proper printout of the (individual step of computations) model::CasComputation.
    function Base.show(io::IO, model::CasComputation)
        sa = Base.string(model)
        print(io, sa)
    end

    # `Base.string(model::CasComputation)`  ... provides a String notation for the variable model::CasComputation.
    function Base.string(model::CasComputation)
        sa = "Model: $(model.name) includes $(length(model.steps)) steps and has been calculated up to the step $(model.previousStep)."
        return( sa )
    end

    """
    `struct  Atomic.CasSettings`  ... a struct for defining the settings for complete active space computations

        + generateScf       ::Bool               ... True, if a SCF need to be generated, and false if just the start orbitals should
                                                     be applied.
        + startOrbitals     ::String             ... Specify how the start orbitals are obtained ["fromNRorbitals", "fromGrasp", "hydrogenic"].
        + rwfFilename       ::String             ... Filename of orbitals, if taken from Grasp.
        + levels            ::Array{Int64,1}     ... Levels on which the optimization need to be carried out.
        + includeBreit      ::Bool               ... True, if the Breit interaction is included into the SCF generation.
        + maxIterations     ::Int64              ... maximum number of SCF iterations
        + scfAccuracy       ::Float64            ... convergence criterion for the SCF field.
        + iterationSequence ::Array{Subshell,1}  ... Sequence of subshells to be optimized.
    """
    struct  CasSettings
        generateScf         ::Bool
        startOrbitals       ::String
        rwfFilename         ::String
        levels              ::Array{Int64,1}
        includeBreit        ::Bool
        maxIterations       ::Int64
        scfAccuracy         ::Float64
        iterationSequence   ::Array{Subshell,1}
    end

    """
    `JAC.Atomic.CasSettings()`  ... constructor for setting the default values.
    """
    function CasSettings()
        CasSettings(false, "fromNRorbitals", "", Int64[1], false, 24, 1.0e-8, Subshell[] )
    end

    # `Base.show(io::IO, settings::Atomic.CasSettings)`  ... prepares a proper printout of the settings::Atomic.CasSettings.
    function Base.show(io::IO, settings::Atomic.CasSettings)
        println(io, "generateScf:        $(settings.generateScf)  ")
        println(io, "startOrbitals:      $(settings.startOrbitals)  ")
        println(io, "rwfFilename:        $(settings.rwfFilename)  ")
        println(io, "levels:             $(settings.levels)  ")
        println(io, "includeBreit:       $(settings.includeBreit)  ")
        println(io, "maxIterations:      $(settings.maxIterations)  ")
        println(io, "scfAccuracy:        $(settings.scfAccuracy)  ")
        println(io, "iterationSequence:  $(settings.iterationSequence)  ")
    end

    # `Base.string(settings::Atomic.CasSettings)`  ... provides a String notation for the variable settings::Atomic.CasSettings.
    function Base.string(settings::Atomic.CasSettings)
        error("Not yet implemented.")
        sa = "Cas settings: maximum No. of iterations = $(settings.maxIterations), accuracy = (settings.scfAccuracy)"
        return( sa )
    end

    """
    `struct  Computation`  ... defines a type for defining  (the model of simple) atomic computation of a single multiplet,
                               including the SCF and CI as well as level properties and transition property calculations.

        + name                           ::String                        ... A name associated to the computation.
        + nuclearModel                   ::Nuclear.Model          ... Model, charge and parameters of the nucleus.
        + grid                           ::Radial.Grid                   ... The radial grid to be used for the computation.
        ##x + calcLevelProperties            ::Bool                          ... True, if level structures and properties are to be calculated
        + properties                     ::Array{AtomicLevelProperty,1}  ... List of atomic properties to be calculated.
        + configs                        ::Array{Configuration,1}        ... A list of non-relativistic configurations.
        + asfSettings                    ::AsfSettings                   ... Provides the settings for the SCF process and for the CI and QED calculations.
        + initialConfigs                 ::Array{Configuration,1}        ... A list of initial-state configurations for some transition
                                                                             property calculation, such as radiative transition, Auger, etc.
        + initialAsfSettings             ::AsfSettings                   ... Provides the SCF and CI settings for the initial-state multiplet.
        + intermediateConfigs            ::Array{Configuration,1}        ... A list of initial-state configurations.
        + intermediateAsfSettings        ::AsfSettings                   ... Provides the SCF settings for the intermediate-state multiplet.
        + finalConfigs                   ::Array{Configuration,1}        ... A list of final-state configurations.
        + finalAsfSettings               ::AsfSettings                   ... Provides the SCF and CI settings for the final-state multiplet.
        + alphaSettings                  ::AlphaVariation.Settings       ... Settings for alpha-variation parameter calculations.
        + einsteinSettings               ::Einstein.Settings             ... Settings for Einstein coefficient calculations.
        + formSettings                   ::FormFactor.Settings           ... Settings for atomic form factor calculations.
        + greenSettings                  ::GreenFunction.Settings        ... Settings for approximate Green function calculations.
        + hfsSettings                    ::Hfs.Settings                  ... Settings for hyperfine parameter calculations.
        + isotopeSettings                ::IsotopeShift.Settings         ... Settings for isotope shift parameter calculations.
        + plasmaSettings                 ::PlasmaShift.Settings          ... Settings for plasma-shift calculations.
        + polaritySettings               ::MultipolePolarizibility.Settings   ... Settings for plasma-shift calculations.
        + yieldSettings                  ::DecayYield.Settings           ... Settings for fluoresence and Auger yield calculations.
        + zeemanSettings                 ::LandeZeeman.Settings          ... Settings for Lande-Zeeman coefficient calculations.
        + process                        ::JAC.AtomicProcess             ... An (additional) process for which the properties are to be evaluated
                                                                             for the given initial- and final-state configurations.
        + processSettings                ::Union{JAC.PhotoEmission.Settings, JAC.AutoIonization.Settings, JAC.PlasmaShift.AugerSettings,
                                                 JAC.PhotoIonization.Settings, JAC.PlasmaShift.PhotoSettings,
                                                 JAC.PhotoExcitation.Settings, JAC.PhotoExcitationAutoion.Settings, JAC.PhotoRecombination.Settings,
                                                 JAC.ImpactExcitation.Settings, JAC.Dielectronic.Settings, RadiativeAuger.Settings,
                                                 JAC.PairAnnihilation1Photon.Settings, JAC.ImpactExcitationAutoion.Settings,
                                                 JAC.MultiPhotonDeExcitation.Settings, JAC.CoulombExcitation.Settings,
                                                 JAC.CoulombIonization.Settings} ... Provides the settings for the selected process.
    """
    struct  Computation
        name                           ::String
        nuclearModel                   ::Nuclear.Model
        grid                           ::Radial.Grid
        ##x calcLevelProperties            ::Bool
        properties                     ::Array{AtomicLevelProperty,1}
        configs                        ::Array{Configuration,1}
        asfSettings                    ::AsfSettings
        initialConfigs                 ::Array{Configuration,1}
        initialAsfSettings             ::AsfSettings
        intermediateConfigs            ::Array{Configuration,1}
        intermediateAsfSettings        ::AsfSettings
        finalConfigs                   ::Array{Configuration,1}
        finalAsfSettings               ::AsfSettings
        alphaSettings                  ::AlphaVariation.Settings
        einsteinSettings               ::Einstein.Settings
        formSettings                   ::FormFactor.Settings
        greenSettings                  ::GreenFunction.Settings
        hfsSettings                    ::Hfs.Settings
        isotopeSettings                ::IsotopeShift.Settings
        plasmaSettings                 ::PlasmaShift.Settings
        polaritySettings               ::MultipolePolarizibility.Settings
        yieldSettings                  ::DecayYield.Settings
        zeemanSettings                 ::LandeZeeman.Settings
        process                        ::AtomicProcess
        processSettings                ::Union{PhotoEmission.Settings, AutoIonization.Settings, PlasmaShift.AugerSettings,
                                               PhotoIonization.Settings, PlasmaShift.PhotoSettings,
                                               PhotoRecombination.Settings, Dielectronic.Settings, ImpactExcitation.Settings,
                                               CoulombExcitation.Settings, CoulombIonization.Settings,
                                               PhotoExcitation.Settings, PhotoExcitationFluores.Settings,
                                               PhotoExcitationAutoion.Settings, RayleighCompton.Settings,
                                               MultiPhotonDeExcitation.Settings, PhotoIonizationFluores.Settings,
                                               PhotoIonizationAutoion.Settings, ImpactExcitationAutoion.Settings,
                                               RadiativeAuger.Settings, MultiPhotonIonization.Settings,
                                               MultiPhotonDoubleIon.Settings, InternalConversion.Settings} #= ,
                                               #
                                               PairAnnihilation1Photon.Settings } =#
    end

    """
    `JAC.Atomic.Computation()`  ... constructor for an 'empty' instance::Atomic.Computation.
    """
    function Computation()
        Computation("", Nuclear.Model(1.), Radial.Grid(), AtomicLevelProperty[],
                    Configuration[], AsfSettings(),
                    Configuration[], AsfSettings(),
                    Configuration[], AsfSettings(),
                    Configuration[], AsfSettings(),
                    AlphaVariation.Settings(), Einstein.Settings(), FormFactor.Settings(), GreenFunction.Settings(),
                    Hfs.Settings(),
                    IsotopeShift.Settings(), PlasmaShift.Settings(), MultipolePolarizibility.Settings(),
                    DecayYield.Settings(),  LandeZeeman.Settings(),
                    ##x NoAmplitude, ElectricDipoleMoment.Settings(),
                    NoProcess, PhotoEmission.Settings() )
    end

    """
    `JAC.Atomic.Computation(sa::String, nm::Nuclear.Model; grid::Radial.Grid = JAC.Radial.Grid('grid: exponential'), ##x calc::Bool = false,
         properties::Array{AtomicLevelProperty,1} = AtomicLevelProperty[], configs::Array{Configuration,1} = Configuration[],
         asfSettings::AsfSettings = AsfSettings(),
         initialConfigs::Array{Configuration,1} = Configuration[], initialAsfSettings::AsfSettings = AsfSettings(),
         intermediateConfigs::Array{Configuration,1} = Configuration[], intermediateAsfSettings::AsfSettings = AsfSettings(),
         finalConfigs::Array{Configuration,1} = Configuration[], finalAsfSettings::AsfSettings = AsfSettings(),
         alphaSettings::AlphaVariation.Settings = AlphaVariation.Settings(), einsteinSettings::Einstein.Settings = Einstein.Settings(),
         formSettings::FormFactor.Settings = FormFactor.Settings(), greenSettings::GreenFunction.Settings = GreenFunction.Settings(),
         hfsSettings::Hfs.Settings = Hfs.Settings(),
         isotopeSettings::IsotopeShift.Settings = IsotopeShift.Settings(), plasmaSettings::PlasmaShift.Settings = PlasmaShift.Settings(),
         polaritySettings::MultipolePolarizibility.Settings = MultipolePolarizibility.Settings(),
         yieldSettings::DecayYield.Settings = DecayYield.Settings(), zeemanSettings::LandeZeeman.Settings = LandeZeeman.Settings(),
         ##x amplitude::JAC.AtomicAmplitude = JAC.NoAmplitude, amplitudeSettings::Any = true,
         process::JAC.AtomicProcess = JAC.NoProcess, processSettings::Any = true)`   ... constructor for an instance::Atomic.Computation for
         which all requested details are given by proper keyword specification. A few internal checks are  made.
    """
    function Computation(sa::String, nm::Nuclear.Model; grid::Radial.Grid = Radial.Grid("grid: exponential"), ##x calc::Bool = false,
                         properties::Array{AtomicLevelProperty,1} = [Basics.NoProperty], configs::Array{Configuration,1} = Configuration[],
                         asfSettings::AsfSettings = AsfSettings(),
                         initialConfigs::Array{Configuration,1} = Configuration[], initialAsfSettings::AsfSettings = AsfSettings(),
                         intermediateConfigs::Array{Configuration,1} = Configuration[], intermediateAsfSettings::AsfSettings = AsfSettings(),
                         finalConfigs::Array{Configuration,1} = Configuration[], finalAsfSettings::AsfSettings = AsfSettings(),
                         alphaSettings::AlphaVariation.Settings = AlphaVariation.Settings(),
                         einsteinSettings::Einstein.Settings = Einstein.Settings(),
                         formSettings::FormFactor.Settings = FormFactor.Settings(),
                         greenSettings::GreenFunction.Settings = GreenFunction.Settings(),
                         hfsSettings::Hfs.Settings = Hfs.Settings(),
                         isotopeSettings::IsotopeShift.Settings = IsotopeShift.Settings(),
                         plasmaSettings::PlasmaShift.Settings = PlasmaShift.Settings(),
                         polaritySettings::MultipolePolarizibility.Settings = MultipolePolarizibility.Settings(),
                         yieldSettings::DecayYield.Settings = DecayYield.Settings(),
                         zeemanSettings::LandeZeeman.Settings = LandeZeeman.Settings(),
                         process::Basics.AtomicProcess = Basics.NoProcess, processSettings::Any = true)
        if      process == Basics.NoProcess         && typeof(processSettings) == Bool && processSettings             procSettings = PhotoEmission.Settings()
        elseif  process == Basics.Auger             && typeof(processSettings) == Bool && processSettings             procSettings = AutoIonization.Settings()
        elseif  process == Basics.Auger             && typeof(processSettings) == AutoIonization.Settings             procSettings = processSettings
        elseif  process == Basics.AugerInPlasma     && typeof(processSettings) == Bool && processSettings             procSettings = PlasmaShift.AugerSettings()
        elseif  process == Basics.AugerInPlasma     && typeof(processSettings) == PlasmaShift.AugerSettings           procSettings = processSettings
        elseif  process == Basics.Radiative         && typeof(processSettings) == Bool && processSettings             procSettings = PhotoEmission.Settings()
        elseif  process == Basics.Radiative         && typeof(processSettings) == PhotoEmission.Settings              procSettings = processSettings
        elseif  process == Basics.PhotoExc          && typeof(processSettings) == Bool && processSettings             procSettings = PhotoExcitation.Settings()
        elseif  process == Basics.PhotoExc          && typeof(processSettings) == PhotoExcitation.Settings            procSettings = processSettings
        elseif  process == Basics.Photo             && typeof(processSettings) == Bool && processSettings             procSettings = PhotoIonization.Settings()
        elseif  process == Basics.Photo             && typeof(processSettings) == PhotoIonization.Settings            procSettings = processSettings
        elseif  process == Basics.PhotoInPlasma     && typeof(processSettings) == Bool && processSettings             procSettings = PlasmaShift.PhotoSettings()
        elseif  process == Basics.PhotoInPlasma     && typeof(processSettings) == PlasmaShift.PhotoSettings           procSettings = processSettings
        elseif  process == Basics.Rec               && typeof(processSettings) == Bool && processSettings             procSettings = PhotoRecombination.Settings()
        elseif  process == Basics.Rec               && typeof(processSettings) == PhotoRecombination.Settings         procSettings = processSettings
        elseif  process == Basics.Dierec            && typeof(processSettings) == Bool && processSettings             procSettings = Dielectronic.Settings()
        elseif  process == Basics.Dierec            && typeof(processSettings) == Dielectronic.Settings               procSettings = processSettings
        elseif  process == Basics.PhotoExcFluor     && typeof(processSettings) == Bool && processSettings             procSettings = PhotoExcitationFluores.Settings()
        elseif  process == Basics.PhotoExcFluor     && typeof(processSettings) == PhotoExcitationFluores.Settings     procSettings = processSettings
        elseif  process == Basics.PhotoExcAuto      && typeof(processSettings) == Bool && processSettings             procSettings = PhotoExcitationAutoion.Settings()
        elseif  process == Basics.PhotoExcAuto      && typeof(processSettings) == PhotoExcitationAutoion.Settings     procSettings = processSettings
        elseif  process == Basics.PhotoIonFluor     && typeof(processSettings) == Bool && processSettings             procSettings = PhotoIonizationFluores.Settings()
        elseif  process == Basics.PhotoIonFluor     && typeof(processSettings) == PhotoIonizationFluores.Settings     procSettings = processSettings
        elseif  process == Basics.PhotoIonAuto      && typeof(processSettings) == Bool && processSettings             procSettings = PhotoIonizationAutoion.Settings()
        elseif  process == Basics.PhotoIonAuto      && typeof(processSettings) == PhotoIonizationAutoion.Settings     procSettings = processSettings
        elseif  process == Basics.MultiPhotonDE     && typeof(processSettings) == Bool && processSettings             procSettings = MultiPhotonDeExcitation.Settings()
        elseif  process == Basics.MultiPhotonDE     && typeof(processSettings) == MultiPhotonDeExcitation.Settings    procSettings = processSettings
        elseif  process == Basics.Compton           && typeof(processSettings) == Bool && processSettings             procSettings = RayleighCompton.Settings()
        elseif  process == Basics.Compton           && typeof(processSettings) == RayleighCompton.Settings            procSettings = processSettings
        elseif  process == Basics.Eimex             && typeof(processSettings) == Bool && processSettings             procSettings = ImpactExcitation.Settings()
        elseif  process == Basics.Eimex             && typeof(processSettings) == ImpactExcitation.Settings           procSettings = processSettings
        elseif  process == Basics.ImpactExcAuto     && typeof(processSettings) == Bool && processSettings             procSettings = ImpactExcitationAutoion.Settings()
        elseif  process == Basics.ImpactExcAuto     && typeof(processSettings) == ImpactExcitationAutoion.Settings    procSettings = processSettings
        elseif  process == Basics.RAuger            && typeof(processSettings) == Bool && processSettings             procSettings = RadiativeAuger.Settings()
        elseif  process == Basics.RAuger            && typeof(processSettings) == RadiativeAuger.Settings             procSettings = processSettings
        elseif  process == Basics.MultiPI           && typeof(processSettings) == Bool && processSettings             procSettings = MultiPhotonIonization.Settings()
        elseif  process == Basics.MultiPI           && typeof(processSettings) == MultiPhotonIonization.Settings      procSettings = processSettings
        elseif  process == Basics.MultiPDI          && typeof(processSettings) == Bool && processSettings             procSettings = MultiPhotonDoubleIon.Settings()
        elseif  process == Basics.MultiPDI          && typeof(processSettings) == MultiPhotonDoubleIon.Settings       procSettings = processSettings
        elseif  process == Basics.InternalConv      && typeof(processSettings) == Bool && processSettings             procSettings = InternalConversion.Settings()
        elseif  process == Basics.InternalConv      && typeof(processSettings) == InternalConversion.Settings         procSettings = processSettings
        elseif  process == Basics.Coulex            && typeof(processSettings) == Bool && processSettings             procSettings = CoulombExcitation.Settings()
        elseif  process == Basics.Coulex            && typeof(processSettings) == CoulombExcitation.Settings          procSettings = processSettings
        elseif  process == Basics.Coulion           && typeof(processSettings) == Bool && processSettings             procSettings = CoulombIonization.Settings()
        elseif  process == Basics.Coulion           && typeof(processSettings) == CoulombIonization.Settings          procSettings = processSettings
        else    error("The processSettings must fit to the given process or should not occur if NoProcess is to be calculated.")
        end

        Computation(sa, nm, grid, properties, configs, asfSettings,
                    initialConfigs, initialAsfSettings,
                    intermediateConfigs, intermediateAsfSettings,
                    finalConfigs, finalAsfSettings,
                    alphaSettings, einsteinSettings, formSettings, greenSettings, hfsSettings, isotopeSettings, plasmaSettings,
                    polaritySettings, yieldSettings, zeemanSettings,
                    ##x amplitude, ampSettings,
                    process, procSettings)
    end

    # `Base.show(io::IO, computation::Atomic.Computation)`  ... prepares a proper printout of the variable computation::Atomic.Computation.
    function Base.show(io::IO, computation::Atomic.Computation)
        println(io, "name:                           $(computation.name)  ")
        println(io, "nuclearModel:                   $(computation.nuclearModel)  ")
        println(io, "grid:                           $(computation.grid)  ")
        ##x println(io, "calcLevelProperties:            $(computation.calcLevelProperties)  ")
        println(io, "properties:                     $(computation.properties)  ")
        println(io, "configs:                        $(computation.configs)  ")
        println(io, "asfSettings:                      computation.asfSettings  ")
        println(io, "initialConfigs:                 $(computation.initialConfigs)  ")
        println(io, "initialAsfSettings:               computation.initialAsfSettings  ")
        println(io, "intermediateConfigs:            $(computation.intermediateConfigs)  ")
        println(io, "intermediateAsfSettings:          computation.intermediateAsfSettings  ")
        println(io, "finalConfigs:                   $(computation.finalConfigs)  ")
        println(io, "finalAsfSettings:                 computation.finalAsfSettings  ")
        println(io, "alphaSettings:                    computation.alphaSettings  ")
        println(io, "einsteinSettings:                 computation.einsteinSettings  ")
        println(io, "formSettings:                     computation.formSettings  ")
        println(io, "greenSettings:                    computation.greenSettings  ")
        println(io, "hfsSettings:                      computation.hfsSettings  ")
        println(io, "isotopeSettings:                  computation.isotopeSettings  ")
        println(io, "plasmaSettings:                   computation.plasmaSettings  ")
        println(io, "polaritySettings:                 computation.polaritySettings  ")
        println(io, "yieldSettings:                    computation.yieldSettings  ")
        println(io, "zeemanSettings:                   computation.zeemanSettings  ")
        println(io, "process:                        $(computation.process)  ")
        println(io, "processSettings:                  computation.processSettings  ")
    end

    """
    `JAC.Atomic.Computation(gui::Guint; comp::Atomic.Computation=Atomic.Computation())`  ... constructor that is defined by a graphical user interface.
    """
    function Computation(gui::Guint; comp::Atomic.Computation=Atomic.Computation())

        if      gui == Gui
            println("aa")
            output = Atomic.ComputationGui(comp)
        elseif  gui == GuiSettings
            println("bb")
            output = Atomic.ComputationGuiSettings(comp)
        else  error("Unsupported Guint = $gui.")
        end

        return( output )
    end

    """
    `JAC.Atomic.ComputationGui(comp::Atomic.Computation)`  ... constructor that defines the standard part of a computation by a graphical
         user interface apart from the setting and the configurations.
    """
    function ComputationGui(comp::Atomic.Computation)
        nmd = comp.nuclearModel

        title = comp.name;    nmd = comp.nuclearModel;    grid = comp.grid;    calcLevelProperties = comp.calcLevelProperties
        properties          = comp.properties
        configs             = comp.configs;                   asfSettings             = comp.asfSettings
        initialConfigs      = comp.initialConfigs;            initialAsfSettings      = comp.initialAsfSettings
        intermediateConfigs = comp.intermediateConfigs;       intermediateAsfSettings = comp.intermediateAsfSettings
        finalConfigs        = comp.finalConfigs;              finalAsfSettings        = comp.finalAsfSettings
        alphaSettings       = comp.alphaSettings;             einsteinSettings        = comp.einsteinSettings
        formSettings        = comp.formSettings;              greenSettings           = comp.greenSettings
        hfsSettings         = comp.hfsSettings;               isotopeSettings         = comp.isotopeSettings
        plasmaSettings      = comp.plasmaSettings;            polaritySettings        = comp.polaritySettings
        yieldSettings       = comp.yieldSettings;             zeemanSettings          = comp.zeemanSettings
        process             = comp.process;                   processSettings         = comp.processSettings

        # Update the nuclear model
        tn1 = "Update nuclear model: "
        bn1 = slider(1:110, label = "charge", value = nmd.Z)
        bn2 = dropdown([nmd.model, "Fermi", "point", "uniform"])
        bn3 = spinbox(label="mass  ";   value=nmd.mass)
        bn4 = spinbox(label="radius";   value=nmd.radius)
        bn5 = spinbox(label="2*spin";   value=nmd.spinI.num)
        bn6 = spinbox(label="mu    ";   value=nmd.mu)
        bn7 = spinbox(label="Q     ";   value=nmd.Q)
        #
        #
        update = button("Update all")
        ui = vbox( hbox( pad(0em, tn1) ),
                   hbox( pad(1em, bn1) ),
                   hbox( pad(1em, bn2), pad(1em, bn3), pad(1em, bn4) ),
                   hbox( pad(1em, bn5), pad(1em, bn6), pad(1em, bn7) ),
                   hbox( pad(2em, update) )
                  )
        #
        Interact.display(ui)
        output = Interact.@map  (&update;     Computation(title,
                    Nuclear.Model( observe(bn1)[], observe(bn2)[], observe(bn3)[], observe(bn4)[],
                                   AngularJ64( Int64(observe(bn5)[])//2 ), observe(bn6)[], observe(bn7)[] ),
                    grid, calcLevelProperties, properties, configs, asfSettings,
                    initialConfigs, initialAsfSettings,
                    intermediateConfigs, intermediateAsfSettings,
                    finalConfigs, finalAsfSettings,
                    alphaSettings, einsteinSettings, formSettings, greenSettings, hfsSettings, isotopeSettings, plasmaSettings,
                    polaritySettings, yieldSettings, zeemanSettings,
                    process, processSettings) )
        #
        return( output )
    end

    """
    `JAC.Atomic.ComputationGuiSettings(comp::Atomic.Computation)`  ... constructor that defines the settings and configurations of a computation
         by a graphical user interface.
    """
    function ComputationGuiSettings(comp::Atomic.Computation)
        ac = comp;   nmd = ac.nuclearModel

        t1 = "A slider: "
        b1 = slider(1:110, label = "charge", value = nmd.Z)
        update = button("Update")
        ui = vbox( hbox( pad(0em, t1) ),
                   hbox( pad(0em, b1) ),
                   hbox( pad(1em, update) )
                  )
        #
        Interact.display(ui)
        output = Interact.@map  (&update; observe(b1)[] )
        return( output )
    end

end # module