@testset "minimal_sqr_rev_test" begin
    # sqrRev [empty] = [empty]
    # sqrRev [-10.0,-1.0] = [empty]
    # sqrRev [0.0,infinity] = [entire]
    # sqrRev [0.0,1.0] = [-1.0,1.0]
    # sqrRev [-0.5,1.0] = [-1.0,1.0]
    # sqrRev [-1000.0,1.0] = [-1.0,1.0]
    # sqrRev [0.0,25.0] = [-5.0,5.0]
    # sqrRev [-1.0,25.0] = [-5.0,5.0]
    # sqrRev [0X1.47AE147AE147BP-7,0X1.47AE147AE147CP-7] = [-0X1.999999999999BP-4,0X1.999999999999BP-4]
    # sqrRev [0.0,0X1.FFFFFFFFFFFE1P+1] = [-0x1.ffffffffffff1p+0,0x1.ffffffffffff1p+0]
end

@testset "minimal_sqr_rev_bin_test" begin
    # sqrRevBin [empty] [-5.0,1.0] = [empty]
    # sqrRevBin [-10.0,-1.0] [-5.0,1.0] = [empty]
    # sqrRevBin [0.0,infinity] [-5.0,1.0] = [-5.0,1.0]
    # sqrRevBin [0.0,1.0] [-0.1,1.0] = [-0.1,1.0]
    # sqrRevBin [-0.5,1.0] [-0.1,1.0] = [-0.1,1.0]
    # sqrRevBin [-1000.0,1.0] [-0.1,1.0] = [-0.1,1.0]
    # sqrRevBin [0.0,25.0] [-4.1,6.0] = [-4.1,5.0]
    # sqrRevBin [-1.0,25.0] [-4.1,7.0] = [-4.1,5.0]
    # sqrRevBin [1.0,25.0] [0.0,7.0] = [1.0,5.0]
    # sqrRevBin [0X1.47AE147AE147BP-7,0X1.47AE147AE147CP-7] [-0.1,infinity] = [-0.1,0X1.999999999999BP-4]
    # sqrRevBin [0.0,0X1.FFFFFFFFFFFE1P+1] [-0.1,infinity] = [-0.1,0x1.ffffffffffff1p+0]
end

@testset "minimal_abs_rev_test" begin
    # absRev [empty] = [empty]
    # absRev [-1.1,-0.4] = [empty]
    # absRev [0.0,infinity] = [entire]
    # absRev [1.1,2.1] = [-2.1,2.1]
    # absRev [-1.1,2.0] = [-2.0,2.0]
    # absRev [-1.1,0.0] = [0.0,0.0]
    # absRev [-1.9,0.2] = [-0.2,0.2]
    # absRev [0.0,0.2] = [-0.2,0.2]
    # absRev [-1.5,infinity] = [entire]
end

@testset "minimal_abs_rev_bin_test" begin
    # absRevBin [empty] [-1.1,5.0] = [empty]
    # absRevBin [-1.1,-0.4] [-1.1,5.0] = [empty]
    # absRevBin [0.0,infinity] [-1.1,5.0] = [-1.1,5.0]
    # absRevBin [1.1,2.1] [-1.0,5.0] = [1.1,2.1]
    # absRevBin [-1.1,2.0] [-1.1,5.0] = [-1.1,2.0]
    # absRevBin [-1.1,0.0] [-1.1,5.0] = [0.0,0.0]
    # absRevBin [-1.9,0.2] [-1.1,5.0] = [-0.2,0.2]
end

@testset "minimal_pown_rev_test" begin
    # pownRev [empty] 0 = [empty]
    # pownRev [1.0,1.0] 0 = [entire]
    # pownRev [-1.0,5.0] 0 = [entire]
    # pownRev [-1.0,0.0] 0 = [empty]
    # pownRev [-1.0,-0.0] 0 = [empty]
    # pownRev [1.1,10.0] 0 = [empty]
    # pownRev [empty] 1 = [empty]
    # pownRev [entire] 1 = [entire]
    # pownRev [0.0,0.0] 1 = [0.0,0.0]
    # pownRev [-0.0,-0.0] 1 = [0.0,0.0]
    # pownRev [13.1,13.1] 1 = [13.1,13.1]
    # pownRev [-7451.145,-7451.145] 1 = [-7451.145,-7451.145]
    # pownRev [0x1.FFFFFFFFFFFFFp1023,0x1.FFFFFFFFFFFFFp1023] 1 = [0x1.FFFFFFFFFFFFFp1023,0x1.FFFFFFFFFFFFFp1023]
    # pownRev [-0x1.FFFFFFFFFFFFFp1023,-0x1.FFFFFFFFFFFFFp1023] 1 = [-0x1.FFFFFFFFFFFFFp1023,-0x1.FFFFFFFFFFFFFp1023]
    # pownRev [0.0,infinity] 1 = [0.0,infinity]
    # pownRev [-0.0,infinity] 1 = [0.0,infinity]
    # pownRev [-infinity,0.0] 1 = [-infinity,0.0]
    # pownRev [-infinity,-0.0] 1 = [-infinity,0.0]
    # pownRev [-324.3,2.5] 1 = [-324.3,2.5]
    # pownRev [0.01,2.33] 1 = [0.01,2.33]
    # pownRev [-1.9,-0.33] 1 = [-1.9,-0.33]
    # pownRev [empty] 2 = [empty]
    # pownRev [-5.0,-1.0] 2 = [empty]
    # pownRev [0.0,infinity] 2 = [entire]
    # pownRev [-0.0,infinity] 2 = [entire]
    # pownRev [0.0,0.0] 2 = [0.0,0.0]
    # pownRev [-0.0,-0.0] 2 = [0.0,0.0]
    # pownRev [0X1.573851EB851EBP+7,0X1.573851EB851ECP+7] 2 = [-0x1.a333333333334p+3,0x1.a333333333334p+3]
    # pownRev [0X1.A794A4E7CFAADP+25,0X1.A794A4E7CFAAEP+25] 2 = [-0x1.d1b251eb851edp+12,0x1.d1b251eb851edp+12]
    # pownRev [0x1.FFFFFFFFFFFFFp1023,0x1.FFFFFFFFFFFFFp1023] 2 = [-0x1p+512,0x1p+512]
    # pownRev [0.0,0X1.9AD27D70A3D72P+16] 2 = [-0x1.444cccccccccep+8,0x1.444cccccccccep+8]
    # pownRev [-0.0,0X1.9AD27D70A3D72P+16] 2 = [-0x1.444cccccccccep+8,0x1.444cccccccccep+8]
    # pownRev [0X1.A36E2EB1C432CP-14,0X1.5B7318FC50482P+2] 2 = [-0x1.2a3d70a3d70a5p+1,0x1.2a3d70a3d70a5p+1]
    # pownRev [0X1.BE0DED288CE7P-4,0X1.CE147AE147AE1P+1] 2 = [-0x1.e666666666667p+0,0x1.e666666666667p+0]
    # pownRev [empty] 8 = [empty]
    # pownRev [entire] 8 = [entire]
    # pownRev [0.0,infinity] 8 = [entire]
    # pownRev [-0.0,infinity] 8 = [entire]
    # pownRev [0.0,0.0] 8 = [0.0,0.0]
    # pownRev [-0.0,-0.0] 8 = [0.0,0.0]
    # pownRev [0X1.9D8FD495853F5P+29,0X1.9D8FD495853F6P+29] 8 = [-0x1.a333333333334p+3,0x1.a333333333334p+3]
    # pownRev [0X1.DFB1BB622E70DP+102,0X1.DFB1BB622E70EP+102] 8 = [-0x1.d1b251eb851edp+12,0x1.d1b251eb851edp+12]
    # pownRev [0x1.FFFFFFFFFFFFFp1023,0x1.FFFFFFFFFFFFFp1023] 8 = [-0x1p+128,0x1p+128]
    # pownRev [0.0,0X1.A87587109655P+66] 8 = [-0x1.444cccccccccep+8,0x1.444cccccccccep+8]
    # pownRev [-0.0,0X1.A87587109655P+66] 8 = [-0x1.444cccccccccep+8,0x1.444cccccccccep+8]
    # pownRev [0X1.CD2B297D889BDP-54,0X1.B253D9F33CE4DP+9] 8 = [-0x1.2a3d70a3d70a5p+1,0x1.2a3d70a3d70a5p+1]
    # pownRev [0X1.26F1FCDD502A3P-13,0X1.53ABD7BFC4FC6P+7] 8 = [-0x1.e666666666667p+0,0x1.e666666666667p+0]
    # pownRev [empty] 3 = [empty]
    # pownRev [entire] 3 = [entire]
    # pownRev [0.0,0.0] 3 = [0.0,0.0]
    # pownRev [-0.0,-0.0] 3 = [0.0,0.0]
    # pownRev [0X1.1902E978D4FDEP+11,0X1.1902E978D4FDFP+11] 3 = [0x1.a333333333332p+3,0x1.a333333333334p+3]
    # pownRev [-0X1.81460637B9A3DP+38,-0X1.81460637B9A3CP+38] 3 = [-0x1.d1b251eb851edp+12,-0x1.d1b251eb851ebp+12]
    # pownRev [0x1.FFFFFFFFFFFFFp1023,0x1.FFFFFFFFFFFFFp1023] 3 = [0x1.428a2f98d728ap+341,0x1.428a2f98d728bp+341]
    # pownRev [-0x1.FFFFFFFFFFFFFp1023,-0x1.FFFFFFFFFFFFFp1023] 3 = [-0x1.428a2f98d728bp+341, -0x1.428a2f98d728ap+341]
    # pownRev [0.0,infinity] 3 = [0.0,infinity]
    # pownRev [-0.0,infinity] 3 = [0.0,infinity]
    # pownRev [-infinity,0.0] 3 = [-infinity,0.0]
    # pownRev [-infinity,-0.0] 3 = [-infinity,0.0]
    # pownRev [-0X1.0436D2F418938P+25,0X1.F4P+3] 3 = [-0x1.444cccccccccep+8,0x1.4p+1]
    # pownRev [0X1.0C6F7A0B5ED8DP-20,0X1.94C75E6362A6P+3] 3 = [0x1.47ae147ae147ap-7,0x1.2a3d70a3d70a5p+1]
    # pownRev [-0X1.B6F9DB22D0E55P+2,-0X1.266559F6EC5B1P-5] 3 = [-0x1.e666666666667p+0,-0x1.51eb851eb851ep-2]
    # pownRev [empty] 7 = [empty]
    # pownRev [entire] 7 = [entire]
    # pownRev [0.0,0.0] 7 = [0.0,0.0]
    # pownRev [-0.0,-0.0] 7 = [0.0,0.0]
    # pownRev [0X1.F91D1B185493BP+25,0X1.F91D1B185493CP+25] 7 = [0x1.a333333333332p+3,0x1.a333333333334p+3]
    # pownRev [-0X1.07B1DA32F9B59P+90,-0X1.07B1DA32F9B58P+90] 7 = [-0x1.d1b251eb851edp+12,-0x1.d1b251eb851ebp+12]
    # pownRev [0x1.FFFFFFFFFFFFFp1023,0x1.FFFFFFFFFFFFFp1023] 7 = [0x1.381147622f886p+146,0x1.381147622f887p+146]
    # pownRev [-0x1.FFFFFFFFFFFFFp1023,-0x1.FFFFFFFFFFFFFp1023] 7 = [-0x1.381147622f887p+146,-0x1.381147622f886p+146]
    # pownRev [0.0,infinity] 7 = [0.0,infinity]
    # pownRev [-0.0,infinity] 7 = [0.0,infinity]
    # pownRev [-infinity,0.0] 7 = [-infinity,0.0]
    # pownRev [-infinity,-0.0] 7 = [-infinity,0.0]
    # pownRev [-0X1.4F109959E6D7FP+58,0X1.312DP+9] 7 = [-0x1.444cccccccccep+8,0x1.4p+1]
    # pownRev [0X1.6849B86A12B9BP-47,0X1.74D0373C76313P+8] 7 = [0x1.47ae147ae147ap-7,0x1.2a3d70a3d70a5p+1]
    # pownRev [-0X1.658C775099757P+6,-0X1.BEE30301BF47AP-12] 7 = [-0x1.e666666666667p+0,-0x1.51eb851eb851ep-2]
    # pownRev [empty] -2 = [empty]
    # pownRev [0.0,infinity] -2 = [entire]
    # pownRev [-0.0,infinity] -2 = [entire]
    # pownRev [0.0,0.0] -2 = [empty]
    # pownRev [-0.0,-0.0] -2 = [empty]
    # pownRev [-10.0,0.0] -2 = [empty]
    # pownRev [-10.0,-0.0] -2 = [empty]
    # pownRev [0X1.7DE3A077D1568P-8,0X1.7DE3A077D1569P-8] -2 = [-0x1.a333333333334p+3,0x1.a333333333334p+3]
    # pownRev [0X1.3570290CD6E14P-26,0X1.3570290CD6E15P-26] -2 = [-0x1.d1b251eb851edp+12,0x1.d1b251eb851edp+12]
    # pownRev [0X0P+0,0X0.0000000000001P-1022] -2 = [entire]
    # pownRev [0X1.3F0C482C977C9P-17,infinity] -2 = [-0x1.444cccccccccep+8,0x1.444cccccccccep+8]
    # pownRev [0X1.793D85EF38E47P-3,0X1.388P+13] -2 = [-0x1.2a3d70a3d70a5p+1,0x1.2a3d70a3d70a5p+1]
    # pownRev [0X1.1BA81104F6C8P-2,0X1.25D8FA1F801E1P+3] -2 = [-0x1.e666666666667p+0,0x1.e666666666667p+0]
    # pownRev [empty] -8 = [empty]
    # pownRev [0.0,infinity] -8 = [entire]
    # pownRev [-0.0,infinity] -8 = [entire]
    # pownRev [0.0,0.0] -8 = [empty]
    # pownRev [-0.0,-0.0] -8 = [empty]
    # pownRev [0X1.3CEF39247CA6DP-30,0X1.3CEF39247CA6EP-30] -8 = [-0x1.a333333333334p+3,0x1.a333333333334p+3]
    # pownRev [0X1.113D9EF0A99ACP-103,0X1.113D9EF0A99ADP-103] -8 = [-0x1.d1b251eb851edp+12,0x1.d1b251eb851edp+12]
    # pownRev [0X0P+0,0X0.0000000000001P-1022] -8 = [entire]
    # pownRev [0X1.34CC3764D1E0CP-67,infinity] -8 = [-0x1.444cccccccccep+8,0x1.444cccccccccep+8]
    # pownRev [0X1.2DC80DB11AB7CP-10,0X1.1C37937E08P+53] -8 = [-0x1.2a3d70a3d70a5p+1,0x1.2a3d70a3d70a5p+1]
    # pownRev [0X1.81E104E61630DP-8,0X1.BC64F21560E34P+12] -8 = [-0x1.e666666666667p+0,0x1.e666666666667p+0]
    # pownRev [empty] -1 = [empty]
    # pownRev [entire] -1 = [entire]
    # pownRev [0.0,0.0] -1 = [empty]
    # pownRev [-0.0,-0.0] -1 = [empty]
    # pownRev [0X1.38ABF82EE6986P-4,0X1.38ABF82EE6987P-4] -1 = [0x1.a333333333332p+3,0x1.a333333333335p+3]
    # pownRev [-0X1.197422C9048BFP-13,-0X1.197422C9048BEP-13] -1 = [-0x1.d1b251eb851eep+12,-0x1.d1b251eb851ebp+12]
    # pownRev [0X0.4P-1022,0X0.4000000000001P-1022] -1 = [0x1.ffffffffffff8p+1023,infinity]
    # pownRev [-0X0.4000000000001P-1022,-0X0.4P-1022] -1 = [-infinity,-0x1.ffffffffffff8p+1023]
    # pownRev [0.0,infinity] -1 = [0.0,infinity]
    # pownRev [-0.0,infinity] -1 = [0.0,infinity]
    # pownRev [-infinity,0.0] -1 = [-infinity,0.0]
    # pownRev [-infinity,-0.0] -1 = [-infinity,0.0]
    # pownRev [0X1.B77C278DBBE13P-2,0X1.9P+6] -1 = [0x1.47ae147ae147ap-7,0x1.2a3d70a3d70a5p+1]
    # pownRev [-0X1.83E0F83E0F83EP+1,-0X1.0D79435E50D79P-1] -1 = [-0x1.e666666666667p+0,-0x1.51eb851eb851ep-2]
    # pownRev [empty] -3 = [empty]
    # pownRev [entire] -3 = [entire]
    # pownRev [0.0,0.0] -3 = [empty]
    # pownRev [-0.0,-0.0] -3 = [empty]
    # pownRev [0X1.D26DF4D8B1831P-12,0X1.D26DF4D8B1832P-12] -3 = [0x1.a333333333332p+3,0x1.a333333333334p+3]
    # pownRev [-0X1.54347DED91B19P-39,-0X1.54347DED91B18P-39] -3 = [-0x1.d1b251eb851edp+12,-0x1.d1b251eb851ebp+12]
    # pownRev [0X0P+0,0X0.0000000000001P-1022] -3 = [0x1p+358,infinity]
    # pownRev [-0X0.0000000000001P-1022,-0X0P+0] -3 = [-infinity,-0x1p+358]
    # pownRev [0.0,infinity] -3 = [0.0,infinity]
    # pownRev [-0.0,infinity] -3 = [0.0,infinity]
    # pownRev [-infinity,0.0] -3 = [-infinity,0.0]
    # pownRev [-infinity,-0.0] -3 = [-infinity,0.0]
    # pownRev [0X1.43CFBA61AACABP-4,0X1.E848P+19] -3 = [0x1.47ae147ae147ap-7,0x1.2a3d70a3d70a5p+1]
    # pownRev [-0X1.BD393CE9E8E7CP+4,-0X1.2A95F6F7C066CP-3] -3 = [-0x1.e666666666667p+0,-0x1.51eb851eb851ep-2]
    # pownRev [empty] -7 = [empty]
    # pownRev [entire] -7 = [entire]
    # pownRev [0.0,0.0] -7 = [empty]
    # pownRev [-0.0,-0.0] -7 = [empty]
    # pownRev [0X1.037D76C912DBCP-26,0X1.037D76C912DBDP-26] -7 = [0x1.a333333333332p+3,0x1.a333333333334p+3]
    # pownRev [-0X1.F10F41FB8858FP-91,-0X1.F10F41FB8858EP-91] -7 = [-0x1.d1b251eb851edp+12,-0x1.d1b251eb851ebp+12]
    # pownRev [0X0P+0,0X0.0000000000001P-1022] -7 = [0x1.588cea3f093bcp+153,infinity]
    # pownRev [-0X0.0000000000001P-1022,-0X0P+0] -7 = [-infinity,-0x1.588cea3f093bcp+153]
    # pownRev [0.0,infinity] -7 = [0.0,infinity]
    # pownRev [-0.0,infinity] -7 = [0.0,infinity]
    # pownRev [-infinity,0.0] -7 = [-infinity,0.0]
    # pownRev [-infinity,-0.0] -7 = [-infinity,0.0]
    # pownRev [0X1.5F934D64162A9P-9,0X1.6BCC41E9P+46] -7 = [0x1.47ae147ae147ap-7,0x1.2a3d70a3d70a5p+1]
    # pownRev [-0X1.254CDD3711DDBP+11,-0X1.6E95C4A761E19P-7] -7 = [-0x1.e666666666667p+0,-0x1.51eb851eb851ep-2]
