Repository: yt-project/yt

Issue:
Allow users to provide list of known fields and their units
### Bug report

**Bug summary**

At the moment, on-disk fields have their default units and string representation stored in `FieldInfoContainer` subclasses. This prevents users from customizing them, and also implies that unknown fields will all default to being dimensionless.
This was brought up in #3332 and on the chat by @dnarayanan .

**Code for reproduction**

For example, in the following
```python
>>> ds = yt.load_sample("output_00080")
>>> ad = ds.all_data()
... ad["nbody", "particle_extra_field_1"]
unyt_array([0., 0., 0., ..., 0., 0., 0.], '(dimensionless)')
```
there is now way to tell yt that `particle_extra_field_1` should have given units (in this case I _think_ it is homogenous to a time).
At the moment, the only possibility is to create a derived field.

**Suggested solution**

Allow the user to list entries in the configuration file following this format:
```toml
[fields.nbody.particle_extra_field_1]
aliases = "particle_other_fancy_name"
# or
aliases = ["particle_other_fancy_name", "particle_alternative_fancy_name"]
units = "code_time"
display_name = "Dinosaurs Density"
```


Discussion:

Resolving patch:
diff --git a/yt/fields/field_info_container.py b/yt/fields/field_info_container.py
--- a/yt/fields/field_info_container.py
+++ b/yt/fields/field_info_container.py
@@ -6,6 +6,7 @@
 from unyt.exceptions import UnitConversionError
 
 from yt._typing import KnownFieldsT
+from yt.config import ytcfg
 from yt.fields.field_exceptions import NeedsConfiguration
 from yt.funcs import mylog, only_on_root
 from yt.geometry.geometry_handler import is_curvilinear
@@ -144,10 +145,16 @@ 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)
+            if units is None:
+                try:
+                    units = ytcfg.get("fields", *field, "units")
+                except KeyError:
+                    units = ""
             self.add_output_field(
                 field,
                 sampling_type="particle",
-                units=self.ds.field_units.get(field, ""),
+                units=units,
             )
         self.setup_smoothed_fields(ptype, num_neighbors=num_neighbors, ftype=ftype)
 
@@ -219,8 +226,19 @@ def setup_fluid_aliases(self, ftype="gas"):
                 raise RuntimeError
             if field[0] in self.ds.particle_types:
                 continue
-            args = known_other_fields.get(field[1], ("", [], None))
-            units, aliases, display_name = args
+            args = known_other_fields.get(field[1], None)
+            if args is not None:
+                units, aliases, display_name = args
+            else:
+                try:
+                    node = ytcfg.get("fields", *field).as_dict()
+                except KeyError:
+                    node = dict()
+
+                units = node.get("units", "")
+                aliases = node.get("aliases", [])
+                display_name = node.get("display_name", None)
+
             # We allow field_units to override this.  First we check if the
             # field *name* is in there, then the field *tuple*.
             units = self.ds.field_units.get(field[1], units)


Test patch: