module INFRARED
using ..AGFFileReader: Glass, GlassID, AGF
export CDSE, GERMANIUM, ZNSE, IRG100, NACL, CALCITE, F_SILICA, PBF2, MGF2, IRG3, CDTE, GEO2, KCL, GE_LONG, AMTIR1, GAAS, TI_1173, BAF2, IRG2, ZBLAN, IRG11, MGO, IRG9, SPINEL, IRG7, LIF, AMTIR3, CAF2, GE_OLD, ZNS_IR, CLRTR_OLD, CLEARTRAN, KBR, ALON, SILICON, ALN, BEO, SRTIO3, KRS5, ZBLA, ZNS_BROAD, IRG15, CSBR, AGCL, SRF2, ZNS_VIS, IRGN6, SAPPHIRE

const CDSE = Glass(GlassID(AGF, 1590), 9, 4.2243, 1.768, 0.227, 3.12, 3380.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 22.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 4.9, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const GERMANIUM = Glass(GlassID(AGF, 1591), 9, 9.280654, 6.73018925, 0.44183692, 0.213136565, 3870.35255, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 15.0, 0.00022, 0.0, 0.0, 0.0, 0.0, 0.0, 20.15, 0.0, -1.0, -1.0, 5.7, -1.0, 0, -1.0, [(1.653, 0.0214, 1.0), (1.771, 0.4061, 1.0), (1.837, 0.7493, 1.0), (2.066, 0.996, 1.0), (10.0, 0.9822, 1.0), (10.9, 0.9851, 1.0), (12.0, 0.9608, 1.0), (12.6, 0.9656, 1.0), (13.1, 0.9569, 1.0), (13.9, 0.9647, 1.0), (14.5, 0.9512, 1.0), (15.0, 0.9213, 1.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 5.327, 0)
const ZNSE = Glass(GlassID(AGF, 1592), 2, 4.2980149, 0.036888196, 0.62776557, 0.143476258, 2.8955633, 2208.49196, 0.0, 0.0, 0.0, 0.0, 0.55, 18.0, 5.54e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 7.1, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 5.264, 0)
const IRG100 = Glass(GlassID(AGF, 1593), 1, 6.84745985, -0.000903779393, 0.508596481, 0.142844163, -0.679989166, 0.599444573, 0.0, 0.0, 0.0, 0.0, 1.0, 14.0, 8.93e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 15.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 4.67, 0)
const NACL = Glass(GlassID(AGF, 1594), 3, 1.52622845, 0.00603188287, -0.000120091689, -0.000303390814, -1.13982878e-7, -6.99966217e-11, 0.0, 0.0, 0.0, 0.0, 1.0, 20.0, -8.7e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 41.099998, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 2.164, 0)
const CALCITE = Glass(GlassID(AGF, 1595), 6, 0.8559, 0.00345744, 0.8391, 0.019881, 0.0009, 0.038809, 0.6845, 49.070025, 0.0, 0.0, 0.2, 2.2, 1.05e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.658643, -1.0, -1.0, 0, 48.541403, 0, 2.711, 0)
const F_SILICA = Glass(GlassID(AGF, 1596), 2, 0.6961663, 0.004679148, 0.4079426, 0.013512063, 0.8974794, 97.9340025, 0.0, 0.0, 0.0, 0.0, 0.21, 3.71, 2.237e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.51, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.458464, -1.0, -1.0, 0, 67.821433, 0, 2.2, 0)
const PBF2 = Glass(GlassID(AGF, 1597), 3, 1.73040656, 0.0115730841, 9.1242913e-7, -0.000887446174, -8.8211947e-7, 3.63237169e-9, 0.0, 0.0, 0.0, 0.0, 0.3, 11.9, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 29.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.766591, -1.0, -1.0, 0, 28.390315, 0, 7.728, 0)
const MGF2 = Glass(GlassID(AGF, 1598), 2, 0.48755108, 0.0018821784, 0.39875031, 0.00895188847, 2.3120353, 566.135591, 0.0, 0.0, 0.0, 0.0, 0.2, 7.04, -2.5e-7, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 9.4, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.377744, -1.0, -1.0, 0, 106.217612, 0, 3.171, 0)
const IRG3 = Glass(GlassID(AGF, 1599), 3, 1.81184032, 0.0106621309, 4.81119505e-5, -0.00316136547, -1.34328122e-5, -2.62059709e-7, 0.0, 0.0, 0.0, 0.0, 0.4047, 4.586, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 8.1, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.844836, -1.0, -1.0, 0, 32.330322, 0, 4.47, 0)
const CDTE = Glass(GlassID(AGF, 1600), 2, 6.1977889, 0.100532751, 3.2243821, 5193.5516, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 6.0, 22.0, 8.25e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 5.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 5.851, 0)
const GEO2 = Glass(GlassID(AGF, 1601), 2, 0.80686642, 0.00475722093, 0.71815848, 0.02370556, 0.85416831, 140.23133, 0.0, 0.0, 0.0, 0.0, 0.36, 4.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 6.3, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.608459, -1.0, -1.0, 0, 41.728763, 0, 3.604, 0)
const KCL = Glass(GlassID(AGF, 1602), 3, 1.47459644, 0.00505460085, -2.57480693e-5, -0.000178283009, -4.49595045e-8, -2.07572994e-11, 0.0, 0.0, 0.0, 0.0, 0.2, 30.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.2, 0.89, 10.0), (0.5, 0.98, 10.0), (1.0, 0.98, 10.0), (3.0, 0.98, 10.0), (5.0, 0.98, 10.0), (6.0, 0.98, 10.0), (7.0, 0.98, 10.0), (8.0, 0.98, 10.0), (9.0, 0.98, 10.0), (10.0, 0.98, 10.0), (12.0, 0.98, 10.0), (15.0, 0.95, 10.0), (20.0, 0.68, 10.0)], 1.490213, -1.0, -1.0, 0, 43.33857, 0, 1.98, 0)
const GE_LONG = Glass(GlassID(AGF, 1603), 3, 4.10394779, -4.31523858, 77.8489771, -0.00110046901, 5.51258845e-6, -1.04612042e-8, 0.0, 0.0, 0.0, 0.0, 6.0, 12.0, 0.00022, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 5.7, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 5.327, 0)
const AMTIR1 = Glass(GlassID(AGF, 1604), 3, 2.51253618, 0.0686942636, 0.0211563734, -0.000155048679, -4.35296121e-8, 1.99858193e-10, 0.0, 0.0, 0.0, 0.0, 1.0, 14.0, 7.05e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 12.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 4.41, 0)
const GAAS = Glass(GlassID(AGF, 1605), 3, 3.30027731, 0.169509791, -0.0147539997, -0.000242819823, 1.49822668e-7, -1.12068202e-9, 0.0, 0.0, 0.0, 0.0, 2.5, 14.0, 0.00013, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 5.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 5.317, 0)
const TI_1173 = Glass(GlassID(AGF, 1606), 1, 6.87439512, -0.000959875026, -0.0953359112, 8.52271054, -37.0505225, -8.78154363, 0.0, 0.0, 0.0, 0.0, 3.0, 12.0, 6.9e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 15.7, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 4.7, 0)
const BAF2 = Glass(GlassID(AGF, 1607), 2, 0.643356, 0.00334, 0.506762, 0.01203, 3.8261, 2151.6981, 0.0, 0.0, 0.0, 0.0, 0.27, 10.3, -4.46e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 18.4, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.474478, -1.0, -1.0, 0, 81.607872, 0, 4.886, 0)
const IRG2 = Glass(GlassID(AGF, 1608), 3, 1.85362871, 0.0121652879, 6.64999251e-5, -0.00266759682, -1.29306852e-7, -2.88756685e-7, 0.0, 0.0, 0.0, 0.0, 0.365, 4.586, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 8.8, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.891717, -1.0, -1.0, 0, 29.896648, 0, 5.0, 0)
const ZBLAN = Glass(GlassID(AGF, 1609), 2, 1.168, 0.00910116, 2.77, 625.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 4.8, -3.944e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 17.5, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 4.52, 0)
const IRG11 = Glass(GlassID(AGF, 1610), 3, 1.66048973, 0.00697542956, -7.60243563e-5, -0.00228659497, -1.13504608e-5, -4.3863678e-9, 0.0, 0.0, 0.0, 0.0, 0.48, 4.586, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 8.2, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.680932, -1.0, -1.0, 0, 44.434326, 0, 3.12, 0)
const MGO = Glass(GlassID(AGF, 1611), 2, 1.111033, 0.00507606376, 0.8460085, 0.0189118604, 7.808527, 723.234525, 0.0, 0.0, 0.0, 0.0, 0.36, 5.4, 2.0e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 10.6, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.737505, -1.0, -1.0, 0, 53.505455, 0, 3.583, 0)
const IRG9 = Glass(GlassID(AGF, 1612), 3, 1.47846793, 0.00272848051, -4.05081178e-5, -0.00176595173, -5.70474685e-6, -3.6422507e-7, 0.0, 0.0, 0.0, 0.0, 0.365, 3.303, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 16.1, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.486056, -1.0, -1.0, 0, 81.251965, 0, 3.63, 0)
const SPINEL = Glass(GlassID(AGF, 1613), 2, 1.8938, 0.0098943364, 3.0755, 250.462276, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.35, 5.5, 1.33176e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 6.97, -1.0, 0, -1.0, [], 1.716226, -1.0, -1.0, 0, 60.607355, 0, 3.577, 0)
const IRG7 = Glass(GlassID(AGF, 1614), 3, 1.54828111, 0.00547927828, -1.89907326e-5, -0.00272088985, -1.22679238e-5, -9.56555185e-7, 0.0, 0.0, 0.0, 0.0, 0.365, 3.303, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 9.6, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.564424, -1.0, -1.0, 0, 43.736647, 0, 3.06, 0)
const LIF = Glass(GlassID(AGF, 1615), 2, 0.92549, 0.0054405376, 6.96747, 1075.1841, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 10.0, -5.0e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 34.4, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.392146, -1.0, -1.0, 0, 97.294682, 0, 2.657, 0)
const AMTIR3 = Glass(GlassID(AGF, 1616), 3, 2.61445159, 0.14913534, -0.287509165, -9.57306751e-5, -5.10891622e-7, 9.89417441e-10, 0.0, 0.0, 0.0, 0.0, 3.0, 14.0, 4.72e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 13.5, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 4.7, 0)
const CAF2 = Glass(GlassID(AGF, 1617), 2, 0.5675888, 0.00252643, 0.4710914, 0.010078333, 3.8484723, 1200.556, 0.0, 0.0, 0.0, 0.0, 0.23, 9.7, -2.66e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 18.9, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.433849, -1.0, -1.0, 0, 94.995854, 0, 3.181, 0)
const GE_OLD = Glass(GlassID(AGF, 1618), 3, 4.00227267, 0.357828773, 0.26127293, -7.43656644e-5, 6.25410641e-7, -1.57100682e-9, 0.0, 0.0, 0.0, 0.0, 2.0, 12.0, 0.00022, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 5.7, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 5.327, 0)
const ZNS_IR = Glass(GlassID(AGF, 1619), 3, 2.25782977, 0.0363264418, -0.00254894212, -0.000504871979, -7.67494675e-7, 2.15389204e-10, 0.0, 0.0, 0.0, 0.0, 4.0, 18.0, 4.4e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 6.6, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 4.09, 0)
const CLRTR_OLD = Glass(GlassID(AGF, 1620), 3, 2.2596285, 0.0311097853, 0.0010251756, -0.000594355286, 4.49796618e-7, -4.12852834e-9, 0.0, 0.0, 0.0, 0.0, 0.334, 2.325, 4.33e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 6.3, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.367678, -1.0, -1.0, 0, 15.282309, 0, 4.36, 0)
const CLEARTRAN = Glass(GlassID(AGF, 1621), 2, 0.563722377, 0.0868376965, 3.53825518, 0.0283234119, 2.77421886, 1168.10941, 0.0, 0.0, 0.0, 0.0, 0.4047, 13.0, 4.33e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 6.3, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.367877, -1.0, -1.0, 0, 15.613699, 0, 4.09, 0)
const KBR = Glass(GlassID(AGF, 1622), 3, 1.53607929, 0.00762349321, -1.03487129e-5, -0.000118770682, 5.21830686e-8, -6.33528443e-11, 0.0, 0.0, 0.0, 0.0, 0.2, 30.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.2, 0.48, 10.0), (0.5, 0.98, 10.0), (1.0, 0.98, 10.0), (3.0, 0.98, 10.0), (5.0, 0.98, 10.0), (6.0, 0.98, 10.0), (7.0, 0.98, 10.0), (8.0, 0.98, 10.0), (9.0, 0.98, 10.0), (10.0, 0.98, 10.0), (12.0, 0.98, 10.0), (15.0, 0.98, 10.0), (20.0, 0.92, 10.0), (30.0, 0.26, 10.0)], 1.559967, -1.0, -1.0, 0, 31.977241, 0, 2.75, 0)
const ALON = Glass(GlassID(AGF, 1623), 2, 2.1375, 0.0105185536, 4.582, 356.001424, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 2.3, 1.637e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 5.66, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.788917, -1.0, -1.0, 0, 58.204971, 0, 3.713, 0)
const SILICON = Glass(GlassID(AGF, 1624), 2, 10.6684293, 0.0909121907, 0.003043475, 1.28766017, 1.54133408, 1.218816e6, 0.0, 0.0, 0.0, 0.0, 1.36, 11.0, 9.96e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 2.62, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 2.329, 0)
const ALN = Glass(GlassID(AGF, 1625), 9, 3.1399, 1.3786, 0.02941, 3.861, 225.9009, 0.0, 0.0, 0.0, 0.0, 0.0, 0.22, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.154291, -1.0, -1.0, 0, 50.733102, 0, 1.0, 0)
const BEO = Glass(GlassID(AGF, 1626), 2, 1.92274, 0.0062536464, 1.24209, 94.3443116, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.44, 7.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.718619, -1.0, -1.0, 0, 93.251706, 0, 1.0, 0)
const SRTIO3 = Glass(GlassID(AGF, 1627), 2, 3.042143, 0.0217828671, 1.170065, 0.0872071692, 30.83326, 1101.31458, 0.0, 0.0, 0.0, 0.0, 0.43, 3.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 8.3, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.408917, -1.0, -1.0, 0, 12.715169, 0, 5.117, 0)
const KRS5 = Glass(GlassID(AGF, 1628), 11, 1.8293958, 0.0225, 1.6675593, 0.0625, 1.1210424, 0.1225, 0.04513366, 0.2025, 12.380234, 27089.8681, 0.6, 39.0, -0.00024, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 58.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 7.372, 0)
const ZBLA = Glass(GlassID(AGF, 1629), 2, 1.291, 0.00938961, 2.76, 626.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.64, 4.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 18.7, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 4.61, 0)
const ZNS_BROAD = Glass(GlassID(AGF, 1630), 3, 2.25967042, 0.0308908108, 0.00104615832, -0.000567944951, -1.7888889e-7, -1.48598858e-9, 0.0, 0.0, 0.0, 0.0, 0.42, 18.2, 4.4e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 6.6, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.367247, -1.0, -1.0, 0, 15.304568, 0, 4.09, 0)
const IRG15 = Glass(GlassID(AGF, 1631), 3, 1.52229817, 0.00423702119, -4.45081715e-5, -0.0027249843, 2.80677977e-5, -2.99845263e-6, 0.0, 0.0, 0.0, 0.0, 0.365, 3.303, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 9.3, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.534275, -1.0, -1.0, 0, 55.700357, 0, 2.8, 0)
const CSBR = Glass(GlassID(AGF, 1632), 2, 0.9533786, 0.00820189243, 0.8303809, 0.0279396908, 2.847172, 14164.6892, 0.0, 0.0, 0.0, 0.0, 0.36, 39.0, -0.00012, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 47.2, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.69706, -1.0, -1.0, 0, 34.108015, 0, 4.488, 0)
const AGCL = Glass(GlassID(AGF, 1633), 2, 2.062508, 0.0107963322, 0.9461465, 0.0594721379, 4.300785, 5020.74704, 0.0, 0.0, 0.0, 0.0, 0.54, 21.0, -8.015e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 32.4, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 5.578, 0)
const SRF2 = Glass(GlassID(AGF, 1634), 2, 0.67805894, 0.00316855172, 0.37140533, 0.011666218, 3.8484723, 1200.55597, 0.0, 0.0, 0.0, 0.0, 0.21, 11.5, -4.8e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 18.1, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.437925, -1.0, -1.0, 0, 91.593259, 0, 4.277, 0)
const ZNS_VIS = Glass(GlassID(AGF, 1635), 1, 5.10214513, -0.00251680615, 0.144900943, 0.0101951975, -0.000638706932, 0.000189682803, 0.0, 0.0, 0.0, 0.0, 0.42, 1.1, 6.4e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 6.6, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.367356, -1.0, -1.0, 0, 15.554146, 0, 4.09, 0)
const IRGN6 = Glass(GlassID(AGF, 1636), 3, 1.57574696, 0.00468056186, -5.25501605e-5, -0.00254947513, -2.84832471e-5, 8.10936671e-8, 0.0, 0.0, 0.0, 0.0, 0.4047, 4.258, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 6.3, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.589096, -1.0, -1.0, 0, 56.33564, 0, 2.81, 0)
const SAPPHIRE = Glass(GlassID(AGF, 1637), 2, 1.023798, 0.00377588, 1.058264, 0.0122544, 5.280792, 321.3616, 0.0, 0.0, 0.0, 0.0, 0.2, 5.5, 1.8e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 6.65, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.768234, -1.0, -1.0, 0, 72.237215, 0, 3.987, 0)
end # module


export iauGst00a
"""
Greenwich apparent sidereal time (consistent with IAU 2000
resolutions).

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

Status:  canonical model.

Given:
   uta,utb    double    UT1 as a 2-part Julian Date (Notes 1,2)
   tta,ttb    double    TT as a 2-part Julian Date (Notes 1,2)

Returned (function value):
               double    Greenwich apparent sidereal time (radians)

Notes:

   1. The UT1 and TT dates uta+utb and tta+ttb respectively, are both
   Julian Dates, apportioned in any convenient way between the
   argument pairs.  For example, JD=2450123.7 could be expressed in
   any of these ways, among others:

         Part A        Part B

      2450123.7           0.0       (JD method)
      2451545.0       -1421.3       (J2000 method)
      2400000.5       50123.2       (MJD method)
      2450123.5           0.2       (date & time method)

   The JD method is the most natural and convenient to use in
   cases where the loss of several decimal digits of resolution
   is acceptable (in the case of UT;  the TT is not at all critical
   in this respect).  The J2000 and MJD methods are good compromises
   between resolution and convenience.  For UT, the date & time
   method is best matched to the algorithm that is used by the Earth
   Rotation Angle function, called internally:  maximum precision is
   delivered when the uta argument is for 0hrs UT1 on the day in
   question and the utb argument lies in the range 0 to 1, or vice
   versa.

   2. Both UT1 and TT are required, UT1 to predict the Earth rotation
   and TT to predict the effects of precession-nutation.  If UT1 is
   used for both purposes, errors of order 100 microarcseconds
   result.

   3. This GAST is compatible with the IAU 2000 resolutions and must be
   used only in conjunction with other IAU 2000 compatible
   components such as precession-nutation.

   4. The result is returned in the range 0 to 2pi.

   5. The algorithm is from Capitaine et al. (2003) and IERS
   Conventions 2003.

Called:
   iauGmst00    Greenwich mean sidereal time, IAU 2000
   iauEe00a     equation of the equinoxes, IAU 2000A
   iauAnp       normalize angle into range 0 to 2pi

References:

   Capitaine, N., Wallace, P.T. and McCarthy, D.D., "Expressions to
   implement the IAU 2000 definition of UT1", Astronomy &
   Astrophysics, 406, 1135-1149 (2003)

   McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
   IERS Technical Note No. 32, BKG (2004)

This revision:  2013 June 18

SOFA release 2018-01-30

Copyright (C) 2018 IAU SOFA Board.  See notes at end.
"""

# double iauGst00a(double uta, double utb, double tta, double ttb)

function iauGst00a(uta::Real, utb::Real, tta::Real, ttb::Real)
   return ccall((:iauGst00a, libsofa_c), Cdouble,
               (Cdouble, Cdouble, Cdouble, Cdouble),
               convert(Float64, uta), convert(Float64, utb),
               convert(Float64, tta), convert(Float64, ttb))
end


module FluxExperimental

using CUDA
using FileIO
using Flux
using Functors
using LinearAlgebra
using Requires
using Serialization
using Statistics
using Wandb
using Zygote

using Flux: hasaffine, ones32, zeros32, _isactive
using Functors: isleaf, children, _default_walk, functor
import NNlibCUDA: batchnorm, ∇batchnorm

function __init__()
    @require ChemistryFeaturization = "6c925690-434a-421d-aea7-51398c5b007a" begin
        include("graph_data.jl")
        # Atomic Graph Net Layers
        export batch_graph_data, BatchedAtomicGraph
    end
end

on_gpu(arr::CuArray) = true
on_gpu(arr::AbstractArray) = false

"""
    on_gpu(layer)

Checks if the parameters and states of the layer is in GPU memory. If one is in GPU and other is not,
then a warning is displayed. If there are no parameters and states then false is returned by default.
"""
on_gpu(layer) = on_gpu(destructure_parameters_states(layer)[1:2]...)

function on_gpu(p, s)
    if length(p) > 0 && length(s) > 0
        p_device = on_gpu(p)
        s_device = on_gpu(s)
        if !(p_device == s_device)
            @warn "Layer parameters and states are not on the same device"
        end
        return p_device && s_device
    elseif length(p) == 0
        return on_gpu(s)
    elseif length(s) == 0
        return on_gpu(p)
    else
        return false
    end
end

abstract type AbstractFluxLayer end

function Base.show(io::IO, l::AbstractFluxLayer)
    p, s, _ = destructure_parameters_states(l)
    device = on_gpu(p, s) ? "GPU" : "CPU"
    return print(io, string(typeof(l).name.name), "() ", string(length(p)), " Trainable Parameters & ",
                 string(length(s)), " States & Device = ", device)
end

"""
    debug_backward_pass(msg::String)

Prints the `msg` during the forward pass. During the backwards pass prints `∇(msg)`. Also prints
the time it took to reach the backward pass since the forward pass happended.
"""
@inline function debug_backward_pass(msg::String)
    start_time = Zygote.@ignore time()
    return Zygote.hook(Δ -> begin
                           println("∇(" * msg * ") -- Took $(time() - start_time) s")
                           return Δ
                       end, println(msg))
end

"""
    enable_fast_mode!()

Enable CUDA.FAST_MATH mode. Note this will reduce floating point precision but we rarely care about that.
"""
function enable_fast_mode!()
    CUDA.math_mode!(CUDA.FAST_MATH)
    return nothing
end

# Needed for destructure
include("layers/fchain.jl")

include("destructure.jl")
include("saving.jl")
include("utils.jl")

# Useful Layer Implementations
include("layers/norm_utils.jl")
include("layers/functional_wrappers.jl")
include("layers/weight_norm.jl")
include("layers/spectral_norm.jl")
include("layers/dropout.jl")
include("layers/utils.jl")
include("layers/agn.jl")
include("layers/group_norm.jl")
include("layers/batch_norm.jl")

# Extending the logging capabilities of wandb
include("wandb.jl")

# Common Utilities
export destructure_parameters_states, destructure_parameters
export enable_fast_mode!
export save_flux_model, load_flux_model
export debug_backward_pass
# Layers
export conv1x1, conv3x3, conv5x5, conv1x1_norm, conv3x3_norm, conv5x5_norm, conv_norm, downsample_module,
       upsample_module
export FChain
export WeightNorm, SpectralNorm
export VariationalHiddenDropout, update_is_variational_hidden_dropout_mask_reset_allowed
export AGNConv, AGNMaxPool, AGNMeanPool
export GroupNormV2, BatchNormV2
export ReshapeLayer, FlattenLayer, SelectDim, NoOpLayer
# Logging
export ParameterStateGradientWatcher

end


export get_ts_s;
export get_units_by_kind;
export get_units_by_bus;
export get_bus;
export sc_opf;
export print_nz;

using Xpress;

function get_bus(launcher::Launcher, names::Set{String})
    result = Set{String}();
    for name in names
        if !haskey(launcher.gen_type_bus, name)
            push!(result, name);
        end
    end
    return result;
end

function get_ech_ts_s_name(launcher::Launcher)
    ech_set = Set{DateTime}();
    ts_set = Set{DateTime}();
    s_set =  Set{String}();
    name_set = Set{String}();
    for uncertainty in launcher.uncertainties
        ts = uncertainty[1][U_H];
        ech = uncertainty[1][U_ECH];
        s = uncertainty[1][U_SCENARIO];
        name = uncertainty[1][U_NAME];
        push!(ech_set, ech);
        push!(ts_set, ts);
        push!(s_set, s);
        push!(name_set, name);
    end
    return sort(collect(ech_set)), sort(collect(ts_set)), sort(collect(s_set)), name_set;
end

function get_units_by_kind(launcher::Launcher)
    result = Dict{String,Dict{String,String}}();
    result[K_LIMITABLE] = Dict{String,String}();
    result[K_IMPOSABLE] = Dict{String,String}();
    for gen in launcher.gen_type_bus
        kind = gen[2][1];
        push!(result[kind],  gen[1] => gen[2][2]);
    end
    return result;
end

function get_units_by_bus(launcher::Launcher, buses::Set{String})
    result = Dict{String,Dict{String,Vector{String}}}();
    result[K_LIMITABLE] = Dict{String,Vector{String}}([bus => Vector{String}() for bus in buses]);
    result[K_IMPOSABLE] = Dict{String,Vector{String}}([bus => Vector{String}() for bus in buses]);
    for gen in launcher.gen_type_bus
        name = gen[1];
        kind = gen[2][1];
        bus  = gen[2][2];
        tmp = get(result[kind], bus, Vector{String}())
        push!(tmp, name);
        result[kind][bus] = tmp;
    end
    return result;
end
function add_limitable!(launcher::Launcher, ech::DateTime, model, units_by_kind, TS, S)
    p_lim = Dict{Tuple{String,DateTime},VariableRef}();
    is_limited = Dict{Tuple{String,DateTime,String},VariableRef}();
    p_enr = Dict{Tuple{String,DateTime,String},VariableRef}();
    is_limited_x_p_lim = Dict{Tuple{String,DateTime,String},VariableRef}();
    c_lim = Dict{Tuple{String,DateTime,String},VariableRef}();
    if ! launcher.NO_LIMITABLE
        for kvp in units_by_kind[K_LIMITABLE], ts in TS
            gen = kvp[1];
            p_lim[gen, ts] =  @variable(model, base_name = @sprintf("p_lim[%s,%s]", gen, ts), lower_bound = 0);
            pLimMax = 0;
            for s in S
                pLimMax = max(pLimMax, launcher.uncertainties[gen, s, ts, ech]);
            end
            for s in S
                name =  @sprintf("is_limited[%s,%s,%s]", gen, ts, s);
                is_limited[gen, ts, s] = @variable(model, base_name = name, binary = true);

                name =  @sprintf("is_limited_x_p_lim[%s,%s,%s]", gen, ts, s);
                is_limited_x_p_lim[gen, ts, s] = @variable(model, base_name = name, lower_bound = 0);

                name =  @sprintf("c_lim[%s,%s,%s]", gen, ts, s);
                c_lim[gen, ts, s] = @variable(model, base_name = name, lower_bound = 0);

                @constraint(model, is_limited_x_p_lim[gen, ts, s] <= p_lim[gen, ts]);
                @constraint(model, is_limited_x_p_lim[gen, ts, s] <= is_limited[gen, ts, s] * pLimMax);
                @constraint(model, is_limited_x_p_lim[gen, ts, s] + pLimMax * (1 - is_limited[gen, ts, s]) - p_lim[gen, ts] >= 0);

                p0 = launcher.uncertainties[gen, s, ts, ech];
                name =  @sprintf("p_enr[%s,%s,%s]", gen, ts, s);
                p_enr[gen, ts, s] = @variable(model, base_name = name, lower_bound = 0, upper_bound = p0);
                @constraint(model, p_enr[gen, ts, s] == (1 - is_limited[gen, ts, s]) * p0 + is_limited_x_p_lim[gen, ts, s]);
                @constraint(model, p_enr[gen, ts, s] <= p_lim[gen, ts]);

                @constraint(model, c_lim[gen, ts, s] >= p0 - p_lim[gen, ts]);

                if launcher.NO_LIMITATION
                    @constraint(model, is_limited[gen, ts, s] == 0);
                end
            end
        end
    end
    return Workflow.LimitableModeler(p_lim, is_limited, p_enr, is_limited_x_p_lim, c_lim);
    # return p_lim, is_limited, is_limited_x_p_lim, c_lim;
end

function add_imposable!(launcher::Launcher, ech, model,  units_by_kind, TS, S)
    p_imposable = Dict{Tuple{String,DateTime,String},VariableRef}();
    p_is_imp = Dict{Tuple{String,DateTime},VariableRef}();
    p_is_imp_and_on = Dict{Tuple{String,DateTime},VariableRef}();
    p_imp = Dict{Tuple{String,DateTime},VariableRef}();
    p_start = Dict{Tuple{String,DateTime},VariableRef}();
    p_on = Dict{Tuple{String,DateTime},VariableRef}();
    c_imp_pos  = Dict{Tuple{String,DateTime,String},VariableRef}();
    c_imp_neg   = Dict{Tuple{String,DateTime,String},VariableRef}();
    if ! launcher.NO_IMPOSABLE
        for kvp in units_by_kind[K_IMPOSABLE]
            gen = kvp[1];
            for ts in TS
                pMin = launcher.units[gen][1];
                pMax = launcher.units[gen][2];

                name =  @sprintf("p_imp[%s,%s]", gen, ts);
                p_imp[gen, ts] = @variable(model, base_name = name);

                name =  @sprintf("p_is_imp[%s,%s]", gen, ts);
                p_is_imp[gen, ts] = @variable(model, base_name = name, binary = true);

                name =  @sprintf("p_start[%s,%s]", gen, ts);
                p_start[gen, ts] = @variable(model, base_name = name, binary = true);

                name =  @sprintf("p_on[%s,%s]", gen, ts);
                p_on[gen, ts] = @variable(model, base_name = name, binary = true);

                name =  @sprintf("p_is_imp_and_on[%s,%s]", gen, ts);
                p_is_imp_and_on[gen, ts] = @variable(model, base_name = name, binary = true);

                # if in(gen, ["park_city", "alta", "sundance"])
                #     println("WARNING");
                #     @constraint(model,  p_on[gen, ts]==0);
                #     @constraint(model,  p_is_imp[gen, ts]==0);
                # end
                for s in S
                    p0 = launcher.uncertainties[gen, s, ts, ech];
                    name =  @sprintf("p_imposable[%s,%s,%s]", gen, ts, s);
                    p_imposable[gen, ts, s] = @variable(model, base_name = name, lower_bound = 0);

                    name =  @sprintf("c_imp_pos[%s,%s,%s]", gen, ts, s);
                    c_imp_pos[gen, ts, s] = @variable(model, base_name = name, lower_bound = 0);

                    name =  @sprintf("c_imp_neg[%s,%s,%s]", gen, ts, s);
                    c_imp_neg[gen, ts, s] = @variable(model, base_name = name, lower_bound = 0);

                    @constraint(model, p_imposable[gen, ts, s] == p0 + c_imp_pos[gen, ts, s] - c_imp_neg[gen, ts, s]);

                    @constraint(model, p_imposable[gen, ts, s] == (1 - p_is_imp[gen, ts]) * p0 + p_imp[gen, ts]);
                    @constraint(model, p_imp[gen, ts] <= pMax * p_is_imp_and_on[gen, ts]);
                    @constraint(model, p_imp[gen, ts] >= pMin * p_is_imp_and_on[gen, ts]);

                    @constraint(model, p_is_imp_and_on[gen, ts] <= p_is_imp[gen, ts]);
                    @constraint(model, p_is_imp_and_on[gen, ts] <= p_on[gen, ts]);
                    @constraint(model, 1 + p_is_imp_and_on[gen, ts] >= p_on[gen, ts] + p_is_imp[gen, ts]);
                end
            end
            i = 0;
            for ts in TS
                i += 1;
                if i > 1
                    ts_1 = TS[i - 1];
                    @constraint(model, p_start[gen, ts] <= p_on[gen, ts]);
                    @constraint(model, p_start[gen, ts] <= 1 - p_on[gen, ts_1]);
                    @constraint(model, p_start[gen, ts] >= p_on[gen, ts] - p_on[gen, ts_1]);
                else
                    prev0 = launcher.previsions[gen, ts, ech];
                    println(@sprintf("%s %s is at %f\n", gen, ts, prev0));
                    if abs(prev0) < 1
                        @constraint(model, p_start[gen, ts] == p_on[gen, ts]);
                    end
                end
            end
        end
    end
    return Workflow.ImposableModeler(p_imposable, p_is_imp, p_imp, p_is_imp_and_on, p_start, p_on, c_imp_pos, c_imp_neg);
end

function add_eod_constraint!(launcher::Launcher, ech, model, units_by_bus, TS, S, BUSES, v_lim::Workflow.LimitableModeler, v_imp::Workflow.ImposableModeler, v_res::Workflow.ReserveModeler, netloads )
    eod_expr = Dict([(ts, s) => AffExpr(0) for ts in TS, s in S]);
    for ts in TS, s in S
        for bus in BUSES
            if ! launcher.NO_IMPOSABLE
                for gen in units_by_bus[K_IMPOSABLE][bus]
                    eod_expr[ts, s] += v_imp.p_imposable[gen, ts, s];
                end
            end
            if ! launcher.NO_LIMITABLE
                for gen in units_by_bus[K_LIMITABLE][bus]
                    p0 = launcher.uncertainties[gen, s, ts, ech];
                    # println(gen, " ", ts, " ", s, " ", p0);
                    eod_expr[ts, s] +=  ((1 -  v_lim.is_limited[gen, ts, s]) * p0 + v_lim.is_limited_x_p_lim[gen, ts, s]);
                end
            end
        end
    end
    for ts in TS, s in S
        eod_expr[ts, s] += v_res.p_res_pos[ts, s];
        eod_expr[ts, s] -= v_res.p_res_neg[ts, s];
    end

    # println(launcher.uncertainties)
    for ts in TS, s in S
        load = 0;
        for bus in BUSES
            load += netloads[bus, ts, s];
        end
        @constraint(model, eod_expr[ts, s] == load);
    end
end

function add_reserve!(launcher::Launcher, ech, model, TS, S, p_res_min, p_res_max)
    p_res_pos = Dict{Tuple{DateTime,String},VariableRef}();
    p_res_neg = Dict{Tuple{DateTime,String},VariableRef}();
    for ts in TS, s in S
        name =  @sprintf("p_res_pos[%s,%s]", ts, s);
        p_res_pos[ts, s] = @variable(model, base_name = name, lower_bound = 0, upper_bound = p_res_max);
        name =  @sprintf("p_res_neg[%s,%s]", ts, s);
        p_res_neg[ts, s] = @variable(model, base_name = name, lower_bound = 0, upper_bound = -p_res_min);
    end
    return Workflow.ReserveModeler(p_res_pos, p_res_neg);
end

function add_obj!(launcher::Launcher, model, TS, S, v_lim::Workflow.LimitableModeler, v_imp::Workflow.ImposableModeler, v_res::Workflow.ReserveModeler)
    eod_obj = AffExpr(0);
    w_lim = 1 / length(S);
    for x in v_lim.c_lim
        gen = x[1][1];
        cProp = launcher.units[gen][4];
        eod_obj += w_lim * cProp * x[2];
    end
    if length(v_lim.is_limited) > 0
        eod_obj += sum(1e-4 * x[2] for x in v_lim.is_limited);
    end
    for x in v_imp.p_start
        gen = x[1][1];
        cStart = launcher.units[gen][3];
        eod_obj += cStart * x[2];
    end
    for x in v_imp.c_imp_pos
        gen = x[1][1];
        cProp = launcher.units[gen][4];
        eod_obj += w_lim * cProp * x[2];
    end
    for x in v_imp.c_imp_neg
        gen = x[1][1];
        cProp = launcher.units[gen][4];
        eod_obj += w_lim * cProp * x[2];
    end
    for x in v_res.p_res_pos
        eod_obj += (1e-3)*w_lim*x[2];
    end
    for x in v_res.p_res_neg
        eod_obj += (1e-3)*w_lim* x[2];
    end
    @objective(model, Min, eod_obj);
end

function add_flow!(launcher::Workflow.Launcher, model, TS, S,  units_by_bus, v_lim::Workflow.LimitableModeler, v_imp::Workflow.ImposableModeler, netloads)
    v_flow = Dict{Tuple{String,DateTime,String},VariableRef}();

    ptdf_by_branch = Dict{String,Dict{String,Float64}}() ;
    for ptdf in launcher.ptdf
        branch, bus = ptdf[1]
        if ! haskey(ptdf_by_branch, branch)
            ptdf_by_branch[branch] = Dict{String,Float64}();
        end
        ptdf_by_branch[branch][bus] = ptdf[2];
    end
    println("ptdf_by_branch : ", ptdf_by_branch);
    for ts in TS, s in S
        for limit in launcher.limits
            branch = limit[1];
            v = limit[2]
            name =  @sprintf("p_flow[%s, %s,%s]", branch, ts, s);
            v_flow[branch, ts, s] = @variable(model, base_name = name, lower_bound = -v, upper_bound = v);
            f_ref = 0;
            xpr = AffExpr(0);
            for (bus, ptdf) in ptdf_by_branch[branch]
                f_ref -= ptdf * netloads[bus, ts, s];
                if ! launcher.NO_IMPOSABLE
                    for gen in units_by_bus[K_IMPOSABLE][bus]
                        xpr += v_imp.p_imposable[gen, ts, s] * ptdf;
                    end
                end
                if ! launcher.NO_LIMITABLE
                    for gen in units_by_bus[K_LIMITABLE][bus]
                        xpr += v_lim.p_enr[gen, ts, s] * ptdf;
                    end
                end
            end
            @constraint(model, v_flow[branch, ts, s] == f_ref + xpr);
        end
    end
    return v_flow;
end