end

@testset "minimal_pown_rev_bin_test" begin
    # pownRevBin [empty] [1.0,1.0] 0 = [empty]
    # pownRevBin [1.0,1.0] [1.0,1.0] 0 = [1.0,1.0]
    # pownRevBin [-1.0,5.0] [-51.0,12.0] 0 = [-51.0,12.0]
    # pownRevBin [-1.0,0.0] [5.0,10.0] 0 = [empty]
    # pownRevBin [-1.0,-0.0] [-1.0,1.0] 0 = [empty]
    # pownRevBin [1.1,10.0] [1.0,41.0] 0 = [empty]
    # pownRevBin [empty] [0.0,100.1] 1 = [empty]
    # pownRevBin [entire] [-5.1,10.0] 1 = [-5.1,10.0]
    # pownRevBin [0.0,0.0] [-10.0,5.1] 1 = [0.0,0.0]
    # pownRevBin [-0.0,-0.0] [1.0,5.0] 1 = [empty]
    # pownRevBin [empty] [5.0,17.1] 2 = [empty]
    # pownRevBin [-5.0,-1.0] [5.0,17.1] 2 = [empty]
    # pownRevBin [0.0,infinity] [5.6,27.544] 2 = [5.6,27.544]
    # pownRevBin [0.0,0.0] [1.0,2.0] 2 = [empty]
    # pownRevBin [0X1.A36E2EB1C432CP-14,0X1.5B7318FC50482P+2] [1.0,infinity] 2 = [1.0,0x1.2a3d70a3d70a5p+1]
    # pownRevBin [0X1.BE0DED288CE7P-4,0X1.CE147AE147AE1P+1] [-infinity,-1.0] 2 = [-0x1.e666666666667p+0,-1.0]
    # pownRevBin [empty] [-23.0,-1.0] 3 = [empty]
    # pownRevBin [entire] [-23.0,-1.0] 3 = [-23.0,-1.0]
    # pownRevBin [0.0,0.0] [1.0,2.0] 3 = [empty]
    # pownRevBin [0X1.0C6F7A0B5ED8DP-20,0X1.94C75E6362A6P+3] [1.0,infinity] 3 = [1.0,0x1.2a3d70a3d70a5p+1]
    # pownRevBin [-0X1.B6F9DB22D0E55P+2,-0X1.266559F6EC5B1P-5] [-infinity,-1.0] 3 = [-0x1.e666666666667p+0,-1.0]
    # pownRevBin [empty] [-3.0,17.3] -2 = [empty]
    # pownRevBin [0.0,infinity] [-5.1,-0.1] -2 = [-5.1,-0.1]
    # pownRevBin [0.0,0.0] [27.2,55.1] -2 = [empty]
    # pownRevBin [0X1.3F0C482C977C9P-17,infinity] [-infinity,-0x1.FFFFFFFFFFFFFp1023] -2 = [empty]
    # pownRevBin [0X1.793D85EF38E47P-3,0X1.388P+13] [1.0,infinity] -2 = [1.0,0x1.2a3d70a3d70a5p+1]
    # pownRevBin [0X1.1BA81104F6C8P-2,0X1.25D8FA1F801E1P+3] [-infinity,-1.0] -2 = [-0x1.e666666666667p+0,-1.0]
    # pownRevBin [empty] [-5.1,55.5] -1 = [empty]
    # pownRevBin [entire] [-5.1,55.5] -1 = [-5.1,55.5]
    # pownRevBin [0.0,0.0] [-5.1,55.5] -1 = [empty]
    # pownRevBin [-infinity,-0.0] [-1.0,1.0] -1 = [-1.0,0.0]
    # pownRevBin [0X1.B77C278DBBE13P-2,0X1.9P+6] [-1.0,0.0] -1 = [empty]
    # pownRevBin [empty] [-5.1,55.5] -3 = [empty]
    # pownRevBin [entire] [-5.1,55.5] -3 = [-5.1,55.5]
    # pownRevBin [0.0,0.0] [-5.1,55.5] -3 = [empty]
    # pownRevBin [-infinity,0.0] [5.1,55.5] -3 = [empty]
    # pownRevBin [-infinity,-0.0] [-32.0,1.1] -3 = [-32.0,0.0]
end

@testset "minimal_sin_rev_test" begin
    # sinRev [empty] = [empty]
    # sinRev [-2.0,-1.1] = [empty]
    # sinRev [1.1, 2.0] = [empty]
    # sinRev [-1.0,1.0] = [entire]
    # sinRev [0.0,0.0] = [entire]
    # sinRev [0X1.1A62633145C06P-53,0X1.1A62633145C07P-53] = [entire]
end

@testset "minimal_sin_rev_bin_test" begin
    # sinRevBin [empty] [-1.2,12.1] = [empty]
    # sinRevBin [-2.0,-1.1] [-5.0, 5.0] = [empty]
    # sinRevBin [1.1, 2.0] [-5.0, 5.0] = [empty]
    # sinRevBin [-1.0,1.0] [-1.2,12.1] = [-1.2,12.1]
    # sinRevBin [0.0,0.0] [-1.0,1.0] = [0.0,0.0]
    # sinRevBin [-0.0,-0.0] [2.0,2.5] = [empty]
    # sinRevBin [-0.0,-0.0] [3.0,3.5] = [0x1.921fb54442d18p+1,0x1.921fb54442d19p+1]
    # sinRevBin [0X1.FFFFFFFFFFFFFP-1,0X1P+0] [1.57,1.58 ] = [0x1.921fb50442d18p+0,0x1.921fb58442d1ap+0]
    # sinRevBin [0.0,0X1P+0] [-0.1,1.58] = [0.0,1.58]
    # sinRevBin [0X1.1A62633145C06P-53,0X1.1A62633145C07P-53] [3.14,3.15] = [0X1.921FB54442D17P+1,0X1.921FB54442D19P+1]
    # sinRevBin [-0X1.72CECE675D1FDP-52,-0X1.72CECE675D1FCP-52] [3.14,3.15] = [0X1.921FB54442D18P+1,0X1.921FB54442D1aP+1]
    # sinRevBin [-0X1.72CECE675D1FDP-52,0X1.1A62633145C07P-53] [3.14,3.15] = [0X1.921FB54442D17P+1,0X1.921FB54442D1aP+1]
    # sinRevBin [0.0,1.0] [-0.1,3.15] = [0.0,0X1.921FB54442D19P+1]
    # sinRevBin [0.0,1.0] [-0.1,3.15] = [-0.0,0X1.921FB54442D19P+1]
    # sinRevBin [-0X1.72CECE675D1FDP-52,1.0] [-0.1,3.15] = [-0x1.72cece675d1fep-52,0X1.921FB54442D1aP+1]
    # sinRevBin [-0X1.72CECE675D1FDP-52,1.0] [0.0,3.15] = [0.0,0X1.921FB54442D1aP+1]
    # sinRevBin [0X1.1A62633145C06P-53,0X1P+0] [3.14,3.15] = [3.14,0X1.921FB54442D19P+1]
    # sinRevBin [-0X1.72CECE675D1FDP-52,0X1P+0] [1.57,3.15] = [1.57,0X1.921FB54442D1AP+1]
    # sinRevBin [0X1.1A62633145C06P-53,0X1.1A62633145C07P-53] [-infinity,3.15] = [-infinity,0X1.921FB54442D19P+1]
    # sinRevBin [-0X1.72CECE675D1FDP-52,-0X1.72CECE675D1FCP-52] [3.14,infinity] = [0X1.921FB54442D18P+1,infinity]
end

@testset "minimal_cos_rev_test" begin
    # cosRev [empty] = [empty]
    # cosRev [-2.0,-1.1] = [empty]
    # cosRev [1.1, 2.0] = [empty]
    # cosRev [-1.0,1.0] = [entire]
    # cosRev [0.0,0.0] = [entire]
    # cosRev [0X1.1A62633145C06P-53,0X1.1A62633145C07P-53] = [entire]
end

@testset "minimal_cos_rev_bin_test" begin
    # cosRevBin [empty] [-1.2,12.1] = [empty]
    # cosRevBin [-2.0,-1.1] [-5.0, 5.0] = [empty]
    # cosRevBin [1.1, 2.0] [-5.0, 5.0] = [empty]
    # cosRevBin [-1.0,1.0] [-1.2,12.1] = [-1.2,12.1]
    # cosRevBin [1.0,1.0] [-0.1,0.1] = [0.0,0.0]
    # cosRevBin [-1.0,-1.0] [3.14,3.15] = [0x1.921fb54442d18p+1,0x1.921fb54442d1ap+1]
    # cosRevBin [0X1.1A62633145C06P-54,0X1.1A62633145C07P-54] [1.57,1.58] = [0X1.921FB54442D17P+0,0X1.921FB54442D19P+0]
    # cosRevBin [-0X1.72CECE675D1FDP-53,-0X1.72CECE675D1FCP-53] [1.57,1.58] = [0X1.921FB54442D18P+0,0X1.921FB54442D1AP+0]
    # cosRevBin [-0X1.72CECE675D1FDP-53,0X1.1A62633145C07P-54] [1.57,1.58] = [0X1.921FB54442D17P+0,0X1.921FB54442D1aP+0]
    # cosRevBin [0X1.1A62633145C06P-54,1.0] [-2.0,2.0] = [-0X1.921FB54442D19P+0, 0X1.921FB54442D19P+0]
    # cosRevBin [0X1.1A62633145C06P-54,1.0] [0.0,2.0] = [0.0,0X1.921FB54442D19P+0]
    # cosRevBin [-0X1.72CECE675D1FDP-53,1.0] [-0.1,1.5708] = [-0.1,0X1.921FB54442D1aP+0]
    # cosRevBin [-0X1P+0,-0X1.FFFFFFFFFFFFFP-1] [3.14,3.15] = [0x1.921fb52442d18p+1,0x1.921fb56442d1ap+1]
    # cosRevBin [-0X1P+0,-0X1.FFFFFFFFFFFFFP-1] [-3.15,-3.14] = [-0x1.921fb56442d1ap+1,-0x1.921fb52442d18p+1]
    # cosRevBin [-0X1P+0,-0X1.FFFFFFFFFFFFFP-1] [9.42,9.45] = [0x1.2d97c7eb321d2p+3,0x1.2d97c7fb321d3p+3]
    # cosRevBin [0X1.87996529F9D92P-1,1.0] [-1.0,0.1] = [-0x1.6666666666667p-1,0.1]
    # cosRevBin [-0X1.AA22657537205P-2,0X1.14A280FB5068CP-1] [0.0,2.1] = [0x1.fffffffffffffp-1,0x1.0000000000001p+1]
    # cosRevBin [0X1.1A62633145C06P-53,0X1.1A62633145C07P-53] [-infinity,1.58] = [-infinity,0X1.921FB54442D18P+0]
    # cosRevBin [0X1.1A62633145C06P-53,0X1.1A62633145C07P-53] [-infinity,1.5] = [-infinity,-0X1.921FB54442D17P+0]
    # cosRevBin [-0X1.72CECE675D1FDP-52,-0X1.72CECE675D1FCP-52] [-1.58,infinity] = [-0x1.921fb54442d1ap+0,infinity]
    # cosRevBin [-0X1.72CECE675D1FDP-52,-0X1.72CECE675D1FCP-52] [-1.5,infinity] = [0x1.921fb54442d19p+0,infinity]
end

@testset "minimal_tan_rev_test" begin
    # tanRev [empty] = [empty]
    # tanRev [-1.0,1.0] = [entire]
    # tanRev [-156.0,-12.0] = [entire]
    # tanRev [0.0,0.0] = [entire]
    # tanRev [0X1.1A62633145C06P-53,0X1.1A62633145C07P-53] = [entire]
end

