![]() |
SuperNOVAS v1.5
The NOVAS C library, made better
|
The NOVAS C astrometry library, made better.
SuperNOVAS is a C/C++ astronomy software library, providing high-precision astrometry such as one might need for running an observatory, a precise planetarium program, or for analyzing astronomical datasets. It started as a fork of the Naval Observatory Vector Astrometry Software (NOVAS) C version 3.1, but since then it has grown into its own, providing bug fixes, tons of new features, and a much improved API compared to the original NOVAS.
SuperNOVAS is easy to use and it is very fast, providing 3–5 orders of magnitude faster position calculations than astropy 7.0.0 in a single thread (see the benchmarks), and its performance will scale with the number of CPUs when calculations are performed in parallel threads.
SuperNOVAS is entirely free to use without licensing restrictions. Its source code is compatible with the C99 standard, and hence should be suitable for old and new platforms alike. And, despite it being a light-weight library, it fully supports the IAU 2000/2006 standards for microarcsecond-level position calculations.
This document has been updated for the v1.5 and later releases.
SuperNOVAS is a fork of the The Naval Observatory Vector Astrometry Software (NOVAS). (It is not related to the separate NOVA / libnova library.)
The primary goal of SuperNOVAS is to improve on the original NOVAS C library via:
At the same time, SuperNOVAS aims to be fully backward compatible with the intended functionality of the upstream NOVAS C library, such that it can be used as a build-time replacement for NOVAS in your application without having to change existing (functional) code you may have written for NOVAS C.
SuperNOVAS is really quite easy to use. Its new API is just as simple and intuitive as that of astropy (or so we strive for it to be), and it is similarly well documented also (see the API documentation). You can typically achieve the same results with similar lines of code with SuperNOVAS as with astropy, notwithstanding a little more involved error handling at every step (due to the lack of try / except style constructs in C).
SuperNOVAS is currently based on NOVAS C version 3.1. We plan to rebase SuperNOVAS to the latest upstream release of the NOVAS C library, if new releases become available.
SuperNOVAS is maintained by Attila Kovács at the Center for Astrophysics | Harvard & Smithsonian, and it is available through the Smithsonian/SuperNOVAS repository on GitHub.
Outside contributions are very welcome. See how you can contribute on how you can make SuperNOVAS even better.
SuperNOVAS fixes a number of outstanding issues with NOVAS C 3.1:
SuperNOVAS strives to maintain API compatibility with the upstream NOVAS C 3.1 library, but not binary (ABI) compatibility.
If you have code that was written for NOVAS C 3.1, it should work with SuperNOVAS as is, without modifications. Simply (re)build your application against SuperNOVAS, and you are good to go.
The lack of binary compatibility just means that you cannot drop-in replace the libraries (e.g. the static libnovas.a, or the shared libnovas.so), from NOVAS C 3.1 with those from SuperNOVAS. Instead, you will have to build (compile) your application referencing the SuperNOVAS headers and/or libraries from the start.
This is because some function signatures have changed, e.g. to use an enum argument instead of the nondescript short int option arguments used in NOVAS C 3.1, or because we added a return value to a function that was declared void in NOVAS C 3.1. We also changed the object structure to contain a long ID number instead of short to accommodate JPL NAIF codes, for which 16-bit storage is insufficient.
The SuperNOVAS distribution contains a GNU Makefile, which is suitable for compiling the library (as well as local documentation, and tests, etc.) on POSIX systems such as Linux, Mac OS X, BSD, Cygwin or WSL – using GNU make.
Before compiling the library take a look a config.mk and edit it as necessary for your needs, or else define the necessary variables in the shell prior to invoking make. For example:
Additionally, you may set number of environment variables to futher customize the build, such as:
Now you are ready to build the library:
will compile the shared (e.g. lib/libsupernovas.so) libraries, and compile the API documentation (into doc/) using doxygen (if available). Alternatively, you can build select components of the above with the make targets shared, and local-dox respectively. And, if unsure, you can always call make help to see what build targets are available.
To build SuperNOVAS as static libraries, use make static.
After building the library you can install the above components to the desired locations on your system. For a system-wide install you may simply run:
Or, to install in some other locations, you may set a prefix and/or DESTDIR. For example, to install under /opt instead, you can:
Or, to stage the installation (to /usr) under a 'build root':
As of v1.5, SuperNOVAS can be built using CMake (many thanks to Kiran Shila). CMake allows for greater portability than the regular GNU Makefile. Note, however, that the CMake configuration does not support all of the build options of the GNU Makefile, such as automatic CALCEPH and CSPICE integration on Linux, supporting legacy NOVAS C style builds, and code coverage tracking.
The basic build recipe for CMake is:
The SuperNOVAS CMake build supports the following options (in addition to the standard CMake options):
For example, to build SuperNOVAS as shared libraries with CALCEPH integration for ephemeris support:
If a CMAKE_BUILD_TYPE is not set, the build will only use the CFLAGS (if any) that were set in the environment. This is ideal for those who want to have full control of the compiler flags used in the build. Specifying Release or Debug will append a particular set of appropriate compiler options which are suited for the given build type. (If you want to use the MinGW compiler on Windows, you'll want to set -DCMAKE_C_COMPILER=gcc -G "MinGW Makefiles" options also.)
After a successful build, you can install the Runtime (libraries), and Development (headers, CMake config, and pkg-config) components, e.g. under /usr/local, as:
Or, on Windows (Microsoft Visual C) you will want:
After the build, you can install the SuperNOVAS files in the location of choice (e.g. /usr/local):
You can also use the --component option to install just the selected components. For example to install just the Runtime component:
As of version 1.5, SuperNOVAS is available through the vcpkg registry. The vcpkg port supports a wide range of platforms, including Linux, Windows, MacOS, and Android – for both arm64 and x64 architectures (and in case of Windows also x86). It is effectively the same as the CMake build (above), only with more simplicity, convenience, and dependency resolution.
You can install the core SuperNOVAS library with vcpkg as:
Or, including the solsys-calceph plugin library as:
The latter will also install the calceph library dependency, as needed.
SuperNOVAS is packaged for both Debian and Fedora / EPEL Linux distributions, and derivatives based on these (Ubuntu, Mint, RHEL, CentOS Stream, Alma Linux, Rocky Linux, Oracle Linux etc.).
On Debian-based platforms you might install all components via:
And or Fedora / EPEL based distributions as:
In both cases the first package is the runtime library, the second is the runtime library for the solsys-calceph plugin, the third is documentation, and the last one is the files needed for application development.
As of version 1.5, there is also a Homebrew package through the maintainer's own Tap, which includes the solsys-calceph plugin by default.
As of version 1.5, there is also a Nix package. This declarative and determinstic package manager can be used on every linux distribution as well as MacOS. The default package includes the solsys-calceph plugin, but can be overriden by changing withCalceph.
Install to your profile with
Or include in your Nix build of other software with
There are a number of ways you can build your application with SuperNOVAS. See which of the options suits your needs best:
Provided you have installed the SuperNOVAS headers and (static or shared) libraries into a standard location, you can build your application against it easily. For example, to build myastroapp.c against SuperNOVAS, you might have a Makefile with contents like:
If you have a legacy NOVAS C 3.1 application, it is possible that the compilation will give you errors due to missing includes for stdio.h, stdlib.h, ctype.h or string.h, because these headers were implicitly included with novas.h in NOVAS C 3.1, but not in SuperNOVAS (at least not by default), as a matter of best practice. If this is a problem for you can 'fix' it in one of two ways: (1) Add the missing #include directives to your application source explicitly, or if that's not an option for you, then (2) set the -DCOMPAT compiler flag when compiling your application:
If your application uses optional planet or ephemeris calculator modules, you may need to specify the additional shared libraries also:
Add the appropriate bits from below to the CMakeLists.txt file of your application (my-application):
SuperNOVAS began deprecating some NOVAS C functions, either because they are no longer needed; or are not easy to use and have better alternatives around; or are internals that should never have been exposed to end-users. The deprecations are marked in the inline and HTML API documentations, suggesting also alternatives.
That said, the deprecated parts of the API are NOT removed, nor we plan on removing them for the foreseeable future. Instead, they serve as a gentle reminder to users that perhaps they should stay away from these features for their own good.
However, you have the option to force yourself to avoid using the deprecated API if you choose to do so, by compiling your application with -D_EXCLUDE_DEPRECATED, or equivalently, by defining _EXCLUDE_DEPRECATED in your source code before including novas.h. E.g.:
After that, your compiler will complain if your source code references any of the deprecated entities, so you may change that part of your code to use the recommended alternatives instead.
The NOVAS C way to handle planet or other ephemeris functions was to link particular modules to provide the solarsystem() / solarsystem_hp() and readeph() functions. This approach is discouraged in SuperNOVAS, with preference for selecting implementations at runtime. The old, deprecated way, of incorporating Solar-system data is supported, nevertheless, for legacy applications with some caveats.
To use your own existing default solarsystem() implementation in the NOVAS C way, you will have to build SuperNOVAS with SOLSYS_SOURCE set to the source file(s) of the implementation (config.mk or the environment).
The same principle applies to using your specific legacy readeph() implementation, except that you must set READEPH_SOURCE to the source file(s) of the chosen implementation when building SuperNOVAS).
(You might have to also add additional include directories to CPPFLAGS, e.g. -I/my-path/include for you custom sources for their associated headers).
A better way to recycle your old planet and ephemeris calculator modules may be to rename solarsystem() / solarsystem_hp() functions therein to e.g. my_planet_calculator() / my_planet_calculator_hp() and then in your application can specify these functions as the provider at runtime.
E.g.:
(You might also change the short type parameters to the SuperNOVAS enum types while at it, to conform to the SuperNOVAS novas_planet_provider / novas_planet_provider_hp types.)
For readeph() implementations, it is recommended that you change both the name and the footprint to e.g.:
and then then apply it in your application as:
While all of that requires some minimal changes to your old code, the advantage of this preferred approach is that you do not need to re-build the library with USER_SOLSYS and/or USER_READEPH defined.
|
|---|
| Figure 1. SuperNOVAS Coordinate Systems and Conversions. Functions indicated in bold face are available in NOVAS C also. All other functions are available in SuperNOVAS only. SuperNOVAS also adds efficient matrix transformations between the equatorial systems. |
The IAU 2000 and 2006 resolutions have completely overhauled the system of astronomical coordinate transformations to enable higher precision astrometry. (Super)NOVAS supports coordinate calculations both in the old (pre IAU 2000) ways, and in the new IAU standard method. The table below provides an overview of how the old and new methods define some of the terms differently:
| Concept | Old standard | New IAU standard |
|---|---|---|
| Catalog coordinate system | MOD (e.g. FK4, FK5, HIP...) | International Celestial Reference System (ICRS) |
| Dynamical system | True of Date (TOD) | Celestial Intermediate Reference System (CIRS) |
| Dynamical R.A. origin | equinox of date | Celestial Intermediate Origin (CIO) |
| Precession, nutation, bias | no tidal terms | IAU 2000/2006 precession/nutation model |
| Celestial Pole offsets | dψ, dε (for TOD) | xp, yp (for ITRS) |
| Earth rotation measure | Greenwich Sidereal Time (GST) | Earth Rotation Angle (ERA) |
| Pseudo Earth-fixed system | PEF | Terrestrial Intermediate Reference System (TIRS) |
| Earth rotation origin | Greenwich Meridian | Terrestrial Intermediate Origin (TIO) |
| Earth-fixed System | WGS84 | International Terrestrial Reference System (ITRS) |
See the various enums and constants defined in novas.h, as well as the descriptions on the various NOVAS routines on how they are appropriate for the old and new methodologies respectively. Figure 1 also shows the relation of the various old and new coordinate systems and the (Super)NOVAS functions for converting position / velocity vectors among them.
SuperNOVAS v1.1 has introduced a new, more intuitive, more elegant, and more efficient approach for calculating astrometric positions of celestial objects. The guide below is geared towards this new method. However, the original NOVAS C approach remains viable also (albeit often less efficient).
A sidereal source may be anything beyond the Solar system with 'fixed' catalog coordinates. It may be a star, or a galactic molecular cloud, or a distant quasar.
First, you must provide the astrometric parameters (coordinates, and optionally radial velocity or redshift, proper motion, and/or parallax or distance also). Let's assume we pick a star for which we have B1950 (i.e. FK4) coordinates. We begin with the assigned name and the R.A. / Dec coordinates.
If you have coordinates as strings in decimal or HMS / DMS format, you might use novas_str_hours() and/or novas_str_degrees() to convert them to hours/degrees for novas_init_cat_entry(), with a fair bit of flexibility on the particulars of the representation, e.g.:
Next, if it's a star or some other source within our own Galaxy, you'll want to specify its proper motion (in the same reference system as the above coordinates), so we can calculate its position for the epoch of observation.
For Galactic sources you will also want to set the parallax using novas_set_parallax() or equivalently the distance (in parsecs) using novas_set_distance(), e.g.:
Finally, for spectroscopic applications you will also want to set the radial velocity. You can use novas_set_ssb_vel() if you have standard radial velocities defined with respect to the Solar System Barycenter; or novas_set_lsr_vel() if the velocity is relative to the Local Standard of Rest (LSR); or else novas_set_redshift() if you have a redshift measure (as is typical for distant galaxies and quasars). E.g.:
Alternatively, if you prefer, you may use the original NOVAS C make_cat_entry() to set the astrometric parameters above all at once.
E.g.:
Next, we wrap that catalog source into a generic celestial object structure. (An object handles various Solar-system sources also, as you'll see further below). Whereas the catalog source may have been defined in any epoch / catalog system, the object structure shall define ICRS coordinates always (no exceptions):
Alternatively, for high-_z_ sources you might simply use the 1-step make_redshifted_object_sys() e.g.:
Next, we define the location where we observe from. Let's assume we have a GPS location:
Again you might use novas_str_degrees() for typical string representations of the longitude and latitude coordinates here, such as:
Alternatively, you can also specify airborne observers, or observers in Earth orbit, in heliocentric orbit, at the geocenter, or at the Solar-system barycenter. The above also sets default, mean annual weather parameters based on the location and a global model based on Feulner et al. (2013).
You can, of course, set actual weather values after, as appropriate, if you need them for the refraction models, e.g.:
Next, we set the time of observation. For a ground-based observer, you will need to provide SuperNOVAS with the UT1 - UTC time difference (a.k.a. DUT1), and the current leap seconds. You can obtain suitable values for DUT1 from IERS, and for the highest precision, interpolate for the time of observations. For the example, let's assume 37 leap seconds, and DUT1 = 0.042,
Then we can set the time of observation, for example, using the current UNIX time:
Alternatively, you may set the time as a Julian date in the time measure of choice (UTC, UT1, TT, TDB, GPS, TAI, TCG, or TCB):
or, for the best precision we may do the same with an integer / fractional split:
Or, you might use string dates, such as an ISO timestamp:
Note, that the likes of novas_set_time() will automatically apply diurnal corrections to the supplied UT1-UTC time difference for libration and ocean tides. Thus, the supplied values should not include these. Rather you should pass dut1 directly (or interpolated) from the IERS Bulletin values for the time of observation.
Next, we set up an observing frame, which is defined for a unique combination of the observer location and the time of observation:
Here xp and yp are small (sub-arcsec level) corrections to Earth orientation. Values for these are are published in the IERS Bulletins. These values should be interpolated for the time of observation, but should NOT be corrected for libration and ocean tides (novas_make_frame() will apply such corrections as appropriate for full accuracy frames). The Earth orientation parameters (EOP) are needed only when converting positions from the celestial CIRS (or TOD) frame to the Earth-fixed ITRS (or PEF) frames. You may ignore these and set zeroes if not interested in Earth-fixed calculations or if sub-arcsecond precision is not required.
The advantage of using the observing frame, is that it enables very fast position calculations for multiple objects in that frame (see the benchmarks), since all sources in a frame have well-defined, fixed, topological positions on the celestial sphere. It is only a matter of expressing these positions as coordinates (and velocities) in a particular coordinate system. So, if you need to calculate positions for thousands of sources for the same observer and time, it will be significantly faster than using the low-level NOVAS C routines instead. You can create derivative frames for different observer locations, if need be, via novas_change_observer().
Now we can calculate the apparent R.A. and declination for our source, which includes proper motion (for sidereal sources) or light-time correction (for Solar-system bodies), and also aberration corrections for the moving observer and gravitational deflection around the major Solar System bodies (in full accuracy mode). You can calculate an apparent location in the coordinate system of choice (ICRS/GCRS, CIRS, J2000, MOD, TOD, TIRS, or ITRS) using novas_sky_pos(). E.g.:
Apart from providing precise apparent R.A. and declination coordinates, the sky_pos structure also provides the x,y,z unit vector pointing in the observed direction of the source (in the designated coordinate system). We also get radial velocity (for spectroscopy), and apparent distance for Solar-system bodies (e.g. for apparent-to-physical size conversion).
If your ultimate goal is to calculate the azimuth and elevation angles of the source at the specified observing location, you can proceed from the sky_pos data you obtained above (in whichever coordinate system!):
Above we converted the apparent coordinates, that were calculated in CIRS, to refracted azimuth and elevation coordinates at the observing location, using the novas_standard_refraction() function to provide a suitable refraction correction. We could have used novas_optical_refraction() instead to use the weather data embedded in the frame's observer structure, or some user-defined refraction model, or else NULL to calculate unrefracted elevation angles.
Of course, SuperNOVAS allows you to go in reverse, for example from an observed Az/El position all the way to proper ICRS R.A./Dec coordinates.
E.g.:
Voila! And, of course you might want the coordinates in some other reference systems, such as B1950. For that you can simply add a transformation before vector2radec() above, e.g. as:
You may be interested to know when sources rise above or set below some specific elevation angle, or at what time they appear to transit at the observer location. SuperNOVAS has routines to help you with that too.
Given that rise, set, or transit times are dependent on the day of observation, and observer location, they are effectively tied to an observer frame.
Note, that in the current implementation these calls are not well suited sources that are at or within the geostationary orbit, such as such as Low Earth Orbit satellites (LEOs), geostationary satellites (which never really rise, set, or transit), or some Near Earth Objects (NEOs), which will rise set multiple times per day. For the latter, the above calls may still return a valid time, only without the guarantee that it is the time of the first such event after the specified frame instant. A future implementation may address near-Earth orbits better, so stay tuned for updates.
Solar-system sources work similarly to the above with a few important differences at the start.
Historically, NOVAS divided Solar-system objects into two categories: (1) major planets (including also the Sun, the Moon, and the Solar-system Barycenter); and (2) 'ephemeris' type objects, which are all other Solar-system objects. The main difference is the numbering convention. NOVAS major planets have definitive ID numbers (see enum novas_planet), whereas 'ephemeris' objects have user-defined IDs. They are also handled by two separate adapter functions (although SuperNOVAS has the option of using the same ephemeris provider for both types of objects also).
Thus, instead of make_cat_object() you define your source as a planet or ephemeris type object with a name or ID number that is used by the ephemeris service you provided. For major planets you might want to use make_planet(), if they use a novas_planet_provider function to access ephemeris data with their NOVAS IDs, or else make_ephem_object() for more generic ephemeris handling via a user-provided novas_ephem_provider. E.g.:
And then, it's the same spiel as before, e.g.:
As of version 1.2 you can also define solar system sources with Keplerian orbital elements (such as the most up-to-date ones provided by the Minor Planet Center for asteroids, comets, etc.):
Finally, as of version 1.4, you might generate approximate (arcmin-level) orbitals for the major planets (but not Earth!), the Moon, and the Earth-Moon Barycenter (EMB) also. E.g.:
While the planet and Moon orbitals are not suitable for precision applications, they can be useful for determining approximate positions (e.g. via the novas_approx_heliocentric() and novas_approx_sky_pos() functions), and for rise/set time calculations.
SuperNOVAS introduces matrix transforms (correctly since version 1.4), which can take a position or velocity vector (geometric or apparent), obtained for an observer frame, from one coordinate system to another efficiently. E.g.:
Transformations support all SupeNOVAS reference systems, that is ICRS/GCRS, J2000, TOD, MOD, CIRS, TIRS, and ITRS. The same transform can also be used to convert apparent positions in a sky_pos structure also, e.g.:
If you want to use SuperNOVAS to calculate positions for a range of Solar-system objects, and/or to do it with precision, you will have to interface it to a suitable provider of ephemeris data. The preferred ways to do that in SuperNOVAS enumerated below. (The legacy NOVAS C ways are not covered here, since they require specialized builds of SuperNOVAS, which are covered further above.)
NASA/JPL provides generic ephemerides for the major planets, satellites thereof, the 300 largest asteroids, the Lagrange points, and some Earth orbiting stations. For example, DE440 covers the major planets, and the Sun, Moon, and barycenters for times between 1550 AD and 2650 AD. Or, you can use the JPL HORIZONS system (via the command-line / telnet or API interfaces) to generate custom ephemerides (SPK/BSP) for just about all known solar systems bodies, down to the tiniest rocks.
The CALCEPH library provides easy-to-use access to JPL and INPOP ephemeris files from C/C++. As of version 1.2, we provide optional support for interfacing SuperNOVAS with the the CALCEPH C library for handling Solar-system objects.
Prior to building SuperNOVAS simply set CALCEPH_SUPPORT to 1 in config.mk or in your environment (or for CMake configure with the -DENABLE_CALCEPH=ON). Depending on the build target (or type), it will build libsolsys-calceph.so[.1] (target shared or CMake option -DBUILD_SHARED_LIBS=ON1) or libsolsys-calceph.a (target static or default CMake build) libraries or solsys-calceph.o (target solsys, no CMake equivalent), which provide the novas_use_calceph() and novas_use_calceph_planets(), and novas_calceph_use_ids() functions.
Of course, you will need access to the CALCEPH C development files (C headers and unversioned libcalceph.so or .a library) for the build to succeed. Here is an example on how you'd use CALCEPH with SuperNOVAS in your application code:
All modern JPL (SPK) ephemeris files should work with the solsys-calceph plugin. When linking your application, add -lsolsys-calceph to your link flags (or else link with solsys-calceph.o), and link against the CALCEPH library also (-lcalceph). That's all there is to it.
When using CALCEPH, ephemeris objects are referenced by their ID numbers (object.number), unless it is set to -1, in which case name-based lookup will be used instead. ID numbers are assumed to be NAIF by default, but novas_calceph_use_ids() can select between NAIF or CALCEPH numbering systems, if necessary.
The NAIF CSPICE Toolkit is the canonical standard library for JPL ephemeris files from C/C++. As of version 1.2, we provide optional support for interfacing SuperNOVAS with CSPICE for handling Solar-system objects.
Prior to building SuperNOVAS simply set CSPICE_SUPPORT to 1 in config.mk or in your environment (or for CMake configure with -DENABLE_CSPICE=ON). Depending on the build target, it will build libsolsys-cspice.so[.1] (target shared or CMake option -DBUILD_SHARED_LIBS=ON) or libsolsys-cspice.a (target static or default CMake build) libraries or solsys-cspice.o (target solsys, no CMake equivalent), which provide the novas_use_cspice(), novas_use_cspice_planets(), and novas_use_cspice_ephem() functions to enable CSPICE for providing data for all Solar-system sources, or for major planets only, or for other bodies only, respectively. You can also manage the active kernels with the cspice_add_kernel() and cspice_remove_kernel() functions.
Of course, you will need access to the CSPICE development files (C headers, installed under a cspice/ directory of an header search location, and the unversioned libcspice.so or .a library) for the build to succeed. You may want to check out the Smithsonian/cspice-sharedlib GitHub repository to help you build CSPICE with shared libraries and dynamically linked tools.
Here is an example on how you might use CSPICE with SuperNOVAS in your application code:
All JPL ephemeris data will work with the solsys-cspice plugin. When linking your application, add -lsolsys-cspice to your link flags (or else link with solsys-cspice.o), and of course the CSPICE library also. That's all there is to it.
When using CSPICE, ephemeris objects are referenced by their NAIF ID numbers (object.number), unless that number is set to -1, in which case name-based lookup will be used instead.
Possibly the most universal way to integrate ephemeris data with SuperNOVAS is to write your own novas_ephem_provider function.
which takes an object ID number (such as a NAIF), an object name, and a split TDB date (for precision) as it inputs, and returns the type of origin with corresponding ICRS position and velocity vectors in the supplied pointer locations. The function can use either the ID number or the name to identify the object or file (whatever is the most appropriate for the implementation and for the supplied parameters). The positions and velocities may be returned either relative to the SSB or relative to the heliocenter, and accordingly, your function should set the value pointed at by origin to NOVAS_BARYCENTER or NOVAS_HELIOCENTER accordingly. Positions and velocities are rectangular ICRS x,y,z vectors in units of AU and AU/day respectively.
This way you can easily integrate current ephemeris data, e.g. for the Minor Planet Center (MPC), or whatever other ephemeris service you prefer.
Once you have your adapter function, you can set it as your ephemeris service via set_ephem_provider():
By default, your custom my_ephem_reader function will be used for NOVAS_EPHEM_OBJECT type objects only (i.e. anything other than the major planets, the Sun, Moon, Solar-system Barycenter...). But, you can use the same function for the major planets (NOVAS_PLANET type objects) also via:
The above simply instructs SuperNOVAS to use the same ephemeris provider function for planets as what was set for NOVAS_EPHEM_OBJECT type objects, provided you compiled SuperNOVAS with BUILTIN_SOLSYS_EPHEM = 1 (in config.mk), or else you link your code against solsys-ephem.c explicitly. Easy-peasy.
Many of the (Super)NOVAS functions take an accuracy argument, which determines to what level of precision quantities are calculated. The argument can have one of two values, which correspond to typical precisions around:
| enum novas_accuracy value | Typical precision |
|---|---|
| NOVAS_REDUCED_ACCURACY | ~ 1 milli-arcsecond (mas) |
| NOVAS_FULL_ACCURACY | ~ 1 micro-arcsecond (μas) |
Note, that some functions will not support full accuracy calculations, unless you have provided a high-precision ephemeris provider for the major planets (and any Solar-system bodies of interest), which does not come with SuperNOVAS out of the box. In the absence of a suitable high-precision ephemeris provider, some functions might return an error if called with NOVAS_FULL_ACCURACY. (Click on the wedges next to each component to expand the details...)
The SuperNOVAS library is in principle capable of calculating positions to microarcsecond, and velocities to mm/s, precision for all types of celestial sources. However, there are certain prerequisites and practical considerations before that level of accuracy is reached. (Click on the wedge next to each heading below to expand the details.)
High precision calculations will generally require that you use SuperNOVAS with the new IAU standard quantities and methods. The old ways were simply not suited for precision much below the milliarcsecond level. In particular, Earth orientation parameters (EOP) should be applied only for converting between TIRS and ITRS systems, and defined either with novas_make_frame() or else with wobble(). The old ways of incorporating (global) offsets in TOD coordinates via cel_pole() should be avoided.
Calculations much below the milliarcsecond level will require accounting for gravitational bending around massive Solar-system bodies, and hence will require you to provide a high-precision ephemeris provider for the major planets. Without it, there is no guarantee of achieving precision below the milli-arcsecond level in general, especially when observing near the Sun or massive planets (e.g. observing Jupiter's or Saturn's moons, near conjunction with their host planet). Therefore, some functions will return with an error, if used with NOVAS_FULL_ACCURACY in the absence of a suitable high-precision planetary ephemeris provider.
Precise calculations for Solar-system sources requires precise ephemeris data for both the target object as well as for Earth, and the Sun. For the highest precision calculations you also need positions for all major planets to calculate gravitational deflection precisely. By default, SuperNOVAS can only provide approximate positions for the Earth and Sun (see earth_sun_calc()) at the tens of arcsecond level. You will need to provide a way to interface SuperNOVAS with a suitable ephemeris source (such as CALCEPH, or the CSPICE toolkit from JPL) to obtain precise positions for Solar-system bodies. See the section further above for more information how you can do that.
Calculating precise positions for any Earth-based observations requires precise knowledge of Earth orientation parameters (EOP) at the time of observation. Earth's pole is subject to predictable precession and nutation, but also small irregular and diurnal variations in the orientation of the rotational axis and the rotation period (a.k.a. polar wobble). You can apply the EOP values in novas_set_time() (for UT1-UTC), and novas_make_frame() (xp and yp) to improve the astrometric precision of Earth based coordinate calculations. Without the EOP values, positions for Earth-based calculations will be accurate at the tenths of arcsecond level only.
The IERS Bulletins provide up-to-date measurements, historical data, and near-term projections for the polar offsets, the UT1-UTC time difference, and leap-seconds (UTC-TAI). For sub-milliarcsecond accuracy the values published by IERS should be interpolated before passing to the likes of novas_set_time() or novas_make_frame(). At the micro-arcsecond (μas) level, you will need to ensure also that the EOP values are provided for the same ITRF realization as the observer's location, e.g. via novas_itrf_transform_eop().
Ground based observations are subject to atmospheric refraction. SuperNOVAS offers the option to include refraction corrections with a set of atmospheric models. Estimating refraction accurately requires local weather parameters (pressure, temperature, and humidity), which may be be specified within the on_surface data structure alongside the observer location. A standard radio refraction model is included as of version 1.1, as well as our implementation of the wavelength-dependent IAU refraction model (novas_wave_refraction() since version 1.4) based on the SOFA iauRefco() function. If none of the supplied options satisfies your needs, you may also implement your own refraction correction to use.
When including SuperNOVAS (C90) headers in your C++ source files, it is necessary to reconcile the different C and C++ namespaces. Therefore, you will have to include the SuperNOVAS headers inside an extern "C" {} block in your source code, such as:
The above is the standard way to include C headers in C++ sources, in general.
When one does not need positions at the microarcsecond level, some shortcuts can be made to the recipe above:
Some of the calculations involved can be expensive from a computational perspective. For the most typical use case however, NOVAS (and SuperNOVAS) has a trick up its sleeve: it caches the last result of intensive calculations so they may be re-used if the call is made with the same environmental parameters again (such as JD time and accuracy).
A direct consequence of the caching of results is that calculations are generally not thread-safe as implemented by the original NOVAS C 3.1 library. One thread may be in the process of returning cached values for one set of input parameters while, at the same time, another thread is saving cached values for a different set of parameters. Thus, when running calculations in more than one thread, the NOVAS C results returned may at times be incorrect, or more precisely they may not correspond to the requested input parameters.
While you should never call the original NOVAS C library from multiple threads simultaneously, SuperNOVAS caches the results in thread local variables (provided your compiler supports it), and is therefore generally safe to use in multi-threaded applications. Just make sure that you:
The NOVAS API has been using conventional units (e.g. AU, km, day, deg, h) typically for its parameters and return values alike. Hence, SuperNOVAS follows the same conventions for its added functions and data structures also. However, when interfacing SuperNOVAS with other programs, libraries, or data files, it is often necessary to use quantities that are expressed in different units, such as SI or CGS. To facilitate such conversions, novas.h provides a set of unit constants, which can be used for converting to/from SI units (and radians).
For example, novas.h contains the following definitions:
You can use these, for example, to convert quantities expressed in conventional units for NOVAS to standard (SI) values, by multiplying NOVAS quantities with the corresponding unit definition. E.g.:
And vice-versa: to convert values expressed in standard (SI) units, you can divide by the appropriate constant to 'cast' an SI value into the particular physical unit, e.g.:
Finally, you can combine them to convert between two different conventional units, e.g.:
SuperNOVAS functions typically input and output times and angles as decimal values (hours and degrees, but also as days and hour-angles), but that is not how these are represented in many cases. Time and right-ascention are often given as string values indicating hours, minutes, and seconds (e.g. "11:32:31.595", or "11h 32m 31.595s"). Similarly angles, are commonly represented as degrees, arc-minutes, and arc-seconds (e.g. "+53 60 19.9"). For that reason, SuperNOVAS provides a set of functions to convert string values expressed in decimal or broken-down format to floating point representation. E.g.,
The conversions have a lot of flexibility. Components can be separated by spaces (as above), by colons, commas, or underscores, by the letters 'h'/'d', 'm', and 's', by single (minutes) and double quotes (seconds), or any combination thereof. Decimal values may be followed by 'h' or 'd' unit markers. Additionally, angles can end with a compass direction, such as 'N', 'E', 'S' or 'W'. So the above could also have been:
or as decimals:
Dates are typically represented broken down into year, month, and day (e.g. "2025-02-16", or "16.02.2025", or "2/16/2025"), with or without a time marker, which itself may or may not include a time zone specification. In astronomy, the most commonly used string representation of dates is with ISO 8601 timestamps. The following are all valid ISO date specifications:
SuperNOVAS provides functions to convert between ISO dates/times and their string representation for convenience. E.g.,
Other SuperNOVAS string date functions will process dates in the astronomical calendar of date by default, that is in the Gregorian calendar after the Gregorian calendar reform of 1582, or the Julian/Roman calendar for dates prior, and support timescales other than UTC also. E.g.:
Or, parse an astronomical date:
Or, parse an astronomical date, including the timescale specification:
Sometimes your input dates are represented in various other formats. You can have additional flexibility for parsing dates using the novas_parse_date_format() and novas_timescale_for_string() functions.
E.g.,
You can enable or disable debugging output to stderr with novas_debug(enum novas_debug_mode), where the argument is one of the defined constants from novas.h:
| novas_debug_mode value | Description |
|---|---|
| NOVAS_DEBUG_OFF | No debugging output (default) |
| NOVAS_DEBUG_ON | Prints error messages and traces to stderr |
| NOVAS_DEBUG_EXTRA | Same as above but with stricter error checking |
The main difference between NOVAS_DEBUG_ON and NOVAS_DEBUG_EXTRA is that the latter will treat minor issues as errors also, while the former may ignore them. For example, place() will return normally by default if it cannot calculate gravitational bending around massive planets in full accuracy mode. It is unlikely that this omission would significantly alter the result in most cases, except for some very specific ones when observing in a direction close to a major planet. Thus, with NOVAS_DEBUG_ON, place() go about as usual even if the Jupiter's position is not known. However, NOVAS_DEBUG_EXTRA will not give it a free pass, and will make place() return an error (and print the trace) if it cannot properly account for gravitational bending around the major planets as it is expected to.
When debug mode is enabled, any error condition (such as NULL pointer arguments, or invalid input values etc.) will be reported to the standard error, complete with call tracing within the SuperNOVAS library, s.t. users can have a better idea of what exactly did not go to plan (and where). The debug messages can be disabled by passing NOVAS_DEBUG_OFF (0) as the argument to the same call. Here is an example error trace when your application calls grav_def() with NOVAS_FULL_ACCURACY while solsys3 provides Earth and Sun positions only and when debug mode is NOVAS_DEBUG_EXTRA (otherwise we'll ignore that we skipped the almost always negligible deflection due to planets):
To get an idea of the speed of SuperNOVAS, you can use make benchmark on your machine. Figure 2 below summarizes the single-threaded results obtained on an AMD Ryzen 5 PRO 6650U laptop. While this is clearly not the state of the art for today's server class machines, it nevertheless gives you a ballpark idea for how a typical, not so new, run-of-the-mill PC might perform.
|
|---|
| Figure 2. SuperNOVAS apparent position calculation benchmarks, including proper motion, the IAU 2000 precession-nutation model, polar wobble, aberration, and gravitational deflection corrections, and precise spectroscopic redhift calculations. |
The tests calculate apparent positions (in CIRS) for a set of sidereal sources with random parameters, using either the SuperNOVAS novas_sky_pos() or the legacy NOVAS C place(), both in full accuracy and reduced accuracy modes. The two methods are equivalent, and both include calculating a precise geometric position, as well as aberration and gravitational deflection corrections from the observer's point of view.
| Description | accuracy | positions / sec |
|---|---|---|
| novas_sky_pos(), same frame | reduced | 3130414 |
| full | 3149028 | |
| place(), same time, same observer | reduced | 831101 |
| full | 831015 | |
| novas_sky_pos(), individual | reduced | 168212 |
| full | 25943 | |
| place(), individual | reduced | 167475 |
| full | 25537 |
For reference, we also provide the reduced accuracy benchmarks from NOVAS C 3.1.
| Description | accuracy | positions / sec |
|---|---|---|
| NOVAS C 3.1 place(), same frame | reduced | 371164 |
| NOVAS C 3.1 place(), individual | reduced | 55484 |
For comparison, a very similar benchmark with astropy (v7.0.0 on Python v3.13.1) on the same machine, provides ~70 positions / second both for a fixed frame and for individual frames. As such, SuperNOVAS is a whopping ~40000 times faster than astropy for calculations in the same observing frame, and 400–2000 times faster than astropy for individual frames. (The astropy benchmarking code is also provided under the benchmark/ folder in the SuperNOVAS GitHub repository).
| Description | positions / sec |
|---|---|
| astropy 7.0.0 (python 3.13.1), same frame | 71 |
| astropy 7.0.0 (python 3.13.1), individual | 70 |
As one may observe, the SuperNOVAS novas_sky_pos() significantly outperforms the legacy place() function, when repeatedly calculating positions for sources for the same instant of time and same observer location, providing 3–4 times faster performance than place() with the same observer and time. The same performance is maintained even when cycling through different frames, a usage scenario in which the performance advantage over place() may be up to 2 orders of magnitude. When observing frames are reused, the performance is essentially independent of the accuracy. By contrast, calculations for individual observing times or observer locations are generally around twice a fast if reduced accuracy is sufficient.
The above benchmarks are all for single-threaded performance. Since SuperNOVAS is generally thread-safe, you can expect that performance shall scale with the number of concurrent CPUs used. So, on a 16-core PC, with similar single core performance, you could calculate up to 32 million precise positions per second, if you wanted to. To put that into perspective, you could calculate precise apparent positions for the entire Gaia dataset (1.7 billion stars) in under one minute.
Below is a non-exhaustive overview new features added by SuperNOVAS on top of the existing NOVAS C API. See CHANGELOG.md for more details.
Many SuperNOVAS changes are focused on improving the usability and promote best coding practices so that both the library and your application will be safer, and less prone to nagging errors. Below is a detailed listing of the principal ways SuperNOVAS has improved on the original NOVAS C library in these respects.
A predictable release schedule and process can help manage expectations and reduce stress on adopters and developers alike.
SuperNOVAS will try to follow a quarterly release schedule. You may expect upcoming releases to be published around February 1, May 1, August 1, and/or November 1 each year, on an as-needed basis. That means that if there are outstanding bugs, or new pull requests (PRs), you may expect a release that addresses these in the upcoming quarter. The dates are placeholders only, with no guarantee that a new release will actually be available every quarter. If nothing of note comes up, a potential release date may pass without a release being published.
New features are generally reserved for the feature releases (e.g. 1.x.0 version bumps), although they may also be rolled out in bug-fix releases as long as they do not affect the existing API – in line with the desire to keep bug-fix releases fully backwards compatible with their parent versions.
In the weeks and month(s) preceding releases one or more release candidates (e.g. 1.0.1-rc3) will be published temporarily on GitHub, under Releases, so that changes can be tested by adopters before the releases are finalized. Please use due diligence to test such release candidates with your code when they become available to avoid unexpected surprises when the finalized release is published. Release candidates are typically available for one week only before they are superseded either by another, or by the finalized release.
Copyright (C) 2025 Attila Kovács