function sc_opf(launcher::Launcher, ech::DateTime, p_res_min, p_res_max)
    ##############################################################
    ### optimisation modelling sets
    ##############################################################
    ECH, TS, S, NAMES = Workflow.get_ech_ts_s_name(launcher);
    BUSES =  Workflow.get_bus(launcher, NAMES);
    units_by_kind = Workflow.get_units_by_kind(launcher);
    units_by_bus =  Workflow.get_units_by_bus(launcher, BUSES);

    println("NAMES : ", NAMES);
    println("BUSES : ", BUSES);
    println("units_by_bus : ", units_by_bus);

    println("Number of scenario ", length(S));
    println("Number of time step is ", length(TS));
    println(ECH);
    K_IMPOSABLE = Workflow.K_IMPOSABLE;
    K_LIMITABLE = Workflow.K_LIMITABLE;

    netloads = Dict([(bus, ts, s) => launcher.uncertainties[bus, s, ts, ech] for bus in BUSES, ts in TS, s in S]);
    eod_slack = Dict([(ts,s) => 0.0 for s in S, ts in TS]);
    factor = Dict([name => 1 for name in NAMES]);
    for bus in BUSES
        factor[bus] = -1;
    end
    for ((name, s, ts, ech), v) in launcher.uncertainties
        eod_slack[ts,  s] += factor[name] * v;
    end
    println("eod_slack is $eod_slack");
    ##############################################################
    # to be in a function ...
    ##############################################################

    model = Model();
    v_lim = add_limitable!(launcher, ech, model, units_by_kind, TS, S);
    p_lim = v_lim.p_lim;
    is_limited = v_lim.is_limited;
    is_limited_x_p_lim = v_lim.is_limited_x_p_lim;
    c_lim = v_lim.c_lim;

    v_imp = add_imposable!(launcher, ech, model, units_by_kind, TS, S);

    p_imposable = v_imp.p_imposable;
    p_is_imp = v_imp.p_is_imp;
    p_imp = v_imp.p_imp;
    p_start = v_imp.p_start;
    p_on = v_imp.p_on;

    v_res = add_reserve!(launcher, ech, model, TS, S, p_res_min, p_res_max);

    add_eod_constraint!(launcher, ech, model, units_by_bus, TS, S, BUSES, v_lim, v_imp, v_res, netloads);

    v_flow = add_flow!(launcher, model, TS, S, units_by_bus,  v_lim, v_imp, netloads);

    # println(units_by_bus)
    # println(eod_expr)
    add_obj!(launcher,  model, TS, S, v_lim, v_imp, v_res);

    # println(model)
    set_optimizer(model, Xpress.Optimizer);
    optimize!(model);

    # print_nz(p_imposable);
    # print_nz(p_is_imp);
    # print_nz(p_start);
    # print_nz(p_on);
    # print_nz(is_limited);

    println("NO_IMPOSABLE   : ", launcher.NO_IMPOSABLE);
    println("NO_LIMITABLE   : ", launcher.NO_LIMITABLE);
    println("NO_LIMITATION  : ", launcher.NO_LIMITATION);
    println("BUSES          : ", BUSES);
    println("NAMES          : ", NAMES);
    println("TS             : ", TS);
    println("S              : ", S);

    @printf("%30s[%s,%s] %4s %10s %10s %10s\n", "gen", "ts", "s", "b_enr", "p_enr", "p_lim_enr", "p0");
    if ! launcher.NO_LIMITABLE
        # println(launcher.uncertainties)
        for bus in BUSES, ts in TS, s in S, gen in units_by_bus[K_LIMITABLE][bus]
            p0 = launcher.uncertainties[gen, s, ts, ech];
            b_enr = value(is_limited[gen, ts, s]);
            p_enr = value((1 - is_limited[gen, ts, s]) * p0 + is_limited_x_p_lim[gen, ts, s]);
            p_lim_enr = value(p_lim[gen, ts]);
            # println(value(is_limited_x_p_lim[gen, ts, s]) - b_enr * p_lim_enr)
            if b_enr > 0.5
                @printf("%10s[%s,%s] %4d %10.3f %10.3f %10.3f\n", gen, ts, s, b_enr, p_enr, p_lim_enr, p0);
            end
            # println(gen, " : ", value((1 - is_limited[gen, ts, s]) * p0 + is_limited_x_p_lim[gen, ts, s]))
        end
    end
    if ! launcher.NO_IMPOSABLE
        # println(launcher.uncertainties)
        for bus in BUSES, ts in TS, gen in units_by_bus[K_IMPOSABLE][bus]
            b_start = value(p_start[gen, ts]);
            b_imp = value(p_is_imp[gen, ts]);
            if b_imp > 0.5
                @printf("%10s[%s] IMPOSED\n", gen, ts);
            end
            if b_start > 0.5
                @printf("%10s[%s] STARTED\n", gen, ts);
            end
            b_on = value(p_on[gen, ts]);
            for s in S
                if b_on > 0.5
                    @printf("%10s[%s] %10.3f\n", gen, ts, value(v_imp.p_imposable[gen, ts, s]));
                end
            end

        end
    end
    println("v_res.p_res_pos");
    print_nz(v_res.p_res_pos);
    println("v_res.p_res_neg");
    print_nz(v_res.p_res_neg);
    println("v_res.v_flow");
    print_nz(v_flow);
    write_output_csv(launcher, ech, TS, S, v_lim, v_imp);

    return model, p_lim, p_imposable;
end

function print_nz(variables)
    for x in variables
        if abs(value(x[2])) > 1e-6
            println(x, " = ", value(x[2]));
        end
    end
end

function write_output_csv(launcher::Workflow.Launcher, ech, TS, S, v_lim::Workflow.LimitableModeler, v_imp::Workflow.ImposableModeler)
    open(joinpath(launcher.dirpath, "limitation.csv"), "w") do file
        write(file, @sprintf("%s;%s;%s;%s;%s;%s;\n", "gen", "TS","S", "is_lim", "p_lim", "p0"));
        for x in v_lim.p_enr
            key = x[1];
            gen = key[1];
            ts =  key[2];
            s = key[3];

            is_lim = false;
            if value(v_lim.is_limited[key]) > 0.5
                is_lim = true;
            end
            p_sol = value(v_lim.p_lim[gen, ts]);
            p0 = launcher.uncertainties[gen, s, ts, ech];
            write(file, @sprintf("%s;%s;%s;%s;%f;%f\n", gen, ts,s, is_lim, p_sol, p0));
        end
    end
    open(joinpath(launcher.dirpath, "imposition.csv"), "w") do file
        write(file, @sprintf("%s;%s;%s;%s;%s;%s;\n", "gen", "TS","S", "is_imp", "p_imp", "p0"));
        for x in v_imp.p_imposable
            key = x[1];
            gen, ts, s = key;

            is_imp = false;
            if value(v_imp.p_is_imp[gen, ts]) > 0.5
                is_imp = true;
            end
            p_sol = value(v_imp.p_imposable[gen, ts, s]);
            p0 = launcher.uncertainties[gen, s, ts, ech];
            write(file, @sprintf("%s;%s;%s;%s;%f;%f\n", gen, ts,s, is_imp, p_sol, p0));
        end
    end
end


function check_method(local_search_method::String)
    if !(local_search_method in ["mutation", "powell", "de", "cmaes"])
        error(
            "\"$local_search_method\": Invalid local_search_method. "
            * "Should be one of [\"mutaion\", \"Powell\", \"DE\", \"CMAES\"]"
        )
    elseif local_search_method in ["powell", "de"] && !isinstalled("scipy.optimize")
        error(
            "Cannnot import scipy.optimize functions. Use \"mutation\" for local search."
        )
    end
end

function optimize(
        model::Model,
        nth_param_set::Int64;
        popsize::Int64=5,
        max_generation::Int64=10000,
        initial_threshold::Float64=1e12,
        allowable_error::Float64=0.0,
        n_children::Int64=50,
        maxiter::Int64=100,
        local_search_method::String="mutation")
    check_method(
        lowercase(local_search_method)
    )

    for dir in ["fitparam", "logs"]
        if !isdir(
            joinpath(
                model.path,
                dir
            )
        )
            mkdir(
                joinpath(
                    model.path,
                    dir
                )
            )
        end
    end

    try
        files = readdir(
            joinpath(
                model.path,
                "fitparam",
                "$nth_param_set"
            )
        )
        for file in files
            if occursin(".dat", file)
                rm(
                    joinpath(
                        model.path,
                        "fitparam",
                        "$nth_param_set",
                        "$file"
                    )
                )
            end
        end
    catch
        mkdir(
            joinpath(
                model.path,
                "fitparam",
                "$nth_param_set"
            )
        )
    end

    search_rgn::Matrix{Float64} = model.search_region()

    n_population::Int64 = popsize * size(search_rgn, 2)
    n_gene::Int64 = size(search_rgn, 2)

    ga_v2(
        model=model,
        nth_param_set=nth_param_set,
        max_generation=max_generation,
        n_population=n_population,
        n_gene=n_gene,
        initial_threshold=initial_threshold,
        allowable_error=allowable_error,
        local_search_method=lowercase(local_search_method),
        n_children=n_children,
        maxiter=maxiter
    )
end

function optimize_continue(
        model::Model,
        nth_param_set::Int64;
        popsize::Int64=5,
        max_generation::Int64=10000,
        initial_threshold::Float64=1e12,
        allowable_error::Float64=0.0,
        n_children::Int64=50,
        maxiter::Int64=100,
        local_search_method::String="mutation",
        p0_bounds::Vector{Float64}=[0.1, 10.0])
    check_method(
        lowercase(local_search_method)
    )

    search_rgn::Matrix{Float64} = model.search_region()

    n_population::Int64 = popsize * size(search_rgn, 2)
    n_gene::Int64 = size(search_rgn, 2)

    if !isdir(
        joinpath(
            model.path,
            "fitparam",
            "$nth_param_set"
        )
    )
        mkdir(
            joinpath(
                model.path,
                "fitparam",
                "$nth_param_set"
            )
        )

        ga_v2(
            model=model,
            nth_param_set=nth_param_set,
            max_generation=max_generation,
            n_population=n_population,
            n_gene=n_gene,
            initial_threshold=initial_threshold,
            allowable_error=allowable_error,
            local_search_method=lowercase(local_search_method),
            n_children=n_children,
            maxiter=maxiter
        )
    else
        ga_v2_continue(
            model=model,
            nth_param_set=nth_param_set,
            max_generation=max_generation,
            n_population=n_population,
            n_gene=n_gene,
            initial_threshold=initial_threshold,
            allowable_error=allowable_error,
            local_search_method=lowercase(local_search_method),
            n_children=n_children,
            maxiter=maxiter,
            p0_bounds=p0_bounds
        )
    end
end


#MDDatasets DataRS (Recursive Sweep) defninitions
#-------------------------------------------------------------------------------

#==Main types
===============================================================================#

#Linked-list representation of multi-dimensional datasets:
#-------------------------------------------------------------------------------
mutable struct DataRS{T} <: DataMD
	sweep::PSweep
	elem::Vector{T}

	function DataRS{T}(sweep::PSweep, elem::Vector{T}) where {T}
		if !elemallowed(DataRS, eltype(elem))
			msg = "Can only create DataRS{T} for T ∈ {DataRS, DataF1, DataFloat, DataInt, DataComplex}"
			throw(ArgumentError(msg))
		elseif length(sweep) != length(elem)
			throw(ArgumentError("sweep length does not match number of elem"))
		end
		return new(sweep, elem)
	end
end

#Shorthand (because default (non-parameterized) constructor was overwritten):
DataRS(sweep::PSweep, elem::Vector{T}) where T = DataRS{T}(sweep, elem)

elemallowed(::Type{DataRS}, t::Type{T}) where T = elemallowed(DataMD, t) #Allow basic types
elemallowed(::Type{DataRS}, ::Type{DataRS}) = true #Also allow recursive structures

#Generate empty DataRS structure:
(::Type{DataRS{T}})(sweep::PSweep) where T = DataRS{T}(sweep, Array{T}(undef, length(sweep)))

#==Type promotions
===============================================================================#
Base.promote_rule(::Type{T1}, ::Type{T2}) where {T1<:DataRS, T2<:Number} = DataRS

#==Accessor functions
===============================================================================#
Base.eltype(d::DataRS{T}) where T = T
Base.length(d::DataRS) = length(d.elem)