@testset "minimal_tan_rev_bin_test" begin
    # tanRevBin [empty] [-1.5708,1.5708] = [empty]
    # tanRevBin [entire] [-1.5708,1.5708] = [-1.5708,1.5708]
    # tanRevBin [0.0,0.0] [-1.5708,1.5708] = [0.0,0.0]
    # tanRevBin [0X1.D02967C31CDB4P+53,0X1.D02967C31CDB5P+53] [-1.5708,1.5708] = [-0x1.921fb54442d1bp+0,0x1.921fb54442d19p+0]
    # tanRevBin [-0X1.1A62633145C07P-53,-0X1.1A62633145C06P-53] [3.14,3.15] = [0X1.921FB54442D17P+1,0X1.921FB54442D19P+1]
    # tanRevBin [0X1.72CECE675D1FCP-52,0X1.72CECE675D1FDP-52] [-3.15,3.15] = [-0X1.921FB54442D19P+1,0X1.921FB54442D1aP+1]
    # tanRevBin [-0X1.D02967C31p+53,0X1.D02967C31p+53] [-infinity,1.5707965] = [-infinity, +0x1.921FB82C2BD7Fp0]
    # tanRevBin [-0X1.D02967C31p+53,0X1.D02967C31p+53] [-1.5707965,infinity] = [-0x1.921FB82C2BD7Fp0, +infinity]
    # tanRevBin [-0X1.D02967C31p+53,0X1.D02967C31p+53] [-1.5707965,1.5707965] = [-0x1.921FB82C2BD7Fp0, +0x1.921FB82C2BD7Fp0]
    # tanRevBin [-0X1.D02967C31CDB5P+53,0X1.D02967C31CDB5P+53] [-1.5707965,1.5707965] = [-1.5707965,1.5707965]
end

@testset "minimal_cosh_rev_test" begin
    # coshRev [empty] = [empty]
    # coshRev [1.0,infinity] = [entire]
    # coshRev [0.0,infinity] = [entire]
    # coshRev [1.0,1.0] = [0.0,0.0]
    # coshRev [0X1.8B07551D9F55P+0,0X1.89BCA168970C6P+432] = [-0X1.2C903022DD7ABP+8,0X1.2C903022DD7ABP+8]
end

@testset "minimal_cosh_rev_bin_test" begin
    # coshRevBin [empty] [0.0,infinity] = [empty]
    # coshRevBin [1.0,infinity] [0.0,infinity] = [0.0,infinity]
    # coshRevBin [0.0,infinity] [1.0,2.0] = [1.0,2.0]
    # coshRevBin [1.0,1.0] [1.0,infinity] = [empty]
    # coshRevBin [0X1.8B07551D9F55P+0,0X1.89BCA168970C6P+432] [-infinity,0.0] = [-0X1.2C903022DD7ABP+8,-0x1.fffffffffffffp-1]
end

@testset "minimal_mul_rev_test" begin
    # mulRev [empty] [1.0, 2.0] = [empty]
    # mulRev [1.0, 2.0] [empty] = [empty]
    # mulRev [empty] [empty] = [empty]
    # mulRev [-2.0, -0.1] [-2.1, -0.4]  = [0X1.999999999999AP-3, 0X1.5P+4]
    # mulRev [-2.0, 0.0] [-2.1, -0.4]  = [0X1.999999999999AP-3, infinity]
    # mulRev [-2.0, 1.1] [-2.1, -0.4]  = [entire]
    # mulRev [0.0, 1.1] [-2.1, -0.4]  = [-infinity, -0X1.745D1745D1745P-2]
    # mulRev [0.01, 1.1] [-2.1, -0.4]  = [-0X1.A400000000001P+7, -0X1.745D1745D1745P-2]
    # mulRev [0.0, 0.0] [-2.1, -0.4] = [empty]
    # mulRev [-infinity, -0.1] [-2.1, -0.4]  = [0.0, 0X1.5P+4]
    # mulRev [-infinity, 0.0] [-2.1, -0.4]  = [0.0, infinity]
    # mulRev [-infinity, 1.1] [-2.1, -0.4]  = [entire]
    # mulRev [-2.0, infinity] [-2.1, -0.4]  = [entire]
    # mulRev [0.0, infinity] [-2.1, -0.4]  = [-infinity, 0.0]
    # mulRev [0.01, infinity] [-2.1, -0.4]  = [-0X1.A400000000001P+7, 0.0]
    # mulRev [entire] [-2.1, -0.4]  = [entire]
    # mulRev [-2.0, -0.1] [-2.1, 0.0]  = [0.0, 0X1.5P+4]
    # mulRev [-2.0, 0.0] [-2.1, 0.0]  = [entire]
    # mulRev [-2.0, 1.1] [-2.1, 0.0]  = [entire]
    # mulRev [0.0, 1.1] [-2.1, 0.0]  = [entire]
    # mulRev [0.01, 1.1] [-2.1, 0.0]  = [-0X1.A400000000001P+7, 0.0]
    # mulRev [0.0, 0.0] [-2.1, 0.0]  = [entire]
    # mulRev [-infinity, -0.1] [-2.1, 0.0]  = [0.0, 0X1.5P+4]
    # mulRev [-infinity, 0.0] [-2.1, 0.0]  = [entire]
    # mulRev [-infinity, 1.1] [-2.1, 0.0]  = [entire]
    # mulRev [-2.0, infinity] [-2.1, 0.0]  = [entire]
    # mulRev [0.0, infinity] [-2.1, 0.0]  = [entire]
    # mulRev [0.01, infinity] [-2.1, 0.0]  = [-0X1.A400000000001P+7, 0.0]
    # mulRev [entire] [-2.1, 0.0]  = [entire]
    # mulRev [-2.0, -0.1] [-2.1, 0.12]  = [-0X1.3333333333333P+0, 0X1.5P+4]
    # mulRev [-2.0, 0.0] [-2.1, 0.12]  = [entire]
    # mulRev [-2.0, 1.1] [-2.1, 0.12]  = [entire]
    # mulRev [0.0, 1.1] [-2.1, 0.12]  = [entire]
    # mulRev [0.01, 1.1] [-2.1, 0.12]  = [-0X1.A400000000001P+7 , 0X1.8P+3]
    # mulRev [0.0, 0.0] [-2.1, 0.12]  = [entire]
    # mulRev [-infinity, -0.1] [-2.1, 0.12]  = [-0X1.3333333333333P+0, 0X1.5P+4]
    # mulRev [-infinity, 0.0] [-2.1, 0.12]  = [entire]
    # mulRev [-infinity, 1.1] [-2.1, 0.12]  = [entire]
    # mulRev [-2.0, infinity] [-2.1, 0.12]  = [entire]
    # mulRev [0.0, infinity] [-2.1, 0.12]  = [entire]
    # mulRev [0.01, infinity] [-2.1, 0.12]  = [-0X1.A400000000001P+7 , 0X1.8P+3]
    # mulRev [entire] [-2.1, 0.12]  = [entire]
    # mulRev [-2.0, -0.1] [0.0, 0.12]  = [-0X1.3333333333333P+0, 0.0]
    # mulRev [-2.0, 0.0] [0.0, 0.12]  = [entire]
    # mulRev [-2.0, 1.1] [0.0, 0.12]  = [entire]
    # mulRev [0.0, 1.1] [0.0, 0.12]  = [entire]
    # mulRev [0.01, 1.1] [0.0, 0.12]  = [0.0, 0X1.8P+3]
    # mulRev [0.0, 0.0] [0.0, 0.12]  = [entire]
    # mulRev [-infinity, -0.1] [0.0, 0.12]  = [-0X1.3333333333333P+0, 0.0]
    # mulRev [-infinity, 0.0] [0.0, 0.12]  = [entire]
    # mulRev [-infinity, 1.1] [0.0, 0.12]  = [entire]
    # mulRev [-2.0, infinity] [0.0, 0.12]  = [entire]
    # mulRev [0.0, infinity] [0.0, 0.12]  = [entire]
    # mulRev [0.01, infinity] [0.0, 0.12]  = [0.0, 0X1.8P+3]
    # mulRev [entire] [0.0, 0.12]  = [entire]
    # mulRev [-2.0, -0.1] [0.01, 0.12]  = [-0X1.3333333333333P+0, -0X1.47AE147AE147BP-8]
    # mulRev [-2.0, 0.0] [0.01, 0.12]  = [-infinity, -0X1.47AE147AE147BP-8]
    # mulRev [-2.0, 1.1] [0.01, 0.12]  = [entire]
    # mulRev [0.0, 1.1] [0.01, 0.12]  = [0X1.29E4129E4129DP-7, infinity]
    # mulRev [0.01, 1.1] [0.01, 0.12]  = [0X1.29E4129E4129DP-7, 0X1.8P+3]
    # mulRev [0.0, 0.0] [0.01, 0.12] = [empty]
    # mulRev [-infinity, -0.1] [0.01, 0.12]  = [-0X1.3333333333333P+0, 0.0]
    # mulRev [-infinity, 0.0] [0.01, 0.12]  = [-infinity, 0.0]
    # mulRev [-infinity, 1.1] [0.01, 0.12]  = [entire]
    # mulRev [-2.0, infinity] [0.01, 0.12]  = [entire]
    # mulRev [0.0, infinity] [0.01, 0.12]  = [0.0, infinity]
    # mulRev [0.01, infinity] [0.01, 0.12]  = [0.0, 0X1.8P+3]
    # mulRev [entire] [0.01, 0.12]  = [entire]
    # mulRev [-2.0, -0.1] [0.0, 0.0]  = [0.0, 0.0]
    # mulRev [-2.0, 0.0] [0.0, 0.0]  = [entire]
    # mulRev [-2.0, 1.1] [0.0, 0.0]  = [entire]
    # mulRev [0.0, 1.1] [0.0, 0.0]  = [entire]
    # mulRev [0.01, 1.1] [0.0, 0.0]  = [0.0, 0.0]
    # mulRev [0.0, 0.0] [0.0, 0.0]  = [entire]
    # mulRev [-infinity, -0.1] [0.0, 0.0]  = [0.0, 0.0]
    # mulRev [-infinity, 0.0] [0.0, 0.0]  = [entire]
    # mulRev [-infinity, 1.1] [0.0, 0.0]  = [entire]
    # mulRev [-2.0, infinity] [0.0, 0.0]  = [entire]
    # mulRev [0.0, infinity] [0.0, 0.0]  = [entire]
    # mulRev [0.01, infinity] [0.0, 0.0]  = [0.0, 0.0]
    # mulRev [entire] [0.0, 0.0]  = [entire]
    # mulRev [-2.0, -0.1] [-infinity, -0.1]  = [0X1.999999999999AP-5, infinity]
    # mulRev [-2.0, 0.0] [-infinity, -0.1]  = [0X1.999999999999AP-5 , infinity]
    # mulRev [-2.0, 1.1] [-infinity, -0.1]  = [entire]
    # mulRev [0.0, 1.1] [-infinity, -0.1]  = [-infinity, -0X1.745D1745D1745P-4]
    # mulRev [0.01, 1.1] [-infinity, -0.1]  = [-infinity, -0X1.745D1745D1745P-4]
    # mulRev [0.0, 0.0] [-infinity, -0.1] = [empty]
    # mulRev [-infinity, -0.1] [-infinity, -0.1]  = [0.0, infinity]
    # mulRev [-infinity, 0.0] [-infinity, -0.1]  = [0.0, infinity]
    # mulRev [-infinity, 1.1] [-infinity, -0.1]  = [entire]
    # mulRev [-2.0, infinity] [-infinity, -0.1]  = [entire]
    # mulRev [0.0, infinity] [-infinity, -0.1]  = [-infinity, 0.0]
    # mulRev [0.01, infinity] [-infinity, -0.1]  = [-infinity, 0.0]
    # mulRev [entire] [-infinity, -0.1]  = [entire]
    # mulRev [-2.0, -0.1] [-infinity, 0.0]  = [0.0, infinity]
    # mulRev [-2.0, 0.0] [-infinity, 0.0]  = [entire]
    # mulRev [-2.0, 1.1] [-infinity, 0.0]  = [entire]
    # mulRev [0.0, 1.1] [-infinity, 0.0]  = [entire]
    # mulRev [0.01, 1.1] [-infinity, 0.0]  = [-infinity, 0.0]
    # mulRev [0.0, 0.0] [-infinity, 0.0]  = [entire]
    # mulRev [-infinity, -0.1] [-infinity, 0.0]  = [0.0, infinity]
    # mulRev [-infinity, 0.0] [-infinity, 0.0]  = [entire]
    # mulRev [-infinity, 1.1] [-infinity, 0.0]  = [entire]
    # mulRev [-2.0, infinity] [-infinity, 0.0]  = [entire]
    # mulRev [0.0, infinity] [-infinity, 0.0]  = [entire]
    # mulRev [0.01, infinity] [-infinity, 0.0]  = [-infinity, 0.0]
    # mulRev [entire] [-infinity, 0.0]  = [entire]
    # mulRev [-2.0, -0.1] [-infinity, 0.3]  = [-0X1.8P+1, infinity]
    # mulRev [-2.0, 0.0] [-infinity, 0.3]  = [entire]
    # mulRev [-2.0, 1.1] [-infinity, 0.3]  = [entire]
    # mulRev [0.0, 1.1] [-infinity, 0.3]  = [entire]
    # mulRev [0.01, 1.1] [-infinity, 0.3]  = [-infinity, 0X1.EP+4]
    # mulRev [0.0, 0.0] [-infinity, 0.3]  = [entire]
    # mulRev [-infinity, -0.1] [-infinity, 0.3]  = [-0X1.8P+1, infinity]
    # mulRev [-infinity, 0.0] [-infinity, 0.3]  = [entire]
    # mulRev [-infinity, 1.1] [-infinity, 0.3]  = [entire]
    # mulRev [-2.0, infinity] [-infinity, 0.3]  = [entire]
    # mulRev [0.0, infinity] [-infinity, 0.3]  = [entire]
    # mulRev [0.01, infinity] [-infinity, 0.3]  = [-infinity, 0X1.EP+4]
    # mulRev [entire] [-infinity, 0.3]  = [entire]
    # mulRev [-2.0, -0.1] [-0.21, infinity]  = [-infinity , 0X1.0CCCCCCCCCCCDP+1]
    # mulRev [-2.0, 0.0] [-0.21, infinity]  = [entire]
    # mulRev [-2.0, 1.1] [-0.21, infinity]  = [entire]
    # mulRev [0.0, 1.1] [-0.21, infinity]  = [entire]
    # mulRev [0.01, 1.1] [-0.21, infinity]  = [-0X1.5P+4, infinity]
    # mulRev [0.0, 0.0] [-0.21, infinity]  = [entire]
    # mulRev [-infinity, -0.1] [-0.21, infinity]  = [-infinity, 0X1.0CCCCCCCCCCCDP+1]
    # mulRev [-infinity, 0.0] [-0.21, infinity]  = [entire]
    # mulRev [-infinity, 1.1] [-0.21, infinity]  = [entire]
    # mulRev [-2.0, infinity] [-0.21, infinity]  = [entire]
    # mulRev [0.0, infinity] [-0.21, infinity]  = [entire]
    # mulRev [0.01, infinity] [-0.21, infinity]  = [-0X1.5P+4, infinity]
    # mulRev [entire] [-0.21, infinity]  = [entire]
    # mulRev [-2.0, -0.1] [0.0, infinity]  = [-infinity, 0.0]
    # mulRev [-2.0, 0.0] [0.0, infinity]  = [entire]
    # mulRev [-2.0, 1.1] [0.0, infinity]  = [entire]
    # mulRev [0.0, 1.1] [0.0, infinity]  = [entire]
    # mulRev [0.01, 1.1] [0.0, infinity]  = [0.0, infinity]
    # mulRev [0.0, 0.0] [0.0, infinity]  = [entire]
    # mulRev [-infinity, -0.1] [0.0, infinity]  = [-infinity, 0.0]
    # mulRev [-infinity, 0.0] [0.0, infinity]  = [entire]
    # mulRev [-infinity, 1.1] [0.0, infinity]  = [entire]
    # mulRev [-2.0, infinity] [0.0, infinity]  = [entire]
    # mulRev [0.0, infinity] [0.0, infinity]  = [entire]
    # mulRev [0.01, infinity] [0.0, infinity]  = [0.0, infinity]
    # mulRev [entire] [0.0, infinity]  = [entire]
    # mulRev [-2.0, -0.1] [0.04, infinity]  = [-infinity, -0X1.47AE147AE147BP-6]
    # mulRev [-2.0, 0.0] [0.04, infinity]  = [-infinity, -0X1.47AE147AE147BP-6]
    # mulRev [-2.0, 1.1] [0.04, infinity]  = [entire]
    # mulRev [0.0, 1.1] [0.04, infinity]  = [0X1.29E4129E4129DP-5, infinity]
    # mulRev [0.01, 1.1] [0.04, infinity]  = [0X1.29E4129E4129DP-5, infinity]
    # mulRev [0.0, 0.0] [0.04, infinity] = [empty]
    # mulRev [-infinity, -0.1] [0.04, infinity]  = [-infinity, 0.0]
    # mulRev [-infinity, 0.0] [0.04, infinity]  = [-infinity, 0.0]
    # mulRev [-infinity, 1.1] [0.04, infinity]  = [entire]
    # mulRev [-2.0, infinity] [0.04, infinity]  = [entire]
    # mulRev [0.0, infinity] [0.04, infinity]  = [0.0, infinity]
    # mulRev [0.01, infinity] [0.04, infinity]  = [0.0, infinity]
    # mulRev [entire] [0.04, infinity]  = [entire]
    # mulRev [-2.0, -0.1] [entire]  = [entire]
    # mulRev [-2.0, 0.0] [entire]  = [entire]
    # mulRev [-2.0, 1.1] [entire]  = [entire]
    # mulRev [0.0, 1.1] [entire]  = [entire]
    # mulRev [0.01, 1.1] [entire]  = [entire]
    # mulRev [0.0, 0.0] [entire]  = [entire]
    # mulRev [-infinity, -0.1] [entire]  = [entire]
    # mulRev [-infinity, 0.0] [entire]  = [entire]
    # mulRev [-infinity, 1.1] [entire]  = [entire]
    # mulRev [-2.0, infinity] [entire]  = [entire]
    # mulRev [0.0, infinity] [entire]  = [entire]
    # mulRev [0.01, infinity] [entire]  = [entire]
    # mulRev [entire] [entire]  = [entire]
