# [
#  [ MPIStateArray Name, Field Name, Maximum, Minimum, Mean, Standard Deviation ],
#  [         :                :          :        :      :          :           ],
# ]

parr = [
    ["3D state", "u[1]", 12, 12, 0, 12],
    ["3D state", "u[2]", 0, 0, 0, 0],
    ["3D state", :η, 12, 12, 0, 12],
    ["3D state", :θ, 15, 15, 15, 15],
    ["3D aux", :y, 15, 15, 15, 15],
    ["3D aux", :w, 12, 12, 0, 12],
    ["3D aux", :pkin, 15, 15, 15, 15],
    ["3D aux", :wz0, 12, 12, 0, 12],
    ["3D aux", "uᵈ[1]", 12, 12, 0, 12],
    ["3D aux", "uᵈ[2]", 0, 0, 0, 0],
    ["3D aux", "ΔGᵘ[1]", 11, 11, 0, 12],
    ["3D aux", "ΔGᵘ[2]", 0, 0, 0, 0],
    ["2D state", :η, 12, 12, 0, 12],
    ["2D state", "U[1]", 12, 12, 0, 12],
    ["2D state", "U[2]", 0, 0, 0, 0],
    ["2D aux", :y, 15, 15, 15, 2],
    ["2D aux", "Gᵁ[1]", 11, 11, 0, 12],
    ["2D aux", "Gᵁ[2]", 0, 0, 0, 0],
    ["2D aux", "Δu[1]", 12, 12, 0, 12],
    ["2D aux", "Δu[2]", 0, 0, 0, 0],
]

