lfborjas / swiss-ephemeris Goto Github PK
View Code? Open in Web Editor NEWHaskell bindings to the Swiss Ephemeris C library, bundles some basic ephemeris files.
License: GNU Affero General Public License v3.0
Haskell bindings to the Swiss Ephemeris C library, bundles some basic ephemeris files.
License: GNU Affero General Public License v3.0
I hadn't noticed that the C library can return -1 when unable to calculate cusps! See the java impl here for reference:
https://github.com/Kibo/AstroAPI/blob/7f2e884abea354aba6ff8c44b4db2aa110085b8f/src/main/java/cz/kibo/api/astrology/domain/Cusp.java#L97 (which references: https://github.com/Kibo/AstroAPI/blob/cfc3d1e5a4c1d8dd5cd9906b15cec855e9e6302e/src/main/java/swisseph/SweConst.java#L95)
It seems fairly unlikely to happen though, more for sunshine houses in polar regions, maybe?
swiss-ephemeris/csrc/swehouse.c
Lines 118 to 163 in 952de1b
Some context:
Potential optimizations:
swe_calc
on each iteration, call it only four times: three points to draw a parabola, and one final time after pure approximation on that parabola is done. See the find_zero
function, and usage for e.g. zodiac crossings (note that the code that does the > 180 check is now immortalized in the difdeg2n
function.It currently fails to build due to:
Configuring swiss-ephemeris-1.2.1.1...
CallStack (from HasCallStack):
$, called at libraries/Cabal/Cabal/Distribution/Simple/Configure.hs:1024:20 in Cabal-3.2.1.0:Distribution.Simple.Configure
configureFinalizedPackage, called at libraries/Cabal/Cabal/Distribution/Simple/Configure.hs:477:12 in Cabal-3.2.1.0:Distribution.Simple.Configure
configure, called at libraries/Cabal/Cabal/Distribution/Simple.hs:625:20 in Cabal-3.2.1.0:Distribution.Simple
confHook, called at libraries/Cabal/Cabal/Distribution/Simple/UserHooks.hs:65:5 in Cabal-3.2.1.0:Distribution.Simple.UserHooks
configureAction, called at libraries/Cabal/Cabal/Distribution/Simple.hs:180:19 in Cabal-3.2.1.0:Distribution.Simple
defaultMainHelper, called at libraries/Cabal/Cabal/Distribution/Simple.hs:116:27 in Cabal-3.2.1.0:Distribution.Simple
defaultMain, called at Setup.hs:2:8 in main:Main
Setup: Encountered missing or private dependencies:
QuickCheck >=2.12 && <=2.14```
See the toils and travails in #6 -- many failures, some of those commits definitely deserved to fail, but even after I cleaned up the code considerably, I see "segfaults". Not sure if it's the C library doing something untoward, but it happens extremely often in Ubuntu and never in macOS (I develop in macOS, and the tests work a frustrating 99.999% of the time, though I've managed to see errors too.) I just don't know enough C, Haskell or CI to get to the bottom of this, and I spent an inordinate amount of hours the past two nights staring at this.
The latest versions of swiss ephemeris include the following files, which allow for generation and consumption of optimized (for storage and reading) "precalculated" ephemeris. They're useful for transits and other use cases where several days of ephemeris may be examined at once:
I believe they need a little bit of tweaking to be able to generate and read the files from a non-hardcoded base path, in addition to the documentation/testing of the Haskell bindings. It would be interesting to benchmark them against calls to the actual ephemeris calculation functionality.
Introduction by the authors at: https://groups.io/g/swisseph/message/8864
Note that this is part of a thread about transit calculation, which seems to be the main use case.
The house system is currently the last argument, perhaps it should be the first? That way people can say things like:
calculateCuspsP = calculateCusps Placidus
Additionally, we should add tests for the other popular systems (as per https://www.astro.com/swisseph/swephprg.htm#_Toc46406845).
I considered making the houses not be a record with 12 keys, but a simple list -- however, all popular house systems (except Gauquelin) have 12 houses, and each house has specific semantics. Perhaps a helper function that transform them to a list would be good, for those who only wish to iterate?
scientific
package to palliate precision issues in other build environments? (see note in README)withEphemeridesPath :: a -> IO a
function that will run a given calculation after setting an ephemerides path, and then close said path? Would be a bit more expensive than the authors intended, but safer on resources.See:
swiss-ephemeris/csrc/swevents.c
Lines 1676 to 1722 in 169e3df
A "parabola fit" is often mentioned in the swisseph forum, and I believe it's using the above. Unlike interpolation, it requires bracketing and some cajoling of values into it to account for "circle jumps," and out of it to actually find the equivalent date value; the gain being that it doesn't require as many ephemeris IO events as regular interpolation. This method is also able to detect double crossings, which we currently don't do (though the tools are there! See the comments for crossingBetween
)
csrc/configurable_sweephe4.c: In function ‘eph4_posit’:
csrc/configurable_sweephe4.c:555:21: error:
warning: ‘%s’ directive writing 5 bytes into a region of size between 1 and 256 [-Wformat-overflow=]
555 | sprintf(s, "%s%sM%d", ephe4d.ephe4path, EP4_FILE, -filenr);
| ^~
|
555 | sprintf(s, "%s%sM%d", ephe4d.ephe4path, EP4_FILE, -filenr);
| ^
csrc/configurable_sweephe4.c:555:18: error:
note: directive argument in the range [1, 214749]
555 | sprintf(s, "%s%sM%d", ephe4d.ephe4path, EP4_FILE, -filenr);
| ^~~~~~~~~
|
555 | sprintf(s, "%s%sM%d", ephe4d.ephe4path, EP4_FILE, -filenr);
| ^
In file included from /usr/include/stdio.h:867,
from csrc/sweodef.h:169,
from csrc/swephexp.h:82,
from csrc/configurable_sweephe4.c:60:0: error:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:36:10: error:
note: ‘__builtin___sprintf_chk’ output between 8 and 268 bytes into a destination of size 256
36 | return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1,
| ^
csrc/configurable_sweephe4.c:553:18: error:
note: directive argument in the range [0, 214748]
553 | sprintf(s, "%s%s%d", ephe4d.ephe4path, EP4_FILE, filenr);
| ^~~~~~~~
|
553 | sprintf(s, "%s%s%d", ephe4d.ephe4path, EP4_FILE, filenr);
| ^
In file included from /usr/include/stdio.h:867,
from csrc/sweodef.h:169,
from csrc/swephexp.h:82,
from csrc/configurable_sweephe4.c:60:0: error:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:36:10: error:
note: ‘__builtin___sprintf_chk’ output between 7 and 267 bytes into a destination of size 256
36 | return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 | __bos (__s), __fmt, __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
36 | return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1,
| ^
csrc/configurable_sweephe4.c: In function ‘ephe4_write_file’:
csrc/configurable_sweephe4.c:990:61: error:
warning: ‘%s’ directive output may be truncated writing up to 255 bytes into a region of size 235 [-Wformat-truncation=]
990 | snprintf(errtext, AS_MAXCH, "error in swe_calc(): %s", serr);
| ^~ ~~~~
|
990 | snprintf(errtext, AS_MAXCH, "error in swe_calc(): %s", serr);
| ^
In file included from /usr/include/stdio.h:867,
from csrc/sweodef.h:169,
from csrc/swephexp.h:82,
from csrc/configurable_sweephe4.c:60:0: error:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:67:10: error:
note: ‘__builtin___snprintf_chk’ output between 22 and 277 bytes into a destination of size 256
67 | return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68 | __bos (__s), __fmt, __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
67 | return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
| ^
Some explanation of declinations:
https://astro-charts.com/blog/2017/easy-way-calculate-parallels-contraparallels/
And https://www.astro.com/swisseph/swephprg.htm, the section 3.3. Options chosen by flag bits (long iflag)
, and the explanation in 3.4 Position and speed
:
Ecliptic position | Equatorial position (SEFLG_EQUATORIAL) |
---|---|
Longitude | right ascension |
Latitude | declination |
Distance in AU | distance in AU |
Speed in longitude (deg/day) | speed in right ascension (deg/day) |
Speed in latitude (deg/day) | speed in declination (deg/day) |
Speed in distance (AU/day) | speed in distance (AU/day) |
If you need rectangular coordinates (SEFLG_XYZ), swe_calc() returns x, y, z, dx, dy, dz in AU.
Once you have computed a planet, e.g., in ecliptic coordinates, its equatorial position or its rectangular coordinates are available, too. You can get them very cheaply (little CPU time used), calling again swe_calc() with the same parameters, but adding SEFLG_EQUATORIAL or SEFLG_XYZ to iflag, swe_calc() will not compute the body again, just return the data specified from internal storage.
And here's how to set the flags:
-- | Combine @CalcFlag@ as bitwise options.
-- For example, can use it to obtain the equatorial,
-- not eliptical (default,) positions of planetary bodies.
calculationOptions :: [CalcFlag] -> CalcFlag
calculationOptions = CalcFlag . foldr ((.|.) . unCalcFlag) 0
(from an older version of this library, funnily enough.)
scientific
to return values, and let downstream consumers convert to other floats: https://hackage.haskell.org/package/scientific-0.3.7.0/docs/Data-Scientific.html#v:fromFloatDigitsThe current behavior includes second fractions, meaning that people who wish to not display second fractions have to do rounding up themselves!
I believe any difference between functions is a misunderstanding. From my observations, calculating the julianTime yields a JulianTime in UTC, to convert to "terrestrial time", which accounts for the non-uniformity of Earth's rotation (more in the Delta T section of the official docs.)
The "Terrestrial Time" (TT
) that the swiss ephemeris test page reports is then: julianTime + deltaTime. We should provide some conversions, namely UTCTime -> JulianTime
, JulianTime -> TerrestrialTime
(or EphemerisTime
?), and UTCTime -> TerrestrialTime
. The first of those would require either porting swe_utc_to_jd
, or introducing a dependency on the time
package, to replicate the below:
I'm currently using the following in my application code, which glues Haskell's UTCTime
with JulianTime
via our very own julianDay
:
-- | Convert between a UTC timestamp and the low-level JulianTime that SwissEphemeris requires.
utcToJulian :: UTCTime -> JulianTime
utcToJulian (UTCTime day time) =
julianDay (fromIntegral $ y) m d h
where
(y, m, d) = toGregorian day
h = 2.77778e-16 * (fromIntegral $ diffTimeToPicoseconds time)
julianToUTC :: JulianTime -> UTCTime
juliantoUTC jd =
UTCTime (fromGregorian y m d) (picosecondsToDiffTime $ round $ h * 3600 * 1e12)
where
(y, m, d, h) = gregorianDateTime jd
Which seems to yield pretty good results according to JPL's conversion tool (though a few seconds behind Swisseph!) for some test data:
import Data.Time
utc <- (parseTimeM True defaultTimeLocale "%Y-%-m-%-d %T" "1989-12-25 09:30:00" :: IO UTCTime)
utcToJulian tsUTC
2447885.89583365
-- and back
> julianToUTC $ JulianTime 2447885.89583365
1989-12-25 09:30:00.027371942996 UTC
JPL's data:
-- the advantage is that it's a pure function, the disadvantage is that it seems to be running afoul of some history-aware corrections that the swe_utc_to_jd
function makes:
swiss-ephemeris/csrc/swedate.c
Line 376 in 74ea5fa
The binding to this function would have to be in IO
, and deal with some funky pointers; but it's clearly very well thought out and a useful utility to expose. One of the original authors says that the difference between the functions is only relevant in specific situations, so maybe the above method is okay for most Haskell users? 🤔
See: https://groups.io/g/swisseph/message/7781 -- which is a follow up of: https://groups.io/g/swisseph/topic/64346793#7779
And: https://gist.github.com/lfborjas/ccde203851906f4b81305f401d03787c
See: lfborjas/timezone-detect#4 -- one-off users should be able to run a computation that needs the files to be open and later closed, so we should provide a withEphePath
bracket for those use cases.
Hi,
am trying this version of swiss ehim code but am getting incorrect cusp values. i had an old version of swiss ephim 2.01-00.02 this version gave accurate cusp. could you please let me know what can cause cusp values change?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.