end

@testset "minimal_mul_rev_ten_test" begin
    # mulRevTen [-2.0, -0.1] [-2.1, -0.4] [-2.1, -0.4]  = [empty]
    # mulRevTen [-2.0, 1.1] [-2.1, -0.4] [-2.1, -0.4]  = [-2.1, -0.4]
    # mulRevTen [0.01, 1.1] [-2.1, 0.0] [-2.1, 0.0]  = [-2.1,0.0]
    # mulRevTen [-infinity, -0.1] [0.0, 0.12] [0.0, 0.12] = [0.0, 0.0]
    # mulRevTen [-2.0, 1.1] [0.04, infinity] [0.04, infinity]  = [0.04, infinity]
end


using SigFigs
using Test

@testset "SigFigs.jl" begin
    # Write your own tests here.
    @test SigFigs.significantfigures( SigFig(35, 1) ) == 40
    @test SigFigs.significantfigures( SigFig(330, 1) ) == 300
    @test SigFigs.significantfigures( SigFig(333, 2) ) == 330
    @test SigFigs.significantfigures( sqrt(SigFig(144, 1)) ) == 10
    @test SigFigs.significantfigures( SigFig(10.1, 3) + SigFig(10.5, 2) ) == 21
    @test SigFigs.significantfigures( SigFig(10.1, 3) + SigFig(10.1, 2) ) == 20
    @test SigFigs.significantfigures( SigFig(10.1, 3) + 10 ) == 20.1
    @test SigFigs.significantfigures( 11.0 + SigFig(10.1, 3) ) == 21.1
    #Test basic linear algebra stuff
    actual = [3,3] * Base.transpose([3,3] )
    @test all(vals([SigFig(3,2), SigFig(3,1)] * Base.transpose([ SigFig(3,2), SigFig(3,1) ] )) .== actual)
    @test transpose([ SigFig(3,2), SigFig(3,1) ]) == [ SigFig(3,2) SigFig(3,1)]
    @test (Base.transpose([SigFig(3,2), SigFig(3,1)]) * [ SigFig(3,2), SigFig(3,1) ]) == SigFig(18,1)
end


function en(an,anm1,Ln)
	return sqrt((1-abs2(an))/((one(an)-abs2(anm1))*(one(an)-abs2(Ln))))
end

function ln(z::Vector{Complex128},an::Complex128,anm1::Complex128,phinm1::Vector{Complex128},phinm1star::Vector{Complex128})
    f1 = [((z[i]-anm1)/(one(Complex128)-conj(an)*z[i]))*conj(phinm1[i]) for i=1:length(z)]
    f2 = [((one(Complex128)-conj(anm1)*z[i])/(one(Complex128)-conj(an)*z[i]))*conj(phinm1star[i]) for i=1:length(z)]
    num = sum(f1)
    den = sum(f2)
    return -num/den
end

function ln(z::Vector{Complex128},a::Vector{Complex128},phinm1::Vector{Complex128})
    if length(a)==1
        an = copy(a[end])
        anm1 = zero(Complex128)
        phinm1star = phinm1
        f1 = [((z[i]-anm1)/(one(Complex128)-conj(an)*z[i]))*conj(phinm1[i]) for i=1:length(z)]
        f2 = [((one(Complex128)-conj(anm1)*z[i])/(one(Complex128)-conj(an)*z[i]))*phinm1star[i] for i=1:length(z)]
        num = sum(f1)
        den = sum(f2)
        r = -num/den
    else
        an = copy(a[end])
        anm1 = copy(a[end-1])
        bProd = length(a)>=3 ? conjBlaschkeProd(z,[a[1:end-2]]) : ones(Complex128,length(z))
        f1 = [((z[i]-anm1)/(one(Complex128)-conj(an)*z[i]))*conj(phinm1[i]) for i=1:length(z)]
        f2 = [((z[i]-anm1)/(one(Complex128)-conj(an)*z[i]))*phinm1[i]*bProd[i] for i=1:length(z)]
        num = sum(f1)
        den = sum(f2)
        r = -num/den
    end
    return r
end

function phiNext(z::Vector{Complex128},a::Vector{Complex128},phinm1::Vector{Complex128})
    phin = zeros(Complex128,length(phinm1))
    if length(a)==1
        an = copy(a[end])
        anm1 = zero(Complex128)
        L = ln(z,a,phinm1)
        c = en(an,anm1,L)
        for k=1:length(z)
            q = (z[k]-anm1)
            p = (one(Complex128)-conj(anm1)*z[k])
            C = c/(z[k]-an)
            PHI = p*phinm1[k]
            PHISTAR = q*phinm1[k]
            phin[k] = C*(PHI+conj(L)*PHISTAR)
        end
    else
        an = a[end]
        anm1 = a[end-1]
        L = ln(z,a,phinm1)
        c = en(an,anm1,L)
        bProd = length(a)>=3 ? blaschkeProd(z,[a[1:end-2]]) : ones(Complex128,length(z))
        for k=1:length(z)
            q = (z[k]-an)
            p = (one(Complex128)-conj(anm1)*z[k])
            C = c*p/q
            phin[k] = C*(phinm1[k]+conj(L)*bProd[k]*conj(phinm1[k]))
        end
    end
    return (phin, L)
end

function phiNext(z::Vector{Complex128},an::Complex128,anm1::Complex128,phinm1::Vector{Complex128},phinm1star::Vector{Complex128})
    phin = zeros(Complex128,length(phinm1))
    phinstar = zeros(Complex128,length(phinm1))
    L = ln(z,an,anm1,phinm1,phinm1star)
    c = en(an,anm1,L)
    for k=1:length(z)
        q = (z[k]-anm1)
        p = (one(Complex128)-conj(anm1)*z[k])
        C = c/(z[k]-an)
        PHI = p*phinm1[k]
        PHISTAR = q*phinm1star[k]
        phin[k] = C*(PHI+conj(L)*PHISTAR)
        phinstar[k] = C*(L*PHI+PHISTAR)
    end
    return (phin, phinstar, L)
end

function phiNext(z::Vector{Complex128},an::Complex128,anm1::Complex128,phinm1::Vector{Complex128},phinm1star::Vector{Complex128},L::Complex128)
    phin = zeros(Complex128,length(phinm1))
    phinstar = zeros(Complex128,length(phinm1))
    c = en(an,anm1,L)
    for k=1:length(z)
        q = (z[k]-anm1)
        p = (one(Complex128)-conj(anm1)*z[k])
        C = c/(z[k]-an)
        PHI = p*phinm1[k]
        PHISTAR = q*phinm1star[k]
        phin[k] = C*(PHI+conj(L)*PHISTAR)
        phinstar[k] = C*(L*PHI+PHISTAR)
    end
    return (phin, phinstar, L)
end

function blaschkeProd(z::Vector{Complex128},aj::Vector{Complex128})
    r = ones(Complex128,length(z))
    for i=1:length(aj)
        t = [(one(Complex128)-conj(aj[i])*z[k])./(z[k]-aj[i]) for k=1:length(z)]
        r = r.*t
    end
    return r
end

function conjBlaschkeProd(z::Vector{Complex128},aj::Vector{Complex128})
    r = ones(Complex128,length(z))
    for i=1:length(aj)
        t = [(z[k]-aj[i])/(one(Complex128)-conj(aj[i])*z[k]) for k=1:length(z)]
        r = r.*t
    end
    return r
end

function qrUpdate(z::Vector{Complex128},p::Vector{Complex128},B::Array{Complex128,2},Bstar::Array{Complex128,2})
    (L,C) = size(B)
    (Q,R) = qr(B)
    Lk = zeros(Complex128,C-1)
    Bnew = copy(Q)
    BstarNew = zeros(Complex128,(L,C))
    for k=2:C
        BstarNew[:,k] = conj(Bnew[:,k]).*blaschkeProd(z,[p[1:k]])
        Lk[k-1] = ln(z,p[k],p[k-1],Bnew[:,k-1],BstarNew[:,k-1])
    end
    return (Bnew[:,2:end],BstarNew[:,2:end],Lk)
end

function genZ(N::Int)
	return exp((im*2*pi/N)*[0:N-1])
end

function genZArc(N::Int,Div::Int)
    return exp((im*2*pi/(Div*N))*[0:N-1])
end

function genBasis(N::Int,z::Vector,a::Vector{Complex128})
    M = length(a)+1
    upfreq = 500;
    a = cat(1,zero(Complex128),a)
    B = ones(Complex128,(N,M))/sqrt(N)
    Bstar = ones(Complex128,(N,M))/sqrt(N)
    #BstarOld = ones(Complex128,N)/sqrt(N)
    Lk = zeros(Complex128,M-1)
    for k=2:M
        (B[:,k],Bstar[:,k],Lk[k-1]) = phiNext(z,a[k],a[k-1],B[:,k-1],Bstar[:,k-1])
        #(B[:,k],BstarNew,Lk[k-1]) = phiNext(z,a[k],a[k-1],B[:,k-1],BstarOld)
        #BstarOld = copy(BstarNew)
        if mod(k+1,upfreq)==0
            println("QR updating")
            (B[:,(k-upfreq+1):k],Bstar[:,(k-upfreq+1):k],Lk[(k-upfreq+1):k]) = qrUpdate(z,a,B[:,(k-upfreq):k],Bstar[:,(k-upfreq):k])
        end
    end
    return (B[:,2:end],Lk,Bstar[:,2:end])
end

function genBasis(N::Int,z::Vector,a::Vector{Complex128},Lk::Vector{Complex128},InitVal)
    M = length(a)+1
    a = cat(1,zero(Complex128),a)
    B = ones(Complex128,(N,M))*InitVal
    BstarOld = ones(Complex128,N)*InitVal
    for k=2:M
        (B[:,k],BstarNew) = phiNext(z,a[k],a[k-1],B[:,k-1],BstarOld,Lk[k-1])
        BstarOld = copy(BstarNew)
    end
    return B[:,2:end]
end

function genWBasis(N::Int,a::Vector{Complex128})
    z = genZ(N)
    B = genBasis(N,z,a)
    return fftshift(B,1)
end

function genTBasis(N::Int,a::Vector{Complex128})
    z = genZ(N)
	(B,L) = genBasis(N,z,a)
	T = fft(B,1)/sqrt(N)
    T = flipud(T)
    return T
end

function unitDiskGrid(n::Int,delta::Float64)
    N = nextpow2(n)
    M = N>>1
    v = linspace(delta,one(Float64)-delta,M)
    Zjk = zeros(Complex128,N,N)
    for j=1:(M)
            #bottom, running counter-clockwise
            Zjk[(M+j),((M-(j-1)):(M+1+(j-1)))] = v[j]*exp(im*linspace(5*pi/4,7*pi/4,2*j))
            #top, running clockwise
            Zjk[(M-j+1),((M-(j-1)):(M+1+(j-1)))] = v[j]*exp(im*linspace(3*pi/4,1*pi/4,2*j))
    end
    for j=1:(M)
            #right, running clockwise
            Zjk[((M-(j-1)):(M+1+(j-1))),M+j] = v[j]*exp(im*linspace(pi/4,-pi/4,2*j)) #right
            #left, running counter-clockwise
            Zjk[((M-(j-1)):(M+1+(j-1))),M-j+1] = v[j]*exp(im*linspace(3*pi/4,5*pi/4,2*j)) #left
    end
    return (reshape(Zjk,N^2))
end

function ompTMFreqDomain(D::Array{Complex128,2},Z::Vector{Complex128},Npoles::Int,Npad::Int)
    D = cat(1,zeros(Npad,size(D,2)),D,zeros(Npad,size(D,2)))
    N = size(D,1)
    Q = size(D,2)
    M = length(Z)
    z = genZ(N)
    B = zeros(Complex128,(N,M))
    Bstar = zeros(Complex128,(N,M))
    Bnm1 = ones(Complex128,(N,M))/sqrt(N)
    Bstarnm1 = ones(Complex128,(N,M))/sqrt(N)
    poles = zeros(Complex128,Npoles+1)
    amps = zeros(Complex128,((Npoles+1),Q))
    jopt = one(Int);
    R = copy(D)
    for k=2:(Npoles+1)
        for j=1:M
            (B[:,j],Bstar[:,j]) = phiNext(z,Z[j],poles[k-1],Bnm1[:,jopt],Bstarnm1[:,jopt])
        end
        Bnm1 = copy(B)
        Bstarnm1 = copy(Bstar)
        B = fftshift(B,1)
        P = B'*R #projection on to column space of data
        O = sum(abs2(P),2) #this is the vector we wish to maximize
        jopt = indmax(O)
        poles[k] = Z[jopt]
        amps[k,:] = P[jopt,:]
        R -= B[:,jopt]*amps[k,:]
    end
    return (poles[2:end], amps[2:end,:])
end

function ompTMTimeDomain(D::Array{Complex128,2},Z::Vector{Complex128},Npoles::Int,Npad::Int)
    D = cat(1,zeros(Npad,size(D,2)),D,zeros(Npad,size(D,2)))
    N = size(D,1)
    Q = size(D,2)
    M = length(Z)
    z = genZ(N)
    B = zeros(Complex128,(N,M))
    Bstar = zeros(Complex128,(Npoles,M))
    Ltemp = zeros(Complex128,M)
    Lopt = zeros(Complex128,Npoles)
    Bnm1 = ones(Complex128,(N,M))/sqrt(N)
    Bstarnm1 = ones(Complex128,(N,M))/sqrt(N)
    poles = zeros(Complex128,Npoles+1)
    amps = zeros(Complex128,((Npoles+1),Q))
    jopt = ones(Int,Npoles+1);
    R = copy(D)
    for k=2:(Npoles+1)
        for j=1:M
            (B[:,j],Bstar[:,j],Ltemp[j]) = phiNext(z,Z[j],poles[k-1],Bnm1[:,jopt[k-1]],Bstarnm1[:,jopt[k-1]])
        end
        Bnm1 = copy(B)
        Bstarnm1 = copy(Bstar)
        B = flipud(fft(B,1))/sqrt(N)
        P = B'*R #projection on to column space of data
        O = sum(abs2(P),2) #this is the vector we wish to maximize
        O[jopt[1:(k-1)]] = 0 #prevent repeat pole selection
        jopt[k] = indmax(O)
        poles[k] = Z[jopt[k]]
        amps[k,:] = P[jopt[k],:]
        R -= B[:,jopt[k]]*amps[k,:]
        println(Ltemp[jopt[k]])
        Lopt[k-1] = Ltemp[jopt[k]]
    end
    return (poles[2:end], amps[2:end,:], Lopt)