uncoupled = [
    [
        "3D state",
        "u[1]",
        -9.58547428246830479637e-01,
        9.58547428246829258391e-01,
        -6.82121026329696195717e-18,
        4.45400655325317695876e-01,
    ],
    [
        "3D state",
        "u[2]",
        -4.13574641907576004779e-14,
        1.84325494853789287544e-14,
        1.94567057477529826177e-17,
        2.12815907156924710074e-15,
    ],
    [
        "3D state",
        :η,
        -8.52721734395889496838e-01,
        8.52834259382379111791e-01,
        2.18869899981655176688e-14,
        6.02983573168680453414e-01,
    ],
    [
        "3D state",
        :θ,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        :y,
        0.00000000000000000000e+00,
        1.00000000000000011642e+06,
        5.00000000000000000000e+05,
        2.92775877460665535182e+05,
    ],
    [
        "3D aux",
        :w,
        -4.04489222828922911912e-04,
        4.04649979048336849198e-04,
        7.79376563286859913379e-19,
        1.63949585575251901345e-04,
    ],
    [
        "3D aux",
        :pkin,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        :wz0,
        -2.00933099660541601228e-04,
        2.00809859469744893647e-04,
        2.62012633811536956698e-19,
        1.42064682697776552755e-04,
    ],
    [
        "3D aux",
        "uᵈ[1]",
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        "uᵈ[2]",
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        "ΔGᵘ[1]",
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        "ΔGᵘ[2]",
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "2D state",
        :η,
        -8.52715894965927923010e-01,
        8.52861218874714666072e-01,
        -7.70938868299708696148e-16,
        6.03458382295490314284e-01,
    ],
    [
        "2D state",
        "U[1]",
        -3.15423945317149438949e+01,
        3.15423945317147342848e+01,
        1.42724412149908289268e-14,
        2.24262219407601044452e+01,
    ],
    [
        "2D state",
        "U[2]",
        -9.38673083374999972374e-13,
        1.16534781393478256382e-12,
        1.93385973223830695204e-14,
        2.16634164433978071172e-13,
    ],
    [
        "2D aux",
        :y,
        0.00000000000000000000e+00,
        1.00000000000000011642e+06,
        5.00000000000000000000e+05,
        2.92775877460665535182e+05,
    ],
    [
        "2D aux",
        "Gᵁ[1]",
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "2D aux",
        "Gᵁ[2]",
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "2D aux",
        "Δu[1]",
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "2D aux",
        "Δu[2]",
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
]

coupled = [
    [
        "3D state",
        "u[1]",
        -9.58544854429326798062e-01,
        9.58544854429326798062e-01,
        -4.09272615797817732838e-17,
        4.45400401208546903309e-01,
    ],
    [
        "3D state",
        "u[2]",
        -4.09119364541065205420e-15,
        3.41954410407034240802e-15,
        1.57855184364031418231e-19,
        7.69982545727116047928e-16,
    ],
    [
        "3D state",
        :η,
        -8.52733075123627615177e-01,
        8.52843573070954374948e-01,
        7.27595761418342649852e-17,
        6.02992194663175995473e-01,
    ],
    [
        "3D state",
        :θ,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        :y,
        0.00000000000000000000e+00,
        1.00000000000000011642e+06,
        5.00000000000000000000e+05,
        2.92775877460665535182e+05,
    ],
    [
        "3D aux",
        :w,
        -4.04456725919283483547e-04,
        4.04616610112968017130e-04,
        1.33892896769793873666e-18,
        1.63945200913093809460e-04,
    ],
    [
        "3D aux",
        :pkin,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        :wz0,
        -2.00813442787548966564e-04,
        2.00686991710668984502e-04,
        1.68642877440561282675e-18,
        1.41979510156587984725e-04,
    ],
    [
        "3D aux",
        "uᵈ[1]",
        -8.79713597641291755735e-01,
        8.79713597641291200624e-01,
        -9.26547727431170607429e-17,
        4.41871673548253185437e-01,
    ],
    [
        "3D aux",
        "uᵈ[2]",
        -5.88884665747485312740e-28,
        6.47654803186450692243e-28,
        1.40221555861782666734e-30,
        4.22950073030020620100e-29,
    ],
    [
        "3D aux",
        "ΔGᵘ[1]",
        -1.50292385646252865781e-09,
        1.50292385640701573972e-09,
        -3.03178502810731714103e-21,
        6.72141424487058536370e-10,
    ],
    [
        "3D aux",
        "ΔGᵘ[2]",
        -2.50330922252795548175e-19,
        2.79421319642066487612e-19,
        8.87468518373638336981e-36,
        5.19465794381941235415e-20,
    ],
    [
        "2D state",
        :η,
        -8.52733075123627615177e-01,
        8.52843573070954374948e-01,
        1.61115565333602722195e-16,
        6.03463098440266798583e-01,
    ],
    [
        "2D state",
        "U[1]",
        -3.15402749945522060671e+01,
        3.15402749945527389741e+01,
        2.12974776703234167348e-14,
        2.24262621677375086904e+01,
    ],
    [
        "2D state",
        "U[2]",
        -1.63647745816416607002e-12,
        1.36781764162808076476e-12,
        6.31420737450488849608e-17,
        3.08233543917742288569e-13,
    ],
    [
        "2D aux",
        :y,
        0.00000000000000000000e+00,
        1.00000000000000011642e+06,
        5.00000000000000000000e+05,
        2.92775877460665535182e+05,
    ],
    [
        "2D aux",
        "Gᵁ[1]",
        -6.01169542562806285963e-07,
        6.01169542585011466433e-07,
        1.21270571032004377760e-18,
        2.69066532005499711718e-07,
    ],
    [
        "2D aux",
        "Gᵁ[2]",
        -1.11768527856826595815e-16,
        1.00132368901118217729e-16,
        -3.50057026691823957451e-33,
        2.07948587451660775232e-17,
    ],
    [
        "2D aux",
        "Δu[1]",
        -6.53936429129693404076e-04,
        6.53936429129569046087e-04,
        -2.70378337774435083119e-18,
        4.64975945389895016328e-04,
    ],
    [
        "2D aux",
        "Δu[2]",
        -3.76049176138251460297e-17,
        3.57641761985933683411e-17,
        -4.12443309266433447246e-19,
        7.09162842149286857838e-18,
    ],
]

thirty_minutes = [
    [
        "3D state",
        "u[1]",
        -9.58527737426365766815e-01,
        9.58527737426364989659e-01,
        -2.95585778076201651428e-17,
        4.45397585011924779241e-01,
    ],
    [
        "3D state",
        "u[2]",
        -3.91776258115697290002e-15,
        3.71573344368902785991e-15,
        9.99453214450878842160e-18,
        7.55533641450465537744e-16,
    ],
    [
        "3D state",
        :η,
        -8.52729582431237531637e-01,
        8.52830944991449846349e-01,
        -2.54658516496419884307e-16,
        6.02990141900936249542e-01,
    ],
    [
        "3D state",
        :θ,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        :y,
        0.00000000000000000000e+00,
        1.00000000000000011642e+06,
        5.00000000000000000000e+05,
        2.92775877460665535182e+05,
    ],
    [
        "3D aux",
        :w,
        -4.06712181304362465229e-04,
        4.06869755985816939844e-04,
        9.07052211118752913371e-19,
        1.64290385445136678539e-04,
    ],
    [
        "3D aux",
        :pkin,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        :wz0,
        -2.09003099782708142351e-04,
        2.08868143992394606845e-04,
        1.33781874467331362536e-18,
        1.47768608464728741676e-04,
    ],
    [
        "3D aux",
        "uᵈ[1]",
        -8.79792191807299950312e-01,
        8.79792191807298729067e-01,
        -9.54969436861574689412e-17,
        4.41911149114936674387e-01,
    ],
    [
        "3D aux",
        "uᵈ[2]",
        -4.49650715975976729085e-28,
        3.22939933074851707840e-28,
        1.28559371735991545201e-30,
        3.12162656228539824003e-29,
    ],
    [
        "3D aux",
        "ΔGᵘ[1]",
        -1.52356619979685413105e-09,
        1.52356619973800994984e-09,
        -3.00415481336788178489e-21,
        6.81373145706173027634e-10,
    ],
    [
        "3D aux",
        "ΔGᵘ[2]",
        -2.17115289587171316651e-19,
        2.99271337844227832879e-19,
        3.57452597678270976423e-36,
        5.23982958192948877053e-20,
    ],
    [
        "2D state",
        :η,
        -8.52729582431237531637e-01,
        8.52830944991449846349e-01,
        -3.20454773827805192348e-16,
        6.03461044074932395631e-01,
    ],
    [
        "2D state",
        "U[1]",
        -3.15407643291233448224e+01,
        3.15407643291237640426e+01,
        2.30678864898692384736e-14,
        2.24265574817805202201e+01,
    ],
    [
        "2D state",
        "U[2]",
        -1.56710503246274425174e-12,
        1.48629337747559491236e-12,
        3.99781285780299048032e-15,
        3.02449468686897625244e-13,
    ],
    [
        "2D aux",
        :y,
        0.00000000000000000000e+00,
        1.00000000000000011642e+06,
        5.00000000000000000000e+05,
        2.92775877460665535182e+05,
    ],
    [
        "2D aux",
        "Gᵁ[1]",
        -6.09426479895203986555e-07,
        6.09426479918741655731e-07,
        1.20165633492970096897e-18,
        2.72762104279987029506e-07,
    ],
    [
        "2D aux",
        "Gᵁ[2]",
        -1.19708535137691129300e-16,
        8.68461158348685235787e-17,
        -1.50869648123518501517e-33,
        2.09756864038773538444e-17,
    ],
    [
        "2D aux",
        "Δu[1]",
        -3.89449877638695009935e-03,
        3.89449877638627485824e-03,
        -2.02396849009311295664e-17,
        2.76903473119681107703e-03,
    ],
    [
        "2D aux",
        "Δu[2]",
        -2.13991366909841745553e-16,
        2.02991354131742782465e-16,
        -1.26781868574011981865e-18,
        4.34606820130057938426e-17,
    ],
]

sixty_minutes = [
    [
        "3D state",
        "u[1]",
        -9.58507705628042327994e-01,
        9.58507705628042550039e-01,
        -5.22959453519433752618e-17,
        4.45394100308371843067e-01,
    ],
    [
        "3D state",
        "u[2]",
        -4.09893498131451878178e-15,
        3.66595063838075513859e-15,
        3.52058149555652693147e-18,
        7.65972478919525466697e-16,
    ],
    [
        "3D state",
        :η,
        -8.52715753705294066123e-01,
        8.52819886810596838878e-01,
        0.00000000000000000000e+00,
        6.02986351734545844572e-01,
    ],
    [
        "3D state",
        :θ,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        :y,
        0.00000000000000000000e+00,
        1.00000000000000011642e+06,
        5.00000000000000000000e+05,
        2.92775877460665535182e+05,
    ],
    [
        "3D aux",
        :w,
        -4.09355343664679748733e-04,
        4.09526445665209497954e-04,
        8.01581023779363006131e-19,
        1.64782726593626366188e-04,
    ],
    [
        "3D aux",
        :pkin,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        :wz0,
        -2.18597874938533713813e-04,
        2.18512725277962983460e-04,
        1.16684439888103955116e-18,
        1.54581525183602542031e-04,
    ],
    [
        "3D aux",
        "uᵈ[1]",
        -8.79886113674857694988e-01,
        8.79886113674856806810e-01,
        -8.81072992342524199517e-17,
        4.41958323665245178535e-01,
    ],
    [
        "3D aux",
        "uᵈ[2]",
        -5.04476548888837049561e-28,
        4.09418809809705127009e-28,
        1.53142189509630563206e-30,
        3.27579623353337388378e-29,
    ],
    [
        "3D aux",
        "ΔGᵘ[1]",
        -1.12461707299293835875e-09,
        1.12461707291060059378e-09,
        -2.89803852573586336546e-21,
        5.02954103857782471789e-10,
    ],
    [
        "3D aux",
        "ΔGᵘ[2]",
        -2.29220407034248886795e-19,
        3.23795504633739099693e-19,
        4.19082355898662551731e-36,
        5.50021671320874203932e-20,
    ],
    [
        "2D state",
        :η,
        -8.52715753705294066123e-01,
        8.52819886810596838878e-01,
        -2.04281036531028799987e-17,
        6.03457250948630341547e-01,
    ],
    [
        "2D state",
        "U[1]",
        -3.15415180428826182890e+01,
        3.15415180428829948767e+01,
        1.77902803599749859788e-14,
        2.24265293235027023400e+01,
    ],
    [
        "2D state",
        "U[2]",
        -1.63957399252576030728e-12,
        1.46638025535227397199e-12,
        1.40823259822193190848e-15,
        3.06628264537953242560e-13,
    ],
    [
        "2D aux",
        :y,
        0.00000000000000000000e+00,
        1.00000000000000011642e+06,
        5.00000000000000000000e+05,
        2.92775877460665535182e+05,
    ],
    [
        "2D aux",
        "Gᵁ[1]",
        -4.49846829164240224277e-07,
        4.49846829197175353425e-07,
        1.15921837490966064206e-18,
        2.01338753352722549629e-07,
    ],
    [
        "2D aux",
        "Gᵁ[2]",
        -1.29518201853495637566e-16,
        9.16881628136995516365e-17,
        -2.08062063752041870574e-33,
        2.20180483211723137251e-17,
    ],
    [
        "2D aux",
        "Δu[1]",
        -7.71677156339491132631e-03,
        7.71677156339359640591e-03,
        -2.97775205101297201914e-17,
        5.48673197909435757247e-03,
    ],
    [
        "2D aux",
        "Δu[2]",
        -5.01794629524824969143e-16,
        5.51655281127317078031e-16,
        3.20748218455203547025e-18,
        9.36761976343254920594e-17,
    ],
]

ninety_minutes = [
    [
        "3D state",
        "u[1]",
        -9.58488051495743564878e-01,
        9.58488051495744008967e-01,
        -2.72848410531878478287e-17,
        4.45390687454859546257e-01,
    ],
    [
        "3D state",
        "u[2]",
        -4.05044403917298945083e-15,
        3.37351039462521829098e-15,
        2.85851376495618499592e-18,
        7.53889134369023261669e-16,
    ],
    [
        "3D state",
        :η,
        -8.52731405452108237597e-01,
        8.52809353848989259994e-01,
        -1.36424205265939223736e-16,
        6.02988963894262375298e-01,
    ],
    [
        "3D state",
        :θ,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        :y,
        0.00000000000000000000e+00,
        1.00000000000000011642e+06,
        5.00000000000000000000e+05,
        2.92775877460665535182e+05,
    ],
    [
        "3D aux",
        :w,
        -4.11959288170114497438e-04,
        4.12126623290113712657e-04,
        8.97615315409439082099e-19,
        1.65355949740610678371e-04,
    ],
    [
        "3D aux",
        :pkin,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
        0.00000000000000000000e+00,
    ],
    [
        "3D aux",
        :wz0,
        -2.28048874140054377109e-04,
        2.27949849600823879541e-04,
        1.39666056497844683559e-18,
        1.61271245461141397555e-04,
    ],
    [
        "3D aux",
        "uᵈ[1]",
        -8.79979607451059520073e-01,
        8.79979607451058964962e-01,
        -9.32232069317251423826e-17,
        4.42005283454923125763e-01,
    ],
    [
        "3D aux",
        "uᵈ[2]",
        -5.48652759581213710664e-28,
        3.92852730800063879095e-28,
        1.36055159257466689218e-30,
        3.58669418355622340063e-29,
    ],
    [
        "3D aux",
        "ΔGᵘ[1]",
        -8.86991425948811828542e-10,
        8.86991425888052000430e-10,
        -2.87311034609816924955e-21,
        3.96682558396691740283e-10,
    ],
    [
        "3D aux",
        "ΔGᵘ[2]",
        -2.66880587297174792697e-19,
        3.32595187063539502433e-19,
        3.45126646034192634633e-36,
        5.71491963745139874533e-20,
    ],
    [
        "2D state",
        :η,
        -8.52731405452108237597e-01,
        8.52809353848989259994e-01,
        -1.20259358027396949872e-16,
        6.03459865148300300675e-01,
    ],
    [
        "2D state",
        "U[1]",
        -3.15423820746578229546e+01,
        3.15423820746582954655e+01,
        2.75259190440912945341e-14,
        2.24266774102465760166e+01,
    ],
    [
        "2D state",
        "U[2]",
        -1.62017761566911967103e-12,
        1.34940415784996204528e-12,
        1.14340550598186518880e-15,
        3.01791152146446424566e-13,
    ],
    [
        "2D aux",
        :y,
        0.00000000000000000000e+00,
        1.00000000000000011642e+06,
        5.00000000000000000000e+05,
        2.92775877460665535182e+05,
    ],
    [
        "2D aux",
        "Gᵁ[1]",
        -3.54796570355220815061e-07,
        3.54796570379524731417e-07,
        1.14925108410943513278e-18,
        1.58796938272805492033e-07,
    ],
    [
        "2D aux",
        "Gᵁ[2]",
        -1.33038074825415802514e-16,
        1.06752234918869909375e-16,
        -1.45939267465887190460e-33,
        2.28775307028972280128e-17,
    ],
    [
        "2D aux",
        "Δu[1]",
        -1.14629123365995507638e-02,
        1.14629123365980502280e-02,
        -3.95057006856089328375e-17,
        8.15149713200660763768e-03,
    ],
    [
        "2D aux",
        "Δu[2]",
        -7.52467366304891760665e-16,
        5.94961001279223574512e-16,
        6.25849006198159897312e-19,
        1.42516297748358802032e-16,
    ],
]

refVals = (
    uncoupled = (uncoupled, parr),
    coupled = (coupled, parr),
    thirty_minutes = (thirty_minutes, parr),
    sixty_minutes = (sixty_minutes, parr),
    ninety_minutes = (ninety_minutes, parr),
)


module Korg
    export synthesize, read_linelist, read_model_atmosphere

    _data_dir = joinpath(@__DIR__, "../data")

    include("constants.jl")                #physical constants
    include("atomic_data.jl")              #symbols and atomic weights
    include("isotopic_abundances.jl")      #self-explanatory
    include("linelist.jl")                 #parse linelists, define Line type
    include("line_opacity.jl")             #opacity, line profile, voigt function
    include("read_statmech_quantities.jl") #approximate Us, Ks, chis
    include("statmech.jl")                 #statistical mechanics, molecular equilibrium
    include("atmosphere.jl")               #parse model atmospheres
    include("transfer.jl")                 #radiative transfer integrals

    #load data when the package is imported.
    const ionization_energies = setup_ionization_energies()
    const partition_funcs = setup_partition_funcs()
    const equilibrium_constants = setup_equilibrium_constants()
    const hline_stark_profiles = setup_hydrogen_stark_profiles()

    include("utils.jl")                                #functions to apply LSF, vac<->air wls, etc.
    include("continuum_absorption/ContinuumAbsorption.jl")  #Define continuum absorption functions.
    include("synthesize.jl")                                #solve radiative transfer equation

end # module


@testset "can_avx" begin

  @test LoopVectorization.ArrayInterface.can_avx(log)
  @test LoopVectorization.ArrayInterface.can_avx(log1p)
  @test LoopVectorization.ArrayInterface.can_avx(exp)
  @test LoopVectorization.ArrayInterface.can_avx(+)
  @test LoopVectorization.ArrayInterface.can_avx(-)
  @test LoopVectorization.ArrayInterface.can_avx(Base.FastMath.add_fast)
  @test LoopVectorization.ArrayInterface.can_avx(/)
  @test LoopVectorization.ArrayInterface.can_avx(sqrt)
  @test LoopVectorization.ArrayInterface.can_avx(tanh_fast)
  @test LoopVectorization.ArrayInterface.can_avx(sigmoid_fast)
  @test LoopVectorization.ArrayInterface.can_avx(LoopVectorization.relu)
  @test !LoopVectorization.ArrayInterface.can_avx(clenshaw)
  @test !LoopVectorization.ArrayInterface.can_avx(println)

end


module Sens
using LightXML, Printf
using DifferentialEquations, Statistics, GlobalSensitivity
using QuasiMonteCarlo
include("Constants.jl")
include("Utils.jl")
include("Batch.jl")
include("Plug.jl")
include("Cstr.jl")
include("Reactions.jl")
include("SurfaceReactions.jl")
struct  gsa_parameters
    monitor::Array{String,1}
    lower_pc::Float64
    upper_pc::Float64
    N::Int64
    gsa_srxn_ids::Array{Int64,1}
    gsa_srxn_constraint_ids::Array{Int64,1}
end
struct Mechanisms
    smd::MechanismDefinition
end

function gsa_xml(input_file::AbstractString, lib_dir::AbstractString)
    xmldoc = parse_file(input_file)
    xmlroot = root(xmldoc)

    # list of gasphase species
    gsa_species = get_collection_from_xml(xmlroot,"gsa_species")
    # lower bound for parameters (in percentages)
    lower_pc = get_value_from_xml(xmlroot,"gsa_lb")
    # upper bound for parameters (in percentages)
    upper_pc = get_value_from_xml(xmlroot,"gsa_ub")

    # Size of the sample
    sample_N = Int(get_value_from_xml(xmlroot,"gsa_N"))

    # get the reactions id which forms the parameter
    gsa_srxn_ids,gsa_srxn_constraint_ids = get_rxn_ids(xmlroot,"p_smech")

    gsa_p = gsa_parameters(gsa_species,lower_pc,upper_pc,sample_N,gsa_srxn_ids,gsa_srxn_constraint_ids)

    # get the reactor model
    model = get_text_from_xml(xmlroot,"gsa_model")

    model_root = root(parse_file(model))
    gsa_model = name(model_root)

    if lowercase(strip(gsa_model)) == "batch"
        params, prob, t_span = Batch.batch(model,lib_dir,true)
        gsa_problem(params, prob, t_span, gsa_p)
    elseif lowercase(strip(gsa_model)) == "plug"
        params, prob, t_span = Plug.plug(model,lib_dir, true)
        gsa_problem(params, prob, t_span, gsa_p)
    elseif lowercase(strip(gsa_model)) == "cstr"
        params, prob, t_span = Cstr.cstr(model,lib_dir,true)
        gsa_problem(params, prob, t_span, gsa_p)
    else
        println("Models yet to be implemented\n")
    end
end

"""
A common function for all reactor models to define the sensitivity problem
#   Usage
    gsa_problem(params, prob, gsa_p)
-   params : a tuple of parameters returned by the respective reactor model invoked
-   prob : the problem definition for the reactor model
-   gsa_p : struct of the type gsa_parameters
"""
function gsa_problem(params, prob, t_span, gsa_p::gsa_parameters)
    gsa_smech_params = Array{Float64,1}()
    state, thermo_obj, md, cp, o_stream = params
    n_surface_species = length(md.sm.species) # number of surface species
    n_gaspahse_species = length(thermo_obj.thermo_all) # number of gasphase species
    n_species = n_gaspahse_species + n_surface_species # total number of species
    # get the parameters from the surface reaction mechaism
    gsa_smech_parameter_map!(gsa_smech_params, gsa_p.gsa_srxn_ids, gsa_p.gsa_srxn_constraint_ids, md)
    println("Parameter size: ", length(gsa_smech_params))
    # create the lower limit for the parameters
    lb = gsa_smech_params .* (1-0.01*gsa_p.lower_pc)
    # create the upper limit for the parameters
    ub = gsa_smech_params .* (1+0.01*gsa_p.upper_pc)
    parameter_ratio = Array{Float64,1}()  # Array for storing the original parameter ratio (forward to reverse)
    if count(x->x>0, gsa_p.gsa_srxn_constraint_ids) > 0
        # get the initial parameter ratios if the reverse reaction constraints are specified
        initial_parameter_ratio!(parameter_ratio, gsa_p.gsa_srxn_ids, gsa_p.gsa_srxn_constraint_ids,md)
    end

    perform_gsa(params,prob, t_span, gsa_p, lb, ub, parameter_ratio)
end

function perform_gsa(params, prob, t_span, gsa_p::gsa_parameters,lb::Array{Float64,1}, ub::Array{Float64,1}, parameter_ratio::Array{Float64,1})
    state, thermo_obj, md, cp, o_stream = params
    time_points = collect(range(t_span[1],stop=t_span[2],length=50))
    #Get the name of all gasphase species
    species_list = Array{String,1}()
    for sp_thermo in thermo_obj.thermo_all
        push!(species_list,sp_thermo.name)
    end
    append!(species_list,md.sm.species)
    #get the id of species to be monitored from the gsa_species list
    monitor_species_ids = map(x->get_index(x,species_list),gsa_p.monitor)
    gsa_out = zeros(length(monitor_species_ids))
    func_eval_count = 0
    sens = function (gsa_params::Array{Float64})
        func_eval_count += 1
        println("Sample @ ", func_eval_count)
        SurfaceReactions.update_params!(md,gsa_params,gsa_p.gsa_srxn_ids,gsa_p.gsa_srxn_constraint_ids,parameter_ratio)
        params_updated = (state, thermo_obj, md, cp, o_stream)
        s_prob = remake(prob,p=params_updated)
        sol = solve(s_prob,alg_hints=[:stiff] , reltol=1e-6, abstol = 1e-8, saveat=time_points)
        for i in eachindex(monitor_species_ids)
            gsa_out[i] = mean(sol[monitor_species_ids[i],:])
        end
        gsa_out
    end
    sampler = SobolSample()
    A,B = QuasiMonteCarlo.generate_design_matrices(gsa_p.N,lb,ub,sampler)
    sobol_indices = gsa(sens,Sobol(order=[0,1,2]),A,B)

    # output file
    sobol_total = open("sobol_total.dat","w")
    sobol_first = open("sobol_first.dat","w")
    sobol_second = open("sobol_second.dat","w")
    header_data = [ "p"*string(k) for k in gsa_p.gsa_srxn_ids]
    create_header(sobol_total,["species"],header_data)
    create_header(sobol_first,["species"],header_data)
    create_header(sobol_second,["species"],header_data)

    for i in eachindex(monitor_species_ids)
        write_to_file(sobol_total,gsa_p.monitor[i],sobol_indices.ST[i,:])
        write_to_file(sobol_first,gsa_p.monitor[i],sobol_indices.ST[i,:])
        write_to_file(sobol_second,gsa_p.monitor[i],sobol_indices.ST[i,:])
    end

    close(sobol_total)
    close(sobol_first)
    close(sobol_second)

end

"""
A function to extract the parameters for sensitivity analysis from the xml input
    The function returnds two integer arrays. forward_rxn_ids contains the ids of the
    reactions whoes parameters will be considered as sensitivity parameters.
    reverse_rxn_ids contains the ids of the reactions aginst with the forward reaction
    parameters are correlated.
"""
function get_rxn_ids(xmlroot::XMLElement, xmltag::AbstractString)
    N = 10000
    p_mech = get_elements_by_tagname(xmlroot,xmltag)
    p_mech_content = ""
    if is_elementnode(p_mech[1])
        p_mech_content = content(p_mech[1])
    end
    p_mech_content_split = split(p_mech_content,",")
    forward_rxn_ids = Array{Int64,1}()
    reverse_rxn_ids = Array{Int64,1}()
    if strip(p_mech_content_split[1]) == ":"
        return [i for i in 1:N], [0 for i in 1:N]
    end
    for items in p_mech_content_split
        if occursin("=",items)
            rxn_sets = split(items,"=")
            forward_sets = rxn_sets[1]
            reverse_sets = rxn_sets[2]
            forward_sets = map(x->parse(Int64,x),split(forward_sets,":"))
            reverse_sets = map(x->parse(Int64,x),split(reverse_sets,":"))
            if length(forward_sets) > 1
                for ids in forward_sets[1]:forward_sets[2]
                    push!(forward_rxn_ids,ids)
                end
                for ids in reverse_sets[1]:reverse_sets[2]
                    push!(reverse_rxn_ids,ids)
                end
            else
                push!(forward_rxn_ids,forward_sets[1])
                push!(reverse_rxn_ids,reverse_sets[1])
            end
        else
            forward_sets = map(x->parse(Int64,x),split(items,":"))
            if length(forward_sets) > 1
                for ids in forward_sets[1]:forward_sets[2]
                    push!(forward_rxn_ids,ids)
                    push!(reverse_rxn_ids,0)
                end
            else
                push!(forward_rxn_ids,forward_sets[1])
                push!(reverse_rxn_ids,0)
            end
        end
    end
    return (forward_rxn_ids, reverse_rxn_ids)
end

"""
This function returns the sticking coefficient or the pre-exponent factor
    corresponding to the reaction ids present in gsa_rxn_ids
#   Usage
    gsa_smech_parameter_map!(gsa_smech_params, gsa_srxn_ids, gsa_srxn_constraint_ids, md )
-   gsa_smech_params : vector containing the parameters extracted from the inoput mechanism (output)
-   gsa_srxn_ids : reaction ids whoes parameters needs to be extracted from the mechanism (input)
-   gsa_srxn_constraint_ids : id of the reactions which needs to be considered for reverse reaction
-   md: MechanismDefinition
"""
function gsa_smech_parameter_map!(gsa_smech_params::Array{Float64,1}, gsa_srxn_ids::Array{Int64,1}, gsa_srxn_constraint_ids::Array{Int64,1}, md )

    function populate(rxn)
        if Int(rxn.type) == Int(stick)
            push!(gsa_smech_params,rxn.params.s)
        else
            push!(gsa_smech_params,rxn.params.k0)
        end
    end
    # if the length of gsa_srxn_ids is very high then it means all reactions parameters need to be considered
    if length(gsa_srxn_ids) >= length(md.sm.reactions)
        for rxn in md.sm.reactions
            populate(rxn)
        end
        gsa_srxn_ids_copy = copy(gsa_srxn_ids)
        empty!(gsa_srxn_ids)
        gsa_srxn_constraint_ids_copy = copy(gsa_srxn_constraint_ids)
        empty!(gsa_srxn_constraint_ids)
        for i in 1:length(md.sm.reactions)
            push!(gsa_srxn_ids,gsa_srxn_ids_copy[i])
            push!(gsa_srxn_constraint_ids,gsa_srxn_constraint_ids_copy[i])
        end
        empty!(gsa_srxn_ids_copy)
        empty!(gsa_srxn_constraint_ids_copy)
    else
        for rxn in md.sm.reactions
            if rxn.id in gsa_srxn_ids
                populate(rxn)
            end
        end
    end

    return gsa_smech_params
end

"""
A function to calculate the intial ratio of pre-exponential factors
"""
function initial_parameter_ratio!(p_ratio::Array{Float64,1},rxn_ids::Array{Int64,1},constraints_ids::Array{Int64,1}, md)
    for i in eachindex(rxn_ids)
        if constraints_ids[i] != 0
            if Int(md.sm.reactions[rxn_ids[i]].type) == Int(stick)
                ratio = md.sm.reactions[rxn_ids[i]].params.k0/md.sm.reactions[constraints_ids[i]].params.k0
                push!(p_ratio,ratio)
            else
                ratio = md.sm.reactions[rxn_ids[i]].params.k0/md.sm.reactions[constraints_ids[i]].params.k0
                push!(p_ratio,ratio)
            end
        else
            push!(p_ratio,0.0)
        end
    end
end

#end of module
end


"""
    Schedule(ts, values, interpolations; unit = :epoch; kwargs...)

Describes how the values of a hyperparameter change over the
training.

## Examples

```julia
# one-cycle schedule over 10 epochs from 0.01 over 0.1 to 0.001
Schedule([0, 2, 10], [0.01, 0.1, 0.001], [sineio(), sineio()])
```

`Schedule` is a wrapper around `Animations.Animation`, see the
[documentation](https://jkrumbiegel.github.io/Animations.jl/dev)
for more detailed information.
"""
struct Schedule{T}
    animation::Animation{T}
    unit::Symbol
end

Schedule(args...; unit = :epoch, kwargs...) =
    Schedule(Animation(args...; kwargs...), unit)

struct Scheduler <: Callback
    schedules::Dict{Type{<:HyperParameter}, Schedule}
end

Base.show(io::IO, scheduler::Scheduler) = print(
    io, "Scheduler(", join(keys(scheduler.schedules), ", "), ")")

function stateaccess(scheduler::Scheduler)
    # TODO: implement proper merging of permissions
    if length(keys(scheduler.schedules)) == 0
        hpstateaccess = (;)
    else
        hpstateaccess = merge(stateaccess.(keys(scheduler.schedules))...)
    end
    return (
        data = Read(), cbstate = (history = Read(), hyperparams = Write()),
        hpstateaccess...
    )
end

function on(::Init, phase, scheduler::Scheduler, learner)
    learner.cbstate.hyperparams = MVHistory()
    # convert schedules to use step as a unit
    epochlength = length(learner.data[1])
    for (H, schedule) in scheduler.schedules
        if schedule.unit == :epoch
            scheduler.schedules[H] = Schedule(schedule.animation * epochlength, :step)
        end
    end
end

function on(::BatchBegin, ::AbstractTrainingPhase, scheduler::Scheduler, learner)
    step = learner.cbstate.history.steps
    for (H, schedule) in scheduler.schedules
        value = Animations.at(schedule.animation, step)
        sethyperparameter!(learner, H, Animations.at(schedule.animation, step))
        push!(learner.cbstate.hyperparams, Symbol(H), step, value)
    end
end

function onecycle(
        epochs, max_lr,
        start_lr = max_lr / 10,
        end_lr = max_lr / 30,
        start_pctg = 0.1)
    return Schedule(
        [0, epochs * start_pctg, epochs],
        [start_lr, max_lr, end_lr],
        [Animations.sineio(), Animations.sineio()]
    )

end


# ------------------------------------------------------------------
# Licensed under the ISC License. See LICENCE in the project root.
# ------------------------------------------------------------------

"""
    State

A geological state to be evolved by processes.
"""
abstract type State end

"""
    LandState(land)

A geological state consisting of a landscape map.
"""
struct LandState <: State
  land::Matrix{Float64}
end

@recipe function f(state::LandState)
  aspect_ratio --> :equal
  colorbar --> :false
  seriestype --> :surface
  state.land
end


using Turing.VarReplay

mutable struct Trace
  task  ::  Task
  vi    ::  VarInfo
  spl   ::  Union{Nothing, Sampler}
  Trace() = (res = new(); res.vi = VarInfo(); res.spl = nothing; res)
end

# NOTE: this function is called by `forkr`
function Trace(f)
  res = Trace();
  # Task(()->f());
  res.task = Task( () -> begin res=f(); produce(Val{:done}); res; end )
  if isa(res.task.storage, Nothing)
    res.task.storage = IdDict()
  end
  res.task.storage[:turing_trace] = res # create a backward reference in task_local_storage
  res
end

function Trace(f, spl::Sampler, vi :: VarInfo)
  res = Trace();
  res.spl = spl
  # Task(()->f());
  res.vi = deepcopy(vi)
  res.vi.num_produce = 0
  res.task = Task( () -> begin vi_new=f(vi, spl); produce(Val{:done}); vi_new; end )
  if isa(res.task.storage, Nothing)
    res.task.storage = IdDict()
  end
  res.task.storage[:turing_trace] = res # create a backward reference in task_local_storage
  res
end

# step to the next observe statement, return log likelihood
Turing.consume(t::Trace) = (t.vi.num_produce += 1; consume(t.task))

# Task copying version of fork for Trace.
function fork(trace :: Trace, is_ref :: Bool = false)
  newtrace = typeof(trace)()
  newtrace.task = Base.copy(trace.task)
  newtrace.spl = trace.spl

  newtrace.vi = deepcopy(trace.vi)
  if is_ref
    set_retained_vns_del_by_spl!(newtrace.vi, newtrace.spl)
  end

  newtrace.task.storage[:turing_trace] = newtrace
  newtrace
end

# PG requires keeping all randomness for the reference particle
# Create new task and copy randomness
function forkr(trace :: Trace)
  newtrace = Trace(trace.task.code)
  newtrace.spl = trace.spl

  newtrace.vi = deepcopy(trace.vi)
  newtrace.vi.num_produce = 0

  newtrace
end

current_trace() = current_task().storage[:turing_trace]


"""
    AbstractPointProcess{T}

Abstract type encoding the notion of [point process](https://en.wikipedia.org/wiki/Point_process)
The type `T` corresponds to the elements' type of the point process (vectors, graph edges, etc.)

**See also**
- [`PRS.AbstractSpatialPointProcess`](@ref)
- [`PRS.AbstractGraphPointProcess`](@ref)
"""
abstract type AbstractPointProcess{T} end
Base.eltype(pp::AbstractPointProcess{T}) where {T} = T

@doc raw"""
    AbstractGraphPointProcess{T<:Vector{Float64}} <: AbstractPointProcess{T}

Abstract type encoding point processes defined on graphs
"""
abstract type AbstractGraphPointProcess{T} <: AbstractPointProcess{T} end

@doc raw"""
    AbstractSpatialPointProcess{T<:Vector{Float64}} <: AbstractPointProcess{T}

Abstract type encoding point processes defined on ``\mathbb{R}^d``.

Concrete instances must have a `window` field of type [`PRS.AbstractSpatialWindow`](@ref)
"""
abstract type AbstractSpatialPointProcess{T<:Vector{Float64}} <: AbstractPointProcess{T} end

"""
    window(pp::AbstractSpatialPointProcess)::AbstractSpatialWindow = pp.window
"""
function window(pp::AbstractSpatialPointProcess)::AbstractSpatialWindow
    return pp.window
end

"""
    dimension(pp::AbstractSpatialPointProcess) = dimension(window(pp))
"""
dimension(pp::AbstractSpatialPointProcess) = dimension(window(pp))

"""
Abstract type representing a window

**See also**

- [`PRS.AbstractDiscreteWindow`](@ref)
- [`PRS.AbstractSpatialWindow`](@ref)
"""
abstract type AbstractWindow end

@doc raw"""
    AbstractSpatialWindow{T<:Float64} <: AbstractWindow{T}

Abstract type representing a spatial window ``\subseteq \mathbb{R}^d``

**See also**

- [`PRS.AbstractRectangleWindow`](@ref)
    - [`PRS.RectangleWindow`](@ref)
    - [`PRS.SquareWindow`](@ref)
- [`PRS.BallWindow`](@ref)
"""
abstract type AbstractSpatialWindow{T<:Float64} <: AbstractWindow end

"""
Abstract type representing a window on a discrete state space

**See also**

- [`PRS.GraphNode`](@ref)
"""
abstract type AbstractDiscreteWindow{T} <: AbstractWindow end

function generate_sample(pp::AbstractPointProcess, args...; kwargs...)
    return generate_sample(Random.default_rng(), pp, args...; kwargs...)
end

function generate_sample_prs(pp::AbstractPointProcess, args...; kwargs...)
    return generate_sample_prs(Random.default_rng(), pp, args...; kwargs...)
end


function test_Robot_Conv(model,predict,w; batchsize=32,saveImages=true)
    @time begin
        printlnFlush("Loading weights from $(w)")
        w = loadNetwork(w)
        colormap = map((x)->convert(RGB,x),linspace(Colors.HSV{Float32}(0.0,1.0,1.0),Colors.HSV{Float32}(240.0,1.0,1.0),5))
        colors = Dict()
        for i=1:length(colormap)
            colors[i] = Float32[colormap[i].r, colormap[i].g, colormap[i].b]
        end
        totalloss = totalcorrect = totalcount = imcounter = 0
        x,y = loadData_Robot(model,"test")
        batchCount = Int64(ceil(size(y,4)/batchsize))
        for batch=1:batchCount
            printUpdate(@sprintf("Batch: %5d/%-5d",batch,batchCount))
            #Calculate current indices
            startIndex = 1+(batch-1)*batchsize
            endIndex = min(batch*batchsize,size(y,4))
            #Minibatch
            batchx = KnetArray(x[:,:,:,startIndex:endIndex])
            batchy = y[:,:,:,startIndex:endIndex]
            #Accuracy
            ypred = Array(predict(w,batchx))
            totalloss += pixelwiseSoftloss(ypred,batchy)
            testAcc = pixelwiseAccuracy(ypred,batchy)
            totalcorrect += testAcc[1]
            totalcount += testAcc[2]
            #Image conversion
            if saveImages
                for im=1:size(ypred,4)
                    savePrediction(ypred[:,:,:,im],colors,outputPath(@sprintf("Robot/Conv/%s/%05d.png",model,imcounter)))
                    imcounter += 1
                end
            end
        end
        totalloss /= batchCount
        printlnFlush(@sprintf("Total Loss: %0.10f\tTotal Accuracy: %0.4f%%",totalloss,totalcorrect/totalcount*100))
    end
end

function test_Robot_LSTM(model,predict,w; saveImages=true)
    @time begin
        printlnFlush("Loading weights from $(w)")
        @time w = loadNetwork(w)
        colormap = map((x)->convert(RGB,x),linspace(Colors.HSV{Float32}(0.0,1.0,1.0),Colors.HSV{Float32}(240.0,1.0,1.0),5))
        colors = Dict()
        for i=1:length(colormap)
            colors[i] = Float32[colormap[i].r, colormap[i].g, colormap[i].b]
        end
        totalloss = totalcorrect = totalcount = imcounter = 0
        x,y = loadData_Robot(model,"test")
        batchCount = size(y,4)
        h = KnetArray(zeros(Float32,28,28,512,1))
        c = KnetArray(zeros(Float32,28,28,512,1))
        for t=1:batchCount
            printUpdate(@sprintf("Time: %5d/%-5d",t,batchCount))
            #Accuracy
            ypred = Array(predict(w,h,c,KnetArray(x[:,:,:,t:t])))
            totalloss += pixelwiseSoftloss(ypred,y[:,:,:,t:t])
            testAcc = pixelwiseAccuracy(ypred,y[:,:,:,t:t])
            totalcorrect += testAcc[1]
            totalcount += testAcc[2]
            #Image conversion
            if saveImages
                savePrediction(ypred[:,:,:,1],colors,outputPath(@sprintf("Robot/LSTM/%s/%05d.png",model,imcounter)))
                imcounter += 1
            end
        end
        totalloss /= batchCount
        printlnFlush(@sprintf("Total Loss: %0.10f\tTotal Accuracy: %0.4f%%",totalloss,totalcorrect/totalcount*100))
    end
end

function run_Robot_Conv_RGBD_test()
    test_Robot_Conv("RGBD",Robot_Conv_RGBD,"Robot/Conv_RGBD.jld"; batchsize=32,saveImages=true)
end

function run_Robot_LSTM_RGBD_test()
    test_Robot_LSTM("RGBD",Robot_LSTM_RGBD,"Robot/LSTM_RGBD.jld"; saveImages=true)
end

export run_Robot_Conv_RGBD_test,run_Robot_LSTM_RGBD_test


# not exported
abstract type ImageEncoder end
struct BigBlocks   <: ImageEncoder end
struct SmallBlocks <: ImageEncoder end

const alpha_chars = ('⋅', '░', '▒', '▓', '█')
function _charof(alpha)
    idx = round(Int, alpha * (length(alpha_chars)-1))
    alpha_chars[clamp(idx + 1, 1, length(alpha_chars))]
end

"""
    encodeimg(enc::ImageEncoder, colordepth::TermColorDepth, img, [maxheight], [maxwidth])

Transforms the pixel of the given image `img`, which has to be an
array of `Colorant`, into a string of unicode characters using
ansi terminal colors.

- The encoder `enc` specifies which kind of unicode represenation
  should be used.

- The `colordepth` can either be `TermColor256()` or `TermColor24bit()`
  and specifies which terminal color codes should be used.

- `maxheight` and `maxwidth` specify the maximum numbers of
  string characters that should be used for the resulting image.
  Larger images are downscaled automatically using `restrict`.

The function returns a tuple with three elements:

1. A vector of strings containing the encoded image.
   Each element represent one line. The lines do not contain
   newline characters.

2. Number of lines in the vector.

3. Number of visible characters per line (the remaining are colorcodes).
"""
function encodeimg(
        ::SmallBlocks,
        colordepth::TermColorDepth,
        img::AbstractMatrix{<:Colorant},
        maxheight::Int = 50,
        maxwidth::Int = 80)
    maxheight = max(maxheight, 5)
    maxwidth  = max(maxwidth,  5)
    h, w = map(length, axes(img))
    while ceil(h/2) > maxheight || w > maxwidth
        img = restrict(img)
        h, w = map(length, axes(img))
    end
    yinds, xinds = axes(img)
    io = IOBuffer()
    for y in first(yinds):2:last(yinds)
        print(io, Crayon(reset = true))
        for x in xinds
            fgcol = _colorant2ansi(img[y,x], colordepth)
            bgcol = if y+1 <= last(yinds)
                _colorant2ansi(img[y+1,x], colordepth)
            else
                # if reached it means that the last character row
                # has only the upper pixel defined.
                Symbol("nothing")
            end
            print(io, Crayon(foreground=fgcol, background=bgcol), "▀")
        end
        println(io, Crayon(reset = true))
    end
    replace.(readlines(seek(io,0)), Ref("\n" => ""))::Vector{String}, length(1:2:h), w
end

function encodeimg(
        ::BigBlocks,
        colordepth::TermColorDepth,
        img::AbstractMatrix{<:Colorant},
        maxheight::Int = 50,
        maxwidth::Int = 80)
    maxheight = max(maxheight, 5)
    maxwidth  = max(maxwidth,  5)
    h, w = map(length, axes(img))
    while h > maxheight || 2w > maxwidth
        img = restrict(img)
        h, w = map(length, axes(img))
    end
    yinds, xinds = axes(img)
    io = IOBuffer()
    for y in yinds
        print(io, Crayon(reset = true))
        for x in xinds
            color = img[y,x]
            fgcol = _colorant2ansi(color, colordepth)
            chr = _charof(alpha(color))
            print(io, Crayon(foreground = fgcol), chr, chr)
        end
        println(io, Crayon(reset = true))
    end
    replace.(readlines(seek(io,0)), Ref("\n" => ""))::Vector{String}, h, 2w
end

# colorant vector
function encodeimg(
        enc::SmallBlocks,
        colordepth::TermColorDepth,
        img::AbstractVector{<:Colorant},
        maxwidth::Int = 80)
    maxwidth  = max(maxwidth, 5)
    w = length(axes(img, 1))
    if w > maxwidth
        img = imresize(img, maxwidth)
        w = length(axes(img, 1))
    end
    io = IOBuffer()
    print(io, Crayon(reset = true))
    for i in axes(img, 1)
        color = img[i]
        fgcol = _colorant2ansi(color, colordepth)
        chr = _charof(alpha(color))
        print(io, Crayon(foreground = fgcol), chr)
    end
    println(io, Crayon(reset = true))
    replace.(readlines(seek(io,0)), Ref("\n" => ""))::Vector{String}, 1, w
end

function encodeimg(
        enc::BigBlocks,
        colordepth::TermColorDepth,
        img::AbstractVector{<:Colorant},
        maxwidth::Int = 80)
    maxwidth  = max(maxwidth, 5)
    inds = axes(img, 1)
    w = length(inds)
    n = 3w > maxwidth ? floor(Int,maxwidth/6) : w
    io = IOBuffer()
    print(io, Crayon(reset = true))
    for i in (0:n-1) .+ first(inds)
        color = img[i]
        fgcol = _colorant2ansi(color, colordepth)
        chr = _charof(alpha(color))
        print(io, Crayon(foreground = fgcol), chr, chr, " ")
    end
    if n < w
        print(io, Crayon(reset = true), " … ")
        for i in last(inds)-n+1:last(inds)
            color = img[i]
            fgcol = _colorant2ansi(color, colordepth)
            chr = _charof(alpha(color))
            print(io, Crayon(foreground = fgcol), chr, chr, " ")
        end
    end
    println(io, Crayon(reset = true))
    replace.(readlines(seek(io,0)), Ref("\n" => ""))::Vector{String}, 1, n < w ? 3*(length(1:n) + 1 + length(w-n+1:w)) : 3w
end


import Dates
using AutoHashEquals

const WEEKDAYS = Dict(map(reverse,enumerate(map(Symbol ∘ uppercase, Dates.ENGLISH.days_of_week_abbr))))
const NTH_PERIODS = ["1st", "2nd", "3rd", "4th", "5th"]
const NTH_LAST_PERIODS = ["Last", "2nd Last", "3rd Last", "4th Last", "5th Last"]
const PERIODS = merge(Dict(map(reverse,enumerate(NTH_PERIODS))), Dict(map(reverse,enumerate(NTH_LAST_PERIODS))))
const MONTHS = Dict(zip(map(Symbol ∘ uppercase, Dates.ENGLISH.months_abbr),range(1,stop=12)))

@auto_hash_equals struct FDOM <: RDate
    calendars::Union{Vector{String}, Nothing}
    FDOM() = new(nothing)
    FDOM(calendars) = new(calendars)
end
function apply(rdate::FDOM, d::Dates.Date, cal_mgr::CalendarManager)
    fdom = Dates.firstdayofmonth(d)
    if rdate.calendars !== nothing
        fdom = apply(HolidayRoundingNBD(), fdom, calendar(cal_mgr, rdate.calendars))
    end

    return fdom
end
multiply_roll(x::FDOM, ::Integer) = x
multiply_no_roll(x::FDOM, ::Integer) = x
Base.:-(x::FDOM) = x

Base.show(io::IO, rdate::FDOM) = print(io, rdate.calendars === nothing ? "FDOM" : "FDOM@$(join(rdate.calendars, "|"))")
register_grammar!(E"FDOM" > FDOM)
register_grammar!(E"FDOM@" + p"[a-zA-Z\\\\\\s\\|]+" > calendar_name -> FDOM(map(String, split(calendar_name, "|"))))

@auto_hash_equals struct LDOM <: RDate
    calendars::Union{Vector{String}, Nothing}
    LDOM() = new(nothing)
    LDOM(calendars) = new(calendars)
end
function apply(rdate::LDOM, d::Dates.Date, cal_mgr::CalendarManager)
    ldom = Dates.lastdayofmonth(d)
    if rdate.calendars !== nothing
        ldom = apply(HolidayRoundingPBD(), ldom, calendar(cal_mgr, rdate.calendars))
    end

    return ldom
end
multiply_roll(x::LDOM, ::Integer) = x
multiply_no_roll(x::LDOM, ::Integer) = x
Base.:-(x::LDOM) = x

Base.show(io::IO, rdate::LDOM) = print(io, rdate.calendars === nothing ? "LDOM" : "LDOM@$(join(rdate.calendars, "|"))")
register_grammar!(E"LDOM" > LDOM)
register_grammar!(E"LDOM@" + p"[a-zA-Z\\\\\\s\\|]+" > calendar_name -> LDOM(map(String, split(calendar_name, "|"))))

struct Easter <: RDate
    yearδ::Int64
end

function apply(rdate::Easter, date::Dates.Date, cal_mgr::CalendarManager)
    y = Dates.year(date) + rdate.yearδ
    a = rem(y, 19)
    b = div(y, 100)
    c = rem(y, 100)
    d = div(b, 4)
    e = rem(b, 4)
    f = div(b + 8, 25)
    g = div(b - f + 1, 3)
    h = rem(19*a + b - d - g + 15, 30)
    i = div(c, 4)
    k = rem(c, 4)
    l = rem(32 + 2*e + 2*i - h - k, 7)
    m = div(a + 11*h + 22*l, 451)
    n = div(h + l - 7*m + 114, 31)
    p = rem(h + l - 7*m + 114, 31)
    return Dates.Date(y, n, p + 1)
end
multiply_roll(x::Easter, count::Integer) = Easter(x.yearδ*count)
multiply_no_roll(x::Easter, count::Integer) = Easter(x.yearδ*count)

Base.:-(rdate::Easter) = Easter(-rdate.yearδ)
Base.show(io::IO, rdate::Easter) = print(io, "$(rdate.yearδ)E")
register_grammar!(PInt64() + E"E" > Easter)

struct Day <: RDate
    days::Int64
end

apply(x::Day, y::Dates.Date, cal_mgr::CalendarManager) = y + Dates.Day(x.days)
multiply_roll(x::Day, count::Integer) = Day(x.days*count)
multiply_no_roll(x::Day, count::Integer) = Day(x.days*count)
Base.:-(x::Day) = Day(-x.days)
Base.:+(x::Day, y::Day) = Day(x.days + y.days)

Base.show(io::IO, rdate::Day) = print(io, "$(rdate.days)d")
register_grammar!(PInt64() + E"d" > Day)

struct Week <: RDate
    weeks::Int64
end

apply(x::Week, y::Dates.Date, cal_mgr::CalendarManager) = y + Dates.Week(x.weeks)
multiply_roll(x::Week, count::Integer) = Week(x.weeks*count)
multiply_no_roll(x::Week, count::Integer) = Week(x.weeks*count)
Base.:-(x::Week) = Week(-x.weeks)
Base.:+(x::Week, y::Week) = Week(x.weeks + y.weeks)
Base.:+(x::Day, y::Week) = Day(x.days + 7*y.weeks)
Base.:+(x::Week, y::Day) = Day(7*x.weeks + y.days)

Base.show(io::IO, rdate::Week) = print(io, "$(rdate.weeks)w")
register_grammar!(PInt64() + E"w" > Week)

struct Month <: RDate
    months::Int64
    idc::InvalidDayConvention
    mic::MonthIncrementConvention

    Month(months::Int64) = new(months, InvalidDayLDOM(), MonthIncrementPDOM())
    Month(months::Int64, idc::InvalidDayConvention, mic::MonthIncrementConvention) = new(months, idc, mic)
end

function apply(rdate::Month, date::Dates.Date, cal_mgr::CalendarManager)
    y, m = Dates.yearmonth(date)
    ny = Dates.yearwrap(y, m, rdate.months)
    nm = Dates.monthwrap(m, rdate.months)
    ay, am, ad = adjust(rdate.mic, date, nm, ny, cal_mgr)
    ld = Dates.daysinmonth(ay, am)
    return ad <= ld ? Dates.Date(ay, am, ad) : adjust(rdate.idc, ad, am, ay)
end
multiply_no_roll(x::Month, count::Integer) = Month(x.months*count, x.idc, x.mic)
Base.:-(x::Month) = Month(-x.months)

Base.show(io::IO, rdate::Month) = (print(io, "$(rdate.months)m["), show(io, rdate.idc), print(io, ";"), show(io, rdate.mic), print(io,"]"))
register_grammar!(PInt64() + E"m" > Month)
register_grammar!(PInt64() + E"m[" + Alt(map(Pattern, collect(keys(INVALID_DAY_MAPPINGS)))...) + E";" + Alt(map(Pattern, collect(keys(MONTH_INCREMENT_MAPPINGS)))...) + E"]" > (d,idc,mic) -> Month(d, INVALID_DAY_MAPPINGS[idc], MONTH_INCREMENT_MAPPINGS[mic]))
# We also have the more complex PDOMEOM month increment which we handle separately.
register_grammar!(PInt64() + E"m[" + Alt(map(Pattern, collect(keys(INVALID_DAY_MAPPINGS)))...) + E";PDOMEOM@" + p"[a-zA-Z\\\\\\s\\|]+" + E"]" > (d,idc,calendar_name) -> Month(d, INVALID_DAY_MAPPINGS[idc], MonthIncrementPDOMEOM(map(String, split(calendar_name, "|")))))

struct Year <: RDate
    years::Int64
    idc::InvalidDayConvention
    mic::MonthIncrementConvention

    Year(years::Int64) = new(years, InvalidDayLDOM(), MonthIncrementPDOM())
    Year(years::Int64, idc::InvalidDayConvention, mic::MonthIncrementConvention) = new(years, idc, mic)
end

function apply(rdate::Year, date::Dates.Date, cal_mgr::CalendarManager)
    oy, m = Dates.yearmonth(date)
    ny = oy + rdate.years
    (ay, am, ad) = adjust(rdate.mic, date, m, ny, cal_mgr)
    ld = Dates.daysinmonth(ay, am)
    return ad <= ld ? Dates.Date(ay, am, ad) : adjust(rdate.idc, ad, am, ay)
end
multiply_no_roll(x::Year, count::Integer) = Year(x.years*count, x.idc, x.mic)
Base.:-(x::Year) = Year(-x.years)

Base.show(io::IO, rdate::Year) = (print(io, "$(rdate.years)y["), show(io, rdate.idc), print(io, ";"), show(io, rdate.mic), print(io,"]"))
register_grammar!(PInt64() + E"y" > Year)
register_grammar!(PInt64() + E"y[" + Alt(map(Pattern, collect(keys(INVALID_DAY_MAPPINGS)))...) + E";" + Alt(map(Pattern, collect(keys(MONTH_INCREMENT_MAPPINGS)))...) + E"]" > (d,idc,mic) -> Year(d, INVALID_DAY_MAPPINGS[idc], MONTH_INCREMENT_MAPPINGS[mic]))
# We also have the more complex PDOMEOM month increment which we handle separately.
register_grammar!(PInt64() + E"y[" + Alt(map(Pattern, collect(keys(INVALID_DAY_MAPPINGS)))...) + E";PDOMEOM@" + p"[a-zA-Z\\\\\\s\\|]+" + E"]" > (d,idc,calendar_name) -> Year(d, INVALID_DAY_MAPPINGS[idc], MonthIncrementPDOMEOM(map(String, split(calendar_name, "|")))))

struct DayMonth <: RDate
    day::Int64
    month::Int64

    DayMonth(day::Int64, month::Int64) = new(day, month)
end

apply(rdate::DayMonth, date::Dates.Date, cal_mgr::CalendarManager) = Dates.Date(Dates.year(date), rdate.month, rdate.day)
multiply_roll(x::DayMonth, ::Integer) = x
multiply_no_roll(x::DayMonth, ::Integer) = x
Base.:-(x::DayMonth) = x

Base.show(io::IO, rdate::DayMonth) = print(io, "$(rdate.day)$(uppercase(Dates.ENGLISH.months_abbr[rdate.month]))")
register_grammar!(PPosInt64() + month_short > (d,m) -> DayMonth(d,MONTHS[Symbol(m)]))

struct Date <: RDate
    date::Dates.Date

    Date(date::Dates.Date) = new(date)
    Date(y::Int64, m::Int64, d::Int64) = new(Dates.Date(y, m, d))
end

multiply_roll(x::Date, ::Integer) = x
multiply_no_roll(x::Date, ::Integer) = x
Base.:-(x::Date) = x

apply(rdate::Date, date::Dates.Date, cal_mgr::CalendarManager) = rdate.date
Base.show(io::IO, rdate::Date) = print(io, "$(Dates.day(rdate.date))$(uppercase(Dates.ENGLISH.months_abbr[Dates.month(rdate.date)]))$(Dates.year(rdate.date))")
register_grammar!(PPosInt64() + month_short + PPosInt64() > (d,m,y) -> Date(Dates.Date(y, MONTHS[Symbol(m)], d)))

struct NthWeekdays <: RDate
    dayofweek::Int64
    period::Int64

    NthWeekdays(dayofweek::Int64, period::Int64) = new(dayofweek, period)
end

function apply(rdate::NthWeekdays, date::Dates.Date, cal_mgr::CalendarManager)
    wd = Dates.dayofweek(date)
    wd1st = mod(wd - mod(Dates.day(date), 7), 7) + 1
    wd1stdiff = wd1st - rdate.dayofweek
    period = wd1stdiff > 0 ? rdate.period : rdate.period - 1
    days = 7*period - wd1stdiff + 1
    return Dates.Date(Dates.year(date), Dates.month(date), days)
end
multiply_roll(x::NthWeekdays, ::Integer) = x
multiply_no_roll(x::NthWeekdays, ::Integer) = x
Base.:-(x::NthWeekdays) = x

Base.show(io::IO, rdate::NthWeekdays) = print(io, "$(NTH_PERIODS[rdate.period]) $(uppercase(Dates.ENGLISH.days_of_week_abbr[rdate.dayofweek]))")

register_grammar!(Alt(map(Pattern, NTH_PERIODS)...) + space + weekday_short > (p,wd) -> NthWeekdays(WEEKDAYS[Symbol(wd)], PERIODS[p]))

struct NthLastWeekdays <: RDate
    dayofweek::Int64
    period::Int64

    NthLastWeekdays(dayofweek::Int64, period::Int64) = new(dayofweek, period)
end

function apply(rdate::NthLastWeekdays, date::Dates.Date, cal_mgr::CalendarManager)
    ldom = LDOM() + date
    ldom_dow = Dates.dayofweek(ldom)
    ldom_dow_diff = ldom_dow - rdate.dayofweek
    period = ldom_dow_diff >= 0 ? rdate.period - 1 : rdate.period
    days_to_sub = 7*period + ldom_dow_diff
    days = Dates.day(ldom) - days_to_sub
    return Dates.Date(Dates.year(date), Dates.month(date), days)
end
multiply_roll(x::NthLastWeekdays, ::Integer) = x
multiply_no_roll(x::NthLastWeekdays, ::Integer) = x
Base.:-(x::NthLastWeekdays) = x

Base.show(io::IO, rdate::NthLastWeekdays) = print(io, "$(NTH_LAST_PERIODS[rdate.period]) $(uppercase(Dates.ENGLISH.days_of_week_abbr[rdate.dayofweek]))")

register_grammar!(Alt(map(Pattern, NTH_LAST_PERIODS)...) + space + weekday_short > (p,wd) -> NthLastWeekdays(WEEKDAYS[Symbol(wd)], PERIODS[p]))

struct Weekdays <: RDate
    dayofweek::Int64
    count::Int64
    inclusive::Bool

    Weekdays(dayofweek::Int64, count::Int64) = new(dayofweek, count, false)
    Weekdays(dayofweek::Int64, count::Int64, inclusive::Bool) = new(dayofweek, count, true)
end

function apply(rdate::Weekdays, date::Dates.Date, cal_mgr::CalendarManager)
    dayδ = Dates.dayofweek(date) - rdate.dayofweek
    weekδ = rdate.count

    if rdate.count < 0 && (dayδ > 0 || (rdate.inclusive && dayδ == 0))
        weekδ += 1
    elseif rdate.count > 0 && (dayδ < 0 || (rdate.inclusive && dayδ == 0))
        weekδ -= 1
    end

    return date + Dates.Day(weekδ*7 - dayδ)
end

multiply_no_roll(x::Weekdays, count::Integer) = Weekdays(x.dayofweek, x.count * count, x.inclusive)
Base.:-(rdate::Weekdays) = Weekdays(rdate.dayofweek, -rdate.count)

Base.show(io::IO, rdate::Weekdays) = print(io, "$(rdate.count)$(uppercase(Dates.ENGLISH.days_of_week_abbr[rdate.dayofweek]))")
register_grammar!(PNonZeroInt64() + weekday_short > (i,wd) -> Weekdays(WEEKDAYS[Symbol(wd)], i))
register_grammar!(PNonZeroInt64() + weekday_short + E"!" > (i,wd) -> Weekdays(WEEKDAYS[Symbol(wd)], i, true))

@auto_hash_equals struct BizDays{S <: HolidayRoundingConvention} <: RDate
    days::Int64
    calendar_names::Vector{String}
    rounding::S

    # I have twisted and turned on what the right default is for holiday start dates.
    # Going with next business day (or previous business day if negated) for 0b and no rounding otherwise.
    # This means that 0b and 1b will give you the same result if you start from a holiday, but I'm comfortable with that.
    BizDays(days::Int64, calendar_names) = BizDays(days, calendar_names, days == 0 ? HolidayRoundingNBD() : HolidayRoundingNR())
    BizDays(days::Int64, calendar_names, rounding::S) where {S <: HolidayRoundingConvention} = new{S}(days, calendar_names, rounding)
end

function apply(rdate::BizDays, date::Dates.Date, cal_mgr::CalendarManager)
    cal = calendar(cal_mgr, rdate.calendar_names)
    date = apply(rdate.rounding, date, cal)

    count = rdate.days
    if rdate.days > 0
        rounding = HolidayRoundingNBD()
        while count > 0
            date = apply(rounding, date + Dates.Day(1), cal)
            count -= 1
        end
    elseif rdate.days < 0
        rounding = HolidayRoundingPBD()
        while count < 0
            date = apply(rounding, date - Dates.Day(1), cal)
            count += 1
        end
    end

    return date
end

function multiply_no_roll(x::BizDays, count::Integer)
    x = count < 0 ? -x : x
    return BizDays(x.count * abs(count), x.calendar_names, x.rounding)
end

Base.:-(x::BizDays{HolidayRoundingNBD}) = BizDays(-x.days, x.calendar_names, HolidayRoundingPBD())
Base.:-(x::BizDays{HolidayRoundingPBD}) = BizDays(-x.days, x.calendar_names, HolidayRoundingNBD())
Base.:-(x::BizDays{HolidayRoundingNBDSM}) = BizDays(-x.days, x.calendar_names, HolidayRoundingPBDSM())
Base.:-(x::BizDays{HolidayRoundingPBDSM}) = BizDays(-x.days, x.calendar_names, HolidayRoundingNBDSM())
Base.:-(x::BizDays{HolidayRoundingNR}) = BizDays(-x.days, x.calendar_names, HolidayRoundingNR())

register_grammar!(PPosZeroInt64() + E"b@" + p"[a-zA-Z\\\\\\s\\|]+" > (days,calendar_name) -> BizDays(days, map(String, split(calendar_name, "|"))))


# Automatically generated by gen_leap_seconds.jl, do not edit!

const LS_EPOCHS = [
    41317,
    41499,
    41683,
    42048,
    42413,
    42778,
    43144,
    43509,
    43874,
    44239,
    44786,
    45151,
    45516,
    46247,
    47161,
    47892,
    48257,
    48804,
    49169,
    49534,
    50083,
    50630,
    51179,
    53736,
    54832,
    56109,
    57204,
    57754,
]

const LEAP_SECONDS = [
    10.0,
    11.0,
    12.0,
    13.0,
    14.0,
    15.0,
    16.0,
    17.0,
    18.0,
    19.0,
    20.0,
    21.0,
    22.0,
    23.0,
    24.0,
    25.0,
    26.0,
    27.0,
    28.0,
    29.0,
    30.0,
    31.0,
    32.0,
    33.0,
    34.0,
    35.0,
    36.0,
    37.0,
]


using TcpInstruments
using Test

w = write
q = query

wg = initialize(Keysight33612A)
@info "Successfully connected $(wg.model) at $(wg.address)"

@info get_output(wg)
enable_output(wg)
@test get_output(wg) == true
disable_output(wg)
@test get_output(wg) == false

write(wg, "SOURce1:APPLy:SIN MAX")
"""
initialize() X
terminate() X
enable_output() X
disable_output() X
get_output() X
{set,get}_frequency() X
{set,get}_amplitude() X
{set,get}_burst_num_cycles() X
{set,get}_time_offset()
{set,get}_voltage_offset() X
{set,get}_burst_period() X
set_mode_burst() X
set_mode_cw() X

get_mode() X
"""

"""
FUNCtion SIN
FREQ 1e4
VOLT 1
VOLT:OFF 0.1
OUTP ON
"""

@info get_function(wg)
set_function(wg, "SIN")
@info get_function(wg)

@info get_frequency(wg)
set_frequency(wg, "1e3")
@info get_frequency(wg)

@info get_amplitude(wg)
set_amplitude(wg, 0.1)
@info get_amplitude(wg)
@info typeof(get_amplitude(wg))

@info get_voltage_offset(wg)
set_voltage_offset(wg, 0)
@info get_voltage_offset(wg)
enable_output(wg)

"""
@info get_burst_mode(wg)
@info get_mode(wg)
set_burst_mode_gated(wg)
@info get_burst_mode(wg)
@info get_mode(wg)
set_mode_burst(wg)
@info get_burst_mode(wg)
@info get_mode(wg)
set_burst_mode_gated(wg)
@info get_burst_mode(wg)
@info get_mode(wg)
"""

set_mode_cw(wg)
w(wg, "TRIGger1:SOURce TIMer")
set_mode_burst(wg)
set_mode_cw(wg)
set_function(wg, "SQU")
enable_output(wg)

@info get_function(wg; chan=2)
set_function(wg, "SIN"; chan=2)
@info get_function(wg; chan=2)

@info get_frequency(wg; chan=2)
set_frequency(wg, "1e3"; chan=2)
@info get_frequency(wg; chan=2)

@info get_amplitude(wg; chan=2)
set_amplitude(wg, 0.1)
@info get_amplitude(wg)
@info typeof(get_amplitude(wg))

@info get_voltage_offset(wg)
set_voltage_offset(wg, 0)
@info get_voltage_offset(wg)
enable_output(wg)

#@info q(wg, "CHAN?"), "SOURCE"
terminate(wg)
@info "Thanks"


module NNlib

export σ, relu, softmax

include("activation.jl")
include("adapt.jl")

end # module


import SPTK: assert_gamma, assert_pade, assert_fftlen

let
    try assert_gamma(-1) catch @test false end
    try assert_gamma(0) catch @test false end
    @test_throws ArgumentError assert_gamma(-2)
end

let
    try assert_pade(4) catch @test false end
    try assert_pade(5) catch @test false end
    @test_throws ArgumentError assert_pade(3)
end

let
    try assert_fftlen(256) catch @test false end
    try assert_fftlen(512) catch @test false end
    @test_throws ArgumentError assert_fftlen(255)
end


@testset "SVDD_util_test" begin

    @testset "merge pool" begin
        pools = labelmap([:U, :Lin, :Lout, :U, :U, :Lin])

        @test [] == SVDD.merge_pools(pools, [])

        @test [1,4,5] == SVDD.merge_pools(pools, :U)
        @test [1,4,5] == sort(SVDD.merge_pools(pools, :U, :U))
        @test [2,6] == SVDD.merge_pools(pools, :Lin)
        @test [3] == SVDD.merge_pools(pools, :Lout)

        @test [2,3,6] == sort(SVDD.merge_pools(pools, :Lin, :Lout))
        @test collect(1:6) == sort(SVDD.merge_pools(pools, :U, :Lin, :Lout))

        @test_throws ArgumentError SVDD.merge_pools(pools, :A)
    end

    @testset "labelmap2vec" begin
        @testset "Symbol" begin
            labelvec = [:yes,:no,:no,:yes,:yes]
            @test labelvec == labelmap2vec(labelmap(labelvec))
            labelvec = Vector{Symbol}(undef, 0)
            @test labelvec == labelmap2vec(labelmap(labelvec))
        end

        @testset "Float64" begin
            labelvec = [1.,-1,-1,1,1]
            @test labelvec == labelmap2vec(labelmap(labelvec))
            labelvec = Vector{Float64}(undef, 0)
            @test labelvec == labelmap2vec(labelmap(labelvec))
        end
    end

    @testset "classify" begin
        @testset "single space" begin
            scores = [2.0, 0.0, 0.01, -0.1, -5.0]
            expected = [:outlier, :inlier, :outlier, :inlier, :inlier]
            actual = SVDD.classify.(scores)
            @test MLLabelUtils.islabelenc(actual, SVDD.class_label_enc)
            @test expected == actual
        end

        @testset "subspaces" begin
            scores = [[2.0, 0.0, 0.01, -0.1, -5.0],
                      [2.0, 0.0, 0.0, -0.1, 5.0]]
            expected_local = [[:outlier, :inlier, :outlier, :inlier, :inlier],
                              [:outlier, :inlier, :inlier, :inlier, :outlier]]
            expected_global = [:outlier, :inlier, :outlier, :inlier, :outlier]

            actual_local = SVDD.classify(scores, Val(:Subspace))
            actual_global = SVDD.classify(scores, Val(:Global))
            @test expected_local == actual_local
            @test expected_global == actual_global
        end
    end

    @testset "adjust kernel" begin
        K1 = [2 -1 0; -1 2 -1; 0 -1 2]
        K2 = [1 2; 2 1]
        @testset "single space" begin
            @assert all(eigen(K1).values .> 0)
            @test K1 ≈ SVDD.adjust_kernel_matrix(K1)

            @assert any(eigen(K2).values .< 0)
            K2_adjusted = SVDD.adjust_kernel_matrix(K2, warn_threshold = 2)
            @test !(K2 ≈ K2_adjusted)
            @test all(eigen(K2_adjusted).values .>= 0.0)

            Random.seed!(42)
            dummy_data, _ = generate_mvn_with_outliers(2, 100, 42, true, true)
            model = SVDD.VanillaSVDD(dummy_data)
            init_strategy = SVDD.FixedParameterInitialization(MLKernels.GaussianKernel(4), 0.1)
            SVDD.initialize!(model, init_strategy)
            K_old = copy(model.K)
            @assert any(eigen(model.K).values .< 0)

            @test_throws ArgumentError SVDD.adjust_kernel_matrix([3 -2; 4 -1])
        end

        @testset "subspaces" begin
            K = [K1, K2]
            @assert all(eigen(K[1]).values .> 0)
            @assert any(eigen(K[2]).values .< 0)
            K_adjusted = SVDD.adjust_kernel_matrix(K, warn_threshold = 2)
            @test K[1] ≈ K_adjusted[1]
            @test !(K[2] ≈ K_adjusted[2])
            @test all(eigen(K_adjusted[2]).values .>= 0.0)
        end
    end

    @testset "min_max_normalize" begin
        @test min_max_normalize([1,2]) ≈ [0.0, 1.0]
        @test min_max_normalize([-1,2]) ≈ [0.0, 1.0]
        @test isnan(min_max_normalize(1))
        @test isnan(min_max_normalize(-1))
        @test min_max_normalize([0, 20, -20, 0]) ≈ [0.5, 1.0, 0.0, 0.5]
    end
end


export hmm_loglik

import ..HMMModel

using mlpack._Internal.io

import mlpack_jll
const hmm_loglikLibrary = mlpack_jll.libmlpack_julia_hmm_loglik

# Call the C binding of the mlpack hmm_loglik binding.
function hmm_loglik_mlpackMain()
  success = ccall((:hmm_loglik, hmm_loglikLibrary), Bool, ())
  if !success
    # Throw an exception---false means there was a C++ exception.
    throw(ErrorException("mlpack binding error; see output"))
  end
end

" Internal module to hold utility functions. "
module hmm_loglik_internal
  import ..hmm_loglikLibrary

import ...HMMModel

# Get the value of a model pointer parameter of type HMMModel.
function IOGetParamHMMModel(paramName::String)::HMMModel
  HMMModel(ccall((:IO_GetParamHMMModelPtr, hmm_loglikLibrary), Ptr{Nothing}, (Cstring,), paramName))
end

# Set the value of a model pointer parameter of type HMMModel.
function IOSetParamHMMModel(paramName::String, model::HMMModel)
  ccall((:IO_SetParamHMMModelPtr, hmm_loglikLibrary), Nothing, (Cstring, Ptr{Nothing}), paramName, model.ptr)
end

# Serialize a model to the given stream.
function serializeHMMModel(stream::IO, model::HMMModel)
  buf_len = UInt[0]
  buf_ptr = ccall((:SerializeHMMModelPtr, hmm_loglikLibrary), Ptr{UInt8}, (Ptr{Nothing}, Ptr{UInt}), model.ptr, Base.pointer(buf_len))
  buf = Base.unsafe_wrap(Vector{UInt8}, buf_ptr, buf_len[1]; own=true)
  write(stream, buf)
end
# Deserialize a model from the given stream.
function deserializeHMMModel(stream::IO)::HMMModel
  buffer = read(stream)
  HMMModel(ccall((:DeserializeHMMModelPtr, hmm_loglikLibrary), Ptr{Nothing}, (Ptr{UInt8}, UInt), Base.pointer(buffer), length(buffer)))
end
end # module

"""
    hmm_loglik(input, input_model; [verbose])

This utility takes an already-trained HMM, specified with the `input_model`
parameter, and evaluates the log-likelihood of a sequence of observations, given
with the `input` parameter.  The computed log-likelihood is given as output.

For example, to compute the log-likelihood of the sequence `seq` with the
pre-trained HMM `hmm`, the following command may be used:

```julia
julia> using CSV
julia> seq = CSV.read("seq.csv")
julia> _ = hmm_loglik(seq, hmm)
```

# Arguments

 - `input::Array{Float64, 2}`: File containing observations,
 - `input_model::HMMModel`: File containing HMM.
 - `verbose::Bool`: Display informational messages and the full list of
      parameters and timers at the end of execution.  Default value `false`.

# Return values

 - `log_likelihood::Float64`: Log-likelihood of the sequence.  Default
      value `0`.

"""
function hmm_loglik(input,
                    input_model::HMMModel;
                    verbose::Union{Bool, Missing} = missing,
                    points_are_rows::Bool = true)
  # Force the symbols to load.
  ccall((:loadSymbols, hmm_loglikLibrary), Nothing, ());

  IORestoreSettings("Hidden Markov Model (HMM) Sequence Log-Likelihood")

  # Process each input argument before calling mlpackMain().
  IOSetParamMat("input", input, points_are_rows)
  hmm_loglik_internal.IOSetParamHMMModel("input_model", convert(HMMModel, input_model))
  if verbose !== nothing && verbose === true
    IOEnableVerbose()
  else
    IODisableVerbose()
  end

  IOSetPassed("log_likelihood")
  # Call the program.
  hmm_loglik_mlpackMain()

  return IOGetParamDouble("log_likelihood")
end


export rrule
"""
    ChainRulesCore.rrule(itp::AbstractInterpolation, x...)
ChainRulesCore.jl `rrule` for integration with automatic differentiation libraries.
"""
function ChainRulesCore.rrule(itp::AbstractInterpolation, x...)
    y = itp(x...)
    function pullback(Δy)
        (ChainRulesCore.DoesNotExist(), Δy * Interpolations.gradient(itp, x...))
    end
    y, pullback
end


struct DiffEqNLSolveTag end

struct DiffCache{T<:AbstractArray, S<:AbstractArray}
    du::T
    dual_du::S
end

Base.@pure function DiffCache(T, size, ::Type{Val{chunk_size}}) where chunk_size
  DiffCache(fill(zero(T), size...), fill(zero(Dual{typeof(ForwardDiff.Tag(DiffEqNLSolveTag(),T)),T,chunk_size}), size...))
end

Base.@pure DiffCache(u::AbstractArray) = DiffCache(eltype(u),size(u),Val{ForwardDiff.pickchunksize(length(u))})
Base.@pure DiffCache(u::AbstractArray,nlsolve) = DiffCache(eltype(u),size(u),Val{get_chunksize(nlsolve)})
Base.@pure DiffCache(u::AbstractArray,T::Type{Val{CS}}) where {CS} = DiffCache(eltype(u),size(u),T)

get_du(dc::DiffCache, ::Type{T}) where {T<:Dual} = dc.dual_du
get_du(dc::DiffCache, T) = dc.du

# Default nlsolve behavior, should move to DiffEqDiffTools.jl

Base.@pure determine_chunksize(u,alg::DiffEqBase.DEAlgorithm) = determine_chunksize(u,get_chunksize(alg))
Base.@pure function determine_chunksize(u,CS)
  if CS != 0
    return CS
  else
    return ForwardDiff.pickchunksize(length(u))
  end
end

struct NLSOLVEJL_SETUP{CS,AD} end
Base.@pure NLSOLVEJL_SETUP(;chunk_size=0,autodiff=true) = NLSOLVEJL_SETUP{chunk_size,autodiff}()
(::NLSOLVEJL_SETUP)(f,u0; kwargs...) = (res=NLsolve.nlsolve(f,u0; kwargs...); res.zero)
function (p::NLSOLVEJL_SETUP{CS,AD})(::Type{Val{:init}},f,u0_prototype) where {CS,AD}
  AD ? autodiff = :forward : autodiff = :central
  OnceDifferentiable(f, u0_prototype, u0_prototype, autodiff,
                     ForwardDiff.Chunk(determine_chunksize(u0_prototype,CS)))
end

get_chunksize(x) = 0
get_chunksize(x::NLSOLVEJL_SETUP{CS,AD}) where {CS,AD} = CS

macro swap!(x,y)
  quote
    local tmp = $(esc(x))
    $(esc(x)) = $(esc(y))
    $(esc(y)) = tmp
  end
end

macro cache(expr)
  name = expr.args[2].args[1].args[1]
  fields = [x for x in expr.args[3].args if typeof(x)!=LineNumberNode]
  cache_vars = Expr[]
  jac_vars = Pair{Symbol,Expr}[]
  for x in fields
    if x.args[2] == :uType || x.args[2] == :rateType ||
       x.args[2] == :kType || x.args[2] == :uNoUnitsType
      push!(cache_vars,:(c.$(x.args[1])))
    elseif x.args[2] == :JCType
      push!(cache_vars,:(c.$(x.args[1]).duals...))
    elseif x.args[2] == :GCType
      push!(cache_vars,:(c.$(x.args[1]).duals))
    elseif x.args[2] == :DiffCacheType
      push!(cache_vars,:(c.$(x.args[1]).du))
      push!(cache_vars,:(c.$(x.args[1]).dual_du))
    elseif x.args[2] == :JType || x.args[2] == :WType
      push!(jac_vars,x.args[1] => :(c.$(x.args[1])))
    end
  end
  quote
    $expr
    $(esc(:full_cache))(c::$name) = tuple($(cache_vars...))
    $(esc(:jac_iter))($(esc(:c))::$name) = tuple($(jac_vars...))
  end
end

# Nest one layer of value in order to get rid of possible Dual{Complex} or Complex{Dual} issues
# value should recurse for anything else.
function constvalue(x)
  _x = DiffEqBase.value(x)
  _x isa Complex ? DiffEqBase.value(real(_x)) : DiffEqBase.value(_x)
end

function diffdir(integrator::DiffEqBase.DEIntegrator)
  difference = maximum(abs, integrator.uprev)*sqrt(eps(typeof(integrator.t)))
  dir = integrator.tdir > zero(integrator.tdir) ?
          integrator.t > integrator.sol.prob.tspan[2] - difference ? -true :  true :
          integrator.t < integrator.sol.prob.tspan[2] + difference ?  true : -true
end


#####
##### Abstract tree/trajectory interface
#####

####
#### Directions
####

"Maximum number of iterations [`next_direction`](@ref) supports."
const MAX_DIRECTIONS_DEPTH = 32

"""
Internal type implementing random directions. Draw a new value with `rand`, see
[`next_direction`](@ref).

Serves two purposes: a fixed value of `Directions` is useful for unit testing, and drawing a
single bit flag collection economizes on the RNG cost.
"""
struct Directions
    flags::UInt32
end

Base.rand(rng::AbstractRNG, ::Type{Directions}) = Directions(rand(rng, UInt32))

"""
$(SIGNATURES)

Return the next direction flag and the new state of directions. Results are undefined for
more than [`MAX_DIRECTIONS_DEPTH`](@ref) updates.
"""
function next_direction(directions::Directions)
    @unpack flags = directions
    Bool(flags & 0x01), Directions(flags >>> 1)
end

####
#### Trajectory interface
####

"""
    $(FUNCTIONNAME)(trajectory, z, is_forward)

Move along the trajectory in the specified direction. Return the new position.
"""
function move end

"""
    $(FUNCTIONNAME)(trajectory, τ)

Test if the turn statistics `τ` indicate that the corresponding tree is turning.

Will only be called on nontrivial trees (at least two nodes).
"""
function is_turning end

"""
    $(FUNCTIONNAME)(trajectory, τ₁, τ₂)

Combine turn statistics on trajectory. Implementation can assume that the trees that
correspond to the turn statistics have the same ordering.

When
```julia
τ = combine_turn_statistics(trajectory, τ₁, τ₂)
is_turning(trajectory, τ)
```
the combined turn statistic `τ` is guaranteed not to escape the caller, so it can eg change
type.
"""
function combine_turn_statistics end

"""
    $(FUNCTIONNAME)(trajectory, v₁, v₂)

Combine visited node statistics for adjacent trees trajectory. Implementation should be
invariant to the ordering of `v₁` and `v₂` (ie the operation is commutative).
"""
function combine_visited_statistics end

"""
    $(FUNCTIONNAME)(trajectory, is_doubling::Bool, ω₁, ω₂, ω)

Calculate the log probability if selecting the subtree corresponding to `ω₂`. Being the log
of a probability, it is always `≤ 0`, but implementations are allowed to return and accept
values `> 0` and treat them as `0`.

When `is_doubling`, the tree corresponding to `ω₂` was obtained from a doubling step (this
can be relevant eg for biased progressive sampling).

The value `ω = logaddexp(ω₁, ω₂)` is provided for avoiding redundant calculations.

See [`biased_progressive_logprob2`](@ref) for an implementation.
"""
function calculate_logprob2 end

"""
    $(FUNCTIONNAME)(rng, trajectory, ζ₁, ζ₂, logprob2::Real, is_forward::Bool)

Combine two proposals `ζ₁, ζ₂` on `trajectory`, with log probability `logprob2` for
selecting `ζ₂`.

 `ζ₁` is before `ζ₂` iff `is_forward`.
"""
function combine_proposals end

"""
    ζωτ_or_nothing, v = $(FUNCTIONNAME)(trajectory, z, is_initial)

Information for a tree made of a single node. When `is_initial == true`, this is the first
node.

The first value is either

1. `nothing` for a divergent node,

2. a tuple containing the proposal `ζ`, the log weight (probability) of the node `ω`, the
turn statistics `τ` (never tested with `is_turning` for leaf nodes).

The second value is the visited node information.
"""
function leaf end

####
#### utilities
####

"""
$(SIGNATURES)

Combine turn statistics with the given direction. When `is_forward`, `τ₁` is before `τ₂`,
otherwise after.

Internal helper function.
"""
@inline function combine_turn_statistics_in_direction(trajectory, τ₁, τ₂, is_forward::Bool)
    if is_forward
        combine_turn_statistics(trajectory, τ₁, τ₂)
    else
        combine_turn_statistics(trajectory, τ₂, τ₁)
    end
end

function combine_proposals_and_logweights(rng, trajectory, ζ₁, ζ₂, ω₁::Real, ω₂::Real,
                                          is_forward::Bool, is_doubling::Bool)
    ω = logaddexp(ω₁, ω₂)
    logprob2 = calculate_logprob2(trajectory, is_doubling, ω₁, ω₂, ω)
    ζ = combine_proposals(rng, trajectory, ζ₁, ζ₂, logprob2, is_forward)
    ζ, ω
end

"""
$(SIGNATURES)

Given (relative) log probabilities `ω₁` and `ω₂`, return the log probabiliy of
drawing a sample from the second (`logprob2`).

When `bias`, biases towards the second argument, introducing anti-correlations.
"""
function biased_progressive_logprob2(bias::Bool, ω₁::Real, ω₂::Real, ω = logaddexp(ω₁, ω₂))
    ω₂ - (bias ? ω₁ : ω)
end

####
#### abstract trajectory interface
####

"""
$(SIGNATURES)

Information about an invalid (sub)tree, using positions relative to the starting node.

1. When `left < right`, this tree was *turning*.

2. When `left == right`, this is a *divergent* node.

3. `left == 1 && right == 0` is used as a sentinel value for reaching maximum depth without
encountering any invalid trees (see [`REACHED_MAX_DEPTH`](@ref). All other `left > right`
values are disallowed.
"""
struct InvalidTree
    left::Int
    right::Int
end

InvalidTree(i::Integer) = InvalidTree(i, i)

is_divergent(invalid_tree::InvalidTree) = invalid_tree.left == invalid_tree.right

function Base.show(io::IO, invalid_tree::InvalidTree)
    msg = if is_divergent(invalid_tree)
        "divergence at position $(invalid_tree.left)"
    elseif invalid_tree == REACHED_MAX_DEPTH
        "reached maximum depth without divergence or turning"
    else
        @unpack left, right = invalid_tree
        "turning at positions $(left):$(right)"
    end
    print(io, msg)
end

"Sentinel value for reaching maximum depth."
const REACHED_MAX_DEPTH = InvalidTree(1, 0)

"""
    result, v = adjacent_tree(rng, trajectory, z, i, depth, is_forward)

Traverse the tree of given `depth` adjacent to point `z` in `trajectory`.

`is_forward` specifies the direction, `rng` is used for random numbers in
[`combine_proposals`](@ref). `i` is an integer position relative to the initial node (`0`).

The *first value* is either

1. an `InvalidTree`, indicating the first divergent node or turning subtree that was
encounteted and invalidated this tree.

2. a tuple of `(ζ, ω, τ, z′, i′), with

    - `ζ`: the proposal from the tree.

    - `ω`: the log weight of the subtree that corresponds to the proposal

    - `τ`: turn statistics

    - `z′`: the last node of the tree

    - `i′`: the position of the last node relative to the initial node.

The *second value* is always the visited node statistic.
"""
function adjacent_tree(rng, trajectory, z, i, depth, is_forward)
    i′ = i + (is_forward ? 1 : -1)
    if depth == 0
        z′ = move(trajectory, z, is_forward)
        ζωτ, v = leaf(trajectory, z′, false)
        if ζωτ ≡ nothing
            InvalidTree(i′), v
        else
            (ζωτ..., z′, i′), v
        end
    else
        # “left” tree
        t₋, v₋ = adjacent_tree(rng, trajectory, z, i, depth - 1, is_forward)
        t₋ isa InvalidTree && return t₋, v₋
        ζ₋, ω₋, τ₋, z₋, i₋ = t₋

        # “right” tree — visited information from left is kept even if invalid
        t₊, v₊ = adjacent_tree(rng, trajectory, z₋, i₋, depth - 1, is_forward)
        v = combine_visited_statistics(trajectory, v₋, v₊)
        t₊ isa InvalidTree && return t₊, v
        ζ₊, ω₊, τ₊, z₊, i₊ = t₊

        # turning invalidates
        τ = combine_turn_statistics_in_direction(trajectory, τ₋, τ₊, is_forward)
        is_turning(trajectory, τ) && return InvalidTree(i′, i₊), v

        # valid subtree, combine proposals
        ζ, ω = combine_proposals_and_logweights(rng, trajectory, ζ₋, ζ₊, ω₋, ω₊,
                                                is_forward, false)
        (ζ, ω, τ, z₊, i₊), v
    end
end

"""
$(SIGNATURES)

Sample a `trajectory` starting at `z`, up to `max_depth`. `directions` determines the tree
expansion directions.

Return the following values

- `ζ`: proposal from the tree

- `v`: visited node statistics

- `termination`: an `InvalidTree` (this includes the last doubling step turning, which is
  technically a valid tree) or `REACHED_MAX_DEPTH` when all subtrees were valid and no
  turning happens.

- `depth`: the depth of the tree that was sampled from. Doubling steps that lead to an
  invalid adjacent tree do not contribute to `depth`.
"""
function sample_trajectory(rng, trajectory, z, max_depth::Integer, directions::Directions)
    @argcheck max_depth ≤ MAX_DIRECTIONS_DEPTH
    (ζ, ω, τ), v = leaf(trajectory, z, true)
    z₋ = z₊ = z
    depth = 0
    termination = REACHED_MAX_DEPTH
    i₋ = i₊ = 0
    while depth < max_depth
        is_forward, directions = next_direction(directions)
        t′, v′ = adjacent_tree(rng, trajectory, is_forward ? z₊ : z₋, is_forward ? i₊ : i₋,
                               depth, is_forward)
        v = combine_visited_statistics(trajectory, v, v′)

        # invalid adjacent tree: stop
        t′ isa InvalidTree && (termination = t′; break)

        # extract information from adjacent tree
        ζ′, ω′, τ′, z′, i′ = t′

        # update edges and combine proposals
        if is_forward
            z₊, i₊ = z′, i′
        else
            z₋, i₋ = z′, i′
        end

        # tree has doubled successfully
        ζ, ω = combine_proposals_and_logweights(rng, trajectory, ζ, ζ′, ω, ω′,
                                                is_forward, true)
        depth += 1

        # when the combined tree is turning, stop
        τ = combine_turn_statistics_in_direction(trajectory, τ, τ′, is_forward)
        is_turning(trajectory, τ) && (termination = InvalidTree(i₋, i₊); break)
    end
    ζ, v, termination, depth
end


module LeafGasExchange

using Cropbox

include("vaporpressure.jl")
include("weather.jl")
include("diffusion.jl")
include("nitrogen.jl")
include("base.jl")
include("c3.jl")
include("c4.jl")
include("boundarylayer.jl")
include("stomata.jl")
include("intercellularspace.jl")
include("irradiance.jl")
include("energybalance.jl")

@system ModelBase(
    Weather, Nitrogen,
    BoundaryLayer, StomataBase, IntercellularSpace, Irradiance, EnergyBalance
)

@system ModelC3BB(ModelBase, StomataBallBerry, C3, Controller)
@system ModelC4BB(ModelBase, StomataBallBerry, C4, Controller)

@system ModelC3MD(ModelBase, StomataMedlyn, C3, Controller)
@system ModelC4MD(ModelBase, StomataMedlyn, C4, Controller)

export ModelC3BB, ModelC3MD, ModelC4BB, ModelC4MD

end


"""
FinEtools (C) 2017-2020, Petr Krysl

Finite Element tools.  Julia implementation  of the finite element method
for continuum mechanics. Package for problems of vibration in fluids.
"""
module FinEtoolsVibInFluids

__precompile__(true)

include("LaplBEM.jl")
include("AlgoVibInFluidsModule.jl")

end # module


module RoME

using
  IncrementalInference,
  Graphs,
  TransformUtils,
  CoordinateTransformations,
  Rotations,
  KernelDensityEstimate,
  Distributions,
  JLD,
  HDF5,
  ProgressMeter,
  DocStringExtensions,
  Compat

import Base: +, \, convert
import TransformUtils: ⊖, ⊕, convert, compare, ominus, veeQuaternion
import IncrementalInference: convert, getSample, reshapeVec2Mat  #, compare

export
  # pass throughs from TransformUtils
  SE2,
  se2vee,
  se2vee!,
  SE3,
  Euler,
  Quaternion,
  AngleAxis,
  SO3,
  so3,
  compare,
  convert,

  # pass throughs from IncrementalInference
  FunctorSingleton,
  FunctorPairwise,
  FunctorPairwiseNH,
  FunctorSingletonNH,
  ls,
  addFactor!,
  addNode!,
  getVert,
  getVertKDE,
  getVal,
  setVal!,
  getData,
  FNDencode,
  FNDdecode,
  localProduct,
  predictbelief,
  VNDencoder,
  VNDdecoder,
  GenericWrapParam,
  wipeBuildNewTree!,
  inferOverTree!,
  inferOverTreeR!,
  writeGraphPdf,
  savejld,
  loadjld,
  FactorGraph,
  initializeNode!,
  isInitialized,
  ensureAllInitialized!,
  # overloaded functions from IIF
  # decodefg,
  # convertfrompackedfunctionnode,

  # RoME specific functions
  initfg,
  measureMeanDist,
  predictBodyBR,
  getLastPose,
  getLastPose2D,
  odomKDE,
  initFactorGraph!,
  addOdoFG!,
  addposeFG!,
  newLandm!,
  addBRFG!,
  addMMBRFG!,
  addAutoLandmBR!,
  projNewLandm!,
  malahanobisBR,
  veePose3,
  veePose,
  \,
  RangeAzimuthElevation,

  # types
  BetweenPoses,

  # helper functions
  get2DSamples,
  getAll2D,
  get2DSampleMeans,
  getAll2DMeans,
  getAll2DPoses,
  get2DPoseSamples,
  get2DPoseMeans,
  getKDE,
  getVertKDE,
  get2DPoseMax,
  getAll2DLandmarks,
  get2DLandmSamples,
  get2DLandmMeans,
  get2DLandmMax,

  # helper functions
  getLastLandm2D,
  getLastPose2D,
  getNextLbl,

  # RobotUtils
  getRangeKDEMax2D,

  # some transform functions
  cart2pol,
  pol2cart,

  # Feature tracking code
  Feature,
  initTrackersFrom,
  propAllTrackers!,
  measUpdateTrackers!,
  assocMeasWFeats!,

  lsrBR,

  # solve with isam in pytslam
  doISAMSolve,
  drawCompPosesLandm,

  # Victoria Park data specific
  LaserFeatures,
  addLandmarksFactoGraph!,
  appendFactorGraph!,
  doBatchRun,
  rotateFeatsToWorld,

  # Didson model
  evalPotential,
  LinearRangeBearingElevation,
  project!,
  project,
  backprojectRandomized!,
  residual!,
  residualLRBE!,
  reuseLBRA,
  ominus,
  ominus!,
  +,
  evalPotential,
  getSample!,
  getSample,
  # obsolete
  WrapParam,
  WrapParamArray,

  # Didson convenience function
  addLinearArrayConstraint,

  # camera model -- TODO --separate out
  CameraIntrinsic,
  CameraExtrinsic,
  CameraModelFull,
  project!,
  project,
  backprojectRandomized!,
  # keep
  cameraResidual!,

  # Point2D
  Point2DPoint2D,
  PackedPoint2DPoint2D,
  Pose2DPoint2DBearingRange,
  PackedPose2DPoint2DBearingRange,
  Pose2DPoint2DBearing,
  Pose2DPoint2DRange,
  Point2DPoint2DRange,
  PackedPoint2DPoint2DRange,
  PriorPoint2D,
  PackedPriorPoint2D,
  solveLandm,
  solvePose2,
  solveSetSeps,
  addPose2Pose2!,

  # Point2D with null hypotheses
  PriorPoint2DensityNH,
  PackedPriorPoint2DensityNH,

  # acoustics
  Pose2DPoint2DBearingRangeDensity,
  PackedPose2DPoint2DBearingRangeDensity,
  Pose2DPoint2DRangeDensity,
  PackedPose2DPoint2DRangeDensity,

  Pose2,
  Point2,
  Pose3,
  Point3,
  Prior,
  Pose2Pose2_NEW,

  # Pose2D
  PriorPose2,
  PackedPriorPose2,
  Pose2Pose2,
  PackedPose2Pose2,
  addPose2Pose2,

  # packedTypes
  passTypeThrough,

  # Pose3D
  PriorPose3,
  PackedPriorPose3,
  Pose3Pose3,
  PackedPose3Pose3,
  projectParticles,
  ⊕,
  Pose3Pose3NH,
  PackedPose3Pose3NH,

  # partial Pose3
  PartialPriorRollPitchZ,
  PackedPartialPriorRollPitchZ,
  PartialPose3XYYaw,
  PackedPartialPose3XYYaw,
  PartialPose3XYYawNH,
  PackedPartialPose3XYYawNH,

  # MultipleFeatures2D constraint functions
  MultipleFeatures2D,
  getUvecScaleFeature2D,
  getUvecScaleBaseline2D,

  # SLAM specific functions
  SLAMWrapper,

  # FG Analysis tools
  rangeErrMaxPoint2,
  rangeCompAllPoses,
  rangeCompAllPoses,

  # new robot navigation functionality
  triggerPose,
  GenericInSituSystem,
  InSituSystem,
  makeInSituSys,
  makeGenericInSituSys,
  advOdoByRules,
  poseTrigAndAdd!,
  poseTrigAndAdd!,
  processTreeTrackersUpdates!,
  addSoftEqualityPoint2D,
  vectoarr2

  ## Visualization tools have been moved to RoMEPlotting.jl
  # draw pose beliefs etc
  # Some vizualization tools
  # togglePrtStbLines,
  # plotLsrScanFeats,
  # drawFeatTrackers,
  # saveImgSeq,
  # drawPoses,
  # drawLandms,
  # drawPosesLandms,
  # drawSubmaps,
  # investigatePoseKDE,
  # plotPose3Pairs,
  # drawMarginalContour,
  # accumulateMarginalContours,
  # progressExamplePlot,
  # plotTrckStep,

@compat abstract type BetweenPoses <: IncrementalInference.FunctorPairwise end

@compat const VoidUnion{T} = Union{Void, T}

@compat const CTs = CoordinateTransformations
@compat const TUs = TransformUtils

vectoarr2(v) = reshape(v, length(v),1)

include("BayesTracker.jl")

include("SensorModels.jl")
include("CameraModel.jl")
include("Point2D.jl")
include("Pose2D.jl")
include("Pose3D.jl")
include("BearingRange2D.jl")

include("BearingRangeDensity2D.jl")

include("Pose3Pose3.jl")
include("PartialPose3.jl")
include("MultipleFeaturesConstraint.jl")

include("InertialPose3.jl")

include("Slam.jl")

# include("RobotViz.jl")
include("RobotUtils.jl")

include("SimulationUtils.jl")

include("FactorGraphAnalysisTools.jl")

include("WheeledRobotUtils.jl")
include("NavigationSystem.jl")

# include("dev/ISAMRemoteSolve.jl")

end


module RigidBodyTreeInspector

using LinearAlgebra
using RigidBodyDynamics
using RigidBodyDynamics.OdeIntegrators
import DrakeVisualizer: Visualizer,
                        settransform!,
                        setgeometry!,
                        addgeometry!,
                        batch,
                        GeometryData,
                        Triad,
                        HyperEllipsoid,
                        center
using StaticArrays: SVector, SMatrix
using CoordinateTransformations: AffineMap, IdentityTransformation, AngleAxis,
                                 LinearMap, RodriguesVec, Quat, compose,
                                 Translation, RotMatrix, RotXYZ, transform_deriv
using GeometryTypes: AbstractGeometry,
                     AbstractMesh,
                     HyperRectangle,
                     HyperSphere,
                     Vec, Point
using ColorTypes: RGBA
import Interpolations: interpolate, Linear, Gridded
import Base: convert, *, one
import Rotations
import LoopThrottle: @throttle
using MechanismGeometries: VisualElement, Skeleton, AbstractGeometrySource,
                           visual_elements, URDFVisuals

const rbd = RigidBodyDynamics

rbd.parse_urdf

export manipulate,
       inspect,
       inspect!,
       Visualizer,
       settransform!,
       addgeometry!,
       setgeometry!,
       animate,
       parse_urdf,
       create_geometry,
       DrakeVisualizerSink,
       visual_elements,
       Skeleton,
       URDFVisuals

include("manipulate.jl")
using .Manipulate
include("visualizer.jl")
include("animate.jl")
include("ode_callback.jl")

end


module LeastsquaresWrapper

using JavaCall

const AbsoluteDate = @jimport org.orekit.time.AbsoluteDate
const AbstractIntegratedPropagator = @jimport org.orekit.propagation.integration.AbstractIntegratedPropagator
const BatchLSEstimator = @jimport org.orekit.estimation.leastsquares.BatchLSEstimator
const BatchLSModel = @jimport org.orekit.estimation.leastsquares.BatchLSModel
const BatchLSODModel = @jimport org.orekit.estimation.leastsquares.BatchLSODModel
const BatchLSObserver = @jimport org.orekit.estimation.leastsquares.BatchLSObserver
const Class = @jimport java.lang.Class
const ConvergenceChecker = @jimport org.hipparchus.optim.ConvergenceChecker
const DSSTBatchLSModel = @jimport org.orekit.estimation.leastsquares.DSSTBatchLSModel
const DSSTPropagator = @jimport org.orekit.propagation.semianalytical.dsst.DSSTPropagator
const EstimatedMeasurement = @jimport org.orekit.estimation.measurements.EstimatedMeasurement
const EstimationsProvider = @jimport org.orekit.estimation.measurements.EstimationsProvider
const Incrementor = @jimport org.hipparchus.util.Incrementor
const IntegratedPropagatorBuilder = @jimport org.orekit.propagation.conversion.IntegratedPropagatorBuilder
const JString = @jimport java.lang.String
const LeastSquaresOptimizer = @jimport org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresOptimizer
const LeastSquaresOptimizer_Optimum = @jimport org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresOptimizer$Optimum
const LeastSquaresProblem_Evaluation = @jimport org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem$Evaluation
const List = @jimport java.util.List
const Map = @jimport java.util.Map
const MeasurementHandler = @jimport org.orekit.estimation.leastsquares.MeasurementHandler
const ModelObserver = @jimport org.orekit.estimation.leastsquares.ModelObserver
const MultivariateJacobianFunction = @jimport org.hipparchus.optim.nonlinear.vector.leastsquares.MultivariateJacobianFunction
const NumericalPropagator = @jimport org.orekit.propagation.numerical.NumericalPropagator
const Object = @jimport java.lang.Object
const ObservedMeasurement = @jimport org.orekit.estimation.measurements.ObservedMeasurement
const Orbit = @jimport org.orekit.orbits.Orbit
const Pair = @jimport org.hipparchus.util.Pair
const ParameterDriversList = @jimport org.orekit.utils.ParameterDriversList
const PreCompensation = @jimport org.orekit.estimation.leastsquares.PreCompensation
const PropagationType = @jimport org.orekit.propagation.PropagationType
const RealMatrix = @jimport org.hipparchus.linear.RealMatrix
const RealVector = @jimport org.hipparchus.linear.RealVector

export BatchLSEstimator
export BatchLSModel
export BatchLSODModel
export BatchLSObserver
export DSSTBatchLSModel
export MeasurementHandler
export ModelObserver
export PreCompensation
export add_measurement
export create_propagators
export equals
export estimate
export evaluation_performed
export fetch_evaluated_measurement
export get_class
export get_date
export get_evaluations_count
export get_iterations_count
export get_last_estimations
export get_measurement
export get_measurements_parameters_drivers
export get_optimum
export get_orbital_parameters_drivers
export get_physical_covariances
export get_propagator_parameters_drivers
export get_selected_propagation_drivers_for_builder
export handle_step
export hash_code
export init
export is_forward_propagation
export model_called
export notify
export notify_all
export set_convergence_checker
export set_evaluations_counter
export set_iterations_counter
export set_max_evaluations
export set_max_iterations
export set_observer
export set_parameters_convergence_threshold
export to_string
export value
export wait

include("batch_ls_estimator.jl")
include("batch_ls_model.jl")
include("batch_ls_observer.jl")
include("batch_lsod_model.jl")
include("dsst_batch_ls_model.jl")
include("measurement_handler.jl")
include("model_observer.jl")
include("pre_compensation.jl")

end


#  MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
#  Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
#  Released under the modified BSD license. See COPYING.md for more details.

using Cbc
using JuMP
using MIPLearn

@testset "LearningSolver" begin
    @testset "Model with annotations" begin
        model = build_knapsack_model()
        solver = LearningSolver(Cbc.Optimizer)
        instance = JuMPInstance(model)
        stats = solve!(solver, instance)
        @test stats["mip_lower_bound"] == 11.0
        @test length(instance.samples) == 1
        fit!(solver, [instance])
        solve!(solver, instance)
    end

    @testset "Model without annotations" begin
        model = build_knapsack_model()
        solver = LearningSolver(Cbc.Optimizer)
        instance = JuMPInstance(model)
        stats = solve!(solver, instance)
        @test stats["mip_lower_bound"] == 11.0
    end

    @testset "Save and load" begin
        solver = LearningSolver(Cbc.Optimizer)
        solver.py.components = "Placeholder"
        filename = tempname()
        save(filename, solver)
        @test isfile(filename)
        loaded = load_solver(filename)
        @test loaded.py.components == "Placeholder"
    end

    # @testset "Discard output" begin
    #     instance = build_knapsack_file_instance()
    #     solver = LearningSolver(Cbc.Optimizer)
    #     solve!(solver, instance, discard_output = true)
    #     loaded = load_instance(instance.filename)
    #     @test length(loaded.samples) == 0
    # end
end