#==Help with construction
===============================================================================#

#Implement "fill(DataRS, ...) do sweepval" syntax:
function Base.fill!(fn::Function, d::DataRS)
	for i in 1:length(d.sweep)
		d.elem[i] = fn(d.sweep.v[i])
	end
	return d
end
Base.fill(fn::Function, ::Type{DataRS{T}}, sweep::PSweep) where T =
	fill!(fn, DataRS(sweep, Array{T}(undef, length(sweep))))
Base.fill(fn::Function, ::Type{DataRS}, sweep::PSweep) = fill(fn, DataRS{DataRS}, sweep)

#==Data generation
===============================================================================#
function _ensuresweepunique(d::DataRS, sweepid::String)
	if sweepid == d.sweep.id
		msg = "Sweep occurs multiple times in DataRS: $sweepid"
		throw(ArgumentError(msg))
	end
end

#Define "parameter".
#(Generates a DataRS object containing the value of a given swept parameter)
#-------------------------------------------------------------------------------

#Deal with non-leaf elements, once the sweep value is found:
function _parameter(d::DataRS{DataRS}, sweepid::String, sweepval::T) where T
	_ensuresweepunique(d, sweepid)
	elem = DataRS[_parameter(d.elem[i], sweepid, sweepval) for i in 1:length(d.sweep)]
	return DataRS(d.sweep, elem)
end

#Deal with leaf elements, once the sweep value is found:
function _parameter(d::DataRS, sweepid::String, sweepval::T) where T
	_ensuresweepunique(d, sweepid)
	elem = T[sweepval for i in 1:length(d.sweep)]
	return DataRS(d.sweep, elem)
end

#Main "parameter" algorithm (non-leaf elements):
function parameter(d::DataRS{DataRS}, sweepid::String)
	if sweepid == d.sweep.id #Sweep found
		elem = DataRS[_parameter(d.elem[i], sweepid, d.sweep.v[i]) for i in 1:length(d.sweep)]
	else
		elem = DataRS[parameter(d.elem[i], sweepid) for i in 1:length(d.sweep)]
	end
	return DataRS(d.sweep, elem)
end
#Main "parameter" algorithm (leaf elements):
function parameter(d::DataRS, sweepid::String)
	T = eltype(d.sweep.v)
	if sweepid == d.sweep.id #Sweep found
		return DataRS(d.sweep, d.sweep.v)
	else
		msg = "Sweep not found in DataRS: $sweepid"
		throw(ArgumentError(msg))
	end
end

#Generate DataRS from DataHR.
#-------------------------------------------------------------------------------
function _buildDataRS(d::DataHR, firstinds::Vector{Int})
	curidx = length(firstinds) + 1
	sweep = d.sweeps[curidx]
	if curidx < length(d.sweeps)
		result = DataRS{DataRS}(sweep)
		for i in 1:length(sweep.v)
			result.elem[i] = _buildDataRS(d, vcat(firstinds, i))
		end
	else #Last index.  Copy data over:
		result = DataRS{eltype(d.elem)}(sweep)
		for i in 1:length(sweep.v)
			result.elem[i] = d.elem[firstinds..., i]
		end
	end
	return result
end

function DataRS(d::DataHR)
	return _buildDataRS(d, Int[])
end

#==User-friendly show functions
===============================================================================#

#Print leaf element:
function printDataRSelem(io::IO, ds::DataRS, idx::Int, indent::String)
	if isassigned(ds.elem, idx)
		println(io, ds.elem[idx])
	else
		println(io, indent, "UNDEFINED")
	end
end
#Print next level of recursive DataRS:
function printDataRSelem(io::IO, ds::DataRS{T}, idx::Int, indent::String) where T<:DataRS
	println(io)
	if isassigned(ds.elem, idx)
		printDataRS(io, ds.elem[idx], indent)
	else
		println(io, indent, "UNDEFINED")
	end
end
#Print DataRS structure:
function printDataRS(io::IO, ds::DataRS, indent::String)
	for i in 1:length(ds.elem)
		print(io, "$indent", ds.sweep.id, "=", ds.sweep.v[i], ": ")
		printDataRSelem(io, ds, i, "$indent  ")
	end
end

function Base.show(io::IO, ds::DataRS)
	print(io, "DataRS[\n")
	printDataRS(io, ds, "  ")
	print(io, "]\n")
end


function plot_lines!(vis::Visualizer, model::Hopper2D, q::AbstractVector;
		r_foot=0.04, offset=0.04, size=10, name::Symbol=:hopper_2D, col::Bool=true)
	p_shift = [0.0, 0.0, r_foot]

	# Point Traj
	top_point = Vector{Point{3,Float64}}()
	bot_point = Vector{Point{3,Float64}}()

	for qi in q
		push!(top_point, Point(qi[1], -offset, qi[2])+p_shift)
		push!(bot_point, Point(kinematics(model, qi)[1], -offset, kinematics(model, qi)[2])+p_shift)
	end

	# Set lines
	orange_mat, blue_mat, black_mat = get_line_material(size)
	if col
		setobject!(vis[name]["/lines/top"], MeshCat.Line(top_point, orange_mat))
		setobject!(vis[name]["/lines/bot"], MeshCat.Line(bot_point, blue_mat))
	else
		setobject!(vis[name]["/lines/top"], MeshCat.Line(top_point, black_mat))
		setobject!(vis[name]["/lines/bot"], MeshCat.Line(bot_point, black_mat))
	end
	return nothing
end

function build_robot!(vis::Visualizer, model::Hopper2D; name::Symbol=:Hopper2D, r=0.04, α=1.0)
	r = convert(Float32, r)
	r_leg = convert(Float32, 0.5 * r)
	body_mat = MeshPhongMaterial(color = RGBA(0.0, 0.0, 0.0, α))
	contact_mat = MeshPhongMaterial(color = RGBA(1.0, 165.0 / 255.0, 0.0, α))

	default_background!(vis)

    setobject!(vis[name][:robot]["body"], Sphere(Point3f0(0),
        convert(Float32, 0.1)), body_mat)
    setobject!(vis[name][:robot]["foot"], Sphere(Point3f0(0), r), contact_mat)
    setobject!(vis[name][:robot]["leg"], Cylinder(Point3f0(0),
		Point3f0(0, 0, 1.0), r_leg), body_mat)
	return nothing
end

function set_robot!(vis::Visualizer, model::Hopper2D, q::AbstractVector;
		name::Symbol=:Hopper2D, r=0.04)
	r = convert(Float32, r)
	r_leg = convert(Float32, 0.5 * r)
	p_shift = [0.0; 0.0; r]

    p_body = [q[1], 0.0, q[2]]
    p_foot = [kinematics(model, q)[1], 0.0, kinematics(model, q)[2]]
	leg_length = norm(p_body - p_foot)

    settransform!(vis[name][:robot]["body"], Translation(p_body + p_shift))
    settransform!(vis[name][:robot]["foot"], Translation(p_foot + p_shift))
	settransform!(vis[name][:robot]["leg"],
		compose(
			cable_transform(p_foot + p_shift, p_body + p_shift),
			LinearMap(Diagonal([1,1,leg_length])))
		)
	return nothing
end

function contact_point(model::Hopper2D, q::AbstractVector)
	p_foot = [kinematics(model, q)[1], 0.0, kinematics(model, q)[2]]
	pc = [p_foot]
	return pc
end

function stairs!(vis)
	setobject!(vis["box1"], GeometryBasics.HyperRectangle(Vec(0.0, 0.0, 0.0),
		Vec(0.25, 0.5, 0.25)), MeshPhongMaterial(color = RGBA(0.5, 0.5, 0.5, 1.0)))
	settransform!(vis["box1"], Translation(0.125, -0.25, 0))

	setobject!(vis["box2"], GeometryBasics.HyperRectangle(Vec(0.0, 0.0, 0.0),
		Vec(0.25, 0.5, 2 * 0.25)), MeshPhongMaterial(color = RGBA(0.5, 0.5, 0.5, 1.0)))
	settransform!(vis["box2"], Translation(0.125 + 0.25, -0.25, 0))

	setobject!(vis["box3"], GeometryBasics.HyperRectangle(Vec(0.0, 0.0, 0.0),
		Vec(0.25, 0.5, 3 * 0.25)), MeshPhongMaterial(color = RGBA(0.5, 0.5, 0.5, 1.0)))
	settransform!(vis["box3"], Translation(0.125 + 2 * 0.25, -0.25, 0))
end

function hopper_parkour_ghost!(vis, sim, traj, ref_traj;
    idx = [1, 35, 50, 110, 130, 190, 210, 265, 270, 284, 295, 300, 305, 320],
    α = [convert(Float64, i / length(idx)) for i = 1:length(idx)],
    _name = "_stairs")

    plot_lines!(vis, sim.s.model, ref_traj.q[1:1:end], size = 5, offset = -0.5)
    stairs!(vis)
    # settransform!(vis["/Cameras/default"],
    #         compose(Translation(0.0, -95.0, -1.0), LinearMap(RotY(0.0 * π) * RotZ(-π / 2.0))))
    # setprop!(vis["/Cameras/default/rotated/<object>"], "zoom", 20)

    for (i, t) in enumerate(idx)
        name = Symbol("Hopper" * "$i" * _name)
        build_robot!(vis, sim.s.model, name=name, α = α[i])
        set_robot!(vis, sim.s.model, traj.q[t], name = name)
    end
end


using Gtk.ShortNames, Gtk.GConstants

using Printf
using GR

x = randn(1000000)
y = randn(1000000)

function plot(ctx, w, h)
    global sl

    ENV["GKS_WSTYPE"] = "142"
    ENV["GKSconid"] = @sprintf("%lu", UInt64(ctx.ptr))

    plt = gcf()
    plt[:size] = (w, h)
    nbins = Int64(Gtk.GAccessor.value(sl))

    hexbin(x, y, nbins=nbins)
end

function draw(widget)
    ctx = Gtk.getgc(widget)
    w = Gtk.width(widget)
    h = Gtk.height(widget)

    Gtk.rectangle(ctx, 0, 0, w, h)
    Gtk.set_source_rgb(ctx, 1, 1, 1)
    Gtk.fill(ctx)

    plot(ctx, w, h)
end