end

function ompSepTMDecomp(Npoles::Int,Nsing::Int,Ngrid::Int,Ntarget::Int,D::Array{Complex128,2})
    (u,s,v) = svd(D)
    vp = v[:,1:Nsing]
    sp = s[1:Nsing]
    up  = u[:,1:Nsing]
    Z = unitDiskGrid(int(floor(sqrt(Ngrid))),0.001)
    (polesx,ampsx,Lk) = ompTMTimeDomain(vp,Z,Npoles,0)
    Bx = genTBasis(Ntarget,polesx)
    R = up*diagm(sp)*(Bx*ampsx)'
    return (polesx,ampsx,R,Lk)
end


"""
ispangram(input)

Program to determine the sentence is pangram or not.The program will return true if it is pangram and false if it is not.A full description of the algorithm can be found on [exercism](https://exercism.org/tracks/julia/exercises/pangram)

# Arguments:
- `input`: The sentence to find if its pangram or not.

# Examples/Tests
```julia
julia> ispangram(Pack my box with five dozen liquor jugs)
true

julia> ispangram(The quick brown fox jumps over the lazy dog)
true

julia> wordcount(hello world!!!)
false

```

# Algorithm:

```julia

for letter in input
    if 'A' <= letter <= 'Z'
        x &= ~(1<<(letter-'A'))
    elseif 'a' <= letter <= 'z'
        x &= ~(1<<(letter-'a'))
    end
    x == 0 && return true
end

```

# References:
(https://exercism.org/tracks/julia/exercises/pangram)

```

# Contributed by:- [Ihjass Thasbekha](https://github.com/Ihjass)
"""
function ispangram(input)
    x = 2^26 - 1
    for letter in input
        if 'A' <= letter <= 'Z'
            x &= ~(1 << (letter - 'A'))
        elseif 'a' <= letter <= 'z'
            x &= ~(1 << (letter - 'a'))
        end
        x == 0 && return true
    end
    return false
end


mw(model::EoSModel) = paramvals(model.params.Mw)

function group_molecular_weight(groups::GroupParam,mw,z = @SVector [1.])
    n = groups.n_flattenedgroups
    res = zero(first(z))
    Σz = sum(z)
    @inbounds for i in 1:length(groups.components)
        ni = n[i]
        gi = groups.i_groups[i]
        mwi = zero(res)
        for idx in 1:length(gi)
            j = gi[idx]
            mwi += mw[j]*ni[j]
        end
        res +=z[i]*mwi
    end
    return 0.001*res/Σz
end

comp_molecular_weight(mw,z = @SVector [1.]) = 0.001*mapreduce(*,+,mw,z)

include("initial_guess.jl")
include("differentials.jl")
include("VT.jl")
include("property_solvers/property_solvers.jl")
include("pT.jl")
include("unitful_base.jl")
include("unitful_methods.jl")


function gaussianpulse(t::Real,p::Vector)
    # Gaussian pulse centered on t=0, area normalized, and starting and finishing at y=0
    σ   = p[1] # gaussian standard deviation (FWHM = 2√(2ln2)σ ≈ 2.355σ)
    tg  = p[2] # total gate time
    ω10 = p[3] # qubit 0-1 transition angular freq
    ϕ   = p[4] # phase
    A   = p[5] # amplitude, (π for π-pulse, etc...)
    B = inv(√(2π)*σ*erf(tg/(√(8)*σ))-tg*gaussian(0.5tg,σ))
    Ɛπ = A*B*(gaussian(t,σ)-gaussian(0.5tg,σ))
    return Ɛπ*cos(ω10*t+ϕ)
end

function cosinepulse(t::Real,p::Vector)
    # Cosine pulse centered on t=0
    σ   = p[1] # not used
    tg  = p[2] # total gate time
    ω10 = p[3] # qubit 0-1 transition angular freq
    ϕ   = p[4] # phase
    A   = p[5] # amplitude, (π for π-pulse, etc...)
    Ɛπ = A/tg*(cos(2π*t/tg)+1)
    return Ɛπ*cos(ω10*t+ϕ)
end

function dragpulse(t::Real,p::Vector)
    # 2nd order DRAG-enhanced Gaussian pulse (Y-only correction) centered on t=0
    # 10.1103/PhysRevA.83.012308
    σ   = p[1] # gaussian standard deviation (FWHM = 2√(2ln2)σ ≈ 2.355σ)
    tg  = p[2] # total gate time
    ω10 = p[3] # qubit 0-1 transition angular freq
    Δ   = p[4] # ω21 - ω10
    λ   = p[5] # drive 1-2 transition amplitude ~√(2)
    ϕ   = p[6] # phase
    A   = p[7] # amplitude, (π for π-pulse, etc...)
    B = inv(√(2π)*σ*erf(tg/(√(8)*σ))-tg*gaussian(0.5tg,σ))
    Ɛπ = A*B*(gaussian(t,σ)-gaussian(0.5tg,σ))
    Ɛπ′ = A*B*gaussianprime(t,σ)
    ωt = ω10*t
    return (Ɛπ - λ^2*(λ^2-4)*Ɛπ^3/32Δ^2)*cos(ωt+ϕ) + (-λ^2*Ɛπ′/4Δ)*sin(ωt+ϕ)
end

function dragpulse_det(t::Real,p::Vector)
    # 2nd order DRAG-enhanced Gaussian pulse (Y-only) centered on t=0
    # with detuning
    # PhysRevLett.116.020501
    σ   = p[1] # gaussian standard deviation (FWHM = 2√(2ln2)σ ≈ 2.355σ)
    tg  = p[2] # total gate time
    ω10 = p[3] # qubit 0-1 transition angular freq
    Δ   = p[4] # ω21 - ω10
    λ   = p[5] # drive 1-2 transition amplitude ~√(2)
    δω  = p[6]
    ϕ   = p[7] # phase
    A   = p[8] # amplitude, (π for π-pulse, etc...)
    Δ = Δ - δω
    B = inv(√(2π)*σ*erf(tg/(√(8)*σ))-tg*gaussian(0.5tg,σ))
    Ɛπ = A*B*(gaussian(t,σ)-gaussian(0.5tg,σ))
    Ɛπ′ = A*B*gaussianprime(t,σ)
    C = (Ɛπ - λ^2*(λ^2-4)*Ɛπ^3/32Δ^2)
    S = (-λ^2*Ɛπ′/4Δ)
    I = C*cos(δω*t) - S*sin(δω*t)
    Q = S*cos(δω*t) + C*sin(δω*t)
    ωt = ω10*t
    return I*cos(ωt+ϕ) + Q*sin(ωt+ϕ)
end

function dragpulse_cos(t::Real,p::Vector)
    # 2nd order DRAG-enhanced cosine pulse (Y-only) centered on t=0
    # 10.1103/PhysRevA.83.012308
    σ   = p[1] # gaussian standard deviation (FWHM = 2√(2ln2)σ ≈ 2.355σ)
    tg  = p[2] # total gate time
    ω10 = p[3] # qubit 0-1 transition angular freq
    Δ   = p[4] # ω21 - ω10
    λ   = p[5] # drive 1-2 transition amplitude ~√(2)
    ϕ   = p[6] # phase
    A   = p[7] # amplitude, (π for π-pulse, etc...)
    B = 1
    Ɛπ = A*(cos(2π*t/tg)+1)/tg
    Ɛπ′ = -A*sin(2π*t/tg)*2π/tg^2
    ωt = ω10*t
    return (Ɛπ - λ^2*(λ^2-4)*Ɛπ^3/32Δ^2)*cos(ωt+ϕ) + (-λ^2*Ɛπ′/4Δ)*sin(ωt+ϕ)
end

function dragpulse5(t::Real,p::Vector)
    # 5th order DRAG-enhanced gaussian pulse centered on t=0
    σ   = p[1] # gaussian standard deviation (FWHM = 2√(2ln2)σ ≈ 2.355σ)
    tg  = p[2] # total gate time
    ω10 = p[3] # qubit 0-1 transition angular freq
    Δ   = p[4] # ω21 - ω10
    λ   = p[5] # drive 1-2 transition amplitude ~√(2)
    ϕ   = p[6] # phase
    A   = p[7] # amplitude, (π for π-pulse, etc...)
    B = inv(√(2π)*σ*erf(tg/(√(8)*σ))-tg*gaussian(0.5tg,σ))
    Ɛπ = A*B*(gaussian(t,σ)-gaussian(0.5tg,σ))
    Ɛπ′ = A*B*gaussianprime(t,σ)
    Ɛx = Ɛπ + (λ^2-4)*Ɛπ^3/8Δ^2 - (13λ^4-76λ^2+112)*Ɛπ^5/128Δ^4
    Ɛy = -Ɛπ′/Δ + 33(λ^2-2)*Ɛπ^2*Ɛπ′/24Δ^3
    δ = (λ^2-4)*Ɛπ^2/4Δ - (λ^4-7λ^2+12)*Ɛπ^4/16Δ^3
    ωt = (ω10-δ)*t
    return Ɛx*cos(ωt+ϕ) + Ɛy*sin(ωt+ϕ)
end

function simplegaussian(t::Real,p::Vector)
    # Gaussian pulse centered on t=0
    FWHM = p[1] # full width at half-maximum
    ω10  = p[2] # qubit 0-1 transition angular freq
    ϕ    = p[3] # phase
    A    = p[4] # amplitude
    #B = sqrtln16/(√(π)*FWHM)
    return A*gaussian_fwhm(t,FWHM)*cos(ω10*t+ϕ)
end

function simpledragpulse(t::Real,p::Vector)
    # 1st order DRAG-enhanced gaussian pulse centered on t=0
    FWHM = p[1] # full width at half-maximum
    ω10  = p[2] # qubit 0-1 transition angular freq
    Δ    = p[3] # ω21 - ω10
    λ    = p[4] # drive 1-2 transition amplitude ~√(2)
    ϕ    = p[5] # phase
    A    = p[6] # amplitude, (π for π-pulse, etc...)
    B = sqrtln16/(√(π)*FWHM)
    Ɛx = A*gaussian_fwhm(t,FWHM)*B
    Ɛy = -A*gaussianprime_fwhm(t,FWHM)*B/Δ
    δ = (λ^2-4)*Ɛx^2/4Δ
    ωt = (ω10-δ)*t
    return Ɛx*cos(ωt+ϕ) + Ɛy*sin(ωt+ϕ)
end

function simpledragpulse5(t::Real,p::Vector)
    # 5th order DRAG-enhanced gaussian pulse centered on t=0
    FWHM = p[1] # full width at half-maximum
    ω10  = p[2] # qubit 0-1 transition angular freq
    ω21  = p[3] # qubit 1-2 transition angular freq
    λ    = p[4] # drive 1-2 transition amplitude ~√(2)
    ϕ    = p[5] # phase
    A    = p[6] # amplitude, (π for π-pulse, etc...)
    Δ = ω21-ω10
    B = sqrtln16/(√(π)*FWHM)
    Ɛπ = A*gaussian_fwhm(t,FWHM)*B
    Ɛπ′ = A*gaussianprime_fwhm(t,FWHM)*B
    Ɛx = Ɛπ + (λ^2-4)*Ɛπ^3/8Δ^2 - (13λ^4-76λ^2+112)*Ɛπ^5/128Δ^4
    Ɛy = -Ɛπ′/Δ + 33(λ^2-2)*Ɛπ^2*Ɛπ′/24Δ^3
    δ = (λ^2-4)*Ɛx^2/4Δ - (λ^4-7λ^2+12)*Ɛπ^4/16Δ^3
    ωt = (ω10-δ)*t
    return Ɛx*cos(ωt+ϕ) + Ɛy*sin(ωt+ϕ)
end

function stepfun(t::Real,p::Vector,u::Vector)
    tg = p[1] # total gate time
    to = p[2] # time offset (to make the time start at 0, e.g. if tspan=(-2,3), to=2)
    return u[round(Int,1+(length(u)-1)*(t+to)/tg)]
end


using LinearAlgebra
using Distributions

abstract type SurrogateOptimizationAlgorithm end

struct SRBF <: SurrogateOptimizationAlgorithm end
struct LCBS <: SurrogateOptimizationAlgorithm end
struct EI <: SurrogateOptimizationAlgorithm end
struct DYCORS <: SurrogateOptimizationAlgorithm end
struct SOP{P} <: SurrogateOptimizationAlgorithm
    p::P
end

function merit_function(point,w,surr::AbstractSurrogate,s_max,s_min,d_max,d_min,box_size)
    if length(point)==1
        D_x = box_size+1
        for i = 1:length(surr.x)
            distance = norm(surr.x[i]-point)
            if distance < D_x
                D_x = distance
            end
        end
        return w*(surr(point) - s_min)/(s_max-s_min) + (1-w)*((d_max - D_x)/(d_max - d_min))
    else
        D_x = norm(box_size)+1
        for i = 1:length(surr.x)
            distance = norm(surr.x[i] .- point)
            if distance < D_x
                D_x = distance
            end
        end
        return w*(surr(point) - s_min)/(s_max-s_min) + (1-w)*((d_max - D_x)/(d_max - d_min))
    end
end

