Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions doc/source/analyzing/fields.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,28 @@ field, like its default units or the source code for it.
print(ds.field_info["gas", "pressure"].get_units())
print(ds.field_info["gas", "pressure"].get_source())

Defining properties for on-disk fields
--------------------------------------

Occasionally a dataset will have extra fields that are not defined in its
associated frontend. When this is the case, those fields will not be
assigned units, aliases, or a properly formatted display name. These properties
can be defined manually by calling
:meth:`~yt.data_objects.static_output.Dataset.register_field`.

.. code-block:: python

ds = yt.load("DD1234/DD1234")
ds.register_field(("enzo", "DinosaurDensity"), units="code_mass/code_length**3")

This can also be used to override properties defined by the frontend. Note, this
must be done immediately after loading the dataset (i.e., before the ``field_list``
has been populated).

To do this within a configuration file, see :ref:`per-field-config`. However, note
that the configuration file method will only operate when the field is not defined
by the frontend; it will not override.

Using fields to access data
---------------------------

Expand Down
42 changes: 42 additions & 0 deletions yt/data_objects/static_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class Dataset(abc.ABC):
_ionization_label_format = "roman_numeral"
_determined_fields: dict[str, list[FieldKey]] | None = None
fields_detected = False
_registered_fields = None

# these are set in self._parse_parameter_file()
domain_left_edge = MutableAttribute(True)
Expand Down Expand Up @@ -1716,6 +1717,47 @@ def quan(self):
self._quan = functools.partial(YTQuantity, registry=self.unit_registry)
return self._quan

def register_field(self, name, units=None, aliases=None, display_name=None):
"""
Register properties for an on-disk field.

Register or override units, aliases, or the display name for an
on-disk field.

Note, this must be called immediately after yt.load (i.e., before
the list of fields is generated).

Parameters
----------

name : str
name of the field
units : str
units of the field
aliases : list
a list of alias names
display_name: str
the name used in plots

"""
if self._instantiated_index:
raise RuntimeError(
"Cannot call register_field after field_list is populated. "
"This must be called immediately after load."
)
if self._registered_fields is None:
self._registered_fields = {}

entry = {}
if units is not None:
entry["units"] = units
if aliases is not None:
entry["aliases"] = aliases
if display_name is not None:
entry["display_name"] = display_name

self._registered_fields[name] = entry

def add_field(
self, name, function, sampling_type, *, force_override=False, **kwargs
):
Expand Down
16 changes: 16 additions & 0 deletions yt/fields/field_info_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,13 @@ def setup_particle_fields(self, ptype, ftype="gas", num_neighbors=64):
raise RuntimeError
if field[0] not in self.ds.particle_types:
continue

units = self.ds.field_units.get(field, None)
rfields = self.ds._registered_fields
if rfields is not None:
if entry := rfields.get(field):
units = entry.get("units", units)

if units is None:
try:
units = ytcfg.get("fields", *field, "units")
Expand Down Expand Up @@ -256,6 +262,7 @@ def setup_fluid_aliases(self, ftype: FieldType = "gas") -> None:
raise RuntimeError
if field[0] in self.ds.particle_types:
continue

args = known_other_fields.get(field[1], None)
if args is not None:
units, aliases, display_name = args
Expand All @@ -273,6 +280,15 @@ def setup_fluid_aliases(self, ftype: FieldType = "gas") -> None:
# field *name* is in there, then the field *tuple*.
units = self.ds.field_units.get(field[1], units)
units = self.ds.field_units.get(field, units)

# allow user to override with call to ds.register_fields
rfields = self.ds._registered_fields
if rfields is not None:
if entry := rfields.get(field):
units = entry.get("units", units)
aliases = entry.get("aliases", aliases)
display_name = entry.get("display_name", display_name)

self.add_output_field(
field, sampling_type="cell", units=units, display_name=display_name
)
Expand Down
12 changes: 12 additions & 0 deletions yt/fields/tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,18 @@ def test_deposit_amr():
assert_allclose_units(gpm, dpm)


@requires_module("h5py")
@requires_file(ISOGAL)
def test_register_field():
field = ("enzo", "Phi_pField")
# this is made up
unit_string = "Msun*code_length/kg"
ds = load(ISOGAL)
ds.register_field(field, units=unit_string)
ds.field_list
assert ds.field_info[field].units == unit_string


def test_ion_field_labels():
fields = [
"O_p1_number_density",
Expand Down
Loading