SPICE metakernel#

A metakernel is a configuration text-kernel with a required KERNELS_TO_LOAD key containing a list of kernel files to load in the SPICE pool. Most of the time, the exact location the kernels are abbreviated with a PATH_SYMBOLS and its PATH_VALUES keys. The values are usually hard coded in the file to a relative or an absolute path.

  PATH_VALUES       = ( '.' )

  PATH_SYMBOLS      = ( 'KERNELS' )

  KERNELS_TO_LOAD   = (

    '$KERNELS/lsk/naif0012.tls'
    '$KERNELS/pck/pck00010.tpc'
    ...

  )

Ready the metakernel#

The MetaKernel object allows the user to parse these file and if needed can change the PATH_VALUES at runtime:

Hint

You can provide any valid path:

MetaKernel(
  'metakernel.tm',
  kernels='/data/kernels/'
)
from planetary_coverage import MetaKernel

mk = MetaKernel('metakernel.tm', kernels='kernels')
mk
<MetaKernel> metakernel.tm

In the example above, we have replace the initial value of $KERNELS (equal to '.') by a custom value which corresponds to the actual location of our kernels (ie. the current directly 'kernels/' in our case).

mk.kernels
['kernels/lsk/naif0012.tls', 'kernels/pck/pck00010.tpc']

If, at least one of the kernels listed is not available locally, the tool will throw a MissingKernel error:

MetaKernel('metakernel.tm', kernels='/invalid/path')
---------------------------------------------------------------------------
MissingKernel                             Traceback (most recent call last)
Cell In[4], line 1
----> 1 MetaKernel('metakernel.tm', kernels='/invalid/path')

File ~/checkouts/readthedocs.org/user_builds/planetary-coverage/checkouts/1.0.0/src/planetary_coverage/spice/metakernel.py:67, in MetaKernel.__init__(self, mk, download, remote, load_kernels, **kwargs)
     64 self.remote = remote
     66 if kwargs:
---> 67     self.update_path_values(**kwargs, download=download)
     68 else:
     69     self.check(download=download)

File ~/checkouts/readthedocs.org/user_builds/planetary-coverage/checkouts/1.0.0/src/planetary_coverage/spice/metakernel.py:199, in MetaKernel.update_path_values(self, download, **kwargs)
    196     index = symbols.index(symbol)
    197     self.data['PATH_VALUES'][index] = str(value)
--> 199 self.check(download=download)

File ~/checkouts/readthedocs.org/user_builds/planetary-coverage/checkouts/1.0.0/src/planetary_coverage/spice/metakernel.py:285, in MetaKernel.check(self, download)
    283 if not Path(kernel).exists():
    284     if not download:
--> 285         raise MissingKernel(kernel, remote=self.remote,
    286                             symbols=self.data.get('PATH_SYMBOLS'))
    288     if url is None:
    289         raise MissingKernelsRemote(kernel)

MissingKernel: `/invalid/path/lsk/naif0012.tls` was not found locally. You can add `download=True` to download it automatically from `https://naif.jpl.nasa.gov/pub/naif/generic_kernels/` or/and you can change the kernel path value(s) by adding `kernels='path/to/my/kernels'`.

Tip

If a remote location (https/http/ftp) is present in the content of the metakernel (in the text or in the data fields) you can add a download=True argument to download the missing kernels:

MetaKernel(
  'metakernel.tm',
  kernels='/data/kernels/',
  download=True,
)
[Download] https://naif.jpl.nasa.gov/pub/naif/generic_kernels/lsk/naif0012.tls
[Download] https://naif.jpl.nasa.gov/pub/naif/generic_kernels/pck/pck00010.tpc

You can also manually set a custom remote location (as a string) to pull your kernels from there:

MetaKernel(
  'metakernel.tm',
  kernels='/data/kernels/',
  remote='https://remote.location.tld/',
  download=True,
)
[Download] https://remote.location.tld/lsk/naif0012.tls
[Download] https://remote.location.tld/pck/pck00010.tpc

These new kernels will stored at their expected location:

  • /data/kernels/lsk/naif0012.tls

  • /data/kernels/pck/pck00010.tpc

If multiple remote urls are present in the content (for example https and ftp), by default, the tool will use the first listed (here https). If you need to use an other one, you need to provide its index (as a integer) in the remote argument:

MetaKernel(
  'metakernel.tm',
  kernels='/data/kernels/',
  remote=1,
  download=True,
)
[Download] ftp://remote.location.tld/naif0012.tls
[Download] ftp://remote.location.tld/pck00010.tpc

Load the kernels#

Similarly to TourConfig and Trajectory objects, you can load the kernels in a MetaKernel at initialization (with load_kernels=True attribute) or later on with the .load_kernels() function:

MetaKernel('metakernel.tm', kernels='kernels', load_kernels=True)
# or
mk.load_kernels()

The MetaKernel object can also be used with spiceypy.furnsh() to load its kernels list into the SPICE pool.

However, contrary to a regular metakernel, the tool will create a temporary copy of the metakernel on the fly with an edited set of PATH_VALUES. The original metakernel is kept untouched, the new file is stored in the system temporary folder. This allow the tool to redefined the location of the kernels at runtime (which was not possible with the default SPICE toolkit).

import spiceypy

spiceypy.kclear()    # Clear the kernel pool
spiceypy.furnsh(mk)  # Load the MetaKernel object

Now we can inspect the pool to see its content:

Tip

The same check can be performed even quicker with the SpicePool object (see SPICE toolbox for details).

nb_kernels_loaded = spiceypy.ktotal('ALL')
nb_kernels_loaded
3
from planetary_coverage import SpicePool

SpicePool
<SpicePool> 3 kernels loaded:
 - /tmp/metakernel-nfn4nyji.tm
 - kernels/lsk/naif0012.tls
 - kernels/pck/pck00010.tpc
for i in range(nb_kernels_loaded):
    print(spiceypy.kdata(i, 'ALL')[0])
/tmp/metakernel-nfn4nyji.tm
kernels/lsk/naif0012.tls
kernels/pck/pck00010.tpc

Caution

The temporary file, /tmp/metakernel-xxxxxxxx.tm, is automatically removed after being loaded in the pool. If you need to keep this file open (for example to inspect it), you should use the python context manager (with with syntax):

from pathlib import Path

with mk as f:
    print(f)                 # New tmp mk file
    print(Path(f).exists())  # The tmp mk exists only here

print(Path(f).exists())      # The tmp mk no longer exists
/tmp/metakernel-p67hbwol.tm
True
False

Important

Even if the name of the temporary metakernel file change each time you load it, its hash is always based on its kernels content and will remain constant, even if you load and purge the pool multiple times. Therefore, it is possible to perform a comparison between the SPICE pool content and the new content provided by the metakernel (to avoid to reload the pool if the list of the kernels to load is the same).

SpicePool.add(mk, purge=True)

SpicePool, hash(SpicePool)
(<SpicePool> 3 kernels loaded:
  - /tmp/metakernel-yjwpn0v8.tm
  - kernels/lsk/naif0012.tls
  - kernels/pck/pck00010.tpc,
 7185006361313938337)
SpicePool.add(mk, purge=True)

SpicePool, hash(SpicePool)
(<SpicePool> 3 kernels loaded:
  - /tmp/metakernel-pt6xsakl.tm
  - kernels/lsk/naif0012.tls
  - kernels/pck/pck00010.tpc,
 7185006361313938337)