"""
The main idea is to pick the new evaluations from a set of candidate points where each candidate point is generated as an N(0, sigma^2)
distributed perturbation from the current best solution.
The value of sigma is modified based on progress and follows the same logic as
in many trust region methods: we increase sigma if we make a lot of progress
(the surrogate is accurate) and decrease sigma when we aren’t able to make progress
(the surrogate model is inaccurate).
More details about how sigma is updated is given in the original papers.

After generating the candidate points, we predict their objective function value
and compute the minimum distance to the previously evaluated point.
Let the candidate points be denoted by C and let the function value predictions
be s(x\\_i) and the distance values be d(x\\_i), both rescaled through a
linear transformation to the interval [0,1]. This is done to put the values on
the same scale.
The next point selected for evaluation is the candidate point x that minimizes
the weighted-distance merit function:

``merit(x) = ws(x) + (1-w)(1-d(x))``

where `` 0 \\leq w \\leq 1 ``.
That is, we want a small function value prediction and a large minimum distance
from the previously evaluated points.
The weight w is commonly cycled between
a few values to achieve both exploitation and exploration.
When w is close to zero, we do pure exploration, while w close to 1 corresponds to exploitation.
"""
function surrogate_optimize(obj::Function,::SRBF,lb,ub,surr::AbstractSurrogate,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)
    scale = 0.2
    success = 0
    failure = 0
    w_range = [0.3,0.5,0.7,0.95]

    #Vector containing size in each direction
    box_size = lb-ub
    success = 0
    failures = 0
    dtol = 1e-3*norm(ub-lb)
    d = length(surr.x)
    num_of_iterations = 0
    for w in Iterators.cycle(w_range)
        num_of_iterations += 1
        if num_of_iterations == maxiters
            index = argmin(surr.y)
            return (surr.x[index],surr.y[index])
        end
        for k = 1:maxiters
            incumbent_value = minimum(surr.y)
            incumbent_x = surr.x[argmin(surr.y)]

            new_lb = incumbent_x .- 3*scale*norm(incumbent_x .-lb)
            new_ub = incumbent_x .+ 3*scale*norm(incumbent_x .-ub)

            @inbounds for i = 1:length(new_lb)
                if new_lb[i] < lb[i]
                    new_lb = collect(new_lb)
                    new_lb[i] = lb[i]
                end
                if new_ub[i] > ub[i]
                    new_ub = collect(new_ub)
                    new_ub[i] = ub[i]
                end
            end

            new_sample = sample(num_new_samples,new_lb,new_ub,sample_type)
            s = zeros(eltype(surr.x[1]),num_new_samples)
            for j = 1:num_new_samples
                s[j] = surr(new_sample[j])
            end
            s_max = maximum(s)
            s_min = minimum(s)

            d_min = norm(box_size .+ 1)
            d_max = 0.0
            for r = 1:length(surr.x)
                for c = 1:num_new_samples
                    distance_rc = norm(surr.x[r] .- new_sample[c])
                    if distance_rc > d_max
                        d_max = distance_rc
                    end
                    if distance_rc < d_min
                        d_min = distance_rc
                    end
                end
            end

            #3)Evaluate merit function in the sampled points

            evaluation_of_merit_function = zeros(float(eltype(surr.x[1])),num_new_samples)
            @inbounds for r = 1:num_new_samples
                evaluation_of_merit_function[r] = merit_function(new_sample[r],w,surr,s_max,s_min,d_max,d_min,box_size)
            end
            new_addition = false
            adaptive_point_x = Tuple{}
            diff_x = zeros(eltype(surr.x[1]),d)
            while new_addition == false
                #find minimum
                new_min_y = minimum(evaluation_of_merit_function)
                min_index = argmin(evaluation_of_merit_function)
                new_min_x = new_sample[min_index]
                for l = 1:d
                    diff_x[l] = norm(surr.x[l] .- new_min_x)
                end
                bit_x = diff_x .> dtol
                #new_min_x has to have some distance from krig.x
                if false in bit_x
                    #The new_point is not actually that new, discard it!

                    deleteat!(evaluation_of_merit_function,min_index[1])
                    deleteat!(new_sample,min_index)

                    if length(new_sample) == 0
                        println("Out of sampling points")
                        index = argmin(surr.y)
                        return (surr.x[index],surr.y[index])
                    end
                else
                    new_addition = true
                    adaptive_point_x = Tuple(new_min_x)
                end
            end

            #4) Evaluate objective function at adaptive point
            adaptive_point_y = obj(adaptive_point_x)

            #5) Update surrogate with (adaptive_point,objective(adaptive_point)
            add_point!(surr,adaptive_point_x,adaptive_point_y)

            #6) How to go on?
            if surr(adaptive_point_x) < incumbent_value
                #success
                incumbent_x = adaptive_point_x
                incumbent_value = adaptive_point_y
                if failure == 0
                    success +=1
                else
                    failure = 0
                    success += 1
                end
            else
                #failure
                if success == 0
                    failure += 1
                else
                    success = 0
                    failure += 1
                end
            end

            if success == 3
                scale = scale*2
                if scale > 0.8*norm(ub-lb)
                    println("Exiting, scale too big")
                    index = argmin(surr.y)
                    return (surr.x[index],surr.y[index])
                end
                success = 0
                failure = 0
            end

            if failure == 5
                scale = scale/2
                #check bounds and go on only if > 1e-5*interval
                if scale < 1e-5
                    println("Exiting, too narrow")
                    index = argmin(surr.y)
                    return (surr.x[index],surr.y[index])
                end
                success = 0
                failure = 0
            end
        end
    end
end

"""
SRBF 1D:
surrogate_optimize(obj::Function,::SRBF,lb::Number,ub::Number,surr::AbstractSurrogate,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)
"""
function surrogate_optimize(obj::Function,::SRBF,lb::Number,ub::Number,surr::AbstractSurrogate,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)
#Suggested by:
#https://www.mathworks.com/help/gads/surrogate-optimization-algorithm.html
    scale = 0.2
    success = 0
    failure = 0
    w_range = [0.3,0.5,0.7,0.95]
    box_size = lb-ub
    success = 0
    failures = 0
    dtol = 1e-3*norm(ub-lb)
    num_of_iterations = 0
    for w in Iterators.cycle(w_range)
        num_of_iterations += 1
        if num_of_iterations == maxiters
            index = argmin(surr.y)
            return (surr.x[index],surr.y[index])
        end
        for k = 1:maxiters
            #1) Sample near incumbent (the 2 fraction is arbitrary here)
            incumbent_value = minimum(surr.y)
            incumbent_x = surr.x[argmin(surr.y)]

            new_lb = incumbent_x-scale*norm(incumbent_x-lb)
            new_ub = incumbent_x+scale*norm(incumbent_x-ub)
            if new_lb < lb
                new_lb = lb
            end
            if new_ub > ub
                new_ub = ub
            end
            new_sample = sample(num_new_samples,new_lb,new_ub,sample_type)

            #2) Create  merit function
            s = zeros(eltype(surr.x[1]),num_new_samples)
            for j = 1:num_new_samples
                s[j] = surr(new_sample[j])
            end
            s_max = maximum(s)
            s_min = minimum(s)

            d_min = box_size + 1
            d_max = 0.0
            for r = 1:length(surr.x)
                for c = 1:num_new_samples
                    distance_rc = norm(surr.x[r]-new_sample[c])
                    if distance_rc > d_max
                        d_max = distance_rc
                    end
                    if distance_rc < d_min
                        d_min = distance_rc
                    end
                end
            end
            #3) Evaluate merit function at the sampled points
            evaluation_of_merit_function = merit_function.(new_sample,w,surr,s_max,s_min,d_max,d_min,box_size)

            new_addition = false
            adaptive_point_x = zero(eltype(new_sample[1]));
            while new_addition == false
                #find minimum
                new_min_y = minimum(evaluation_of_merit_function)
                min_index = argmin(evaluation_of_merit_function)
                new_min_x = new_sample[min_index]

                diff_x = abs.(surr.x .- new_min_x)
                bit_x = diff_x .> dtol
                #new_min_x has to have some distance from krig.x
                if false in bit_x
                    #The new_point is not actually that new, discard it!
                    deleteat!(evaluation_of_merit_function,min_index)
                    deleteat!(new_sample,min_index)
                    if length(new_sample) == 0
                        println("Out of sampling points")
                        index = argmin(surr.y)
                        return (surr.x[index],surr.y[index])
                    end
                else
                new_addition = true
                adaptive_point_x = new_min_x
                end
            end
            #4) Evaluate objective function at adaptive point
            adaptive_point_y = obj(adaptive_point_x)

            #5) Update surrogate with (adaptive_point,objective(adaptive_point)
            add_point!(surr,adaptive_point_x,adaptive_point_y)

            #6) How to go on?
            if surr(adaptive_point_x) < incumbent_value
                #success
                incumbent_x = adaptive_point_x
                incumbent_value = adaptive_point_y
                if failure == 0
                    success +=1
                else
                    failure = 0
                    success += 1
                end
            else
                #failure
                if success == 0
                    failure += 1
                else
                    success = 0
                    failure += 1
                end
            end

            if success == 3
                scale = scale*2
                #check bounds cannot go more than [a,b]
                if scale > 0.8*norm(ub-lb)
                    println("Exiting, scale too big")
                    index = argmin(surr.y)
                    return (surr.x[index],surr.y[index])
                end
                success = 0
                failure = 0
            end

            if failure == 5
                scale = scale/2
                #check bounds and go on only if > 1e-5*interval
                if scale < 1e-5
                    println("Exiting, too narrow")
                    index = argmin(surr.y)
                    return (surr.x[index],surr.y[index])
                end
                sucess = 0
                failure = 0
            end
        end
    end
end

"""
This is an implementation of Lower Confidence Bound (LCB),
a popular acquisition function in Bayesian optimization.
Under a Gaussian process (GP) prior, the goal is to minimize:
``LCB(x) := E[x] - k * \\sqrt{(V[x])}``
default value ``k = 2``.
"""
function surrogate_optimize(obj::Function,::LCBS,lb::Number,ub::Number,krig,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)
    #Default value
    k = 2.0
    dtol = 1e-3 * norm(ub-lb)
    for i = 1:maxiters
        new_sample = sample(num_new_samples,lb,ub,sample_type)
        evaluations = zeros(eltype(krig.x[1]), num_new_samples)
        for j = 1:num_new_samples
            evaluations[j] = krig(new_sample[j]) +
                             k*sqrt(std_error_at_point(krig,new_sample[j]))
        end

        new_addition = false
        min_add_x = zero(eltype(new_sample[1]));
        min_add_y = zero(eltype(krig.y[1]));
        while new_addition == false
            #find minimum
            new_min_y = minimum(evaluations)
            min_index = argmin(evaluations)
            new_min_x = new_sample[min_index]

            diff_x = abs.(krig.x .- new_min_x)
            bit_x = diff_x .> dtol
            #new_min_x has to have some distance from krig.x
            if false in bit_x
                #The new_point is not actually that new, discard it!
                deleteat!(evaluations,min_index)
                deleteat!(new_sample,min_index)

                if length(new_sample) == 0
                    println("Out of sampling points")
                    index = argmin(krig.y)
                    return (krig.x[index],krig.y[index])
                end
             else
                new_addition = true
                min_add_x = new_min_x
                min_add_y = new_min_y
            end
        end
        if min_add_y < 1e-6*(maximum(krig.y) - minimum(krig.y))
            return
        else
            if (abs(min_add_y) == Inf || min_add_y == NaN)
                println("New point being added is +Inf or NaN, skipping.\n")
            else
                add_point!(krig,min_add_x,min_add_y)
            end
        end
    end
end

"""
This is an implementation of Lower Confidence Bound (LCB),
a popular acquisition function in Bayesian optimization.
Under a Gaussian process (GP) prior, the goal is to minimize:

``LCB(x) := E[x] - k * \\sqrt{(V[x])}``

default value ``k = 2``.
"""
function surrogate_optimize(obj::Function,::LCBS,lb,ub,krig,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)
    #Default value
    k = 2.0
    dtol = 1e-3 * norm(ub-lb)
    for i = 1:maxiters
        d = length(krig.x)
        new_sample = sample(num_new_samples,lb,ub,sample_type)
        evaluations = zeros(eltype(krig.x[1]),num_new_samples)
        for j = 1:num_new_samples
            evaluations[j] = krig(new_sample[j]) +
                             k*sqrt(std_error_at_point(krig,new_sample[j]))
        end

        new_addition = false
        min_add_x = Tuple{}
        min_add_y = zero(eltype(krig.y[1]));
        diff_x = zeros(eltype(krig.x[1]),d)
        while new_addition == false
            #find minimum
            new_min_y = minimum(evaluations)
            min_index = argmin(evaluations)
            new_min_x = new_sample[min_index]
            for l = 1:d
                diff_x[l] = norm(krig.x[l] .- new_min_x)
            end
            bit_x = diff_x .> dtol
            #new_min_x has to have some distance from krig.x
            if false in bit_x
                #The new_point is not actually that new, discard it!
                deleteat!(evaluations,min_index)
                deleteat!(new_sample,min_index)

                if length(new_sample) == 0
                    println("Out of sampling points")
                    index = argmin(krig.y)
                    return (krig.x[index],krig.y[index])
                end
             else
                new_addition = true
                min_add_x = new_min_x
                min_add_y = new_min_y
            end
        end
        if min_add_y < 1e-6*(maximum(krig.y) - minimum(krig.y))
            index = argmin(krig.y)
            return (krig.x[index],krig.y[index])
        else
            min_add_y = obj(min_add_x) # I actually add the objc function at that point
            if (abs(min_add_y) == Inf || min_add_y == NaN)
                println("New point being added is +Inf or NaN, skipping.\n")
            else
                add_point!(krig,Tuple(min_add_x),min_add_y)
            end
        end
    end
end

"""
Expected improvement method 1D
"""
function surrogate_optimize(obj::Function,::EI,lb::Number,ub::Number,krig,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)
        dtol = 1e-3*norm(ub-lb)
        eps = 0.01
        for i = 1:maxiters
            new_sample = sample(num_new_samples,lb,ub,sample_type)
            f_max = maximum(krig.y)
            evaluations = zeros(eltype(krig.x[1]),num_new_samples)
            point_found = false
            new_x_max = zero(eltype(krig.x[1]))
            new_y_max = zero(eltype(krig.x[1]))
            while point_found == false
                for j = 1:length(new_sample)
                    std = std_error_at_point(krig,new_sample[j])
                    u = krig(new_sample[j])
                    if abs(std) > 1e-6
                        z = (u - f_max - eps)/std
                    else
                        z = 0
                    end
                    evaluations[j] = (u-f_max-eps)*cdf(Normal(),z) + std*pdf(Normal(),z)
                end
                index_max = argmax(evaluations)
                x_new = new_sample[index_max]
                y_new = maximum(evaluations)
                diff_x = abs.(krig.x .- x_new)
                bit_x = diff_x .> dtol
                #new_min_x has to have some distance from krig.x
                if false in bit_x
                    #The new_point is not actually that new, discard it!
                    deleteat!(evaluations,index_max)
                    deleteat!(new_sample,index_max)
                    if length(new_sample) == 0
                        println("Out of sampling points")
                        index = argmin(krig.y)
                        return (krig.x[index],krig.y[index])
                    end
                 else
                    point_found = true
                    new_x_max = x_new
                    new_y_max = y_new
                end
            end
            if new_y_max < 1e-6*norm(maximum(krig.y)-minimum(krig.y))
                index = argmin(krig.y)
                return (krig.x[index],krig.y[index])
            end
            add_point!(krig,new_x_max,obj(new_x_max))
        end
        println("Completed maximum number of iterations")
end

"""
This is an implementation of Expected Improvement (EI),
arguably the most popular acquisition function in Bayesian optimization.
Under a Gaussian process (GP) prior, the goal is to
maximize expected improvement:

``EI(x) := E[max(f_{best}-f(x),0)``

"""
function surrogate_optimize(obj::Function,::EI,lb,ub,krig,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)
        dtol = 1e-3*norm(ub-lb)
        eps = 0.01
        for i = 1:maxiters
            d = length(krig.x)
            new_sample = sample(num_new_samples,lb,ub,sample_type)
            f_max = maximum(krig.y)
            evaluations = zeros(eltype(krig.x[1]),num_new_samples)
            point_found = false
            new_x_max = zero(eltype(krig.x[1]))
            new_y_max = zero(eltype(krig.x[1]))
            diff_x = zeros(eltype(krig.x[1]),d)
            while point_found == false
                for j = 1:length(new_sample)
                    std = std_error_at_point(krig,new_sample[j])
                    u = krig(new_sample[j])
                    if abs(std) > 1e-6
                        z = (u - f_max - eps)/std
                    else
                        z = 0
                    end
                    evaluations[j] = (u-f_max-eps)*cdf(Normal(),z) + std*pdf(Normal(),z)
                end
                index_max = argmax(evaluations)
                x_new = new_sample[index_max]
                y_new = maximum(evaluations)
                for l = 1:d
                    diff_x[l] = norm(krig.x[l] .- x_new)
                end
                bit_x = diff_x .> dtol
                #new_min_x has to have some distance from krig.x
                if false in bit_x
                    #The new_point is not actually that new, discard it!
                    deleteat!(evaluations,index_max)
                    deleteat!(new_sample,index_max)
                    if length(new_sample) == 0
                        println("Out of sampling points")
                        index = argmin(krig.y)
                        return (krig.x[index],krig.y[index])
                    end
                 else
                    point_found = true
                    new_x_max = x_new
                    new_y_max = y_new
                end
            end
            if new_y_max < 1e-6*norm(maximum(krig.y)-minimum(krig.y))
                index = argmin(krig.y)
                return (krig.x[index],krig.y[index])
            end
            add_point!(krig,Tuple(new_x_max),obj(new_x_max))
        end
        println("Completed maximum number of iterations")