function resize_event(widget)
    ctx = Gtk.getgc(widget)
    h = Gtk.height(widget)
    w = Gtk.width(widget)

    Gtk.paint(ctx)
end

function motion_notify_event(widget::Gtk.GtkCanvas, event::Gtk.GdkEventMotion)
    Gtk.GAccessor.text(lb, @sprintf("(%g, %g)", event.x, event.y))
end

function value_changed(widget::Gtk.GtkScale)
    global canvas
    draw(canvas)
end

win = Window("Gtk") |> (bx = Box(:v))
Gtk.set_gtk_property!(win, :double_buffered, false)
lb = Label("(-, -)")
sl = Scale(false, 10, 100, 1)
Gtk.GAccessor.value(sl, 30)
canvas = Canvas(600, 450)
push!(bx, lb, sl, canvas)

signal_connect(motion_notify_event, canvas, "motion-notify-event")
signal_connect(value_changed, sl, "value_changed")

canvas.resize = resize_event
canvas.draw = draw

Gtk.showall(win)

if !isinteractive()
    c = Condition()
    signal_connect(win, :destroy) do widget
        notify(c)
    end
    wait(c)
end


"""

Structure containing the options and parameters of the simulation.

### Examples
```julia-repl
julia> opt = Options()

julia> opt = Options(nsteps=2000)

```

"""
@with_kw struct Options
  # Time
  dt::Float64 = 0.05
  nsteps::Int = 2000

  # System parameters
  eps::Float64 = 1.
  sig::Float64 = 2.

  # Temperature bath parameters
  initial_velocities::Symbol = :normal
  kT::Float64 = 0.6
  ibath::Int = 10
  iequil::Int = 10
  tau::Int = 10
  lambda::Float64 = 0.1
  alpha::Float64 = 0.1

  # Output and printing options
  printxyz::Bool = true
  iprintxyz::Int = 10
  iprint::Int = div(nsteps,100)
  minprint::Bool = false
  trajectory_file::String = "traj.xyz"
  velocity_file::String = "vel.xyz"

end


#####
##### Outer functions for setting velocity on boundary and filling halo beyond boundary.
#####

@kernel function set_east_west_u_velocity!(u, i_boundary, bc, grid, clock, state)
    j, k = @index(Global, NTuple)
    @inbounds u[i_boundary, j, k] = getbc(bc, j, k, grid, clock, state)
end

@kernel function set_north_south_v_velocity!(v, j_boundary, bc, grid, clock, state)
    i, k = @index(Global, NTuple)
    @inbounds v[i, j_boundary, k] = getbc(bc, i, k, grid, clock, state)
end

@kernel function set_top_bottom_w_velocity!(w, k_boundary, bc, grid, clock, state)
    i, j = @index(Global, NTuple)
    @inbounds w[i, j, k_boundary] = getbc(bc, i, j, grid, clock, state)
end

  fill_west_halo!(u, bc::NFBC, arch, dep, grid, clock, state) = launch!(arch, grid, :yz, set_east_west_u_velocity!,   u,           1, bc, grid, clock, state, dependencies=dep)
  fill_east_halo!(u, bc::NFBC, arch, dep, grid, clock, state) = launch!(arch, grid, :yz, set_east_west_u_velocity!,   u, grid.Nx + 1, bc, grid, clock, state, dependencies=dep)
 fill_south_halo!(v, bc::NFBC, arch, dep, grid, clock, state) = launch!(arch, grid, :xz, set_north_south_v_velocity!, v,           1, bc, grid, clock, state, dependencies=dep)
 fill_north_halo!(v, bc::NFBC, arch, dep, grid, clock, state) = launch!(arch, grid, :xz, set_north_south_v_velocity!, v, grid.Ny + 1, bc, grid, clock, state, dependencies=dep)
fill_bottom_halo!(w, bc::NFBC, arch, dep, grid, clock, state) = launch!(arch, grid, :xy, set_top_bottom_w_velocity!,  w,           1, bc, grid, clock, state, dependencies=dep)
   fill_top_halo!(w, bc::NFBC, arch, dep, grid, clock, state) = launch!(arch, grid, :xy, set_top_bottom_w_velocity!,  w, grid.Nz + 1, bc, grid, clock, state, dependencies=dep)


"""
    LocalizationGroup

A set of [`Localization`](@ref)s determined to be associated together and treated as a single entity.
"""
mutable struct LocalizationGroup <: DataEntity
    index::Int            # unique identifier within this dataset
    channel::String
    x::Float64
    y::Float64
    z::Float64
    localizations::Vector{Localization}

    LocalizationGroup(localization::Localization) =
        new(localization.index, localization.channel, localization.x, localization.y, localization.z, [localization])
end

import Base.push!
function push!(group::LocalizationGroup, newlocalization::Localization...)
    push!(group.localizations, newlocalization...)
    calcposition!(group)
end

import Base.append!
function append!(group::LocalizationGroup, newlocalizations::Vector{Localization})
    append!(group.localizations, newlocalizations)
    calcposition!(group)
end

append!(group::LocalizationGroup, othergroup::LocalizationGroup) =
    append!(group, othergroup.localizations)

function calcposition!(group::LocalizationGroup)
    count = length(group.localizations)
    group.x = sum(l -> l.x, group.localizations) / count
    group.y = sum(l -> l.y, group.localizations) / count
    group.z = sum(l -> l.z, group.localizations) / count
    return group
end


using DataFrames: DataFrame
import CSV

mutable struct Provide{V} <: State{V}
    value::V
end

Provide(; index, init, step, autounit, _value, _type, _...) = begin
    i = value(index)
    t = value(init)
    Δt = value(step)
    df = DataFrame(_value isa String ? CSV.File(_value) : _value)
    df = autounit ? unitfy(df) : df
    v = filter(r -> r[i] >= t && iszero(typeof(Δt)(r[i] - t) % Δt), df)
    v[1, i] != t && error("incompatible index for initial time = $t\n$v")
    !all(isequal(Δt), diff(v[!, i])) && error("incompatible index for time step = $Δt\n$v")
    V = _type
    Provide{V}(v)
end

constructortags(::Val{:Provide}) = (:index, :init, :step, :autounit)

updatetags!(d, ::Val{:Provide}; _...) = begin
    !haskey(d, :index) && (d[:index] = QuoteNode(:index))
    !haskey(d, :init) && (d[:init] = :(context.clock.time))
    !haskey(d, :step) && (d[:step] = :(context.clock.step))
    !haskey(d, :autounit) && (d[:autounit] = true)
end

genvartype(v::VarInfo, ::Val{:Provide}; V, _...) = @q Provide{$V}

gendefault(v::VarInfo, ::Val{:Provide}) = istag(v, :parameter) ? genparameter(v) : genbody(v)

genupdate(v::VarInfo, ::Val{:Provide}, ::MainStep; kw...) = nothing


const flowerFolder = raw"C:\Users\James\.julia\dev\MushroomBody\ImageData\kaggle\natural_images\flower"
const imageFolder = raw"C:\Users\James\.julia\dev\MushroomBody\ImageData\kaggle\natural_images"

function ColorInput(arraysize::Tuple; folder=flowerFolder, normalise_to=350, stages=[0,1,0], input_bool=Bool[0,1,0], reward_bool=Bool[0,1,0], punishment_bool=Bool[0,0,0])
	img = random_image(folder)
	init = normalise(bin(get_hues(img),arraysize[1]),normalise_to)
	return ColorInput(init, stages, input_bool, reward_bool, punishment_bool, cumsum(stages),img)
end

function ColorInput(img::Image, arraysize::Tuple; normalise_to=350, stages=[0,1,0], input_bool=Bool[0,1,0], reward_bool=Bool[0,1,0], punishment_bool=Bool[0,0,0])

	ColorInput(normalise(bin(get_hues(img), arraysize[1]), normalise_to), stages, input_bool, reward_bool, punishment_bool, cumsum(stages), img)
end

function normalise(array, norm_to)

	# normalises the hues by the maximum
	array .* (norm_to / maximum(array))
end

function display_hues(hues)

	minH = 0
	maxH = 360
	hue_list = range(minH, maxH, length=length(hues))
	out = Array{HSL,1}(undef,length(hues))
	hues = normalise(hues,1)

	for i = 1:length(hues)

		out[i] = HSL(hue_list[i],hues[i],0.5)

	end
	return out
end

function many_folders_sequence(arraysize, nstim; folder=imageFolder, stages=[0,1,0], input_bool=Bool[0,1,0], reward_bool=Bool[0,1,0], punishment_bool=Bool[0,0,0])

	out = Vector{Inputs}(undef, nstim)
	multifolders = readdir(folder)
	for i = 1:nstim
		image_folder = folder * "\\" * rand(multifolders)
		out[i] = ColorInput(arraysize, folder=image_folder, stages=stages, input_bool=input_bool, reward_bool=reward_bool, punishment_bool=punishment_bool)
	end

	return InputSequence(out, RestInput(arraysize))
end

function color_sequence(arraysize, nstim; folder=flowerFolder, stages=[0,1,0], input_bool=Bool[0,1,0], reward_bool=Bool[0,1,0], punishment_bool=Bool[0,0,0])

	out = Vector{Inputs}(undef, nstim)
	for i = 1:nstim
		out[i] = ColorInput(arraysize, folder=folder, stages=stages, input_bool=input_bool, reward_bool=reward_bool, punishment_bool=punishment_bool)
	end

	return InputSequence(out, RestInput(arraysize))
end


using Random

randstring(6)


# AUTO GENERATED FILE - DO NOT EDIT

export daq_gauge

