#=
   The Exponential Functions `exp(x)` and `exp(z)`.
   The associaed functions `expm1(x)` and `expm1(z)`.
=#

const Ln2_D64 = Double64((0.6931471805599453, 2.3190468138462996e-17))

function Base.exp(x::Double64)
   exp_specialvalues(Hi(x))
   # 4.3108e-78 <= absx < 709.9
   absx = abs(x)

   # Solve for k satisfying x = 2^k * log(2) r
   #   with |r| <= 1/2 log(2)
   k = ceil(Int64, (absx / Ln2) - 0.5)
   # p = 2^k
   p = Int64(1) << k
   # Determine r using the k we computed
   r = absx - (k * Ln2)

end

function exp_specialvalues(x::Float64)
   isnan(x) && return x
   absx = abs(x)
   absx > 709.0 && return signbit(x) ? -InfD64 : InfD64
   abx < 2.0^-257 && return one(Double64)
   return nothing
end

#=
   source: Efficient implementation of elementary functions in the medium-precision range
           by Fredrik Johansson
           section 3: Argument reduction

   result = exp(x)

   m = fld(x, log(2))
   t = x - m * log(2)

  julia> FF4(log(BigFloat(2)))
  (0.6931471805599453, 2.3190468138462996e-17, 5.707708438416212e-34, -3.5824322106018114e-50)
  julia> FF4(exp(BigFloat(1)))
  (2.718281828459045, 1.4456468917292502e-16, -2.1277171080381768e-33, 1.5156301598412191e-49)
  julia> FF4(exp(BigFloat(-1)))
  (0.36787944117144233, -1.2428753672788363e-17, -5.830044851072742e-34, -2.8267977849017436e-50)
  log2(exp(1))
  (1.4426950408889634, 2.0355273740931033e-17, -1.0614659956117258e-33, -1.3836716780181402e-50)

   exp(x) = exp(t) * (2.0^m)
            where t  in [0, log(2)) # log(2) ~0.6931471805599453

   exp(t) = (exp(t/(2.0^r)))^(2.0^r)
            where t/(2.0^r) in [0, 2.0^(-r))   # at the expense of r squarings

julia> 2.0^(-8) = 0.00390625
julia> 2.0^(-9) = 0.001953125

   if we precompute exp(i/(2^r)) for i = 0..(2^r - 1)
      we can write exp(x) = exp(x - i/2^r) * exp(i/2^r)  wher i = floor(x * 2^r)
      this achieves r halvings of argument reduction for the cost of a multiplication

sum([x^k / factorial(k) for k=0:n]) # perhaps 11
ex2(x,n) = sum([(x^(2*k) * (1 + 2*k +x)) / factorial(1+2*k) for k=0:n])  # 5 perhaps 6

=#
#=
ir(r) = collect(0:(2^r - 1));
ir2(r1,r2) = collect((2^r1):(2^r2 - 1))
exps(r) = exp.(ir(r) / big"2"^r)
exps2(r1,r2) = exp.(ir2(r1,r2) / big"2"^r2)

expi1 = exps(8);
expi2 = exps2(8,9);
expi1s = expi1[1:128]; # length 128
expi2s = expi2; # leingth 256

expi1s[1],expi1s[end]
(1.0, 1.642293515618909941146175177943829065898476010090130767464052857406618001282157)
expi2s[1],expi2s[end]
(1.648721270700128146848650787814163571653776100710148011575079311640661021194214, 2.712977865600149830047947983547967696224441828481010944922219814560357243651112)

# 256 values
expi = [
big"1.0",
big"1.0039138893383475734436096039034602818988136",
big"1.0078430972064479776934535597601235791933921",
big"1.0117876835593314915141138433716067245783427",
big"1.0157477085866857474585350720823517489067163",
big"1.0197232327137741547831816318953623989389768",
big"1.0237143166023579169688505321657689580745566",
big"1.0277210211516216579160287393882763310060287",
big"1.0317434074991026709387478152815071441944983",
big"1.0357815370216238047361682727501377703668922",
big"1.0398354713362300005766220098751917925969306",
big"1.0439052723011284949845543336459217728408202",
big"1.0479910020166327022767382918196849669390532",
big"1.0520927228261097913502841465226716758711456",
big"1.0562104973169319711813367037681297731537515",
big"1.0603443883214314995499437191070563220034387",
big"1.0644944589178594295633905946428896731007254",
big"1.0686607724313481086073309335198834903610129",
big"1.072843392434877444411300095010813766928624",
big"1.077042382750244952972680570165861926965078",
big"1.0812578074490396031408946517428286548338917",
big"1.0854897308536194727215323833540410098046093",
big"1.0897382175380932310182820254185122044095245",
big"1.0940033323293054627889171570399958544544903",
big"1.0982851403078258486502099342567867188098263",
big"1.1025837068089422170254848406286398617008396",
big"1.106899097423657482787602393741778963630765",
big"1.1112313779996904878094686123406995149077797",
big"1.1155806146424807586937045083794438798390782",
big"1.1199468737161971970128813548111838167408411",
big"1.1243302218447507174517329054449565537714046",
big"1.1287307259128108493029960233035576751121704",
big"1.1331484530668263168290072278117938725655031",
big"1.1375834707160496140618954216220641739226107",
big"1.1420358465335655896761614313488004988947112",
big"1.1465056484573240576286239229647318858420707",
big"1.1509929446911764493221396657803508410236923",
big"1.1554978037059165231111749561077458362413083",
big"1.1600202942403251470292152138940958101001022",
big"1.1645604853022191706801522774745177736791979",
big"1.1691184461695044022981846915118914445916047",
big"1.1736942463912327070434062632478574421242414",
big"1.1782879557886632426631433081958559090232306",
big"1.1828996444563278487122322779162386958636926",
big"1.1875293827631006055888078228331305661056264",
big"1.1921772413532715797057977592610942010914603",
big"1.1968432911476247711821968527700683464587539",
big"1.201527603344520280502316774392647998778235",
big"1.2062302494209807106555860104464335480403936",
big"1.2109513011337818213341018932134198490219412",
big"1.215690830520547451830018254540396832863575",
big"1.2204489099008487293399874776109514959456667",
big"1.2252256118773075794492659275775547905529261",
big"1.2300210093367045556337378772080063588467264",
big"1.2348351754510910046840161108760027424078237",
big"1.2396681836789055850219383947130699551294591",
big"1.2445201077660951549461989520766544581233531",
big"1.2493910217472400479105339951661133401110873",
big"1.2542809999466837520048212530906856313930507",
big"1.2591901169796670108766563253994731033558361",
big"1.2641184477534663633984346034590176916127065",
big"1.2690660674685371394526974695586839474881616",
big"1.2740330516196609292764965387163400150598443",
big"1.2790194759970975438737908883703017739782832",
big"1.2840254166877414840734205680624364583362809",
big"1.2890509500762829358789962405026343223583583",
big"1.2940961528463733098261106268100709645926577",
big"1.299161101981795342131613566113939048057404",
big"1.3042458747676377754893000110524664638914931",
big"1.3093505487914746374362402281617419935314083",
big"1.314475201944549134284134921979872958797392",
big"1.3196199124229621786805060243653563910202619",
big"1.3247847587288655689352375606889644453814644",
big"1.3299698196716598383189614010596868898353478",
big"1.335175174369196792611040910663613496051334",
big"1.3404009022489867542464426159453927247594736",
big"1.345647083049410531482603094316017723141301",
big"1.3509137968209361310794964701714017243589766",
big"1.3562011239273402330584882593857570013204578",
big"1.3615091450469344461782249525402249833846504",
big"1.3668379411737963628387567727212086721727333",
big"1.3721875936190054321983245998746874054136052",
big"1.3775581840118836703607622378892548520433121",
big"1.3829497943012412265652731346321226949971137",
big"1.3883625067566268243844374753000036641797537",
big"1.3937964039695830970106923862996621838753668",
big"1.3992515688549068357862059454951895801349576",
big"1.4047280846519141712060359345473907335921464",
big"1.4102260349257107056997279341815912975975336",
big"1.4157455035684666175720656019875394367736763",
big"1.4212865748006967555595399377075767858324404",
big"1.4268493331725457435352551903480186121366873",
big"1.4324338635650781149714379568249381105086462",
big"1.4380402511915734968454641297483462771412234",
big"1.4436685815988268627523668434427752933667452",
big"1.4493189406684538750641386180656840287525084",
big"1.454991414618201336053793691987518508346842",
big"1.4606860900032627679801132473277319831728503",
big"1.4664030537175991422072580622261772341235882",
big"1.4721423929952647775120012602512819589278704",
big"1.4779041954117384278102094711614577636044574",
big"1.4836885488852595796133850715926159360653458",
big"1.4894955416781699796055764474013653746366884",
big"1.4953252623982604128107686243383379457966073",
big"1.5011778000001227519009843681945126839847195",
big"1.5070532437865072982757571820568627066188943",
big"1.5129516834096854356243837541143330540396894",
big"1.518873208872817616763425566660741826592259",
big"1.5248179105313267046233088024596115443256307",
big"1.5307858790942766883395696202202495812292224",
big"1.5367772056257567954863095631977568976482128",
big"1.5427919815462710215717645653643101046597946",
big"1.5488302986341330979985519845954923375583037",
big"1.5548922490268669197741445831814759100397908",
big"1.5609779252226124543404296589529359617213441",
big"1.5670874200815371529748468766060916540358334",
big"1.5732208268272528862995610345959599306077298",
big"1.5793782390482384255194173094123070388118751",
big"1.5855597506992674910940477323437952296789346",
big"1.5917654561028423906344500651913320024805135",
big"1.5979954499506332678996451470842104572159357",
big"1.6042498273049229848546374859283226869322636",
big"1.6105286836000576588368576715138990015137373",
big"1.6168321146439028769645554044834985475711929",
big"1.6231602166193056100072398828952478310491124",
big"1.6295130860855618480252312878508519071585423",
big"1.6358908199798899801726944886054803066336858",
big"1.642293515618909941146175177943829065898476",
big"1.6487212707001281468486507878141635716537761",
big"1.6551741833034282419274450659151529161544963",
big"1.661652351892567681933037464039017553408944",
big"1.6681558753166801729348278516747020845493453",
big"1.6746848528117839915192948821484471915616397",
big"1.6812393840022962081857139672252104808506591",
big"1.6878195689025528372446796287495748265689688",
big"1.694425507918334936415108367064433784118984",
big"1.7010573018484006794061834954848285349306956",
big"1.7077150518860234248618440232984793229206938",
big"1.7143988596205358051369170173112717453784532",
big"1.7211088270388798584658483299957258431122154",
big"1.7278450565271632281772015524381343960105588",
big"1.7346076508722214526996709396061731128182903",
big"1.7413967132631863701982922765711232180120641",
big"1.7482123472930606617728376253439213210762717",
big"1.7550546569602985572440470365989676887382375",
big"1.7619237466703927276473840580130012741833298",
big"1.768819721237467388648403656081329732171722",
big"1.7757426858858776391885924315490433000564346",
big"1.7826927462518150597656831961070971624870767",
big"1.7896700083849195948479605415087339387426964",
big"1.7966745787498977440179624291990663920674301",
big"1.8037065642281470865372465220624332490665196",
big"1.8107660721193871641205304388476395701804874",
big"1.8178532101432967468045338108486506072754985",
big"1.8249680864411575068942484398874703584971162",
big"1.8321108095775041260671424827506305635923199",
big"1.8392814885417808608139669119715291919683603",
big"1.8464802327500045914933790240679012662617646",
big"1.8537071520464343803765299877394712853248167",
big"1.8609623567052475641570828556931428635577371",
big"1.8682459574322224065018356201881044531149723",
big"1.8755580653664273363172222955086314557144833",
big"1.8828987920819167975074551877605054582064688",
big"1.890268249589433736100954997008217074841387",
big"1.8976665503381187507229937281862390730851715",
big"1.9050938072172259324941501108153118386642345",
big"1.9125501335578454205362498947690069141803877",
big"1.9200356431346326993699355576741048656485595",
big"1.9275504501675446645908831925937307660930033",
big"1.9350946693235824833149602121576936617744414",
big"1.9426684157185412759862975831367569870468297",
big"1.9502718049187666462463361756410743039045826",
big"1.9579049529429180856664000618841160115832721",
big"1.965567976263739280251251825234587055462156",
big"1.9732609918098353457263977417630639167722963",
big"1.980984116967457018727635680625228052334163",
big"1.9887374695822918311174773496469253668482552",
big"1.9965211679612622947596307079391523309846108",
big"2.0043353308743311241896996041635187235500792",
big"2.0121800775563135247276476094443781622950744",
big"2.0200555277086965736853832365247240976992445",
big"2.0279618015014657224310559166446580204412786",
big"2.0358990195749384471803078942778508475105447",
big"2.0438673030416050764938082552508055357536932",
big"2.0518667734879768235699032903388202787086325",
big"2.0598975529764410515311539851427396253726037",
big"2.0679597640471238000138982954032159493019471",
big"2.076053529719759601480774698971190974886495",
big"2.0841789734955686157873760020828031213826762",
big"2.0923362193591411116458702156591367747823646",
big"2.1005253917803293237405302109843070944234469",
big"2.1087466157161467143626575238964185488599182",
big"2.1170000166126746685453698198370956101344916",
big"2.1252857204069766517921478827499607656345219",
big"2.1336038535290198596069082796475876104173024",
big"2.141954542903604388147683817205012365763677",
big"2.1503379159522999554407572913112531245709817",
big"2.1587541005953902027073065862709664584426364",
big"2.1672032252538246054702826653026868576426094",
big"2.1756854188511780242253581730150091331184588",
big"2.1842008108156179245763550154521229783421722",
big"2.1927495310818792968525861728144356991424522",
big"2.2013317100932473053430319197695160332670769",
big"2.2099474788035476974002153710531479045782357",
big"2.2185969686791450027850486355161375002671637",
big"2.2272803117009485537427906566397217584734165",
big"2.2359976403664263564195928556324133682743723",
big"2.2447490876916268443489107954443761003418463",
big"2.2535347872132085448573310784292862413086861",
big"2.2623548729904776893601044121401684567152138",
big"2.2712094796074337986378900692397035812348409",
big"2.2800987421748232743079056783226487661726783",
big"2.2890227963322010278248412694130178222761383",
big"2.2979817782500001784695396251036787167203531",
big"2.3069758246316098519065681271390235943873022",
big"2.3160050727154611110154123174147474653913428",
big"2.32506966027712105082411019794800030841958",
big"2.3341697256313950894987207698035707748063283",
big"2.3433054076344374874670823571053781999975876",
big"2.352476845685870126880867787415201591593135",
big"2.3616841797309095837459864196889344674000366",
big"2.3709275502625025251779192489894964270765329",
big"2.3802070983234694643656048039370752132460841",
big"2.3895229655086569059550222268607428894033582",
big"2.3988752939670979146916457337162967482854659",
big"2.4082642264041811402894735446052064189541318",
big"2.4176899060838283316233663173665756270227349",
big"2.4271524768306803734709670750917334949139414",
big"2.4366520830322918791605185701090931800750108",
big"2.4461888696413343726114469563210060388105308",
big"2.4557629821778080933856445408266406336725324",
big"2.4653745667312624584989612544143600966570358",
big"2.4750237699630252148745063264598957734822349",
big"2.4847107391084403164519704885869968967885934",
big"2.4944356219791145601003068865880476028893769",
big"2.5041985669651730146147577829358268760753641",
big"2.5139997230375232772133861220671538815112637",
big"2.5238392397501285920829681547893051234396304",
big"2.533717267242289865659327631968429446833967",
big"2.5436339562409366134629456444676263089122303",
big"2.5535894580629268734469650775485663612146274",
big"2.5635839246173561209515269431795734112490795",
big"2.5736175084078752204957296405891146914479675",
big"2.5836903625350174497763935698217882499996026",
big"2.5938026406985346313812445890522234238021264",
big"2.6039544971997424078631026762839444413754604",
big"2.6141460869438746959611789503655914445178259",
big"2.6243775654424473558956470538559895767179813",
big"2.6346490888156311118022659383545698498240101",
big"2.6449608137946337595149924670414734811886784",
big"2.6553128977240916980462361132966847731583179",
big"2.6657054985644708212566765507744459129605862",
big"2.676138774894476806349390270046054291415995",
big"2.6866128859134748359664166992354320756243384",
big"2.6971279914439187908098398388318050409222763",
big"2.7076842519337899498539703405097468565320357",
];

