Skip to content

Commit 6ab7cbb

Browse files
author
Martin D. Weinberg
committed
Added a warning to the docs that Units requires the devel branch
1 parent fd7fd2d commit 6ab7cbb

1 file changed

Lines changed: 298 additions & 0 deletions

File tree

topics/units.rst

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
.. role:: python(code)
2+
:language: python
3+
:class: highlight
4+
5+
.. _units:
6+
7+
Units in EXP and pyEXP
8+
======================
9+
10+
.. attention::
11+
12+
The Units interface described below is available in the EXP `devel`
13+
branch only! You will need to ``git checkout devel`` and recompile
14+
to use this feature.
15+
16+
17+
Overview
18+
--------
19+
20+
The EXP N-body code assumes the gravitational constant is unity and
21+
that mass, position and velocity units can have any value defined by
22+
the user. In other words, the EXP N-body code does not enforce any
23+
particular unit set consistent with ``G=1``.
24+
25+
pyEXP will also accept mass, position, time, and velocity with any
26+
unit set defined by the imported simulation as well as an arbitrary
27+
value of the gravitational constant. Explicit units types and the
28+
gravitational constant ``G`` may be set at construction time as we
29+
will describe below in :ref:`unit-schema`. pyEXP **requires** the
30+
user to set units explicitly when coefficient sets are
31+
constructed. There is no default. See :ref:`intro-pyEXP-tutorial` for
32+
an end-to-end example of coefficient computation.
33+
34+
All unit information is written into the EXP coefficient files as HDF5
35+
attributes. Also see :ref:`hdf5-unit-attributes` for details. You
36+
may add units to previously computed coefficient files using the
37+
script described in :ref:`units_old`.
38+
39+
.. _unit-schema:
40+
41+
The EXP unit schema
42+
-------------------
43+
44+
EXP requires one of the following two sequences of 4 unit types:
45+
46+
1. Mass, Length, Time, gravitational constant (G)
47+
48+
2. Mass, Length, Velocity, gravitational constant (G)
49+
50+
51+
Other combinations are possible in principle, such as Mass, Length,
52+
Velocity and Time. However, this would require an automatic deduction
53+
of the gravitational constant. EXP does not current know about
54+
physical unit conversion. This feature may be added in the future.
55+
56+
Each separate unit is defined as a ``tuple`` which takes the form::
57+
58+
('unit type', 'unit name', <float value>)
59+
60+
The type and name strings are checked against the allowed values as follows:
61+
62+
- The ``unit type`` is one of 'length', 'mass', 'time', 'velocity' or
63+
'G'. The internal parser will accept a variety of aliases for these
64+
such as 'Length', 'Len', 'len', 'l', 'L' for 'length'; 'Mass', 'm',
65+
'M' for 'mass'; 'Time', 't', 'T' for 'time', 'vel',
66+
'Vel', 'Velocity', 'v', 'V' for 'velocity'; and 'Grav', 'grav',
67+
'grav_constant', 'Grav_constant', 'gravitational_constant',
68+
'Gravitational_constant' for 'G'.
69+
70+
- The ``unit name`` is one of the usual unit names for each of the
71+
``unit type``. The allowed list is a subset of the standard
72+
`astropy` strings. For example, 'pc' or 'kpc' for 'length'. There
73+
is also a 'none' type which implies no assigned physical units.
74+
75+
- The floating value defines the number of each unit for the type.
76+
The value can be any valid floating-point number.
77+
78+
The allowed types and names may be queried interactively in pyEXP
79+
using the :python:`getAllowedUnitTypes()` and
80+
:python:`getAllowedUnitNames(type)`, see :ref:`units-interface`. For
81+
development reference, these allowed strings are defined in
82+
``expui/UnitValidator.cc`` in the EXP source tree.
83+
84+
As an example, a Gadget user might define mass units as::
85+
86+
('mass', 'Msun', 1.0e10)
87+
88+
The full units specification is a list of tuples that includes one of
89+
the four sequences. In C++, the list is a :cpp:any:`std::vector<>` of
90+
tuples.
91+
92+
As an example, all native EXP simulations have the following unit
93+
lists:
94+
95+
.. code-block:: python
96+
[('mass', 'none', 1.0f), ('length', 'none', 1.0f), ('time', 'none', 1.0f), ('G', 'none', 1.0f)]
97+
98+
99+
Units in pyEXP
100+
--------------
101+
102+
The ``pyEXP.basis`` classes will use the units specified by the user
103+
in the ``pyEXP.coefs`` class created from simulation snapshots (see
104+
:ref:`intro-pyEXP-tutorial`) or read by the coefficient factory from
105+
an existing HDF5 file to produce accelerations using the value of the
106+
gravitational constant and length units provided by the user.
107+
108+
109+
.. _units-interface:
110+
111+
The Units interface
112+
-------------------
113+
114+
The `pyEXP` user interface includes two member functions for explicitly
115+
setting and removing units as part of the `Coef` class. For setting
116+
units, we have:
117+
118+
.. code-block:: python
119+
120+
setUnits(type, unit, value)
121+
setUnits(list)
122+
removeUnits(type)
123+
getAllowedUnitTypes()
124+
getAllowedTypeAliases(type)
125+
getAllowedUnitName(type)
126+
127+
where ``type`` and ``unit`` are strings and ``value`` is a float. The
128+
list is a list of tuples of ``(name, unit, value)``. The last three
129+
members return the list of unit types, the recognized aliases for each
130+
type, and the allowed unit names for each unit type, respectively.
131+
132+
For an example, suppose you are making a set of coefficients for a
133+
simulation with default Gadget units. Say your coefficients instance
134+
is called ``coefs``. The following command will register the unit set:
135+
136+
.. code-block:: python
137+
138+
coefs.setUnits([ ('mass', 'Msun', 1e10), ('length', 'kpc', 1.0),
139+
('velocity', 'km/s', 1.0), ('G', 'mixed', 43007.1) ])
140+
141+
These units will be in the HDF5 that you create using
142+
:python:`coefs.WriteH5Coefs('filename')`. You can query, for example,
143+
the allowed 'mass' units with the call
144+
:python:`coefs.getAllowedUnitNames('mass')` which returns:
145+
146+
.. code-block:: python
147+
148+
['gram', 'earth_mass', 'solar_mass', 'kilograms', 'kg', 'g', 'Mearth', 'Msun', 'None', 'none']
149+
150+
The allowed mass units for 'G' are:
151+
152+
.. code-block:: python
153+
154+
['gram', 'earth_mass', 'solar_mass', 'kilograms', 'kg', 'g', 'Mearth', 'Msun', 'None', 'none']
155+
156+
157+
A quick note: 'mixed' is an allowed alias when dealing with
158+
gravitational constants that have physical units. You can see all
159+
unit types with :python:`getAllowedUnitTypes()`; this returns
160+
:python:`['mass', 'length', 'time', 'velocity', 'G']`. You can see
161+
the recognized aliases for each type using
162+
:python:`getAllowedTypeAliases(type)`. The gravitational constant is
163+
a special case. The recognized unit names for 'G' are: :python:`['','mixed', 'none', 'unitless']``.
164+
165+
The C++ UI echos the functions above and adds functions to retrieve
166+
units
167+
168+
.. code-block:: C++
169+
170+
// Add or replace a unit by specifying its unit tuple
171+
void setUnits(const std::string name, const std::string unit, float value);
172+
// Add or replace a unit(s) by specifying an array of unit tuple
173+
void setUnits(std::vector<std::tuple<std::string, std::string, float>> list);
174+
// Remove a unit tuple by type
175+
void removeUnits(const std::string type);
176+
// Retrieve the currently define unit set
177+
std::vector<std::tuple<std::string, std::string, float>> getUnits();
178+
// Get a list of unit types and their aliases
179+
std::vector<std::string> getAllowedTypes();
180+
// Get a list aliases for each type
181+
std::vector<std::string> getAllowedTypeAliases(const std::string& type);
182+
// Get a list of unit name and their aliases for a given unit type
183+
std::vector<std::string> getAllowedUnits(const std::string& type)
184+
185+
and to interact with HDF files that will only be of interest to
186+
developers creating new coefficient classes.
187+
188+
189+
.. _hdf5-unit-attributes:
190+
191+
The HDF5 specification
192+
----------------------
193+
194+
.. note:: This and the following sections assumes basic familiarity
195+
with HDF5 and `h5py` in particular.
196+
197+
EXP HDF5 coefficient files contain a full set of metadata describing
198+
their basis type, basis parameters, geometry, and units as attributes
199+
of the root ("/") group. The ``snapshots`` group contains all of the
200+
coefficient data and metadata (such as center and rotation
201+
information) for each snapshot time in the coefficient set.
202+
203+
The units information is stored in the root group as dataset named
204+
"Units". The dataset is a sequence or list of 4 tuples. Each tuple
205+
has three fields: two fixed length strings of sixteen (16) characters
206+
and a float value.
207+
208+
For an EXP run, the units specification appears as dataset in the root
209+
group with the following hierarchy::
210+
211+
DATASET "Units" {
212+
DATATYPE H5T_COMPOUND {
213+
H5T_STRING {
214+
STRSIZE 16;
215+
STRPAD H5T_STR_NULLTERM;
216+
CSET H5T_CSET_UTF8;
217+
CTYPE H5T_C_S1;
218+
} "name";
219+
H5T_STRING {
220+
STRSIZE 16;
221+
STRPAD H5T_STR_NULLTERM;
222+
CSET H5T_CSET_UTF8;
223+
CTYPE H5T_C_S1;
224+
} "unit";
225+
H5T_IEEE_F32LE "value";
226+
}
227+
DATASPACE SIMPLE { ( 4 ) / ( 4 ) }
228+
DATA {
229+
(0): {
230+
"G",
231+
"none",
232+
1
233+
},
234+
(1): {
235+
"length",
236+
"none",
237+
1
238+
},
239+
(2): {
240+
"mass",
241+
"none",
242+
1
243+
},
244+
(3): {
245+
"time",
246+
"none",
247+
1
248+
}
249+
}
250+
}
251+
252+
253+
.. _units_old:
254+
255+
Backward compatibility
256+
----------------------
257+
258+
A units attribute list could be easily added to existing HDF5 files
259+
using `h5py` using the schema described above. For example, assuming
260+
that you already have an open HDF5 coefficient file named `f`, the
261+
units described in the scheme above could be added to `f` with the
262+
following code:
263+
264+
.. code-block:: python
265+
266+
import h5py
267+
import numpy as np
268+
269+
# Define the compound datatype with fixed-length ASCII strings and a float32
270+
dt = np.dtype([
271+
('name', 'S16'), # Fixed-length ASCII string of 16 bytes
272+
('unit', 'S16'), # Fixed-length ASCII string of 16 bytes
273+
('value', '<f4') # Little-endian 32-bit float
274+
])
275+
276+
# Create the dataset data as a numpy array
277+
data = np.array([
278+
(b'G', b'none', 1.0),
279+
(b'length', b'none', 1.0),
280+
(b'mass', b'none', 1.0),
281+
(b'time', b'none', 1.0),
282+
], dtype=dt)
283+
284+
# Open the HDF5 coefficient file and add the dataset
285+
with h5py.File('outcoef.disk.runtag', 'a') as f:
286+
# 'a' mode opens file for read/write, creates if not exist
287+
288+
# Create dataset 'Units'
289+
if 'Units' in f:
290+
# Optional: remove existing dataset if you want to overwrite
291+
del f['Units']
292+
dset = f.create_dataset('Units', data=data, dtype=dt)
293+
294+
# File is automatically closed on leaving the 'with' block, flush and
295+
# update saved data on disk
296+
297+
You can update the :python:`data` array in the example above to match your
298+
unit set.

0 commit comments

Comments
 (0)