"""
    daq_gauge(;kwargs...)

A Gauge component.
A Gauge component that points to
a value between some range.
Keyword arguments:
- `id` (String; optional): The ID used to identify this compnent in Dash callbacks
- `base` (Real; optional): Base to be used in logarithmic scale.
- `className` (String; optional): Class to apply to the root component element.
- `color` (optional): Color configuration for the gauge's track.. color has the following type: String | lists containing elements 'default', 'gradient', 'ranges'.
Those elements have the following types:
  - `default` (String; optional): Color used for current value text and other minor accents
  - `gradient` (Bool; optional): Display ranges as a gradient between given colors.
  - `ranges` (optional): Define multiple color ranges on the gauge's track.
The key determines the color of the range and
the value is the start,end of the range itself.
Ranges must be contiguous along the entirety
of the gauge's range of values.. ranges has the following type: lists containing elements 'color'.
Those elements have the following types:
  - `color` (Array of Reals; optional)
- `label` (optional): Description to be displayed alongside the control. To control styling, pass an object with label and style properties.. label has the following type: String | lists containing elements 'style', 'label'.
Those elements have the following types:
  - `style` (Dict; optional)
  - `label` (String; optional)
- `labelPosition` (a value equal to: 'top', 'bottom'; optional): Where the component label is positioned.
- `logarithmic` (Bool; optional): If set to true, a logarithmic scale will be
used.
- `max` (Real; optional): The maximum value of the gauge. If logarithmic,
represents the maximum exponent.
- `min` (Real; optional): The minimum value of the gauge. If logarithmic,
represents the minimum exponent.
- `scale` (optional): Configuration for the component scale.. scale has the following type: lists containing elements 'start', 'interval', 'labelInterval', 'custom'.
Those elements have the following types:
  - `start` (Real; optional): Value to start the scale from. Defaults
to min.
  - `interval` (Real; optional): Interval by which the scale goes up. Attempts
to dynamically divide min-max range by
default.
  - `labelInterval` (Real; optional): Interval by which labels are added to
scale marks. Defaults to 2 (every other
mark has a label).
  - `custom` (optional): Custom scale marks. The key determines the position
and the value determines what will show. If you want
to set the style of a specific mark point, the value
should be an object which contains style and label
properties. custom has the following type: Real | lists containing elements 'style', 'label'.
Those elements have the following types:
  - `style` (String; optional)
  - `label` (String; optional)
- `showCurrentValue` (Bool; optional): If true, the current value of the gauge
will be displayed
- `size` (Real; optional): The size (diameter) of the gauge in pixels
- `style` (Dict; optional): Style to apply to the root component element.
- `theme` (Dict; optional): Theme configuration to be set by a ThemeProvider
- `units` (String; optional): Label for the current value
- `value` (Real; optional): The value of gauge. If logarithmic, the displayed
value will be the logarithm of the inputted value.
"""
function daq_gauge(; kwargs...)
        available_props = Symbol[:id, :base, :className, :color, :label, :labelPosition, :logarithmic, :max, :min, :scale, :showCurrentValue, :size, :style, :theme, :units, :value]
        wild_props = Symbol[]
        return Component("daq_gauge", "Gauge", "dash_daq", available_props, wild_props; kwargs...)
end


function set_mesh!(vis)
    l,w,h = 0.4,0.4,1.0
    body = Sphere(Point3f0(0,0,0), 7l/16)
    setobject!(vis["robot"]["torso"]["body"], body,
                            MeshPhongMaterial(color=colorant"yellow"))
    axle = Cylinder(Point3f0(0,0,0), Point3f0(0,w/2,0), 0.03f0)
    setobject!(vis["robot"]["torso"]["Laxle"], axle,
                            MeshPhongMaterial(color=colorant"black"))
    setobject!(vis["robot"]["torso"]["Raxle"], axle,
                            MeshPhongMaterial(color=colorant"black"))
    settransform!(vis["robot"]["torso"]["Laxle"], Translation(0,+l/4,0))
    settransform!(vis["robot"]["torso"]["Raxle"], Translation(0,-3l/4,0))

    foot = HyperSphere(Point3f0(0,0,0f0), 0.05f0)
    Lleg = Cylinder(Point3f0(0,+l/2,0), Point3f0(0,+l/2,1), 0.03f0)
    Rleg = Cylinder(Point3f0(0,-l/2,0), Point3f0(0,-l/2,1), 0.03f0)
    setobject!(vis["robot"]["torso"]["Lleg"]["geom"], Lleg,
                        MeshPhongMaterial(color=colorant=colorant"blue"))
    setobject!(vis["robot"]["torso"]["Rleg"]["geom"], Rleg,
                        MeshPhongMaterial(color=colorant=colorant"green"))

    Lfoot = setobject!(vis["robot"]["torso"]["Lleg"]["geom"]["Lfoot"]["geom"],
                    foot, MeshPhongMaterial(color=colorant"firebrick"))
    setobject!(vis["robot"]["torso"]["Rleg"]["geom"]["Rfoot"]["geom"],
                    foot, MeshPhongMaterial(color=colorant"firebrick"))
    settransform!(vis["robot"]["torso"]["Lleg"]["geom"]["Lfoot"]["geom"],
                    Translation(0,+l/2,h))
    settransform!(vis["robot"]["torso"]["Rleg"]["geom"]["Rfoot"]["geom"],
                    Translation(0,-l/2,h))
return Lfoot
end

function animate_walk!(vis, x)
    xb,yb,zb = x[1], x[2], ones(length(x[1]))
    xl,yl,zl = x[3], x[4], x[5]
    xr,yr,zr = x[6], x[7], x[8]
    dt = 0.1
    anim = Animation()
    for i=1:length(xb)
        atframe(anim, i) do
            Llen = norm(SA[xl[i]-xb[i], zl[i]-zb[i]])
            Rlen = norm(SA[xr[i]-xb[i], zr[i]-zb[i]])
            θl = atan(xl[i]-xb[i], zl[i]-zb[i])
            θr = atan(xr[i]-xb[i], zr[i]-zb[i])
            settransform!(vis["robot"]["torso"]["Lleg"], LinearMap(RotY(θl)))
            settransform!(vis["robot"]["torso"]["Rleg"], LinearMap(RotY(θr)))
            settransform!(vis["robot"]["torso"], Translation(xb[i],yb[i],zb[i]))
            settransform!(vis["robot"]["torso"]["Lleg"]["geom"],
                                    LinearMap(Diagonal(SA[1,1,Llen])))
            settransform!(vis["robot"]["torso"]["Rleg"]["geom"],
                                    LinearMap(Diagonal(SA[1,1,Rlen])))
        end
    end
    setanimation!(vis, anim)
end

function animate_walking_trajectory(X)
    vis  = Visualizer()
    setprop!(vis["/Background"], "top_color", colorant"white")
    setprop!(vis["/Background"], "bottom_color", colorant"white")
    setprop!(vis["/Grid"], "visible",false)
    set_mesh!(vis)
    animate_walk!(vis, X)
    render(vis)
end

# X = move_to_position([0.0,0.0,1.0], goal_position=[5.0, 0.0, 1.0])
# animate_walking_trajectory(X)


# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

module TestBind
using MXNet
using Test

using ..Main: rand_dims

################################################################################
# Test Implementations
################################################################################
function test_arithmetic(::Type{T}, uf, gf) where T <: mx.DType
  shape = rand_dims()
  @info "Bind::arithmetic::$T::$uf::dims = $shape"

  lhs = mx.Variable(:lhs)
  rhs = mx.Variable(:rhs)
  ret = uf(lhs, rhs)
  @test mx.list_arguments(ret) == [:lhs, :rhs]

  lhs_arr  = NDArray(rand(T, shape))
  rhs_arr  = NDArray(rand(T, shape))
  lhs_grad = NDArray{T}(undef, shape)
  rhs_grad = NDArray{T}(undef, shape)

  exec2 = mx.bind(ret, mx.Context(mx.CPU), [lhs_arr, rhs_arr], args_grad=[lhs_grad, rhs_grad])
  exec3 = mx.bind(ret, mx.Context(mx.CPU), [lhs_arr, rhs_arr])
  exec4 = mx.bind(ret, mx.Context(mx.CPU), Dict(:lhs=>lhs_arr, :rhs=>rhs_arr),
                  args_grad=Dict(:rhs=>rhs_grad, :lhs=>lhs_grad))

  mx.forward(exec2)
  mx.forward(exec3)
  mx.forward(exec4)

  out1 = uf(copy(lhs_arr), copy(rhs_arr))
  out2 = copy(exec2.outputs[1])
  out3 = copy(exec3.outputs[1])
  out4 = copy(exec4.outputs[1])
  @test isapprox(out1, out2)
  @test isapprox(out1, out3)
  @test isapprox(out1, out4)

  # test gradients
  out_grad = mx.NDArray(ones(T, shape))
  lhs_grad2, rhs_grad2 = gf(copy(out_grad), copy(lhs_arr), copy(rhs_arr))
  mx.backward(exec2, out_grad)
  @test isapprox(copy(lhs_grad), lhs_grad2)
  @test isapprox(copy(rhs_grad), rhs_grad2)

  # reset grads
  lhs_grad[:] = 0
  rhs_grad[:] = 0
  # compute using another binding
  mx.backward(exec4, out_grad)
  @test isapprox(copy(lhs_grad), lhs_grad2)
  @test isapprox(copy(rhs_grad), rhs_grad2)
end

function test_arithmetic()
  for T in [mx.fromTypeFlag(TF) for TF in instances(mx.TypeFlag)]
    test_arithmetic(T, (x,y) -> x .+ y, (g,x,y) -> (g,g))
    test_arithmetic(T, (x,y) -> x .- y, (g,x,y) -> (g,-g))
    test_arithmetic(T, (x,y) -> x .* y, (g,x,y) -> (y.*g, x.*g))
    if T <: Integer || T == Float16
      @warn "Not running division test for $T"
    else
      test_arithmetic(T, (x,y) -> x ./ y, (g,x,y) -> (g ./ y, -x .* g ./ (y.^2)))
    end
  end
end

function test_forward()
  # forward with data keyword argument
  x = @var x
  y = x .+ 42

  A = 1:5
  B = A .+ 42

  e = bind(y, args = Dict(:x => NDArray(24:28)))
  z = forward(e, x = NDArray(A))[1]

  @test copy(z) == collect(B)
end

################################################################################
# Run tests
################################################################################
@testset "Bind Test" begin
  test_arithmetic()
  test_forward()
end

end


"""
This module contains classical rewriting functions and utilities
"""
module Classic

import ..Rule
import ..Theory
# import ..gettheory
import ..closure_generator
import ..@metatheory_init
using ..Util
using Base.Meta

@metatheory_init

include("matchcore_compiler.jl")
include("rewrite.jl")
include("match.jl")

export rewrite
export @rewrite
export @esc_rewrite
export @compile_theory
export @matcher
export @rewriter

end


using SimulatedNeuralMoments
using Test

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


module Romeo

include("_utils.jl")

include("lti/LTI.jl")
include("optimization/Optimization.jl")

end # module