julia> function accurateC1C2(C,p=53)
         R3 = FF3(1/C)
         R = sum(BigFloat.(R3))
         C1a = FF3(1/R)
         C1b = map(x->round(x, sigdigits=p-2, base=2), C1a)
         C1 = FF3(sum(BigFloat.(C1b))); C13 = sum(BigFloat.(C1))
         C2a = ((C-C13) / (8*eps(eps(C13)))) * (8*eps(eps(C13)))
         C2 = FF3(C2a)
         return C1, C2
       end
accurateC1C2 (generic function with 2 methods)

julia> accurateC1C2(g)
((1.618033988749895, -5.4321152036825055e-17, 2.6543252083815655e-33), (-6.162975822039155e-33, -3.304991997502108e-50, 6.608118949502031e-67))

julia> function accurateargreduction(x, z, C1, C2)
           u = x - z*C1[1]
           v1 = u - z*C2[1]
           p1,p2 = two_prod(z, C2[1])
           t1,t2 = two_hilo_sum(u, -p1)
           v2 = (((t1-v1)+t2)-p2)
           return v2
       end

=#

#=
   function exp(x)
      signbit(x) && return inv(exp(abs(x))
=#

#=
 function v = exp( v )
            if ~isreal( v )
                [ sin_imag_v, cos_imag_v ] = sincos( imag( v ) );
                v = exp( real( v ) ) .* ( cos_imag_v + 1i .* sin_imag_v );
                return
            end

            % Strategy:  We first reduce the size of x by noting that
            % exp(kr + m * log(2)) = 2^m * exp(r)^k
            % where m and k are integers.  By choosing m appropriately
            % we can make |kr| <= log(2) / 2 = 0.347.  Then exp(r) is
            % evaluated using the familiar Taylor series.  Reducing the
            % argument substantially speeds up the convergence.
            k = 512.0;
            inv_k = 1.0 / k;
            Threshhold = inv_k .* DoubleDouble.eps.v1;

            m = floor( v.v1 ./ DoubleDouble.log_2.v1 + 0.5 );
            r = TimesPowerOf2( v - DoubleDouble.log_2 .* m, inv_k );

            p = r .* r;
            s = r + TimesPowerOf2( p, 0.5 );
            p = p .* r;
            t = p .* DoubleDouble.Make( DoubleDouble.InverseFactorial( 1, 1 ), DoubleDouble.InverseFactorial( 1, 2 ) );
            for i = 2 : DoubleDouble.NInverseFactorial
                s = s + t;
                p = p .* r;
                t = p .* DoubleDouble.Make( DoubleDouble.InverseFactorial( i, 1 ), DoubleDouble.InverseFactorial( i, 2 ) );
                if all( abs( t.v1(:) ) <= Threshhold )
                    break
                end
            end

            s = s + t;

            s = TimesPowerOf2( s, 2.0 ) + s .* s;
            s = TimesPowerOf2( s, 2.0 ) + s .* s;
            s = TimesPowerOf2( s, 2.0 ) + s .* s;
            s = TimesPowerOf2( s, 2.0 ) + s .* s;
            s = TimesPowerOf2( s, 2.0 ) + s .* s;
            s = TimesPowerOf2( s, 2.0 ) + s .* s;
            s = TimesPowerOf2( s, 2.0 ) + s .* s;
            s = TimesPowerOf2( s, 2.0 ) + s .* s;
            s = TimesPowerOf2( s, 2.0 ) + s .* s;
            s = s + 1.0;

            v = DoubleDouble.Make( pow2( s.v1, m ), pow2( s.v2, m ) );
        end

https://github.com/tholden/DoubleDouble/blob/master/DoubleDouble.m
=#
#=
https://www.pseudorandom.com/implementing-exp

def reduced_taylor_exp(x):
    """
    Evaluates f(x) = e^x for any x in the interval [-709, 709].
    If x < -709 or x > 709, raises an assertion error because of
    underflow/overflow limits. Performs a range reduction step
    by applying the identity e^x = e^r * 2^k for |r| <= 1/2 log(2)
    and positive integer k. e^r is approximated using a Taylor series
    truncated at 14 terms, which is enough because e^r is in the
    interval [0, 1/2 log(2)]. The result is left shifted by k
    to obtain e^r * 2^k = e^x.
    Performance: In the worst case we have 51 operations:
    - 16 multiplications
    - 16 divisions
    - 14 additions
    - 2 subtractions
    - 1 rounding
    - 1 left shift
    - 1 absolute value
    Accuracy: Over a sample of 10,000 equi-spaced points in
    [-709, 709] we have the following statistics:
    - Max relative error = 7.98411243625574e-14
    - Min relative error = 0.0
    - Avg relative error = 1.7165594254816366e-14
    - Med relative error = 1.140642685235478e-14
    - Var relative error = 2.964698541882666e-28
    - 6.29 percent of the values have less than 14 digits of precision
    :param x: (int) or (float) power of e to evaluate
    :return: (float) approximation of e^x
    """
    # If x is not a valid value, exit early
    assert(-709 <= x <= 709)
    # If x = 0, we know e^x = 1 so exit early
    if x == 0:
        return 1
    # Normalize x to a positive value since we can use reciprocal
    # symmetry to only work on positive values. Keep track of
    # the original sign for return value
    x0 = np.abs(x)
    # Hard code the value of natural log(2) in double precision
    l = 0.6931471805599453
    # Solve for an integer k satisfying x = k log(2) + r
    k = math.ceil((x0 / l) - 0.5)
    # p = 2^k
    p = 1 << k
    # r is a value between 0 and 0.5 log(2), inclusive
    r = x0 - (k * l)
    # Setup the Taylor series to evaluate e^r after
    # range reduction x -> r. The Taylor series
    # only approximates on the interval [0, log(2)/2]
    Tn = 1
    # We need at most 14 terms to achieve 16 digits
    # of precision anywhere on the interval [0, log(2)/2]
    for i in range(14, 0, -1):
        Tn = Tn * (r / i) + 1
    # e^x = e^r * 2^k, so multiply p by Tn. This loses us
    # about two digits of precision because we compound the
    # relative error by 2^k.
    p *= Tn
    # If the original sign is negative, return reciprocal
    if x < 0:
        p = 1 / p
    return p

double reduced_taylor_exp(double x) {
    /*
     * Evaluates f(x) = e^x for any x in the interval [-709, 709].
     * If x < -709 or x > 709, raises an assertion error. Performs a
     * range reduction step by applying the identity e^x = e^r * 2^k
     * for |r| <= 1/2 log(2) and positive integer k. e^r is evaluated
     * using Taylor series truncated at 14 terms, which is sufficient
     * to achieve 16 digits of precision for r so close to 0. The
     * result is left shifted by k to obtain e^r * 2^k = e^x.
     * Performance: In the worst case we have 51 operations:
     * - 16 multiplications
     * - 16 divisions
     * - 14 additions
     * - 2 subtractions
     * - 1 rounding
     * - 1 left shift
     * - 1 absolute value
     * Accuracy: Over a sample of 10,000 linearly spaced points in
     * [-709, 709], we have the following error statistics:
     * - Max relative error = 7.99528e-14
     * - Min relative error = 0
     * - Avg relative error = 0
     * - Med relative error = 2.27878e-14
     * - Var relative error = 0
     * - 6.4 percent of the values have less than 14 digits of precision.
     * Args:
     *      - x (double float) power of e to evaluate
     * Returns:
     *      - (double float) approximation of e^x
     */
    // Make sure x is a valid input
    assert(-709 <= x && x <= 709);
    // When x is 0, we know e^x is 1
    if (x == 0) {
        return 1;
    }
    // Normalize x to a positive value to take advantage of
    // reciprocal symmetry, but keep track of the original value
    double x0 = abs(x);
    // Solve for k satisfying x = 2^k * log(2) r, with |r| <= 1/2 log(2)
    int k = ceil((x0 / M_LN2) - 0.5);
    // Determine r using the k we computed
    double r = x0 - (k * M_LN2);
    // Setup the Taylor series to evaluate e^r after range reduction
    // x -> r. This only approximates over the interval [0, 1/2 log(2)]
    double tk = 1.0;
    double tn = 1.0;
    // We only need 14 terms to achieve 16 digits of precision for e^r
    for (int i = 1; i < 14; i++) {
        tk *= r / i;
        tn += tk;
    };
    tn = tn * pow2(k);
    if (x < 0) {
        return 1 / tn;
    }
    return tn;
}

=#

#=
kv-master

```

julia> x
Double64((19.73721077166514, -1.6936500472509631e-15))

julia> x/Ln2
Double64((28.474776101261526, -8.19680688222626e-17))

julia> xoLn2 = ans
Double64((28.474776101261526, -8.19680688222626e-17))

julia> x_i = floor(x)
Double64((19.0, 0.0))

julia> x_f = x - x_i
Double64((0.7372107716651383, -2.831551031322832e-17))

julia> x_f = x_f - 1
Double64((-0.2627892283348618, 2.7195640918029507e-17))

julia> x_i = x_i + 1
Double64((20.0, 0.0))

julia> r = y = Double64(1);

julia> for i=1:25
         y = y * x_f
         y = y / i
                  r = r + y
       end

julia> x_i
Double64((20.0, 0.0))

julia> e1 = Double64(exp(BigFloat(1)))
Double64((2.718281828459045, 1.4456468917292502e-16))

julia> e2 = Double64(BigFloat(e1)^BigFloat(x_i))
Double64((4.851651954097903e8, 4.880277289790481e-10))

julia> r2 = r * e2
Double64((3.730454319498937e8, -6.494042474083988e-10))

julia> expa = Double64(exp(BigFloat(a)))
Double64((3.730454319498937e8, -6.49404247408404e-10))
```
	friend dd exp(const dd& x) {
		dd x_i, x_f, tmp;
		dd r, y;
		int i;

		if (x == dd(std::numeric_limits<double>::infinity(), 0.)) {
			return dd(std::numeric_limits<double>::infinity(), 0.);
		}
		if (x == -dd(std::numeric_limits<double>::infinity(), 0.)) {
			return (dd)0.;
		}

		if (x >= 0.) {
			x_i = floor(x);
			x_f = x - x_i;
			if (x_f >= 0.5) {
				x_f -= 1.;
				x_i += 1.;
			}
		} else {
			x_i = -floor(-x);
			x_f = x - x_i;
			if (x_f <= -0.5) {
				x_f += 1.;
				x_i -= 1.;
			}
		}

		r = 1.;
		y = 1.;
		for (i=1;  i<=25 ; i++) {
			y *= x_f;
			y /= i;
			r += y;
		}

		if (x_i >= 0.) {
			// r *= pow(constants<dd>::e(), (int)x_i);
			r *= ipower(e(), (double)x_i);
		} else {
			// r /= pow(constants<dd>::e(), -(int)x_i);
			r /= ipower(e(), -(double)x_i);
		}

		return r;
	}

	static dd expm1_origin(const dd& x) {
		dd r, y;
		int i;

		r = 0.;
		y = 1.;
		for (i=1; i<=25 ; i++) {
			y *= x;
			y /= i;
			r += y;
		}

		return r;
	}

	friend dd expm1(const dd& x) {
		if (x >= -0.5 && x <= 0.5) {
			return expm1_origin(x);
		} else {
			return exp(x) - 1.;
		}
	}

=#

#=
   function expsmall6(v)
    t1 = v ^ 2
    t5 = t1 ^ 2
    t13 = t5 ^ 2
    return(1 + v + (3 + v) * t1 / 6 + (5 + v) * t5 / 120 + (7 + v) * t1 * t5 / 5040 + (9 + v) * t13 / 362880 + (11 + v) * t1 * t13 / 39916800 + (13 + v) * t5 * t13 / 6227020800)
   end

   function expsmall8horner(v)
      1 + (1 + (1//2 + (1//6 + (1//24 + (1//120 + (1//720 + (1//5040 + (1//40320 + (1//362880 + (1//3628800 + (1//39916800 + (1//479001600 + (1//6227020800 +
          (1//87178291200 + v / 1307674368000) * v) * v) * v) * v) * v) * v) * v) * v) * v) * v) * v) * v) * v) * v
    end
=#


#=

    discrepancies.jl

    Contains code to assess discrepancy between data and model simulation using Cramér–von Mises distance.

    Author:     Alexander P. Browning
                ======================
                School of Mathematical Sciences
                Queensland University of Technology
                ======================
                ap.browning@icloud.com
                alexbrowning.me

=#

"""
    makecdf(x::Vector;n=1000)

Creates a linear interpolation approximation of the distribution function of `x`.

"""
function makecdf(x::Vector;n=1000)
    n_x = length(x)
    xp = range(minimum(x),maximum(x) + 1e-3,length=n)
    p = [count(x .< xpᵢ) for xpᵢ in xp] / n_x
    itp = LinearInterpolation(xp, p, extrapolation_bc = Interpolations.Flat())
    return itp
end

"""
    cvmises(cdf::AbstractInterpolation,y::Vector)

Computes Cramér–von Mises distance between distribution defined by the distribution function `cdf`
(given as an interpolation) and the univariate sample `y`.

"""
function cvmises(cdf::AbstractInterpolation,y::Vector)
    n = length(y)
    cdfs = cdf(sort(y))
    return sqrt(sum( ((2(1:n) .- 1.0) / 2n - cdfs).^2 ) / n + 1 / 12n^2)
end

"""
    addist(cdf::AbstractInterpolation,y::Vector)

Computes Anderson-Darling distance between distribution defined by the distribution function `cdf`
(given as an interpolation) and the univariate sample `y`.

"""
function addist(cdf::AbstractInterpolation,y::Vector)
    n = length(y)
    cdfs = min.(max.(cdf(sort(y)),1e-9),1-1e-9)
    sqrt(-n - sum( (2(1:n) .- 1.0) / n .* (log.(cdfs) + log.(1.0 .- cdfs[end:-1:1]) )  ))
end

"""
    ksdist(cdf::AbstractInterpolation,y::Vector)

Computes Kolmogorov–Smirnov distance between distribution defined by the distribution function `cdf`
(given as an interpolation) and the univariate sample `y`.

"""
function ksdist(cdf::AbstractInterpolation,y::Vector)
    n = length(y)
    cdfs = cdf(sort(y))
    δp = maximum((1:n) / n - cdfs)
    δn = -minimum((0:n-1) / n - cdfs)
    return max(δn, δp)
end

"""
kuiperdist(cdf::AbstractInterpolation,y::Vector)

Computes Kuiper's distance between distribution defined by the distribution function `cdf`
(given as an interpolation) and the univariate sample `y`.

"""
function kuiperdist(cdf::AbstractInterpolation,y::Vector)
    n = length(y)
    cdfs = cdf(sort(y))
    δp = maximum((1:n) / n - cdfs)
    δn = -minimum((0:n-1) / n - cdfs)
    return δn + δp
end

"""
    makediscrepancy(X;w=[0.5,0.5])

Create discrepancy function `dist(Y)` using data `X` where:
    X[i] = [[Q̄,Ū],[Q,U]]
and
    dist(Y) = ΣᵢΣⱼ( 2w[2] * cor(X[i][j]...) + w[1] * Σₖ addist(X[i][j][k]) )

By default, `w = [0.5,0.5]` so Anderson-Darling distance is weighted equally with correlation.

"""
function makediscrepancy(X::Vector{Vector{Vector{Vector{Float64}}}},f=addist;w=[0.5,0.5])
    Xcdf = [[makecdf.(X[i][j]) for j = 1:length(X[1])] for i = 1:length(X)]
    Xcor = [[cor(X[i][j]...) for j = 1:length(X[1])] for i = 1:length(X)]
    function dist(Y::Vector{Vector{Vector{Vector{Float64}}}})
        d = 0.0
        for i = 1:length(X), j = 1:length(X[1])
            d += 2 * w[1] * f(Xcdf[i][j][1],Y[i][j][1]) +    # Quenchable
                 1 * w[1] * f(Xcdf[i][j][2],Y[i][j][2]) +    # Unquenchable
                 1 * w[2] * norm(Xcor[i][j] - cor(Y[i][j]...))  # Correlations
        end
        return d
    end
    return dist
end

"""
    makediscrepancy_univariate(X;w=[0.5,0.5])

Create discrepancy function `dist(Y)` using data `X` where:
    X[i] = [[Q̄,Ū],[Q,U]]
and
    dist(Y) = ΣᵢΣⱼ addist(X[i][j][1])

Compared to `makediscrepancy`, only information relating to the quenchable probe is considered.

"""
function makediscrepancy_univariate(X::Vector{Vector{Vector{Vector{Float64}}}},f=addist)
    Xcdf = [[makecdf.(X[i][j]) for j = 1:length(X[1])] for i = 1:length(X)]
    function dist(Y::Vector{Vector{Vector{Vector{Float64}}}})
        d = 0.0
        for i = 1:length(X), j = 1:length(X[1])
            d += w[1] * f(Xcdf[i][j][1],Y[i][j][1])   # Quenchable
        end
        return d
    end
    return dist
end


using CompScienceMeshes, BEAST

function blkdiagm(blks...)
    m = sum(size(b,1) for b in blks)
    n = sum(size(b,2) for b in blks)
    A = zeros(eltype(first(blks)), m, n)
    o1 = 1
    o2 = 1
    for b in blks
        m1 = size(b,1)
        n1 = size(b,2)
        A[o1:o1+m1-1,o2:o2+n1-1] .= b
        o1 += m1
        o2 += n1
    end
    A
end

width, height, h = 1.0, 0.5, 0.05
G1 = meshrectangle(width, height, h)
G2 = CompScienceMeshes.rotate(G1, 0.5π * x̂)
G3 = CompScienceMeshes.rotate(G1, 1.0π * x̂)

G12 = weld(G1,-G2)
G23 = weld(G2,-G3)
G31 = weld(G3,-G1)

X12 = lagrangec0d1(G12)
X23 = lagrangec0d1(G23)
X31 = lagrangec0d1(G31)

Y12 = duallagrangecxd0(G12)
Y23 = duallagrangecxd0(G23)
Y31 = duallagrangecxd0(G31)

κ = 1.0
SL = Helmholtz3D.singlelayer(wavenumber=κ)
HS = Helmholtz3D.hypersingular(gamma=κ*im)
N = Identity()

X = X12 × X23 × X31
Y = Y12 × Y23 × Y31

E = Helmholtz3D.planewave(direction=ẑ, wavenumber=κ)
e = strace(E, G12)
ex = assemble(e, X)

Sxx = assemble(SL, X, X)
N1 = assemble(Identity(), X12, Y12)
N2 = assemble(Identity(), X23, Y23)
N3 = assemble(Identity(), X31, Y31)
Nxy = blkdiagm(N1,N2,N3)
Syy = assemble(HS, Y, Y)

Dyx = inv(Nxy)
Q = transpose(Dyx) * Syy * Dyx * Sxx
R = transpose(Dyx) * Syy * Dyx * ex

u1, ch1 = solve(BEAST.GMRESSolver(Sxx),ex)
u2, ch2 = solve(BEAST.GMRESSolver(Q),R)

@show ch1.iters
@show ch2.iters

using LinearAlgebra
cond(Sxx)
cond(Q)

w1 = eigvals(Sxx)
w2 = eigvals(Q)

using Plots
plot()
scatter!(w1)
scatter!(w2)


using DifferentialEquations
using Plots
plotlyjs()

#constants
g=9.81       #acceleration due to gravity [ms^-2]
m=3000       #mass of capsule [Kg]

#initial conditions
u₀=[3,0.1,0.1,0.5]
r₀=6
v₀=0

#function to compute lagrangian
function lagrangian!(du,u,p,t)
    θ,ω,ϕ,ψ=u
    v=v₀
    r=r₀+v*t
    du[1]=dθ=ω
    du[2]=dω=sin(θ)*cos(θ)*ψ^2-((2*v*ω)/r)+((g*sin(θ))/r)
    du[3]=dϕ=ψ
    du[4]=dψ=-((2*v*ψ)/r)-(2*cot(θ)*ω*ψ)
end

#time interval
tspan=(0.0,100.0)

#defining the problem and solving
prob=ODEProblem(lagrangian!,u₀,tspan)
sol=solve(prob,saveat=0.1)

#extracting solution
U=sol[1:end,:]
(θ,ω,ϕ,ψ)=[U[x,:] for x in 1:size(U,1)]
t=sol.t
r=r₀ .+ v₀*t
v=v₀*ones(size(t))
x=r.*sin.(θ).*cos.(ϕ)
y=r.*sin.(θ).*sin.(ϕ)
z=r.*cos.(θ)
uₜ=U[:,end]
rₜ=r[end]
vₜ=v[end]
FC=[rₜ,vₜ,uₜ]

#plotting the solution
display(plot(x,y,z,label="trajectory",xaxis="x (m)",
    yaxis="y (m)",zaxis="z (m)",grid=(:on,:black)))
savefig("./plots/trajectory-sp.png")

p1=plot(sol.t,r,label="r",color="black",
    xaxis="t (s)", yaxis="r (m)")
p2=plot(sol.t,v,label="v",color="red",
    xaxis="t (s)", yaxis="v (m/s)")
p3=plot(sol,vars=(0,1),label="θ",
    color="green", xaxis="t (s)", yaxis="θ (rad)")
p4=plot(sol,vars=(0,2),label="ω",
    color="orange", xaxis="t (s)", yaxis="ω (rad/s)")
p5=plot(sol,vars=(0,3),label="ϕ",
    color="purple", xaxis="t (s)", yaxis="ϕ (rad)")
p6=plot(sol,vars=(0,4),label="ψ",
    color="blue", xaxis="t (s)", yaxis="ψ (rad/s)")
display(plot(p1,p2,p3,p4,p5,p6,layout=(3,2)))
savefig("./plots/plots-sp.png")

#energy plot
KE=0.5*m*(v.^2 .+ (r.*ω).^2 .+ (r.*ψ.*sin.(θ)).^2)
PE=m*g.*z
TE=KE.+PE
p7=plot(t,KE,label="KE",
    color="black",xaxis="t (s)", yaxis="KE (J)")
p8=plot(t,PE,label="PE",
    color="green",xaxis="t (s)", yaxis="PE (J)")
p9=plot(t,TE,label="TE",
    color="red",xaxis="t (s)", yaxis="TE (J)")
display(plot!(p7,p8,p9))
savefig("./plots/energyplot-sp.png")

Modeling the Dragon Module as a Pendulum/Scripts/spherical pendulum.jl


@testset "Test interpolators" begin

    using jFEMTools
    import Tensors
    jF = jFEMTools

    # Monomials
    ###############################################
    P1_basis = jF.Monomials(2,1,Tensors.Vec{2}((0.0,0.0)),1.0)
    @test jF.value(P1_basis,1,Tensors.Vec{2}((1.0,2.0))) == 1.0
    @test jF.value(P1_basis,2,Tensors.Vec{2}((1.0,2.0))) == 1.0
    @test jF.value(P1_basis,3,Tensors.Vec{2}((1.0,2.0))) == 2.0
    @test jF.getnbasefunctions(P1_basis) == 3

end


# Activation normalization from Kingma & Dhariwal (2018)
# Author: Philipp Witte, pwitte3@gatech.edu
# Date: January 2020

using InvertibleNetworks, LinearAlgebra, Test

# Input
nx = 64
ny = 64
k = 10
batchsize = 4

# Input image: nx x ny x k x batchsize
X = randn(Float32, nx, ny, k, batchsize)
Y = randn(Float32, nx, ny, k, batchsize)

# Activation normalization
AN = ActNorm(k; logdet=true)

# Test invertibility
Y_, logdet = AN.forward(X)
ΔY = Y_ - Y

# Backpropagation
ΔX, X_ = AN.backward(ΔY, Y_)

# Test invertibility
isapprox(norm(X - X_)/norm(X), 0f0, atol=1f-6)


# construct the extensive formulation

using JuMP, Gurobi;
using NPZ;
# using NLopt;

function find_topology(ks, fs, gs)

    ### find network topology given degree sequence, steady states, and network dynamics
    # ks: list of degree sequence; fs, gs: lists of function values for self dynamics and interaction between adjacent nodes at steady states
    ###

    # initialize model
    model = Model(optimizer_with_attributes(() -> Gurobi.Optimizer(), "OutputFlag" => 1));

    # define variables
    n_node = length(ks);
    @variable(model, Adj[i in 1:n_node, j in 1:n_node], Bin);
    @variable(model, aux_var[i in 1:n_node]);

    # constraints
    # symmetry
    @constraint(model, symmetry[i in 1:n_node, j in 1:n_node], Adj[i,j] == Adj[j,i]);
    # degree sequence
    @constraint(model, degree[i in 1:n_node], sum(Adj[i,j] for j in 1:n_node) == ks[i]);

    # objective function
    @expression(model, cost[i in 1:n_node], fs[i] + sum(Adj[i,j]*gs[i,j] for j in 1:n_node) );
    # linearize the absolute term in the obj
    @constraint(model, LB1[i in 1:n_node], aux_var[i] >= cost[i]);
    @constraint(model, LB2[i in 1:n_node], aux_var[i] >= -cost[i]);

    @objective(model, Min, sum(aux_var[i] for i in 1:n_node) );

    # @NLobjective(model, Min, sum(cost[i]*cost[i] for i in 1:n_node) );

    # solve the model
    t0 = time();
    optimize!(model);
    t1 = time();

    if termination_status(m) != MOI.OPTIMAL
        @warn("===== Final solution not optimal =====")
        return
    end

    # return solution time and solution
    t_sol = t1 - t0;
    println("===== Solution time for ", n, "-node network is", "t_sol");

    obj = objective_value(model);
    println("===== Optimal objective is ", obj);

    Adj = value.(model[:Adj]);

    return obj, Adj, t_sol;
end

# test
function main()
    # load ks, gs, fs
    cd("C:/code/network_reconstruction/data");
    gs = npzread("gs.npy");
    fs = npzread("fs.npy");
    ks = npzread("ks.npy");
    obj, Adj, t_sol = find_topology(ks, fs, gs);
end

main()


# TODO Move this to GPLikelihoods.jl
@doc raw"""
    StudentTLikelihood(ν::Real, σ::Real)

Likelihood with a Student-T likelihood:
```math
    p(y|f,\sigma, \nu) = \frac{\Gamma\left(\frac{\nu+1}{2}\right)}{\Gamma\left(\frac{\nu}{2}\right)\sqrt{\pi\nu}\sigma}\left(1 + \frac{1}{\nu}\left(\frac{x-\nu}{\sigma}\right)^2\right)^{-\frac{\nu+1}{2}}.
```

## Arguments
- `ν::Real`, number of degrees of freedom, should be positive and larger than 0.5 to be able to compute moments
- `σ::Real`, scaling of the inputs.
"""
struct StudentTLikelihood{Tν<:Real,Tσ<:Real,Thalfν<:Real} <: AbstractLikelihood
    ν::Tν
    σ::Tσ
    σ²::Tσ
    halfν::Thalfν
end

StudentTLikelihood(ν::Real, σ::Real) = StudentTLikelihood(ν, σ, abs2(σ), ν / 2)

function (lik::StudentTLikelihood)(f::Real)
    return Distributions.AffineDistribution(f, lik.σ, TDist(lik.ν))
end

_α(lik::StudentTLikelihood) = (lik.ν + 1) / 2

function (lik::StudentTLikelihood)(f::AbstractVector{<:Real})
    return Product(lik.(f))
end

aux_field(::StudentTLikelihood, Ω) = getproperty(Ω, :ω)

function init_aux_variables(rng::AbstractRNG, ::StudentTLikelihood, n::Int)
    return TupleVector((; ω=rand(rng, Gamma(), n)))
end

function init_aux_posterior(T::DataType, lik::StudentTLikelihood, n::Int)
    α = _α(lik)
    return For(TupleVector(; β=zeros(T, n))) do φ
        NTDist(Gamma(α, inv(φ.β))) # Distributions uses a different parametrization
    end
end

function aux_full_conditional(lik::StudentTLikelihood, y::Real, f::Real)
    return NTDist(Gamma(_α(lik), 2 / (lik.ν / abs2(lik.σ) + abs2(y - f))))
end

function aux_posterior!(
    qΩ, lik::StudentTLikelihood, y::AbstractVector, qf::AbstractVector{<:Normal}
)
    φ = only(qΩ.inds)
    map!(φ.β, y, qf) do yᵢ, fᵢ
        (lik.ν / abs2(lik.σ) + second_moment(fᵢ, yᵢ)) / 2
    end
    return qΩ
end

function auglik_potential(::StudentTLikelihood, Ω, y::AbstractVector)
    return (y .* Ω.ω,)
end

function auglik_precision(::StudentTLikelihood, Ω, ::AbstractVector)
    return (Ω.ω,)
end

function expected_auglik_potential(::StudentTLikelihood, qΩ, y::AbstractVector)
    return (tvmean(qΩ).ω .* y,)
end

function expected_auglik_precision(::StudentTLikelihood, qΩ, ::AbstractVector)
    return (tvmean(qΩ).ω,)
end

function logtilt(::StudentTLikelihood, ω::Real, y::Real, f::Real)
    return logpdf(Normal(f, sqrt(inv(ω))), y)
end

function expected_logtilt(::StudentTLikelihood, qωᵢ::NTDist{<:Gamma}, yᵢ::Real, qfᵢ::Normal)
    θ = ntmean(qωᵢ)
    return logpdf(Normal(yᵢ, sqrt(inv(θ.ω))), mean(qfᵢ)) - var(qfᵢ) * θ.ω / 2
end

function aux_prior(lik::StudentTLikelihood, y::AbstractVector{<:Real})
    return For(length(y)) do _
        NTDist(aux_prior(lik))
    end
end

aux_prior(lik::StudentTLikelihood) = Gamma(lik.halfν, lik.σ² / lik.halfν)


@testset "Test 1D" begin
    x = [0.0, 1.0, 2.0]                        # function nodes
    u = x.^2                                   # function values in nodes
    s = [2.0]                                  # function first derivative nodes
    v = [4.0]                                  # function first derivative values

    @testset "Test 1D-RK_H0 kernel" begin
        spl = interpolate(x, u, RK_H0(0.1))  # create spline
        cond = estimate_cond(spl)                 # get estimation of the Gram matrix condition number
        @test cond ≈ 100.0
        σ = evaluate(spl, x)                # evaluate spline in nodes
        @test σ ≈ u                         # compare with exact function values in nodes

        spl = prepare(x, RK_H0(0.1))
        spl = construct(spl, u)
        σ = evaluate(spl, x)
        @test σ ≈ u

        # Check that we get close when evaluating near the nodes
        p = x .+ 1e-4*randn(size(x))   # evaluation points near the nodes
        f = p.^2                       # exact function values in evaluation points
        σ = evaluate(spl, p)           # evaluate spline in evaluation points
        # compare spline values with exact function values in evaluation point
        @test all(isapprox.(σ, f, atol = 0.05))

        σ = evaluate_one(spl, p[3])
        @test σ ≈ f[3] atol = 0.05
    end

    @testset "Test 1D-RK_H1 kernel" begin
        spl = interpolate(x, u, RK_H1(0.1))  # create spline
        cond = estimate_cond(spl)              # get estimation of the gram matrix condition number
        @test cond ≈ 1.0e5
        σ = evaluate(spl, x)           # evaluate spline in nodes
        @test σ ≈ u                    # compare with exact function values in nodes

        d1_σ = evaluate_derivative(spl, 1.0)     # evaluate spline first derivative in the node
        @test d1_σ ≈ 2.0 atol = 0.05              # compare with exact function first derivative value in the node

        # Check that we get close when evaluating near the nodes
        p = x .+ 1e-4*randn(size(x))   # evaluation points near the nodes
        f = p.^2                       # exact function values in evaluation points
        σ = evaluate(spl, p)                # evaluate spline in evaluation points
        # compare spline values with exact function values in evaluation point
        @test all(isapprox.(σ, f, atol = 1e-2))

        ###
        spl = interpolate(x, u, s, v, RK_H1(0.1)) # create spline by function and
                                            # first derivative values in nodes
        cond = estimate_cond(spl)              # get estimation of the gram matrix condition number
        @test cond ≈ 1.0e5
        σ = evaluate(spl, x)                # evaluate spline in nodes
        @test σ ≈ u                    # compare with exact function values in nodes

        # Check that we get close when evaluating near the nodes
        p = x .+ 1e-3*randn(size(x))   # evaluation points near the nodes
        f = p.^2                       # exact function values in evaluation points
        σ = evaluate(spl, p)                # evaluate spline in evaluation points
        # compare spline values with exact function values in evaluation point
        @test all(isapprox.(σ, f, atol = 1e-2))

        spl = prepare(x, s, RK_H1(0.1))
        spl = construct(spl, u, v)
        σ = evaluate_one(spl, p[3])
        @test σ ≈ f[3] atol = 0.05
    end
    #
    @testset "Test 1D-RK_H2 kernel" begin
        spl = interpolate(x, u, RK_H2(0.1))  # create spline
        cond = estimate_cond(spl)              # get estimation of the gram matrix condition number
        @test cond ≈ 1.0e7
        σ = evaluate(spl, x)           # evaluate spline in nodes
        @test σ ≈ u                    # compare with exact function values in nodes

        d1_σ = evaluate_derivative(spl, 1.0)     # evaluate spline first derivative in the node
        @test d1_σ ≈ 2.0 atol = 0.005              # compare with exact function first derivative value in the node

        # Check that we get close when evaluating near the nodes
        p = x .+ 1e-4*randn(size(x))   # evaluation points near the nodes
        f = p.^2                       # exact function values in evaluation points
        σ = evaluate(spl, p)                # evaluate spline in evaluation points
        # compare spline values with exact function values in evaluation point
        @test all(isapprox.(σ, f, atol = 1e-2))

        ###
        spl = interpolate(x, u, s, v, RK_H2(0.1)) # create spline by function and
                                                  # first derivative values in nodes
        σ = evaluate(spl, x)                # evaluate spline in nodes
        @test σ ≈ u                    # compare with exact function values in nodes

        # Check that we get close when evaluating near the nodes
        p = x .+ 1e-3*randn(size(x))   # evaluation points near the nodes
        f = p.^2                       # exact function values in evaluation points
        σ = evaluate(spl, p)                # evaluate spline in evaluation points
        # compare spline values with exact function values in evaluation point
        @test all(isapprox.(σ, f, atol = 1e-2))
    end
end


function filter_gauss(image, sigma::Number=1.0, mask_order::Number=3)
  # Input description:
  #   image:      The image, wich is going to be blurred. The pixel-values must be an float matrix - range 0~1.
  #   sigma:      the sigma value for the mask creation, just as in the normal distribuition.
  #   mask_order: As the name says, it is basically the order of the mask, wich will be used to
  #               do the convolution trough the given image.
  #               mask_order must be a positive even number, bigger or equal to 3.

  tipo = typeof(image)

  try
    image = convert(Array{Float32}, image)
  catch er
    error(er," --- Problem converting the single band image into an array.")
  end

  d = size(image)

  img1 = zeros(d[1]-mask_order+1,d[2]-mask_order+1)

  # Creates the mask wich will be used
  factor = (1/(2*pi*(sigma^2)))
  a = floor(Int,mask_order/2)

  mask1 = ones(mask_order, mask_order)

  for x=-a:a
    for y=-a:a
      mask1[x+a+1,y+a+1] = factor*exp(-(x^2 + y^2)/(2*(sigma^2)))
    end
  end
  mask = mask1/sum(mask1)

  for i = 1:(d[1]-mask_order+1)
    for j = 1:(d[2]-mask_order+1)
      ai=i
      ia=i+mask_order-1
      aj=j
      ja=j+mask_order-1

      img1[i,j] = sum(mask.*image[ai:ia,aj:ja])
      if img1[i,j] > 1.0
          img1[i,j] = 1.0
      end
    end
  end

  img1 = convert(tipo, img1)

  return img1
end


module BlackBoxOptimizationBenchmarking

    using Distributions, Memoize, Optim
    using LinearAlgebra, NamedDims

    import Base: show
    import Optim: minimizer, minimum, optimize
    export BBOBFunction

## Constants and Functions

    const maximum_dimension = 100

    T_asy(x::Vector{T}, β) where T <: Number = [x[i] > 0 ? x[i]^(1+β*(i-1)/(length(x)-1)*√x[i]) : x[i] for i=1:length(x)]

    function T_asy(x::Matrix{T}, β) where T <: Number
        z = similar(x)
        for i=1:size(x, 1)
            z[i, :] = T_asy(x[i, :], β)
        end
        z
    end

    function T_osz(xi::T) where T <: Number
        xhat = xi != zero(T) ? log(abs(xi)) : zero(T)
        c1 = xi > 0 ? 10. : 5.5
        c2 = xi > 0 ? 7.9 : 3.1

        sign(xi) * exp(xhat + 0.049*(sin(c1*xhat)+sin(c2*xhat) ))
    end

    function T_osz(x::Vector{T}) where T <: Number
        z = similar(x)
        for i=1:length(x)
            z[i] = T_osz(x[i])
        end
        z
    end

    function T_osz(x::Matrix{T}) where T <: Number
        z = similar(x)
        for i=1:size(x, 1)
            z[i, :] = T_osz(x[i, :])
        end
        z
    end

    function Λ(α, D)
        m = zeros(D, D)
        for i=1:D
            m[i, i] = α^(1/2 *(i-1)/(D-1))
        end
        m
    end

    C(i, D, n) = 10^(2*(i-1)/(D-1))#conditioning

    f_pen(x) =  sum(max(0, abs(xi)-5)^2 for xi in x)

    const one_pm = sign.(randn(maximum_dimension))

    #rotation matrices (probably a bit wrong)
    @memoize function Q(D)
        r = randn(D); r = r/norm(r)
        Q = [r nullspace(Matrix(r'))]
    end
    @memoize function R(D)
        r = randn(D); r = r/norm(r)
        R = [r nullspace(Matrix(r'))]
    end

    ∑(x) = sum(x)

## BBOBFunction

    struct BBOBFunction{F<:Function}
        name::String
        f::F
        x_opt::Array{Float64, 1}
        f_opt::Float64
    end

    (f::BBOBFunction)(x) = f.f(x)
    show(io::IO, f::BBOBFunction) =  print(io, f.name)
    Base.broadcastable(f::BBOBFunction) = Ref(f)

    minimum(f::BBOBFunction) = f.f_opt
    minimizer(f::BBOBFunction, D) = f.x_opt[1:D]

    test_x_opt(f::BBOBFunction) = @assert f(f.x_opt) ≈ f.f_opt

## helpers to define function

    const BBOBFunctions = BBOBFunction[]
    list_functions() = BBOBFunctions

    fun_symbols(n) = (map(Symbol, ["F$(n)", "f$(n)", "x$(n)_opt", "f$(n)_opt"])..., )

    macro BBOBFunction(name, simple_name, n)
        F, f, x_opt, f_opt = fun_symbols(n)
        fname = Symbol(simple_name)

        esc(quote
            $fname = BBOBFunction($name, $f, $x_opt, $f_opt)
            push!(BBOBFunctions, $fname)
        end)
    end

    """
        Define x1_opt and f1_opt.

    """
    macro define_x_and_f_opt(n)
        F, f, x_opt, f_opt = fun_symbols(n)
        esc(quote
            const $x_opt = rand(Uniform(-4, 4), maximum_dimension)
            const $f_opt = min(1000, max(-1000, round(rand(Cauchy(0, 100)), digits=2)))
        end)
    end

## Functions

    ## f1, Sphere Function

    @define_x_and_f_opt(1)

    """ Sphere Function """
    f1(x) = ∑( (x[i] .- x1_opt[i])^2 for i=1:length(x) ) + f1_opt

    @BBOBFunction("Sphere", "sphere", 1)

    ## f2, Ellipsoidal Function

    @define_x_and_f_opt(2)

    """ Ellipsoidal Function """
    function f2(x)
        D = length(x)
        z = T_osz(x .- x2_opt[1:D])
        ∑( 10^(6*(i-1)/(D-1)) * z[i]^2 for i=1:length(x) ) + f2_opt
    end

    @BBOBFunction("Ellipsoidal", "ellipsoidal", 2)

    ## f3, Rastrigin Function

    @define_x_and_f_opt(3)

    """ Rastrigin Function """
    function f3(x)
        D = length(x)
        z = Λ(10, D) * T_asy(T_osz(x .- x3_opt[1:D]), 0.2)
        10*(D - ∑( cos(2π*z[i]) for i=1:D )) + norm(z)^2 + f3_opt
    end

    @BBOBFunction("Rastrigin", "rastrigin", 3)

    ## f4, Buche-Rastrigin Function

    @define_x_and_f_opt(4)

    """ Buche-Rastrigin Function """
    function f4(x)
        D = length(x)
        z = T_osz(x .- x4_opt[1:D])
        s = [isodd(i) ? 10*10^(0.5*(i-1)/(D-1)) : 10^(0.5*(i-1)/(D-1)) for i=1:D]

        for i=1:D
            @inbounds z[i] = s[i]*z[i]
        end

        10*(D - ∑( cos(2π*z[i]) for i=1:D )) + norm(z)^2 + 100*f_pen(x) + f4_opt
    end

    @BBOBFunction("Buche-Rastrigin", "buche_rastrigin", 4)

    ## f5, Linear Slope

    const x5_opt = 5*one_pm
    const f5_opt = min(1000, max(-1000, round(rand(Cauchy(0, 100)), digits=2)))

    """ Linear Slope """
    function f5(x)
        D = length(x)
        z = [ x5_opt[i]*x[i] < 25 ? x[i] : x5_opt[i] for i=1:D ]
        s = [sign(x5_opt[i])*10^((i-1)/(D-1))  for i=1:D]

        ∑( 5*abs(s[i]) -s[i]*z[i] for i=1:D ) + f5_opt
    end

    @BBOBFunction("Linear Slope", "linear_slope", 5)

    ## f6, Attractive Sector Function

    @define_x_and_f_opt(6)

    """ Attractive Sector Function """
    function f6(x)
        D = length(x)

        z = Q(D)*Λ(10, D)*R(D)*(x .- x6_opt[1:D])

        @inbounds for i=1:D
            z[i] = x6_opt[i]*z[i] > 0 ? 100*z[i] : z[i]
        end

        T_osz( ∑( z[i]^2 for i=1:D ))^0.9 + f6_opt
    end

    @BBOBFunction("Attractive Sector", "attractive_sector", 6)

    ## f7, Step Ellipsoidal Function

    @define_x_and_f_opt(7)

    """ Step Ellipsoidal Function """
    function f7(x)
        D = length(x)

        z = Λ(10, D)*R(D)*(x .- x7_opt[1:D])
        zhat_1 = copy(z[1])

        @inbounds for i=1:D
            z[i] = z[i] > 0.5 ? floor(0.5 + z[i]) : floor(0.5 + 10*z[i])/10
        end
        z = Q(D)*z

        0.1*max(abs(zhat_1)/(10^4), ∑( 10^(2*(i-1)/(D-1)) * z[i]^2 for i=1:D ) ) + f_pen(x) + f7_opt
    end

    @BBOBFunction("Step Ellipsoidal Function", "step_ellipsoidal", 7)

    ## f8, Rosenbrock Function, original

    @define_x_and_f_opt(8)

    """ Rosenbrock Function, original """
    function f8(x)
        D = length(x)

        z = max(1, √D/8)*(x .- x8_opt[1:D]) .+ 1

        ∑( 100*(z[i]^2 - z[i+1])^2 + (z[i]-1)^2 for i=1:D-1 ) + f8_opt
    end

    @BBOBFunction("Rosenbrock Function, original", "rosenbrock_original", 8)

    ## f9, Rosenbrock Function, rotated

    @define_x_and_f_opt(9)

    """ Rosenbrock Function, rotated """
    function f9(x)
        D = length(x)

        z = max(1, √D/8)*R(D)*(x .- x9_opt[1:D]) .+ 1

        ∑( 100*(z[i]^2 - z[i+1])^2 + (z[i]-1)^2 for i=1:D-1 ) + f9_opt
    end

    @BBOBFunction("Rosenbrock Function, rotated", "rosenbrock_rotated", 9)

    ## f10, Ellipsoidal Function

    @define_x_and_f_opt(10)

    """ Ellipsoidal Function """
    function f10(x)
        D = length(x)

        z = T_osz( R(D)*(x .- x10_opt[1:D]))

        ∑( C(i, D, 6)*z[i]^2 for i=1:D ) + f10_opt
    end

    @BBOBFunction("Ellipsoidal Function", "ellipsoidal_second", 10)

    ## f11, Discus Function

    @define_x_and_f_opt(11)

    """ Discus Function """
    function f11(x)
        D = length(x)

        z = T_osz( R(D)*(x .- x11_opt[1:D]))

        10^6*z[1]^2 + ∑( z[i]^2 for i=2:D ) + f11_opt
    end

    @BBOBFunction("Discus Function", "discus", 11)

    ## f12, Bent Cigar Function

    @define_x_and_f_opt(12)

    """ Bent Cigar Function """
    function f12(x)
        D = length(x)

        z = R(D)*T_asy( R(D)*(x .- x12_opt[1:D]), 0.5)

        z[1]^2 + 10^6*∑( z[i]^2 for i=2:D ) + f12_opt
    end

    @BBOBFunction("Bent Cigar Function", "bent_cigar", 12)

    ## f13, Sharp Ridge Function

    @define_x_and_f_opt(13)

    """ Sharp Ridge Function """
    function f13(x)
        D = length(x)

        z = Q(D)*Λ(10, D)*R(D)*(x .- x13_opt[1:D])

        z[1]^2 + 100*√(∑(z[i]^2 for i=2:D )) + f13_opt
    end

    @BBOBFunction("Sharp Ridge Function", "sharp_ridge", 13)

    ## f14, Different Powers Function

    @define_x_and_f_opt(14)

    """ Different Powers Function """
    function f14(x)
        D = length(x)

        z = R(D)*(x .- x14_opt[1:D])

        √(∑(abs(z[i])^(2+4(i-1)/(D-1)) for i=1:D )) + f14_opt
    end

    @BBOBFunction("Different Powers Function", "different_powers", 14)

    ## f15, Rastrigin Function

    @define_x_and_f_opt(15)

    """ Rastrigin Function """
    function f15(x)
        D = length(x)

        z = R(D)*Λ(10, D)*Q(D)*T_asy(T_osz( R(D)*(x .- x15_opt[1:D]) ), 0.2)
        10*(D - ∑( cos(2π*z[i]) for i=1:D )) + norm(z)^2 + f15_opt
    end

    @BBOBFunction("Rastrigin Function", "rastrigin2", 15)

    ## f16, Weierstrass Function

    @define_x_and_f_opt(16)

    const f0 = ∑( 1/2^k * cos(2π*3^k*1/2) for k=0:11 )

    """ Weierstrass Function """
    function f16(x)
        D = length(x)

        z = R(D)*Λ(1/100, D)*Q(D)*T_osz( R(D)*(x .- x16_opt[1:D]) )
        s = ∑( ∑( 1/2^k * cos(2π*3^k*(z[i]+1/2)) for k=0:11 ) for i=1:D )
        10*( 1/D*s - f0 )^3 + 10/D*f_pen(x) + f16_opt
    end

    @BBOBFunction("Weierstrass Function", "weierstrass", 16)

    ## f17, Schaffers F7 Function

    @define_x_and_f_opt(17)

    """ Schaffers F7 Function"""
    function f17(x)
        D = length(x)

        z = Λ(10, D)*Q(D)*T_asy( R(D)*(x .- x17_opt[1:D]), 0.5)
        s = [√(z[i]^2 + z[i+1]^2) for i=1:D-1]

        (1/(D-1)*∑( √s[i]*(1 + sin(50*s[i]^1/5 )^2 ) for i=1:D-1 ))^2 + 10*f_pen(x) + f17_opt
    end

    @BBOBFunction("Schaffers F7 Function", "schaffers_F7", 17)

    ## f18, Schaffers F7 Function, moderately ill-conditioned

    @define_x_and_f_opt(18)

    """ Schaffers F7 Function, moderately ill-conditioned """
    function f18(x)
        D = length(x)

        z = Λ(1000, D)*Q(D)*T_asy( R(D)*(x .- x18_opt[1:D]), 0.5)
        s = [√(z[i]^2 + z[i+1]^2) for i=1:D-1]

        (1/(D-1)*∑( √s[i]*(1 + sin(50*s[i]^1/5 )^2 ) for i=1:D-1 ))^2 + 10*f_pen(x) + f18_opt
    end

    @BBOBFunction("Schaffers F7 Function, moderately ill-conditioned", "schaffers_F7_ill_conditioned", 18)

    ## f19, Composite Griewank-Rosenbrock Function F8F2

    @define_x_and_f_opt(19)

    """ Composite Griewank-Rosenbrock Function F8F2 """
    function f19(x)
        D = length(x)

        z = max(1, √D/8)*R(D)*(x .- x19_opt[1:D]) .+ 1
        s = [ 100*(z[i]^2 - z[i+1])^2 + (z[i]-1)^2 for i=1:D-1]

        10/(D-1)*∑( s[i]/4000 -cos(s[i]) for i=1:D-1 ) + 10 + f19_opt
    end

    @BBOBFunction("Composite Griewank-Rosenbrock Function F8F2", "composite_griewank_rosenbrock", 19)

    ## f20, Schwefel Function
    # https://github.com/numbbo/coco/issues/837

    const x20_opt = 4.2096874633/2 * one_pm
    const f20_opt = min(1000, max(-1000, round(rand(Cauchy(0, 100)), digits=2)))

    """ Schwefel Function """
    function f20(x)
        D = length(x)

        x = 2*one_pm[1:D] .* x

        z = [i==1 ? x[1] : x[i] + 0.25*(x[i-1] .- 2*abs(x20_opt[i-1]) ) for i=1:D]
        z = 100*( Λ(10, D)*(z- 2*abs.(x20_opt[1:D]) ) + 2*abs.(x20_opt[1:D]) )

        -1/(100*D)*∑( z[i]*sin(√(abs(z[i]))) for i=1:D ) + 4.189828872724339 + 100*f_pen(z/100) + f20_opt
    end

    @BBOBFunction("Schwefel Function", "schwefel_function",  20)

## Tests

    map(test_x_opt, BBOBFunctions)

## utilities

    include("benchmark.jl")

    #function run_benchmark()
    #    include(joinpath(Pkg.dir(), "BlackBoxOptimizationBenchmarking", "src", "run_benchmark.jl"))
    #end

    #x = [collect(linspace(-10, 10, 500)) collect(linspace(-10, 10, 500))]
    #
    #T_osz(x)
    #
    #y = [f3(x[i, :]) for i=1:size(x, 1)]
    #
    #plot(x=x[:, 1], y=y, Geom.line)

##
end # module


function jacobian(f::Function, x::Vector; δ = 0.001)

    y = f(x)

    J = Matrix{Union{eltype(y), Missing}}(undef, length(y), length(x))

    for i in 1:length(x)
        if ismissing(x[i])
            J[:, i] .= missing
        else
            dx = copy(x)
            dx[i] += δ
            J[:, i] .= (y .- f(dx)) ./ δ
        end
    end

    return J
end


using GalaxyWarp
using Test
using Parameters
using PCHIPInterpolation
using Optim
using BlackBoxOptim
using CairoMakie
using Distributions: Normal

@testset "1D Signal Fitting" begin
    vv=collect(-400.0:20.0:400.0)

    test_parss=[[1.2,-100.0,22.0],
        [1.2,-100.0,22.0,  2.0,  11.0,30.0],
        [0.5,-100.0,22.0,  0.6,-170.0,26.0,  2.0,11.0,30.0],
        [0.5,-100.0,22.0,  0.5,-130.0,16.0,  2.0,11.0,30.0]
    ]
    for test_pars in test_parss
        test_signal=GalaxyWarp.gmodel(vv,test_pars)
        result_pars = GalaxyWarp.fit_pixel(test_signal,0.1,vv,0.1;smin=0.8,smax=100.0,svmin=15,svmax=34.0)[1]

        AA=test_pars[1:3:end]
        VVc=test_pars[2:3:end]
        SSv=test_pars[3:3:end]

        fAA=result_pars[1:3:end]
        fVVc=result_pars[2:3:end]
        fSSv=result_pars[3:3:end]
        @testset "$(size(AA)[1]) gaussians" begin
            @test isapprox(size(AA)[1] ,size(fAA)[1], atol=1)
            if size(AA)[1] == size(fAA)[1]
                    @test isapprox(minimum(abs.(sort(AA) .- sort(fAA))), 0.0, atol=0.5)
                    @test isapprox(minimum(abs.(sort(VVc) .- sort(fVVc))), 0.0, atol=15.0)
                    @test isapprox(minimum(abs.(sort(SSv) .- sort(fSSv))), 0.0, atol=10.0)
            end
        end
    end

    for test_pars in test_parss
        for rms in [0.1,0.15,0.2]
            test_signal=GalaxyWarp.gmodel(vv,test_pars)
            test_signal=rand.(GalaxyWarp.Normal.(test_signal,rms))
            result_pars = GalaxyWarp.fit_pixel(test_signal,rms,vv,rms;smin=0.8,smax=100.0,svmin=15,svmax=34.0)[1]

            AA=test_pars[1:3:end]
            VVc=test_pars[2:3:end]
            SSv=test_pars[3:3:end]

            fAA=result_pars[1:3:end]
            fVVc=result_pars[2:3:end]
            fSSv=result_pars[3:3:end]
            @testset "$(size(AA)[1]) gaussians" begin
                @test isapprox(size(AA)[1] ,size(fAA)[1], atol=1)
                if size(AA)[1] == size(fAA)[1]
                        @test isapprox(minimum(abs.(sort(AA) .- sort(fAA))), 0.0, atol=0.5)
                        @test isapprox(minimum(abs.(sort(VVc) .- sort(fVVc))), 0.0, atol=15.0)
                        @test isapprox(minimum(abs.(sort(SSv) .- sort(fSSv))), 0.0, atol=10.0)
                end
            end
        end
    end
end

@with_kw mutable struct p_test
    Fit::Array{Symbol}=[
        :pa0,:pa15,:pa5,
        :i0,:i15,:i5,
        :v0,:a,:n]

    pa0::Float64=0.0
    pa15::Float64=20.0
    pa5::Float64=30.0

    PA::Array{Symbol}=[:pa0,:pa15,:pa5]
    R_pa::Vector{Float64}=[0.0,1.5,5.0]
    ppa = Interpolator

    i0::Float64=70.0
    i15::Float64=74.0
    i5::Float64=70.0

    I::Array{Symbol}=[:i0,:i15,:i5]
    R_i::Vector{Float64}=[0.0,1.5,5.0]
    ppi = Interpolator

    v0::Float64=300.0
    a::Float64= 2.0
    n::Float64= 1.0

    drc=0.05
    Rd=collect(0.01:drc:5.0)
    dR =0.07
    dV0=300.0
    dv=32.0
    V::Array{Symbol}=[:v0,:a,:n]

    Vcirc::Function = (r::Float64,v0::Float64,a::Float64,n::Float64) -> v0*r/(r^2.0+a^2.0)^n

end

@testset "Simulated data Fitting" begin
    filename="$(@__DIR__)/simulated1/"
    run(`mkdir -p $(filename)`)
    pixel_fit_steps=800
    kinematics_fittting_steps=5000

    @time simdata=GalaxyWarp.sim_data("simulated1";theta_dist="uniform", Vcirc = x-> 320.0*x/(x^2.0+1.5^2.0)^1.2,I=x->70.0,PA=x->6.0*x, Sb = x->100.0, sigma_inst=x->10.0, cloud_mass=1e5 ,xl=3.0,yl=5.0,vl=350.0,dx=0.05,dy=0.05,dv=20.0,beam=[0.08,0.07],Rmin=0.1,dR=0.1,Rmax = 4.5,a=0.01,rms=0.05,Rcmax=2.1,Rcmap=1.7,k=3)

    @test length(simdata["Xc"])>100000
    @test length(simdata["Yc"])==length(simdata["Xc"])
    @test length(simdata["Vc"])==length(simdata["Xc"])
    @test length(simdata["Zc"])==length(simdata["Xc"])

    clouds_sim=GalaxyWarp.fit_pixelst(simdata;x0=-5.0,x1=5.0,y0=-5.5,y1=5.5,sigma=3.0, ncrit=1,N0=1,Nmax=7,vmin=-400.0, vmax=400.0,svmin=15.0, svmax=32.0,maxsteps=pixel_fit_steps, popsize=16)

    @time GalaxyWarp.cloud_fitting_diagnostics(simdata,clouds_sim,"$(filename)"*"test")

    lenX0=length(clouds_sim.Xp)
    @test lenX0>2000
    @test length(clouds_sim.Yp)==lenX0
    @test length(clouds_sim.Vp)==lenX0
    @test length(clouds_sim.belongs)==0
    @test length(clouds_sim.belongs)==0

    clouds_sim_f1=GalaxyWarp.filter_clouds(clouds_sim,[Dict("x0"=>-1.0)])

    lenX1=length(clouds_sim_f1.Xp)
    @test lenX1<lenX0
    @test length(clouds_sim_f1.Vp)==lenX1
    @test length(clouds_sim_f1.Yp)==lenX1

    @time GalaxyWarp.merge_pixel_clouds!(clouds_sim;dr=0.1,dv=50.0,mp=7,df=3.0*simdata["rms"],roll_d=8,max_d=1.0)

    lenX1m=length(clouds_sim.Xc)
    @test lenX1m>10
    @test lenX1m<lenX1
    @test length(clouds_sim.Yc)==lenX1m
    @test length(clouds_sim.Vc)==lenX1m

    clouds_sim_f2=GalaxyWarp.filter_clouds(clouds_sim,[Dict("x0"=>-1.0)])
    lenX1_2=length(clouds_sim_f2.Xp)
    @test lenX1_2 == lenX1
    @test length(clouds_sim_f2.Vp)==lenX1_2
    @test length(clouds_sim_f2.Yp)==lenX1_2

    lenX1_2c=length(clouds_sim_f2.Xc)
    @test lenX1_2c < length(clouds_sim.Xc)
    @test length(clouds_sim_f2.Vc)==lenX1_2c
    @test length(clouds_sim_f2.Yc)==lenX1_2c

    GalaxyWarp.cloud_fitting_diagnostics(simdata,clouds_sim_f1,"$(filename)"*"test")

    Pars=p_test()
    par0=copy([getfield(Pars,s) for s in Pars.Fit])
    GalaxyWarp.update_parameters!(Pars,par0)
    disks0 = GalaxyWarp.make_disks(Pars.Rd,Pars)

    function logposterior(pars::Vector{Float64})::Float64
		GalaxyWarp.update_parameters!(Pars,pars)
		likedV=GalaxyWarp.likelihood(clouds_sim,Pars;dv=30.0)
		#prior=GalaxyWarp.logprior(Pars,Priors)
		return likedV
	end

    #res =optimize(logposterior, par0, NelderMead(),Optim.Options(iterations = 3600, store_trace = true, show_trace = true,g_tol=1.0e-3))

	#MAP=Optim.minimizer(res)

    plower=[0.0,0.0,0.0,
    0.0,50.0,50.0,
    50.0,0.5,0.3]

    pupper=[180.0,180.0,50.0,
    90.0,90.0,90.0,
    500.0,4.0,3.0]

    opt_L = bbsetup(logposterior;
		SearchRange = collect(zip(plower, pupper)),
		PopulationSize=120,MaxSteps = kinematics_fittting_steps
		)
	res_L = bboptimize(opt_L)
	MAP=best_candidate(res_L)

	@show MAP
    @show logposterior(MAP)

    GalaxyWarp.update_parameters!(Pars,MAP)
    disks = GalaxyWarp.make_disks(Pars.Rd,Pars)
	GalaxyWarp.cloud_geometry!(clouds_sim,disks,Pars)

    fsky=GalaxyWarp.plot_sky(clouds_sim,disks,msize=7)
	save("$(filename)"*"sky.png",fsky)

    ## Filtering by dV
    clouds_sim_res=GalaxyWarp.filter_clouds(clouds_sim,[Dict("dv"=>10.0)])
    GalaxyWarp.cloud_fitting_diagnostics(simdata,clouds_sim_res,"$(filename)"*"test_res")

    lenX1_3=length(clouds_sim_res.Xp)
    @test lenX1_3 < length(clouds_sim.Xp)
    @test length(clouds_sim_res.Vp)==lenX1_3
    @test length(clouds_sim_res.Yp)==lenX1_3

    ## Checking how good is the fit
    pa_real0=0.0
    pa_real15=6.0*1.5
    pa_real5=6.0*5.0

    i_real0=70.0
    i_real15=70.0
    i_real5=70.0

    v0_real=320.0
    a_real=1.5
    n_real=1.2

    dpa=7.0
    di=7.0
    @test isapprox(pa_real0,Pars.pa0,atol=dpa)
    @test isapprox(pa_real15,Pars.pa15,atol=dpa)
    @test isapprox(pa_real5,Pars.pa5,atol=dpa)
    @test isapprox(i_real0,Pars.i0,atol=di)
    @test isapprox(i_real15,Pars.i15,atol=di)
    @test isapprox(i_real5,Pars.i5,atol=di)
    @test isapprox(v0_real,Pars.v0,atol=20.0 )
    @test isapprox(a_real,Pars.a,atol=0.2 )
    @test isapprox(n_real,Pars.n,atol=0.2 )

    REAL=[pa_real0,pa_real15,pa_real5,i_real0,i_real15,i_real5,v0_real,a_real,n_real]

    @show REAL
    @show logposterior(REAL)

    GalaxyWarp.update_parameters!(Pars,REAL)
    disks_real = GalaxyWarp.make_disks(Pars.Rd,Pars)
	GalaxyWarp.cloud_geometry!(clouds_sim,disks_real,Pars)
    fsky=GalaxyWarp.plot_sky(clouds_sim,disks,msize=7)
	save("$(filename)"*"sky_real.png",fsky)

	#GalaxyWarp.cloud_geometry!(clouds_sim,disks,Pars)

	fdisks=GalaxyWarp.plot_disks(Dict("MAP"=>disks,"d0"=>disks0,"REAL"=>disks_real))
	save("$(filename)"*"disks.png",fdisks)

    for ang in [0.0,15.0,30.0]
		pvdf=GalaxyWarp.plot_pvd(simdata,ang;disks=Dict("MAP"=>disks,"d0"=>disks0,"REAL"=>disks_real),clouds=clouds_sim,merged=true)
		save("$(filename)"*"pvd_$(ang).png",pvdf)
	end
end

@testset "Noisy Simulated data Fitting" begin
    simdata=GalaxyWarp.sim_data("noisy_cube";theta_dist="uniform", Vcirc = x-> 320.0*x/(x^2.0+1.5^2.0)^1.2,I=x->70.0,PA=x->6.0*x, Sb = x->100.0, sigma_inst=x->10.0, cloud_mass=1e5 ,xl=3.0,yl=5.0,vl=350.0,dx=0.05,dy=0.05,dv=20.0,beam=[0.08,0.07],Rmin=0.1,dR=0.1,Rmax = 4.5,a=0.01,rms=0.05,Rcmax=2.1,Rcmap=1.7,k=3)

    simdata["data"] .=rand.(Normal.(simdata["data"], abs.(0.1 .* simdata["data"])))

    @show size(simdata["data"])
    @test length(simdata["Xc"])>100000
    @test length(simdata["Yc"])==length(simdata["Xc"])
    @test length(simdata["Vc"])==length(simdata["Xc"])
    @test length(simdata["Zc"])==length(simdata["Xc"])

    clouds_sim=GalaxyWarp.fit_pixelst(simdata;x0=-5.0,x1=5.0,y0=-5.5,y1=5.5,sigma=3.0, ncrit=1,N0=1,Nmax=7,vmin=-400.0, vmax=400.0,svmin=15.0, svmax=32.0,maxsteps=800, popsize=16)

    GalaxyWarp.cloud_fitting_diagnostics(simdata,clouds_sim,"$(@__DIR__)/test")

    lenX0=length(clouds_sim.Xp)
    @test lenX0>2000
    @test length(clouds_sim.Yp)==lenX0
    @test length(clouds_sim.Vp)==lenX0
    @test length(clouds_sim.belongs)==0
    @test length(clouds_sim.belongs)==0

    clouds_sim_f1=GalaxyWarp.filter_clouds(clouds_sim,[Dict("x0"=>-1.0)])

    lenX1=length(clouds_sim_f1.Xp)
    @test lenX1<lenX0
    @test length(clouds_sim_f1.Vp)==lenX1
    @test length(clouds_sim_f1.Yp)==lenX1

    @time GalaxyWarp.merge_pixel_clouds!(clouds_sim;dr=0.1,dv=50.0,mp=7,df=3.0*simdata["rms"],roll_d=8,max_d=1.0)

    GalaxyWarp.cloud_fitting_diagnostics(simdata,clouds_sim_f1,"$(@__DIR__)/test")

    lenX1m=length(clouds_sim.Xc)
    @test lenX1m>10
    @test lenX1m<lenX1
    @test length(clouds_sim.Vc)==lenX1m
    @test length(clouds_sim.Yc)==lenX1m

    Pars=p_test()
    par0=copy([getfield(Pars,s) for s in Pars.Fit])
    GalaxyWarp.update_parameters!(Pars,par0)
    disks0 = GalaxyWarp.make_disks(Pars.Rd,Pars)

    function logposterior(pars::Vector{Float64})::Float64
		GalaxyWarp.update_parameters!(Pars,pars)
		likedV=GalaxyWarp.likelihood(clouds_sim,Pars;dv=30.0)
		#prior=GalaxyWarp.logprior(Pars,Priors)
		return likedV
	end

    #res =optimize(logposterior, par0, NelderMead(),Optim.Options(iterations = 3600, store_trace = true, show_trace = true,g_tol=1.0e-3))

	#MAP=Optim.minimizer(res)

    plower=[0.0,0.0,0.0,
    0.0,50.0,50.0,
    50.0,0.5,0.3]

    pupper=[180.0,180.0,50.0,
    90.0,90.0,90.0,
    500.0,4.0,3.0]

    steps=10000
    opt_L = bbsetup(logposterior;
		SearchRange = collect(zip(plower, pupper)),
		PopulationSize=120,MaxSteps = steps
		)
	res_L = bboptimize(opt_L)
	MAP=best_candidate(res_L)

	@show MAP
    @show logposterior(MAP)

    GalaxyWarp.update_parameters!(Pars,MAP)
    disks = GalaxyWarp.make_disks(Pars.Rd,Pars)
	GalaxyWarp.cloud_geometry!(clouds_sim,disks,Pars)

    fsky=GalaxyWarp.plot_sky(clouds_sim,disks,msize=7)
	save("$(@__DIR__)/sky.png",fsky)

    pa_real0=0.0
    pa_real15=6.0*1.5
    pa_real5=6.0*5.0

    i_real0=70.0
    i_real15=70.0
    i_real5=70.0

    v0_real=320.0
    a_real=1.5
    n_real=1.2

    dpa=7.0
    di=7.0
    @test isapprox(pa_real0,Pars.pa0,atol=dpa)
    @test isapprox(pa_real15,Pars.pa15,atol=dpa)
    @test isapprox(pa_real5,Pars.pa5,atol=dpa)
    @test isapprox(i_real0,Pars.i0,atol=di)
    @test isapprox(i_real15,Pars.i15,atol=di)
    @test isapprox(i_real5,Pars.i5,atol=di)
    @test isapprox(v0_real,Pars.v0,atol=20.0 )
    @test isapprox(a_real,Pars.a,atol=0.2 )
    @test isapprox(n_real,Pars.n,atol=0.2 )

    REAL=[pa_real0,pa_real15,pa_real5,i_real0,i_real15,i_real5,v0_real,a_real,n_real]

    @show REAL
    @show logposterior(REAL)

    GalaxyWarp.update_parameters!(Pars,REAL)
    disks_real = GalaxyWarp.make_disks(Pars.Rd,Pars)
	GalaxyWarp.cloud_geometry!(clouds_sim,disks_real,Pars)
    fsky=GalaxyWarp.plot_sky(clouds_sim,disks,msize=7)
	save("$(@__DIR__)/sky_real.png",fsky)

	#GalaxyWarp.cloud_geometry!(clouds_sim,disks,Pars)

	fdisks=GalaxyWarp.plot_disks(Dict("MAP"=>disks,"d0"=>disks0,"REAL"=>disks_real))
	save("$(@__DIR__)/disks.png",fdisks)

    for ang in [0.0,15.0,30.0]
		pvdf=GalaxyWarp.plot_pvd(simdata,ang;disks=Dict("MAP"=>disks,"d0"=>disks0,"REAL"=>disks_real),clouds=clouds_sim,merged=true)
		save("$(@__DIR__)/pvd_$(ang).png",pvdf)
	end
end

const G = 4.304574992e-06

@with_kw mutable struct p_mrk79_
    Fit::Array{Symbol}=[
        :pa0,:pa15,:pa5,
        :i0,:i15,:i5,
        :v0,:a,:n]

    pa0::Float64=130.0
    pa15::Float64=130.0
    pa5::Float64=130.0

    PA::Array{Symbol}=[:pa0,:pa15,:pa5]
    R_pa::Vector{Float64}=[0.0,5.0,15.0]
    ppa = Interpolator

    i0::Float64=50.0
    i15::Float64=50.0
    i5::Float64=50.0

    I::Array{Symbol}=[:i0,:i15,:i5]
    R_i::Vector{Float64}=[0.0,5.0,15.0]
    ppi = Interpolator

    Mbh=1.0e8
    v0::Float64=300.0
    a::Float64= 2.0
    n::Float64= 1.0

    drc=0.1
    Rd=collect(0.01:drc:15.0)
    dR =0.1
    dV0=300.0
    dv=32.0
    V::Array{Symbol}=[:v0,:a,:n]

    Vcirc::Function = (r::Float64,v0::Float64,a::Float64,n::Float64) -> sqrt(
		G * Mbh / r + (v0*r/(r^2.0+a^2.0)^n)^2.0)

end

pwd()
#@testset "real data Fitting" begin
    data=GalaxyWarp.load_cube("$(@__DIR__)/data/COcube.fits", "CO21"; z=0.022296, rms=0.0012, centerx="median", centery="median",zaxis="vel", line_rest=230.538e9, x0=-15.5, x1=15.5, y0=-15.5, y1=15.5, v0=-550.0, v1=550.0)

    figmom21=GalaxyWarp.moments(data,1400,800;x0=-10.0,x1=10.0,y0=-10.0,y1=10.0,v0=-300.0,v1=300.0,dx=1.0,dv=5.0,vmax=80.0,denoise=4.5)

    #@show size(data["data"])
    @test length(data["Xc"])>100000
    @test length(data["Yc"])==length(data["Xc"])
    @test length(data["Vc"])==length(data["Xc"])
    @test length(data["Zc"])==length(data["Xc"])

    clouds_mrk79=GalaxyWarp.fit_pixelst(data;x0=-10.0,x1=10.0,y0=-10.5,y1=10.5,sigma=3.0, ncrit=1,N0=1,Nmax=7,vmin=-400.0, vmax=400.0,svmin=15.0, svmax=32.0,maxsteps=800, popsize=16)

    GalaxyWarp.cloud_fitting_diagnostics(data,clouds_mrk79,"$(@__DIR__)/mrk79")

    lenX0=length(clouds_mrk79.Xp)
    @test lenX0>2000
    @test length(clouds_mrk79.Yp)==lenX0
    @test length(clouds_mrk79.Vp)==lenX0
    @test length(clouds_mrk79.belongs)==0
    @test length(clouds_mrk79.belongs)==0

    data["beam"]
	pixels_per_beam=Int(round(data["beam"][1]*data["beam"][2]/data["dx"]^2.0))

    @time GalaxyWarp.merge_pixel_clouds!(clouds_mrk79;dr=1.5,dv=50.0,mp=10,df=3.0*data["rms"],roll_d=8,max_d=1.0)

    GalaxyWarp.cloud_fitting_diagnostics(data,clouds_mrk79,"$(@__DIR__)/mrk79_m";slit=1.0)

    lenX1m=length(clouds_mrk79.Xc)
    @test length(clouds_mrk79.Vc)==lenX1m
    @test length(clouds_mrk79.Yc)==lenX1m

    Pars=p_mrk79_()
    par0=copy([getfield(Pars,s) for s in Pars.Fit])
    GalaxyWarp.update_parameters!(Pars,par0)
    disks0 = GalaxyWarp.make_disks(Pars.Rd,Pars)

    function logposterior(pars::Vector{Float64})::Float64
		GalaxyWarp.update_parameters!(Pars,pars)
		likedV=GalaxyWarp.likelihood(clouds_mrk79,Pars;dv=30.0)
		#prior=GalaxyWarp.logprior(Pars,Priors)
		return likedV
	end

    #res =optimize(logposterior, par0, NelderMead(),Optim.Options(iterations = 3600, store_trace = true, show_trace = true,g_tol=1.0e-3))

	#MAP=Optim.minimizer(res)

    plower=[0.0,0.0,0.0,
    0.0,0.0,0.0,
    50.0,0.5,0.3]

    pupper=[180.0,180.0,50.0,
    90.0,90.0,90.0,
    500.0,4.0,3.0]

    steps=10000
    opt_L = bbsetup(logposterior;
		SearchRange = collect(zip(plower, pupper)),
		PopulationSize=120,MaxSteps = steps
		)
	res_L = bboptimize(opt_L)
	MAP=best_candidate(res_L)

	@show MAP
    @show logposterior(MAP)

    GalaxyWarp.update_parameters!(Pars,MAP)
    disks = GalaxyWarp.make_disks(Pars.Rd,Pars)
	GalaxyWarp.cloud_geometry!(clouds_mrk79,disks,Pars)

    fsky=GalaxyWarp.plot_sky(clouds_mrk79,disks;msize=10,zoom_image=[-10.0,10.0,-10.0,10.0,2.0,2.0])
	save("$(@__DIR__)/sky.png",fsky)

    # pa_real0=0.0
    # pa_real15=6.0*1.5
    # pa_real5=6.0*5.0

    # i_real0=70.0
    # i_real15=70.0
    # i_real5=70.0

    # v0_real=320.0
    # a_real=1.5
    # n_real=1.2

    # dpa=7.0
    # di=7.0
    # @test isapprox(pa_real0,Pars.pa0,atol=dpa)
    # @test isapprox(pa_real15,Pars.pa15,atol=dpa)
    # @test isapprox(pa_real5,Pars.pa5,atol=dpa)
    # @test isapprox(i_real0,Pars.i0,atol=di)
    # @test isapprox(i_real15,Pars.i15,atol=di)
    # @test isapprox(i_real5,Pars.i5,atol=di)
    # @test isapprox(v0_real,Pars.v0,atol=20.0 )
    # @test isapprox(a_real,Pars.a,atol=0.2 )
    # @test isapprox(n_real,Pars.n,atol=0.2 )

    # REAL=[pa_real0,pa_real15,pa_real5,i_real0,i_real15,i_real5,v0_real,a_real,n_real]

    # @show REAL
    # @show logposterior(REAL)

    # GalaxyWarp.update_parameters!(Pars,REAL)
    # disks_real = GalaxyWarp.make_disks(Pars.Rd,Pars)
	# GalaxyWarp.cloud_geometry!(clouds_sim,disks_real,Pars)
    # fsky=GalaxyWarp.plot_sky(clouds_sim,disks,msize=7)
	# save("$(@__DIR__)/sky_real.png",fsky)

	#GalaxyWarp.cloud_geometry!(clouds_sim,disks,Pars)

	fdisks=GalaxyWarp.plot_disks(Dict("MAP"=>disks,"d0"=>disks0))
	save("$(@__DIR__)/mrk_79_disks.png",fdisks)

    for ang in [130.0]
		pvdf=GalaxyWarp.plot_pvd(data,ang;disks=Dict("MAP"=>disks,"d0"=>disks0),clouds=clouds_mrk79,merged=true,slit=1.0)
		save("$(@__DIR__)/mrk79_pvd_$(ang).png",pvdf)
	end
#end

const G = 4.304574992e-06
const K1 =0.0013329419990568908
function VNFW(R::Float64, M_vir::Float64, c::Float64)
	K2=119.73662477388707 * R * c
	return K1 *sqrt(M_vir) *
		sqrt((log(1.0 + K2 / M_vir^0.333333333) - K2 / (M_vir^0.3333333 *(1.0 + K2 / M_vir^0.333333333))
		) / (-c / (c + 1.0) + log(c + 1.0))) / sqrt(R)
end

@with_kw mutable struct p_5055_
    Rmax=7.5
    Fit::Array{Symbol}=[
        :pa0,:pa15,:pa5,
        :i0,:i15,:i5,
        :v0,:a,:n]

    pa0::Float64=130.0
    pa15::Float64=130.0
    pa5::Float64=130.0

    PA::Array{Symbol}=[:pa0,:pa15,:pa5]
    R_pa::Vector{Float64}=[0.0,Rmax/2,Rmax]
    ppa = Interpolator

    i0::Float64=50.0
    i15::Float64=50.0
    i5::Float64=50.0

    I::Array{Symbol}=[:i0,:i15,:i5]
    R_i::Vector{Float64}=[0.0,Rmax/2,Rmax]
    ppi = Interpolator

    Mbh=1.0e8
    v0::Float64=300.0
    a::Float64= 2.0
    n::Float64= 1.0

    drc=0.2
    Rd=collect(0.01:drc:Rmax)
    dR =0.1
    dV0=300.0
    dv=32.0
    V::Array{Symbol}=[:v0,:a,:n]

    Vcirc::Function = (r::Float64,v0::Float64,a::Float64,n::Float64) -> sqrt(
		G * Mbh / r + (v0*r/(r^2.0+a^2.0)^n)^2.0)

end

using GalaxyWarp
pwd()
#@testset "real data Fitting" begin
    run(`mkdir -p test/NGC5055/`)
    data_5055_HI=GalaxyWarp.load_cube("$(@__DIR__)/data/NGC5055_HI_lab.fits", "HI"; z=0.001678, rms=0.00035, centerx="median", centery="median",beam=[0.1,0.1,0.0],zaxis="vel", x0=-15.5, x1=15.5, y0=-15.5, y1=15.5, v0=-550.0, v1=550.0)

    figmom=GalaxyWarp.moments(data_5055_HI,1400,800;x0=-10.0,x1=10.0,y0=-10.0,y1=10.0,v0=-300.0,v1=300.0,dx=1.0,dv=5.0,vmax=80.0,denoise=4.5)

    clouds_5055_HI=GalaxyWarp.fit_pixelst(data_5055_HI;x0=-10.0,x1=10.0,y0=-10.5,y1=10.5,sigma=3.0, ncrit=1,N0=1,Nmax=7,vmin=-400.0, vmax=400.0,svmin=15.0, svmax=32.0,maxsteps=800, popsize=16)

    ## CO21

    data_5055_CO=GalaxyWarp.load_cube("$(@__DIR__)/data/NGC5055_CO21_lab.fits", "CO21"; z=0.001678, rms=0.1, centerx="median", centery="median",zaxis="vel", x0=-7.5, x1=7.5, y0=-5.5, y1=5.5, v0=-550.0, v1=550.0) #beam=[0.1,0.1,0.0],)

    figmom=GalaxyWarp.moments(data_5055_CO,1400,800;x0=-7.0,x1=7.0,y0=-5.0,y1=5.0,v0=-300.0,v1=300.0,dx=1.0,dv=5.0,vmax=80.0,denoise=4.0)
    save("$(@__DIR__)/NGC5055/CO_moments.png",figmom)

    clouds_5055_CO=GalaxyWarp.fit_pixelst(data_5055_CO;x0=-7.0,x1=7.0,y0=-5.0,y1=5.0,sigma=3.0, ncrit=1,N0=1,Nmax=5,vmin=-340.0, vmax=340.0,svmin=3.0, svmax=55.0,maxsteps=1000, popsize=16)

    GalaxyWarp.cloud_fitting_diagnostics(data_5055_CO,clouds_5055_CO,"$(@__DIR__)/NGC5055/NGC5055")

    clouds_5055_CO_f=GalaxyWarp.filter_clouds(clouds_5055_CO,[Dict("f0"=>3.5)])

    GalaxyWarp.cloud_fitting_diagnostics(data_5055_CO,clouds_5055_CO_f,"$(@__DIR__)/NGC5055/NGC5055_filt")

    data_5055_CO["beam"]
	pixels_per_beam=Int(round(data_5055_CO["beam"][1]*data_5055_CO["beam"][2]/data_5055_CO["dx"]^2.0))

    @time GalaxyWarp.merge_pixel_clouds!(clouds_5055_CO;dr=0.5,dv=50.0,mp=10,df=3.0*data_5055_CO["rms"],roll_d=8,max_d=1.0)

    GalaxyWarp.cloud_fitting_diagnostics(data_5055_CO,clouds_5055_CO,"$(@__DIR__)/NGC5055/NGC5055_m";slit=0.1)

    lenX1m=length(clouds_5055_CO.Xc)
    @test length(clouds_5055_CO.Vc)==lenX1m
    @test length(clouds_5055_CO.Yc)==lenX1m

    Pars=p_5055_()
    par0=copy([getfield(Pars,s) for s in Pars.Fit])
    GalaxyWarp.update_parameters!(Pars,par0)
    disks0 = GalaxyWarp.make_disks(Pars.Rd,Pars)

    function logposterior(pars::Vector{Float64})::Float64
		GalaxyWarp.update_parameters!(Pars,pars)
		likedV=GalaxyWarp.likelihood(clouds_5055_CO,Pars;dv=30.0)
		#prior=GalaxyWarp.logprior(Pars,Priors)
		return likedV
	end

    #res =optimize(logposterior, par0, NelderMead(),Optim.Options(iterations = 3600, store_trace = true, show_trace = true,g_tol=1.0e-3))

	#MAP=Optim.minimizer(res)

    plower=[0.0,0.0,0.0,
    0.0,0.0,0.0,
    50.0,0.1,0.3]

    pupper=[360.0,360.0,360.0,
    90.0,90.0,90.0,
    500.0,4.0,3.0]

    steps=10000
    opt_L = bbsetup(logposterior;
		SearchRange = collect(zip(plower, pupper)),
		PopulationSize=120,MaxSteps = steps
		)
	res_L = bboptimize(opt_L)
	MAP=best_candidate(res_L)

	@show MAP
    @show logposterior(MAP)

    GalaxyWarp.update_parameters!(Pars,MAP)
    disks = GalaxyWarp.make_disks(Pars.Rd,Pars)
	GalaxyWarp.cloud_geometry!(clouds_5055_CO,disks,Pars)

    fsky=GalaxyWarp.plot_sky(clouds_5055_CO,disks;msize=10,zoom_image=[-10.0,10.0,-10.0,10.0,2.0,2.0])
	save("$(@__DIR__)/sky.png",fsky)

    # pa_real0=0.0
    # pa_real15=6.0*1.5
    # pa_real5=6.0*5.0

    # i_real0=70.0
    # i_real15=70.0
    # i_real5=70.0

    # v0_real=320.0
    # a_real=1.5
    # n_real=1.2

    # dpa=7.0
    # di=7.0
    # @test isapprox(pa_real0,Pars.pa0,atol=dpa)
    # @test isapprox(pa_real15,Pars.pa15,atol=dpa)
    # @test isapprox(pa_real5,Pars.pa5,atol=dpa)
    # @test isapprox(i_real0,Pars.i0,atol=di)
    # @test isapprox(i_real15,Pars.i15,atol=di)
    # @test isapprox(i_real5,Pars.i5,atol=di)
    # @test isapprox(v0_real,Pars.v0,atol=20.0 )
    # @test isapprox(a_real,Pars.a,atol=0.2 )
    # @test isapprox(n_real,Pars.n,atol=0.2 )

    # REAL=[pa_real0,pa_real15,pa_real5,i_real0,i_real15,i_real5,v0_real,a_real,n_real]

    # @show REAL
    # @show logposterior(REAL)

    # GalaxyWarp.update_parameters!(Pars,REAL)
    # disks_real = GalaxyWarp.make_disks(Pars.Rd,Pars)
	# GalaxyWarp.cloud_geometry!(clouds_sim,disks_real,Pars)
    # fsky=GalaxyWarp.plot_sky(clouds_sim,disks,msize=7)
	# save("$(@__DIR__)/sky_real.png",fsky)

	#GalaxyWarp.cloud_geometry!(clouds_sim,disks,Pars)

	fdisks=GalaxyWarp.plot_disks(Dict("MAP"=>disks,"d0"=>disks0))
	save("$(@__DIR__)/mrk_79_disks.png",fdisks)

    for ang in [0.0,70.0,130.0]
		pvdf=GalaxyWarp.plot_pvd(data_5055_CO,ang;disks=Dict("MAP"=>disks,"d0"=>disks0),clouds=clouds_5055_CO,merged=true,slit=0.2)
		save("$(@__DIR__)/NGC5055/NGC5055_pvd_$(ang).png",pvdf)
	end
#end


module FractionalSystems

using ControlSystems, ControlSystemIdentification
using LinearAlgebra, SpecialFunctions
import FractionalDiffEq: FODESystem
using ForwardDiff

include("foss/foss.jl")

include("fotf/fotf.jl")
include("fotf/show.jl")
include("fotf/rlocus.jl")
include("fotf/freqresp.jl")

include("FOLE.jl")

export FOTF, fotf, fotfdata, base_order, fotf2cotf, rlocus, polyuniq, simplify, freqresp

export polyshow

export FOSS, foss, foss2ss

export FOLyapunov

end


# Make plot of positive definiteness of Gaussian approximation:
using PyPlot
using ForwardDiff

#function posdef()
ntime = 10000
time = collect(linspace(1e-2,1000,ntime))
#nc = 3
#amp = [2.7518074963750734,-0.3546256365636662,-1.3968344706386124]
#b = [1.5312499407621147,4.753504292338284,1.7967431129812566]
#omega = [0.34148888178746056,2.042571639590954,1.7641684073510928]
nc = 4
#amp = [2.953823582534888,-0.04961614737314587,-1.6900719772140012,-0.21413545794774125]
#b = [1.589510614489265,3.4729345651560872,1.8698492189074047,6.363451205766626]
#omega = [0.3329119766576696,4.5999414581901386,1.7115890219170917,2.205939216106191]

amp = [2.953823582534889, -0.04961614737314587, -1.6900719772140012, -0.21413545794774125]
b =[1.589510614489265, 3.4729345651560872, 1.8698492189074047, 6.363451205766626]
omega =[0.3329119766576696, 4.5999414581901386,1.7115890219170917, 2.205939216106191]

function func(x)
fout=0.0
for k=1:nc
  fout +=amp[k]*exp(-b[k]*sqrt(x))*cos(omega[k]*sqrt(x))
end
#fout=exp(-x*0.5)
return fout
end

y = zeros(ntime)
dy = zeros(ntime)
d2y = zeros(ntime)
for i=1:ntime
  y[i]=func(time[i])
  dy[i]=ForwardDiff.derivative(func,time[i])
  d2y[i]=ForwardDiff.derivative(z -> ForwardDiff.derivative(func,z),time[i])
end
plot(time,y)
plot(time,dy)
plot(time,d2y)

#return
#end


using StructuralEstimation
srand(123)
# State 1
X1 = 0:1
F1 = [[1. 0.; 1. 0.], [0. 1.; 0. 1.]]

# State 2
X2 = 1:5
nX2 = length(X2)
F2 = 1./(1+abs(ones(length(X2),1)*X2'-X2*ones(1, length(X2))))
F2 = F2./sum(F2,1)'

# States
S = States(State(X1, F1), CommonState(X2, F2))

# Utility variables
Z1 = zeros(nX2*2, 3)
Z2 = [ones(nX2) X2 -ones(nX2);
      ones(nX2) X2 zeros(nX2)]

U = LinearUtility([Z1, Z2], 0.99, [-.1;.2;1])

sol = solve!(U, S)
# 3 Simulate Data
T = 12*3
N = 20
M = 3
D = simulate(U, S, M, N, T)

# Estimate market probability matrix
F2hat = zeros(5, 5)
for n = 1:T*N
   if n%T != 0
      F2hat[ind2sub((5,2), D.x[n])[1],
            ind2sub((5,2), D.x[n + 1])[1]] += 1
   end
end

F2hat = F2hat./vec(sum(F2hat,2))

# States based on estimated transitions
estimated_S = States(State(X1, F1), CommonState(X2, F2hat))

# Fit using NFXP
U = LinearUtility([Z1, Z2], 0.99, 0*[-.1;.2;1])
@time dixit_nfxp = fit_nfxp(U, estimated_S, D)
U = LinearUtility([Z1, Z2], 0.99, 0*[-.1;.2;1])
@time dixit_npl = fit_npl(U, S, D)


using LinearAlgebraicRepresentation, ViewerGL
Lar = LinearAlgebraicRepresentation
GL = ViewerGL

filename = "./test/svg/Lar.svg"
V,EV = Lar.svg2lar(filename)

GL.VIEW([
	GL.GLLines(V,EV,GL.COLORS[1]),
	GL.GLFrame2
]);

function pointsinout(V,EV, n=2_000_000)
	point_in = []
	point_out = []
	point_on = []
	classify = Lar.pointInPolygonClassification(V,EV)
	for k=1:n
		queryPoint = rand(1,2)
		inOut = classify(queryPoint)
		# println("k = $k, queryPoint = $queryPoint, inOut = $inOut")
		if inOut=="p_in"
			push!(point_in, queryPoint);
		elseif inOut=="p_out"
			push!(point_out, queryPoint);
		elseif inOut=="p_on"
			push!(point_on, queryPoint);
		end
	end
	#GL.GLPoints(queryPoint,GL.COLORS[2])
	return point_in,point_out,point_on
end

using Random
const rnglist = [MersenneTwister() for i in 1:Threads.nthreads()]

function pointsinout_multithreading(V,EV, n=2_000_000,t=Threads.nthreads())

	point_in = [[] for i in 1:t]
	point_out = [[] for i in 1:t]
	point_on = [[] for i in 1:t]

	classify = Lar.pointInPolygonClassification(V,EV)

	Threads.@threads for k=1:n
		queryPoint = rand(rnglist[Threads.threadid()],1,2)
		#queryPoint = rand(1,2)
		inOut = classify(queryPoint)
		if inOut=="p_in"
			push!(point_in[Threads.threadid()], queryPoint);
		elseif inOut=="p_out"
			push!(point_out[Threads.threadid()], queryPoint);
		elseif inOut=="p_on"
			push!(point_on[Threads.threadid()], queryPoint);
		end
	end
	return vcat(point_in...),vcat(point_out...),vcat(point_on...)
end

using Distributed
#nprocs()
#addprocs(1)
#n_cpu = length(Sys.cpu_info()) or Sys.CPU_THREADS

function pointsinout_multiproc(V,EV, n=2_000_000,ws=Distributed.nworkers())

	point_in = [[] for i in 1:ws]
	point_out = [[] for i in 1:ws]
	point_on = [[] for i in 1:ws]
	# al posto di questi canale remoto??

	classify = Lar.pointInPolygonClassification(V,EV)

	@distributed for k=1:n

		# va fatta la chiamata remota
		queryPoint = rand(rnglist[Threads.threadid()],1,2)
		#queryPoint = rand(1,2)
		inOut = classify(queryPoint)
		if inOut=="p_in"
			push!(point_in[Threads.threadid()], queryPoint);
		elseif inOut=="p_out"
			push!(point_out[Threads.threadid()], queryPoint);
		elseif inOut=="p_on"
			push!(point_on[Threads.threadid()], queryPoint);
		end
	end

	#a,b,c, = fetch()

	return vcat(a),vcat(b),vcat(c)
end

#@time points_in, points_out, points_on = pointsinout_multiproc(V,EV)

@time points_in, points_out, points_on = pointsinout_multithreading(V,EV);
@time points_in, points_out, points_on = pointsinout(V,EV);

pointsin = [vcat(points_in...) zeros(length(points_in),1)]
pointsout = [vcat(points_out...) zeros(length(points_out),1)]

polygon = [GL.GLLines(V,EV,GL.COLORS[1])];
in_mesh = [GL.GLPoints(pointsin, GL.COLORS[2])]
out_mesh = [GL.GLPoints(pointsout, GL.COLORS[3])]

result = cat([polygon,in_mesh,out_mesh])
GL.VIEW(result);

#=

n = 2_000_000

with @btime macro:

serial -> 158.054 s (1772000042 allocations: 158.60 GiB)
multithread -> 51.618 s (1772000187 allocations: 158.62 GiB)
multithread with separate RNG for each thread -> 51.141 s (1772000205 allocations: 158.62 GiB)

=#


data = readdlm("RLBinding.csv",',') # julia uses "" to declare
# strings, '' for characters and # for comments

# one of the improvements over matlab - the use of squared brackets [] to
# index into arrays, to clearly distinguish it from function calls.
# Indices start at 1 in julia
t = data[:,1]; R = data[:,2]; L = data[:,3]; RL = data[:,4];

# similar to import in python - declare which package to use
using Winston # Winston is one of the three graphics packages available for julia at the moment

# FramedPlot can be used for plotting a single plot
p = FramedPlot("xlabel","time")
setattr(p,"ylabel","concentration") # attributes can be changed with setter functions
# similar to MATLABs `set` and `get`

# building a plot using Winston is like composing a collage:
# build one element after the other
# then add it all to the canvas (the FramedPlot)
r = Points(time,R,"type","diamond", "color","blue")
l = Points(time,L,"type","circle", "color","blue")
rl = Points(time,RL,"type","cross", "color","blue")

cr = Curve(time,R, "color","blue")
cl = Curve(time,L, "color","blue")
crl = Curve(time,RL, "color","blue")

setattr(r,"label","R")
setattr(l,"label","L")
setattr(rl,"label","RL")
lgnd = Legend(.8,.9,{r,l,rl})

# adding does not! need to be done at the end
add(p,r,l,rl,cr,cl,crl,lgnd)
# Winston/ijulia do not yet support the display of the generated plots,
# but it is possible to save the plot to a file
# eps, png and svg are supposed to be supported, but
# svg did not work on my machine out of the box
file(p,"julia_rlbinding.png")


module Shared_funcs
#
  # Waiting for https://github.com/JuliaLang/julia/pull/6884
  using GZip
#
  function format_percent(v)
    return strip(strip(@sprintf("%0.2f", 100.0 * v), '0'), '.') * "%"
  end
  function comp_density(A::AbstractMatrix)
    return countnz(A) / length(A)
  end
  function _read_matrix_mtx(filename, infoonly::Bool=false)
    function skewsymmetric!(M::AbstractMatrix)
        m,n = size(M)
        m == n || throw(DimensionMismatch())
        for i=1:n, j=1:n
            if M[i,j] != 0
                M[j,i] = -M[i,j]
            end
        end
        return M
    end
    function symmetric!(M::AbstractMatrix)
        m,n = size(M)
        m == n || throw(DimensionMismatch())
        for i=1:n, j=1:n
            if M[i,j] != 0
                M[j,i] = M[i,j]
            end
        end
        return M
    end
    function hermitian!(M::AbstractMatrix)
        m,n = size(M)
        m == n || throw(DimensionMismatch())
        for i=1:n, j=1:n
            if M[i,j] != 0
                M[j,i] = conj(M[i,j])
            end
        end
        return M
    end
    #println(pwd())
    mmfile = Nothing()
    if (endswith(filename, ".gz"))
      mmfile = GZip.open(filename,"r")
    else
      mmfile = open(filename,"r")
    end
    # Read first line
    firstline = chomp(readline(mmfile))
    tokens = split(firstline)
    if length(tokens) != 5
      throw(ParseError(string("Not enough words on first line: ", ll)))
    end
    if tokens[1] != "%%MatrixMarket"
      throw(ParseError(string("Not a valid MatrixMarket header:", ll)))
    end
    (head1, rep, field, symm) = map(lowercase, tokens[2:5])
    if head1 != "matrix"
        throw(ParseError("Unknown MatrixMarket data type: $head1 (only \"matrix\" is supported)"))
    end
    eltype = field == "real" ? Float64 :
             field == "complex" ? Complex128 :
             field == "pattern" ? Bool :
             throw(ParseError("Unsupported field $field (only real and complex are supported)"))

    symlabel = symm == "general" ? identity :
               symm == "symmetric" ? symmetric! :
               symm == "hermitian" ? hermitian! :
               symm == "skew-symmetric" ? skewsymmetric! :
               throw(ParseError("Unknown matrix symmetry: $symm (only general, symmetric, skew-symmetric and hermitian are supported)"))
    # Skip all comments and empty lines
    ll   = readline(mmfile)
    while length(chomp(ll))==0 || (length(ll) > 0 && ll[1] == '%')
        ll = readline(mmfile)
    end
    # Read matrix dimensions (and number of entries) from first non-comment line
    dd = map(parseint, split(ll))
    if length(dd) < (rep == "coordinate" ? 3 : 2)
        throw(ParseError(string("Could not read in matrix dimensions from line: ", ll)))
    end
    rows = dd[1]
    cols = dd[2]
    entries = (rep == "coordinate") ? dd[3] : (rows * cols)
    if infoonly
        return (rows, cols, entries, rep, field, symm)
    end
    if rep == "coordinate"
        rr = Array(Int, entries)
        cc = Array(Int, entries)
        xx = Array(eltype, entries)
        for i in 1:entries
            flds = split(readline(mmfile))
            rr[i] = parseint(flds[1])
            cc[i] = parsefloat(flds[2])
            if eltype == Complex128
                xx[i] = Complex128(parsefloat(flds[3]), parsefloat(flds[4]))
            elseif eltype == Float64
                xx[i] = parsefloat(flds[3])
            else
                xx[i] = true
            end
        end
        return symlabel(sparse(rr, cc, xx, rows, cols))
    end
    return symlabel(reshape([parsefloat(readline(mmfile)) for i in 1:entries], (rows,cols)))
  end
  function read_matrix_mtx(filename)
    return _read_matrix_mtx(filename)
  end
  function random_matrix(seed, scale, dense, n, m, show)
    function toindex(x,n) return clamp(int((n)* 0.5*(1.0+x)), 1, n) end
    rng = MersenneTwister(seed)
    B = A = scale * rand(rng, (m, n))
    if dense != 100
      #zeros = rand(rng, 1:(m*n), zero_n) # Needs next version of Julia
      zero_n = int(round((m*n) * (1.0 - dense / 100.0)))
      if (zero_n > 0)
        nz_i = [1:m*n]
        while length(nz_i) + zero_n > (m*n)
          splice!(nz_i, toindex(rand(rng), length(nz_i)))
        end
        B = zeros(Float64, (m,n))
        for i in nz_i B[i] = A[i] end
      end
    end
    if (show)
      println("A", B)
    end
    return B
  end
  function random_matrix(params)
    seed = int(get(params, "seed", int(time_ns()) % 32768))
    scale = int(get(params, "scale", 1.0))
    dense = int(get(params, "dense", 100 ))
    n = int(get(params, "n", 10))
    m = int(get(params, "m", n))
    show = get(params, "show", false)
    println("/seed:", seed)
    return random_matrix(seed, scale, dense, n, m, show)
  end
  function str_range(str, list = [])
  	function single_range(str)
      beg = int(split(str, "-")[1])
      nd = int(split(split(str, "-")[2],":")[1])
      stp = contains(str,":") ? int(split(str, ":")[2]) : 1
      cnt = int((nd-beg+1)/stp)
    	return [range(beg,stp,cnt)]
    end
    if str == "all" return 1:length(list) end
    rngs = Array(Int, 0)
    for x in split(str, ",")
      if length(x) != 0 rngs = vcat(rngs, contains(x, "-") ? single_range(x) : [int(x)]) end
    end
    return rngs
  end
  function print_choose(list, autochoose::Bool = false)
    if length(list) == 0 return [] end
    if length(list) == 1 && autochoose return list end
    cols = [:white, :yellow]
    for i in 1:length(list)
      print_with_color(cols[1+i%2], " $i. $(list[i]) \n")
    end
    print(" > ")
    return str_range(strip(readline()), list)
  end
end