Source code for pynbody.snapshot.namemapper
"""Tool for mapping from individual simulation code's naming conventions to pynbody naming conventions.
This module is a detail that is not intended to be used directly by end users. It is used by the snapshot classes to
map between the names of arrays in different simulation codes.
"""
import configparser
import sys
from .. import config_parser
[docs]
def setup_name_maps(config_name, gadget_blocks=False, with_alternates=False):
name_map = {}
rev_name_map = {}
alternates = {}
try:
for a, b in config_parser.items(config_name):
if gadget_blocks:
a = a.upper().ljust(4).encode("utf-8")
b_alternates = alternates.get(b, [])
b_alternates.append(a)
alternates[b] = b_alternates
rev_name_map[a] = b
name_map[b] = a
except configparser.NoOptionError:
pass
if with_alternates:
return name_map, rev_name_map, alternates
else:
return name_map, rev_name_map
[docs]
def name_map_function(name_map, rev_name_map):
def _translate_array_name(name, reverse=False):
try:
if reverse:
return rev_name_map[name]
else:
return name_map[name]
except KeyError:
return name
return _translate_array_name
[docs]
class AdaptiveNameMapper:
"""A class to map between the names of arrays in different simulation codes.
This class is designed to be used in a context where the names of arrays in a simulation code (a 'format name') may
not be fully known in advance. For example, we might be unsure whether pynbody's ``pos`` array corresponds to the
format name ``Coordinates`` or ``Position`` in a given simulation snapshot. This class allows us possible different
mappings to be given in the configuration, and then once one of the mappings is used, it is locked in for the
duration of the object's lifetime.
One may wish to set return_all_format_names to True to just get all possible format names for a given pynbody name.
"""
[docs]
def __init__(self, config_name, gadget_blocks=False, return_all_format_names=False):
"""Create a new AdaptiveNameMapper object.
Parameters
----------
config_name : str
The name of the section in the configuration file to use for the mapping.
gadget_blocks : bool
If True, the mapping is case-insensitive and pads the names to 4 characters, as is the convention for GADGET
block names.
return_all_format_names : bool
If True, return all the possible format-specific names for a pynbody name, rather than only the most
recently accessed (which is the default behaviour).
Notes
-----
Generally, return_all_format_names = False is the obvious choice, where we don't know in advance the name of the
array in the simulation snapshot, but we know there will be only one such name. However, swift uses a different
name for the masses of its black holes (``SubgridMasses``) than for the masses of its other particles
(``Masses``), so in this case we need to set ``allow_ambiguous = True``. There may be other cases where this
is necessary.
"""
self._pynbody_to_format_map, self._format_to_pynbody_map, self._pynbody_to_all_format_map = setup_name_maps(config_name, gadget_blocks, True)
self._return_all_format_names = return_all_format_names
def _select_alternate_target_if_required(self, name):
if name not in list(self._pynbody_to_format_map.values()):
for k, v in self._pynbody_to_all_format_map.items():
if name in v:
self._pynbody_to_format_map[k] = name
def __call__(self, name, reverse=False):
"""Map a pynbody name to a format name.
If reverse=True, the mapping is done in the reverse direction (i.e. from format name to pynbody name).
If return_all_format_names=True, when reverse=False, this function returns a list of all possible format names
"""
result_if_no_match = name
if reverse:
self._select_alternate_target_if_required(name)
current_map = self._format_to_pynbody_map
else:
if self._return_all_format_names:
current_map = self._pynbody_to_all_format_map
result_if_no_match = [name]
else:
current_map = self._pynbody_to_format_map
return current_map.get(name, result_if_no_match)