end

function adjust_step_size(sigma_n,sigma_min,C_success,t_success,C_fail,t_fail)
    if C_success >= t_success
        sigma_n = 2*sigma_n
        C_success = 0
    end
    if C_fail >= t_fail
        sigma_n = max(sigma_n/2,sigma_min)
        C_fail = 0
    end
    return sigma_n,C_success,C_fail
end

function select_evaluation_point_1D(new_points1,surr1::AbstractSurrogate,numb_iters,maxiters)
    v = [0.3,0.5,0.8,0.95]
    k = 4
    n = length(surr1.x)
    if mod(maxiters-1,4) != 0
        w_nR = v[mod(maxiters-1,4)]
    else
        w_nR = v[4]
    end
    w_nD = 1 - w_nR

    l = length(new_points1)
    evaluations1 = zeros(eltype(surr1.y[1]),l)

    for i = 1:l
        evaluations1[i] = surr1(new_points1[i])
    end
    s_max = maximum(evaluations1)
    s_min = minimum(evaluations1)
    V_nR = zeros(eltype(surr1.y[1]),l)
    for i = 1:l
        if abs(s_max-s_min) <= 10e-6
            V_nR[i] = 1.0
        else
            V_nR[i] = (evaluations1[i] - s_min)/(s_max-s_min)
        end
    end

    #Compute score V_nD
    V_nD = zeros(eltype(surr1.y[1]),l)
    delta_n_x = zeros(eltype(surr1.x[1]),l)
    delta = zeros(eltype(surr1.x[1]),n)
    for j = 1:l
        for i = 1:n
            delta[i] = norm(new_points1[j]-surr1.x[i])
        end
        delta_n_x[j] = minimum(delta)
    end
    delta_n_max = maximum(delta_n_x)
    delta_n_min = minimum(delta_n_x)
    for i = 1:l
        if abs(delta_n_max-delta_n_min) <= 10e-6
            V_nD[i] = 1.0
        else
            V_nD[i] = (delta_n_max - delta_n_x[i])/(delta_n_max-delta_n_min)
        end
    end

    #Compute weighted score
    W_n = w_nR*V_nR + w_nD*V_nD
    return new_points1[argmin(W_n)]
end

"""
surrogate_optimize(obj::Function,::DYCORS,lb::Number,ub::Number,surr1::AbstractSurrogate,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)

DYCORS optimization method in 1D, following closely: Combining radial basis function
surrogates and dynamic coordinate search in high-dimensional expensive black-box optimization".

"""
function surrogate_optimize(obj::Function,::DYCORS,lb::Number,ub::Number,surr1::AbstractSurrogate,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)
    x_best = argmin(surr1.y)
    y_best = minimum(surr1.y)
    sigma_n = 0.2*norm(ub-lb)
    d = length(lb)
    sigma_min = 0.2*(0.5)^6*norm(ub-lb)
    t_success = 3
    t_fail = max(d,5)
    C_success = 0
    C_fail = 0
    for k = 1:maxiters
        p_select = min(20/d,1)*(1-log(k))/log(maxiters-1)
        # In 1D I_perturb is always equal to one, no need to sample
        d = 1
        I_perturb = d
        new_points = zeros(eltype(surr1.x[1]),num_new_samples)
        for i = 1:num_new_samples
            new_points[i] = x_best + rand(Normal(0,sigma_n))
            while new_points[i] < lb || new_points[i] > ub
                if new_points[i] > ub
                    #reflection
                    new_points[i] = maximum(surr1.x) - norm(new_points[i] - maximum(surr1.x))
                end
                if new_points[i] < lb
                    #reflection
                    new_points[i] = minimum(surr1.x) + norm(new_points[i]-minimum(surr1.x))
                end
            end
        end

        x_new = select_evaluation_point_1D(new_points,surr1,k,maxiters)
        f_new = obj(x_new)

        if f_new < y_best
            C_success = C_success + 1
            C_fail = 0
        else
            C_fail = C_fail + 1
            C_success = 0
        end

        sigma_n,C_success,C_fail = adjust_step_size(sigma_n,sigma_min,C_success,t_success,C_fail,t_fail)

        if f_new < y_best
            x_best = x_new
            y_best = f_new
            add_point!(surr1,x_best,y_best)
        end
    end
    index = argmin(surr1.y)
    return (surr1.x[index],surr1.y[index])
end

function select_evaluation_point_ND(new_points,surrn::AbstractSurrogate,numb_iters,maxiters)
    v = [0.3,0.5,0.8,0.95]
    k = 4
    n = size(surrn.x,1)
    d = size(surrn.x,2)
    if mod(maxiters-1,4) != 0
        w_nR = v[mod(maxiters-1,4)]
    else
        w_nR = v[4]
    end
    w_nD = 1 - w_nR

    l = size(new_points,1)
    evaluations = zeros(eltype(surrn.y[1]),l)
    for i = 1:l
        evaluations[i] = surrn(Tuple(new_points[i,:]))
    end
    s_max = maximum(evaluations)
    s_min = minimum(evaluations)
    V_nR = zeros(eltype(surrn.y[1]),l)
    for i = 1:l
        if abs(s_max-s_min) <= 10e-6
            V_nR[i] = 1.0
        else
            V_nR[i] = (evaluations[i] - s_min)/(s_max-s_min)
        end
    end

    #Compute score V_nD
    V_nD = zeros(eltype(surrn.y[1]),l)
    delta_n_x = zeros(eltype(surrn.x[1]),l)
    delta = zeros(eltype(surrn.x[1]),n)
    for j = 1:l
        for i = 1:n
            delta[i] = norm(new_points[j,:]-collect(surrn.x[i]))
        end
        delta_n_x[j] = minimum(delta)
    end
    delta_n_max = maximum(delta_n_x)
    delta_n_min = minimum(delta_n_x)
    for i = 1:l
        if abs(delta_n_max-delta_n_min) <= 10e-6
            V_nD[i] = 1.0
        else
            V_nD[i] = (delta_n_max - delta_n_x[i])/(delta_n_max-delta_n_min)
        end
    end
    #Compute weighted score
    W_n = w_nR*V_nR + w_nD*V_nD
    return new_points[argmin(W_n),:]
end

"""
surrogate_optimize(obj::Function,::DYCORS,lb::Number,ub::Number,surr1::AbstractSurrogate,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)

This is an implementation of the DYCORS strategy by Regis and Shoemaker:

Rommel G Regis and Christine A Shoemaker.
Combining radial basis function surrogates and dynamic coordinate search in high-dimensional expensive black-box optimization.
Engineering Optimization, 45(5): 529–555, 2013.

This is an extension of the SRBF strategy that changes how the
candidate points are generated. The main idea is that many objective
functions depend only on a few directions so it may be advantageous to
perturb only a few directions. In particular, we use a perturbation probability
to perturb a given coordinate and decrease this probability after each function
evaluation so fewer coordinates are perturbed later in the optimization.
"""
function surrogate_optimize(obj::Function,::DYCORS,lb,ub,surrn::AbstractSurrogate,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)
    x_best = collect(surrn.x[argmin(surrn.y)])
    y_best = minimum(surrn.y)
    sigma_n = 0.2*norm(ub-lb)
    d = length(lb)
    sigma_min = 0.2*(0.5)^6*norm(ub-lb)
    t_success = 3
    t_fail = max(d,5)
    C_success = 0
    C_fail = 0
    for k = 1:maxiters
        p_select = min(20/d,1)*(1-log(k))/log(maxiters-1)
        new_points = zeros(eltype(surrn.x[1]),num_new_samples,d)
        for j = 1:num_new_samples
            w = sample(d,0,1,sample_type)
            I_perturb = w .< p_select
            if ~(true in I_perturb)
                val = rand(1:d)
                I_perturb = vcat(zeros(Int,val-1),1,zeros(Int,d-val))
            end
            I_perturb = Int.(I_perturb)
            for i = 1:d
                if I_perturb[i] == 1
                    new_points[j,i] = x_best[i] + rand(Normal(0,sigma_n))
                else
                    new_points[j,i] = x_best[i]
                end
            end
        end

        for i = 1:num_new_samples
            for j = 1:d
                while new_points[i,j] < lb[j] || new_points[i,j] > ub[j]
                    if new_points[i,j] > ub[j]
                        new_points[i,j] = maximum(surrn.x)[j] - norm(new_points[i,j] - maximum(surrn.x)[j])
                    end
                    if new_points[i,j] < lb[j]
                        new_points[i,j] = minimum(surrn.x)[j] + norm(new_points[i]-minimum(surrn.x)[j])
                    end
                end
            end
        end

        #ND version
        x_new = select_evaluation_point_ND(new_points,surrn,k,maxiters)
        f_new = obj(x_new)

        if f_new < y_best
            C_success = C_success + 1
            C_fail = 0
        else
            C_fail = C_fail + 1
            C_success = 0
        end

        sigma_n,C_success,C_fail = adjust_step_size(sigma_n,sigma_min,C_success,t_success,C_fail,t_fail)

        if f_new < y_best
            x_best = x_new
            y_best = f_new
            add_point!(surrn,Tuple(x_best),y_best)
        end
    end
    index = argmin(surrn.y)
    return (surrn.x[index],surrn.y[index])
end

function obj2_1D(value,points)
    min = +Inf
    my_p = filter(x->abs(x-value)>10^-6,points)
    for i = 1:length(my_p)
        new_val = norm(my_p[i]-value)
        if new_val < min
            min = new_val
        end
    end
    return min
end

function I_tier_ranking_1D(P,surrSOP::AbstractSurrogate)
    #obj1 = objective_function
    #obj2 = obj2_1D
    Fronts = Dict{Int,Array{eltype(surrSOP.x[1]),1}}()
    i = 1
    while true
        F = []
        j = 1
        for p in P
            n_p = 0
            k = 1
            for q in P
                #I use equality with floats because p and q are in surrSOP.x
                #for sure at this stage
                p_index = j
                q_index = k
                val1_p = surrSOP.y[p_index]
                val2_p = obj2_1D(p,P)
                val1_q = surrSOP.y[q_index]
                val2_q = obj2_1D(q,P)
                p_dominates_q = (val1_p < val1_q || abs(val1_p-val1_q) <= 10^-5) &&
                                (val2_p < val2_q || abs(val2_p-val2_q) <= 10^-5) &&
                                ((val1_p < val1_q) || (val2_p < val2_q))

                q_dominates_p = (val1_p < val1_q || abs(val1_p-val1_q) < 10^-5) &&
                                (val2_p < val2_q || abs(val2_p-val2_q) < 10^-5) &&
                                ((val1_p < val1_q) || (val2_p < val2_q))
                if q_dominates_p
                    n_p += 1
                end
                k = k + 1
            end
        if n_p == 0
            # no individual dominates p
            push!(F,p)
        end
        j = j + 1
        end
        if length(F) > 0
            Fronts[i] = F
            P = setdiff(P,F)
            i = i + 1
        else
            return Fronts
        end
    end
    return F
end

function II_tier_ranking_1D(D::Dict,srg::AbstractSurrogate)
    for i = 1:length(D)
        pos = []
        yn = []
        for j = 1:length(D[i])
            push!(pos,findall(e->e==D[i][j],srg.x))
            push!(yn,srg.y[pos[j]])
        end
        D[i] = D[i][sortperm(D[i])]
    end
    return D
end

function Hypervolume_Pareto_improving(f1_new,f2_new,Pareto_set)
    if size(Pareto_set,1) == 1
        area_before = zero(eltype(f1_new))
    else
        my_p = Pareto_set
        #Area before
        v_ref = [maximum(Pareto_set[:,1]),maximum(Pareto_set[:,2])]
        my_p = vcat(my_p,v_ref)
        v = sortperm(my_p[:,2])
        my_p[:,1] = my_p[:,1][v]
        my_p[:,2] = my_p[:,2][v]
        area_before = zero(eltype(f1_new))
        for j = 1:length(v)-1
            area_before += (my_p[j+1,2]-my_p[j,2])*(v_ref[1]-my_p[j])
        end
    end
    #Area after
    Pareto_set = vcat(Pareto_set,[f1_new f2_new])
    v_ref = [maximum(Pareto_set[:,1]) maximum(Pareto_set[:,2])]
    Pareto_set = vcat(Pareto_set,v_ref)
    v = sortperm(Pareto_set[:,2])
    Pareto_set[:,1] = Pareto_set[:,1][v]
    Pareto_set[:,2] = Pareto_set[:,2][v]
    area_after = zero(eltype(f1_new))
    for j = 1:length(v)-1
        area_after += (Pareto_set[j+1,2]-Pareto_set[j,2])*(v_ref[1]-Pareto_set[j])
    end
    return area_after - area_before
end

"""
surrogate_optimize(obj::Function,::SOP,lb::Number,ub::Number,surr::AbstractSurrogate,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)

SOP Surrogate optimization method, following closely the following papers:

    - SOP: parallel surrogate global optimization with Pareto center selection for computationally expensive single objective problems by Tipaluck Krityakierne
    - Multiobjective Optimization Using Evolutionary Algorithms by Kalyan Deb
#Suggested number of new_samples = min(500*d,5000)
"""
function surrogate_optimize(obj::Function,sop1::SOP,lb::Number,ub::Number,surrSOP::AbstractSurrogate,sample_type::SamplingAlgorithm;maxiters=100,num_new_samples=100)
    d = length(lb)
    N_fail = 3
    N_tenure = 5
    tau = 10^-5
    num_P = sop1.p
    centers_global = surrSOP.x
    r_centers_global = 0.2*norm(ub-lb)*ones(length(surrSOP.x))
    N_failures_global = zeros(length(surrSOP.x))
    tabu = []
    N_tenures_tabu = []
    for k = 1:maxiters
        N_tenures_tabu .+= 1
        #deleting points that have been in tabu for too long
        del = N_tenures_tabu .> N_tenure

        if length(del) > 0
            for i = length(del)
                if del[i]
                    del[i] = i
                end
            end
            deleteat!(N_tenures_tabu,del)
            deleteat!(tabu,del)
        end

        ##### P CENTERS ######
        C = []

        #S(x) set of points already evaluated
        #Rank points in S with:
        #1) Non dominated sorting
        Fronts_I = I_tier_ranking_1D(centers_global,surrSOP)
        #2) Second tier ranking
        Fronts = II_tier_ranking_1D(Fronts_I,surrSOP)
        ranked_list = []
        for i = 1:length(Fronts)
            for j = 1:length(Fronts[i])
                push!(ranked_list,Fronts[i][j])
            end
        end
        ranked_list = eltype(surrSOP.x[1]).(ranked_list)

        centers_full = 0
        i = 1
        while i <= length(ranked_list) && centers_full == 0
            flag = 0
            for j = 1:length(ranked_list)
                for m = 1:length(tabu)
                    if abs(ranked_list[j]-tabu[m]) < tau
                        flag = 1
                    end
                end
                for l = 1:length(centers_global)
                    if abs(ranked_list[j]-centers_global[l]) < tau
                        flag = 1
                    end
                end
            end
            if flag == 1
                skip
            else
                push!(C,ranked_list[i])
                if length(C) == num_P
                    centers_full = 1
                end
            end
            i = i + 1
        end

        #I examined all the points in the ranked list but num_selected < num_p
        #I just iterate again using only radius rule
        if length(C) < num_P
            i = 1
            while i <= length(ranked_list) && centers_full == 0
                flag = 0
                for j = 1:length(ranked_list)
                    for m = 1:length(centers_global)
                        if abs(centers_global[j] - ranked_list[m]) < tau
                            flag = 1
                        end
                    end
                end
                if flag == 1
                    skip
                else
                    push!(C,ranked_list[i])
                    if length(C) == num_P
                        centers_full = 1
                    end
                end
                i = i + 1
            end
        end

        #If I still have num_selected < num_P, I double down on some centers iteratively
        if length(C) < num_P
            i = 1
            while i <= length(ranked_list)
                push!(C,ranked_list[i])
                if length(C) == num_P
                    centers_full = 1
                end
                i = i + 1
            end
        end

        #Here I have selected C = [] containing the centers
        r_centers = 0.2*norm(ub-lb)*ones(num_P)
        N_failures = zeros(num_P)
        #2.3 Candidate search
        new_points = zeros(eltype(surrSOP.x[1]),num_P,2)
        for i = 1:num_P
            N_candidates = zeros(eltype(surrSOP.x[1]),num_new_samples)
            #Using phi(n) just like DYCORS, merit function = surrogate
            #Like in DYCORS, I_perturb = 1 always
            evaluations = zeros(eltype(surrSOP.y[1]),num_new_samples)
            for j = 1:num_new_samples
                a = lb - C[i]
                b = ub - C[i]
                N_candidates[j] = C[i] + rand(truncated(Normal(0,r_centers[i]), a,b))
                evaluations[j] = surrSOP(N_candidates[j])
            end
            x_best = N_candidates[argmin(evaluations)]
            y_best = minimum(evaluations)
            new_points[i,1] = x_best
            new_points[i,2] = y_best
        end

        #new_points[i] now contains:
        #[x_1,y_1; x_2,y_2,...,x_{num_new_samples},y_{num_new_samples}]

        #2.4 Adaptive learning and tabu archive
        for i=1:num_P
            if new_points[i,1] in centers_global
                r_centers[i] = r_centers_global[i]
                N_failures[i] = N_failures_global[i]
            end

            f_1 = new_points[i,1]
            f_2 = obj2_1D(f_1,surrSOP.x)

            l = length(Fronts[1])
            Pareto_set = zeros(eltype(surrSOP.x[1]),l,2)

            for j = 1:l
                val = obj2_1D(Fronts[1][j],surrSOP.x)
                Pareto_set[j,1] = Fronts[1][j]
                Pareto_set[j,2] = val
            end
            if (Hypervolume_Pareto_improving(f_1,f_2,Pareto_set)<tau)
                #failure
                r_centers[i] = r_centers[i]/2
                N_failures[i] += 1
                if N_failures[i] > N_fail
                    push!(tabu,C[i])
                    push!(N_tenures_tabu,0)
                end
            else
                #P_i is success
                #Adaptive_learning
                add_point!(surrSOP,new_points[i,1],new_points[i,2])
                push!(r_centers_global,r_centers[i])
                push!(N_failures_global,N_failures[i])
            end
        end
    end
    index = argmin(surrSOP.y)
    return (surrSOP.x[index],surrSOP.y[index])
end


#!/usr/bin/env Julia

using Distributions

size, subsize, depth =
    parse(Int, ARGS[1]), parse(Int, ARGS[2]), parse(Int, ARGS[3])

oname = ARGS[4]

function generator(μ, subsize, size, depth)
    N = MvNormal(μ, eye(2) * ((depth * 0.5) ^ 4))
    if depth == 1
        for _ in 1:size
            X = rand(N)
            produce([X' N.μ'])
        end
    else
        for seed in 1:subsize
            generator(rand(N), subsize, size, depth-1)
        end
    end
end

p = Task(() -> generator(zeros(2), subsize, size, depth))

Data = vcat([X for X in p]...)

using DataFrames

df = DataFrame(x=Data[:, 1], y=Data[:, 2])

title = "$(oname)_$(size)_$(subsize)_$(depth)"
writetable("$(title).csv", df)

using Plots

df[:group] = (df[:x] .* df[:y])

plt = scatter(df[:x], df[:y], color=df[:group])

png(title)


using CompScienceMeshes, BEAST
o, x, y, z = euclidianbasis(3)

trc = X->ntrace(X,γ)
dvg = divergence

h1 = h2 = h3 = 1/15; h = max(h1, h2, h3)
width, height = 1.0, 0.5
Γ1 = meshrectangle(width, height, h1)
Γ2 = meshrectangle(width, height, h2); CompScienceMeshes.rotate!(Γ2, 0.5π * [1,0,0])
Γ3 = meshrectangle(width, height, h3); CompScienceMeshes.rotate!(Γ3, 1.0π * [1,0,0])
γ = meshsegment(width, width, 3)

κ = 1.0
S, T, I = SingleLayerTrace(κ), MWSingleLayer3D(κ), Identity()
E = planewavemw3d(direction=z, polarization=x, wavenumber=κ)
e = (n×E)×n

X1, X2, X3 = raviartthomas(Γ1, γ), raviartthomas(Γ2, γ), raviartthomas(Γ3, γ)
X = X1 × X2 × X3

@hilbertspace j
@hilbertspace k
α, β = 1/(im*κ), log(abs(h))/(im*κ)
Eq = @varform T[k,j] + α*S[trc(k), dvg(j)] + α*S.'[dvg(k), trc(j)] + β*I[trc(k), trc(j)] == e[k]
eq = @discretise Eq  j∈X k∈X
u = solve(eq)

fcr, geo = facecurrents(u, X)
include(Pkg.dir("CompScienceMeshes","examples","plotlyjs_patches.jl"))
p = patch(geo, real.(norm.(fcr)))
PlotlyJS.plot(p)


function create_splittable_from_params(params::cluster_parameters, α::Float32)
    params_l = deepcopy(params)
    params_l.distribution = sample_distribution(params.posterior_hyperparams)
    params_r = deepcopy(params)
    params_r.distribution = sample_distribution(params.posterior_hyperparams)
    lr_weights = rand(Dirichlet(Float64.([α / 2, α / 2])))
    return splittable_cluster_params(params,params_l,params_r,lr_weights, false, [-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf])
end

function merge_clusters_to_splittable(cpl::cluster_parameters,cpr::cluster_parameters, α::Float32)
    suff_stats = aggregate_suff_stats(cpl.suff_statistics,cpr.suff_statistics)
    posterior_hyperparams = calc_posterior(cpl.hyperparams, suff_stats)
    lr_weights = rand(Dirichlet(Float64.([cpl.suff_statistics.N + (α / 2), cpr.suff_statistics.N + (α / 2)])))
    cp = cluster_parameters(cpl.hyperparams, cpl.distribution, suff_stats, posterior_hyperparams)
    return splittable_cluster_params(cp,cpl,cpr,lr_weights, false, [-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf,-Inf])
end

function should_merge!(should_merge::AbstractArray{Float32,1}, cpl::cluster_parameters,cpr::cluster_parameters, α::Float32, final::Bool)
    new_suff = aggregate_suff_stats(cpl.suff_statistics, cpr.suff_statistics)
    cp = cluster_parameters(cpl.hyperparams, cpl.distribution, new_suff,cpl.posterior_hyperparams)
    cp.posterior_hyperparams = calc_posterior(cp.hyperparams, cp.suff_statistics)
    log_likihood_l = log_marginal_likelihood(cpl.hyperparams, cpl.posterior_hyperparams, cpl.suff_statistics)
    log_likihood_r = log_marginal_likelihood(cpr.hyperparams, cpr.posterior_hyperparams, cpr.suff_statistics)
    log_likihood = log_marginal_likelihood(cp.hyperparams, cp.posterior_hyperparams, cp.suff_statistics)
    log_HR = (-log(α) + lgamma(α) -2*lgamma(0.5*α) + lgamma(cp.suff_statistics.N) -lgamma(cp.suff_statistics.N + α) +
        lgamma(cpl.suff_statistics.N + 0.5*α)-lgamma(cpl.suff_statistics.N)  - lgamma(cpr.suff_statistics.N) +
        lgamma(cpr.suff_statistics.N + 0.5*α)+ log_likihood- log_likihood_l- log_likihood_r)
    # log_HR = -(log(α) +
    #     lgamma(cpl.suff_statistics.N) + log_likihood_l +
    #     lgamma(cpr.suff_statistics.N) + log_likihood_r -
    #     (lgamma(cp.suff_statistics.N) + log_likihood))
    if (log_HR > log(rand())) || (final && log_HR > log(0.1))
        should_merge .= 1
    end
end

function sample_cluster_params(params::splittable_cluster_params, α::Float32, first::Bool)
    points_count = Vector{Float32}()
    params.cluster_params.distribution = sample_distribution(first ? params.cluster_params.hyperparams : params.cluster_params.posterior_hyperparams)
    params.cluster_params_l.distribution = sample_distribution(first ? params.cluster_params_l.hyperparams : params.cluster_params_l.posterior_hyperparams)
    params.cluster_params_r.distribution = sample_distribution(first ? params.cluster_params_r.hyperparams : params.cluster_params_r.posterior_hyperparams)
    push!(points_count, params.cluster_params_l.suff_statistics.N)
    push!(points_count, params.cluster_params_r.suff_statistics.N)
    points_count .+= α / 2
    params.lr_weights = rand(Dirichlet(Float64.(points_count)))

    log_likihood_l = log_marginal_likelihood(params.cluster_params_l.hyperparams,params.cluster_params_l.posterior_hyperparams, params.cluster_params_l.suff_statistics)
    log_likihood_r = log_marginal_likelihood(params.cluster_params_r.hyperparams,params.cluster_params_r.posterior_hyperparams, params.cluster_params_r.suff_statistics)

    params.logsublikelihood_hist[1:burnout_period-1] = params.logsublikelihood_hist[2:burnout_period]
    params.logsublikelihood_hist[burnout_period] = log_likihood_l + log_likihood_r
    logsublikelihood_now = 0.0
    for i=1:burnout_period
        logsublikelihood_now += params.logsublikelihood_hist[i] *(1/(burnout_period-0.1))
    end
    if logsublikelihood_now != -Inf && logsublikelihood_now - params.logsublikelihood_hist[burnout_period] < 1e-2 # propogate abs change to other versions?
        # println(params.logsublikelihood_hist)
        params.splittable = true
    end

    return params
end


"""
LDA Slater exchange (DOI: 10.1017/S0305004100016108 and 10.1007/BF01340281)
"""
function lda_x!(ρ; α=1, E=nothing, Vρ=nothing)
    tmp = @. -α * cbrt(3/π * ρ)

    if E !== nothing
        E .= 3/4 .* tmp
    end
    if Vρ !== nothing
        Vρ .= tmp
    end

    (E=E, Vρ=Vρ)
end


function LogKernelAsymptotics(S::Fourier,k)
    d=domain(S)
    r=d.radius
    if k==-1  # coefficient in front of logabs term
        FiniteOperator([2r].',S,ConstantSpace())
    else
        error("Not implemented")
    end
end

LogKernelAsymptotics(S::Laurent,k)=LogKernelAsymptotics(Fourier(domain(S)),k)*Conversion(S,Fourier(domain(S)))

function LogKernelAsymptotics{CD<:ChebyshevDirichlet{1,1}}(S::JacobiWeight{CD},k)
    @assert S.α==S.β==-0.5
    d=domain(S)
    if k==-1  # coefficient in front of logabs term
        FiniteOperator([arclength(d)/2,0,-arclength(d)/2].',S)
    else
        error("Not implemented")
    end
end

function LogKernelAsymptotics{C<:Chebyshev}(S::JacobiWeight{C},k)
    @assert S.α==S.β==-0.5
    d=domain(S)
    if k==-1  # coefficient in front of logabs term
        FiniteOperator([arclength(d)/2].',S,ConstantSpace())
    else
        error("Not implemented")
    end
end


import Distributions.Distribution
import Distributions.rand
import Distributions.support
import StatsBase.quantile
import StatsBase.mean
import StatsBase.median
import StatsBase.middle
import StatsBase.quantile
import StatsBase.std

import Distributions.pdf

import Base: minimum, maximum

abstract type AbstractEmpiricalDistribution end

struct FittedDistribution{D <: Distribution} <: AbstractEmpiricalDistribution
    distribution::D
end

Broadcast.broadcastable(fd::FittedDistribution) = Ref(fd.distribution)

Distributions.rand(fd::FittedDistribution) = rand(fd.distribution)
Distributions.rand(fd::FittedDistribution, n::Int) = rand(fd.distribution, n)
Distributions.support(fd::FittedDistribution) = support(fd.distribution)
Distributions.pdf(fd::FittedDistribution, x) = pdf(fd.distribution, x)
StatsBase.mean(fd::FittedDistribution) = mean(fd.distribution)
StatsBase.median(fd::FittedDistribution) = median(fd.distribution)
StatsBase.middle(fd::FittedDistribution) = middle(fd.distribution)
StatsBase.quantile(fd::FittedDistribution, q) = quantile(fd.distribution, q)
StatsBase.std(fd::FittedDistribution) = std(fd.distribution)

Base.minimum(uv::FittedDistribution) = minimum(uv.distribution)
Base.maximum(uv::FittedDistribution) = maximum(uv.distribution)

export
AbstractEmpiricalDistribution,
FittedDistribution


"""
Orthogonalize!(V, hv, vv, orth; verbose=false)

Orthogonalize the Krylov vectors using your (my) choice of
methods. Anything other than classical Gram-Schmidt twice (cgs2) is
likely to become an undocumented and UNSUPPORTED option. Methods other
than cgs2 are mostly for CI for the linear solver.

DO NOT use anything other than "cgs2" with Anderson acceleration.
"""
function Orthogonalize!(V, hv, vv, orth = "cgs2"; verbose=false)
    orthopts = ["mgs1", "mgs2", "cgs1", "cgs2"]
    orth in orthopts || error("Impossible orth spec in Orthogonalize!")
    if orth == "mgs1"
        mgs!(V, hv, vv; verbose=verbose)
    elseif orth == "mgs2"
        mgs!(V, hv, vv, "twice"; verbose=verbose)
    elseif orth == "cgs1"
        cgs!(V, hv, vv, "once"; verbose=verbose)
    else
        cgs!(V, hv, vv, "twice"; verbose=verbose)
    end
end

"""
mgs!(V, hv, vv, orth; verbose=false)
"""
function mgs!(V, hv, vv, orth = "once"; verbose=false)
    k = length(hv) - 1
    normin = norm(vv)
    #p=copy(vv)
    @views for j = 1:k
        p = vec(V[:, j])
        hv[j] = p' * vv
        vv .-= hv[j] * p
    end
    hv[k+1] = norm(vv)
    if (normin + 0.001 * hv[k+1] == normin) && (orth == "twice")
        @views for j = 1:k
            p = vec(V[:, j])
            hr = p' * vv
            hv[j] += hr
            vv .-= hr * p
        end
        hv[k+1] = norm(vv)
    end
    nv = hv[k+1]
    #
    # Watch out for happy breakdown
    #
    #if hv[k+1] != 0
    #@views vv .= vv/hv[k+1]
    (nv != 0) || (verbose && (println("breakdown in mgs1")))
    if nv != 0
        vv ./= nv
    end
end

"""
cgs!(V, hv, vv, orth="twice"; verbose=false)

Classical Gram-Schmidt.
"""
function cgs!(V, hv, vv, orth="twice"; verbose=false)
    #
    #   no BLAS. Seems faster than BLAS since 1.6 and allocates
    #   far less memory.
    #
    k = length(hv)
    T = eltype(V)
    onep = T(1.0)
    zerop = T(0.0)
    @views rk = hv[1:k-1]
    pk = zeros(T, size(rk))
    qk = vv
    Qkm = V
    # Orthogonalize
    rk .+= Qkm' * qk
#    qk .-= Qkm * rk
    mul!(qk, Qkm, rk, -1.0, 1.0)
    if orth == "twice"
        # Orthogonalize again
        pk .= Qkm' * qk
#        qk .-= Qkm * pk
        mul!(qk, Qkm, pk, -1.0, 1.0)
        rk .+= pk
    end
    # Keep track of what you did.
    nqk = norm(qk)
    (nqk != 0) || (verbose && (println("breakdown in cgs")))
    (nqk > 0.0) && (qk ./= nqk)
    hv[k] = nqk
end