12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697(* Claude Code
*
* Copyright (C) 2026 Yoann Padioleau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* (LGPL) as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*)(* See Celestial.mli *)typeequatorial={ra:float;dec:float}typehorizontal={az:float;alt:float}letpi=Float.piletdegrees(d:float):float=d*.pi/.180.lethours(h:float)(m:float)(s:float):float=degrees(15.*.(h+.(m/.60.)+.(s/.3600.)))letdms(sign:int)(d:float)(m:float)(s:float):float=float_of_intsign*.degrees(d+.(m/.60.)+.(s/.3600.))letto_degrees(a:float):float=a*.180./.piletto_hours(a:float):float=to_degreesa/.15.letarcseconds(s:float):float=degrees(s/.3600.)letnormalize(a:float):float=leta=Float.rema(2.*.pi)inifa<0.thena+.(2.*.pi)elsea(*****************************************************************************)(* Time *)(*****************************************************************************)(* the Unix epoch, 1970-01-01 at 0h, was JD 2440587.5 (a Julian day
* starts at noon) *)letjulian_date(t:float):float=(t/.86400.)+.2440587.5letj2000=2451545.0letcenturies(jd:float):float=(jd-.j2000)/.36525.(* Meeus 12.4: the Earth's rotation angle, 360.98564736629 deg a day;
* the two small terms are the precession's share *)letgmst(jd:float):float=lett=centuriesjdinnormalize(degrees(280.46061837+.(360.98564736629*.(jd-.j2000))+.(0.000387933*.t*.t)-.(t*.t*.t/.38710000.)))letlst(jd:float)~(lon:float):float=normalize(gmstjd+.lon)(*****************************************************************************)(* Frames *)(*****************************************************************************)(* Meeus 22.2, its first two terms: 23 deg 26' 21.448 arcsec, less 47 arcsec a
* century *)letobliquity(jd:float):float=lett=centuriesjdindegrees(23.+.(26./.60.)+.(21.448/.3600.))-.arcseconds(46.8150*.t)letof_vector((x,y,z):float*float*float):equatorial=letr=sqrt((x*.x)+.(y*.y)+.(z*.z))in{ra=normalize(atan2yx);dec=asin(z/.r)}letto_vector(p:equatorial):float*float*float=(cosp.dec*.cosp.ra,cosp.dec*.sinp.ra,sinp.dec)(* the ecliptic is the equator tilted by the obliquity about the
* equinox's direction, the x axis both share *)letof_ecliptic~(obliquity:float)(lon:float)(lat:float):equatorial=letx=coslat*.coslonandy=coslat*.sinlonandz=sinlatinof_vector(x,(cosobliquity*.y)-.(sinobliquity*.z),(sinobliquity*.y)+.(cosobliquity*.z))(* Meeus 21.2 and 21.3: three rotations, by zeta about the pole, theta
* about the new x axis, z about the new pole *)letprecess(jd:float)(p:equatorial):equatorial=lett=centuriesjdinletzeta=arcseconds((2306.2181*.t)+.(0.30188*.t*.t)+.(0.017998*.t*.t*.t))inletz=arcseconds((2306.2181*.t)+.(1.09468*.t*.t)+.(0.018203*.t*.t*.t))inlettheta=arcseconds((2004.3109*.t)-.(0.42665*.t*.t)-.(0.041833*.t*.t*.t))inleta=cosp.dec*.sin(p.ra+.zeta)inletb=(costheta*.cosp.dec*.cos(p.ra+.zeta))-.(sintheta*.sinp.dec)inletc=(sintheta*.cosp.dec*.cos(p.ra+.zeta))+.(costheta*.sinp.dec)in{ra=normalize(atan2ab+.z);dec=asinc}(* Meeus 13.5 and 13.6, with the azimuth from north rather than from
* south: the hour angle H is how far the star has gone west of the
* meridian *)letto_horizontal~(lat:float)~(lst:float)(p:equatorial):horizontal=leth=lst-.p.rainletalt=asin((sinlat*.sinp.dec)+.(coslat*.cosp.dec*.cosh))inletaz=atan2(-.(cosp.dec*.sinh))((sinp.dec*.coslat)-.(cosp.dec*.cosh*.sinlat))in{az=normalizeaz;alt}letseparation(p:equatorial)(q:equatorial):float=letx1,y1,z1=to_vectorpandx2,y2,z2=to_vectorqin(* the cross product's length with the dot product: exact for small
* angles too, unlike acos *)letcx=(y1*.z2)-.(z1*.y2)andcy=(z1*.x2)-.(x1*.z2)andcz=(x1*.y2)-.(y1*.x2)inatan2(sqrt((cx*.cx)+.(cy*.cy)+.(cz*.cz)))((x1*.x2)+.(y1*.y2)+.(z1*.z2))