Skip to content

Commit

Permalink
Simplifies the unit system by introducing "unit categories" (#826)
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherlovell authored Feb 12, 2025
2 parents beb74c9 + c911bca commit d4d111d
Show file tree
Hide file tree
Showing 21 changed files with 494 additions and 372 deletions.
71 changes: 59 additions & 12 deletions docs/source/advanced/units.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"source": [
"# Units\n",
"\n",
"In synthesizer all quantities a user interacts with (that are not dimensionless) have units associated with them, implemented with the unyt package. \n",
"In Synthesizer all quantities a user interacts with (that are not dimensionless) have units associated with them. We implement this unit system using the ``unyt`` package. \n",
"\n",
"Synthesizer objects and methods should always be provided with quantites and associated units. This can be easily achieved with the unyt package."
]
Expand All @@ -33,7 +33,7 @@
"id": "7fd94ede",
"metadata": {},
"source": [
"All unit functionality in synthesizer is contained in the `units` module. In this module we have defined a dictionary containing the default units of all attributes throughout synthesizer."
"All unit functionality in synthesizer is contained in the `units` module. This module contains an importable object containing the default units of all attributes throughout synthesizer."
]
},
{
Expand All @@ -53,11 +53,11 @@
"id": "5a671fb3",
"metadata": {},
"source": [
"As you can see, most units are defined symbollically, however, you may notice units such as `Msun` (the default used for masses) is defined as a compound unit in terms of `kg`s. Shortly we will cover working with the quantities returned by synthesizer and how compound units work in practice.\n",
"\n",
"## The `Units` object\n",
"\n",
"The unit system is defined by the `Units` object. This object contains a collection of attributes defining the units associated to each quantity throughout synthesizer which is not dimensionless. Importantly, `Units` is a `Singleton` object. This means there can only ever be one instance of `Units`; if a second is instantiated then the first is returned. This ensures that the unit system remains consistent when running synthesizer."
"The unit system is defined by the `Units` object. This object contains a collection of attributes defining the units associated to each \"category\" of quantity throughout Synthesizer. \n",
"\n",
"Importantly, `Units` is a `Singleton` object. This means there can only ever be one instance of `Units`; if a second is instantiated then the first is returned. This ensures that the unit system remains consistent when running Synthesizer."
]
},
{
Expand Down Expand Up @@ -101,7 +101,9 @@
"source": [
"## Modifying the default unit system\n",
"\n",
"If the default unit system works for your needs then you don't need to do anything. You will never interact with the `Units` object and all quantites will have the default units associated to them automatically. However, if you need to change one or more of the units used you can import `Units` and instantiate it with a dictionary of the modified quantities.\n"
"If the default unit system works for your needs then you don't need to do anything. You will never interact with the `Units` object and all quantites will have the default units associated to them automatically. However, if you need to change one or more of the units used you can import `Units` and instantiate it with a dictionary of the modified quantities.\n",
"\n",
"This dictionary of modified quantities can either modify an existing category or it can defining a unit for a specific attribute. We demonstrate this below by modifying the default unit system to use `kpc` for the ``\"spatial\"`` category, but also override this to use `Mpc` for coordinates and `Myr` for ages specifically.\n"
]
},
{
Expand All @@ -111,13 +113,17 @@
"metadata": {},
"outputs": [],
"source": [
"from unyt import Msun, Myr, kpc\n",
"# Ensure warnings are printed\n",
"import warnings\n",
"\n",
"from unyt import Mpc, Msun, Myr, kpc\n",
"\n",
"warnings.simplefilter(\"always\")\n",
"\n",
"# Make the dictionary containing the units we want to change\n",
"new_units = {\n",
" \"coordinates\": kpc,\n",
" \"smoothing_lengths\": kpc,\n",
" \"softening_length\": kpc,\n",
" \"spatial\": kpc,\n",
" \"coordinates\": Mpc,\n",
" \"ages\": Myr,\n",
"}\n",
"\n",
Expand All @@ -133,7 +139,7 @@
"id": "b2216885",
"metadata": {},
"source": [
"Something has gone wrong... but recall that the unit system will return the original if one exists, so actually this should be completely expected.\n",
"You'll notice that something has gone wrong here... but recall that the unit system will return the original if one exists, so actually this should be completely expected.\n",
"\n",
"This issue highlights the need to set up `Units` **before** doing anything else. If any computations have been done the `Units` instance will exist and will not be modifiable after the fact. However, should you fall in this trap the code will warn you as above - no hidden gotchas here!\n",
"\n",
Expand All @@ -154,6 +160,47 @@
"print(units)"
]
},
{
"cell_type": "markdown",
"id": "3da631df",
"metadata": {},
"source": [
"### Permenantly modify the default unit system\n",
"\n",
"If you want to permenantly modify the default unit system you can do so by first modifying the `Units` object and then calling the ``overwrite_defaults_yaml`` method. This will write the modified unit system to the default units file. \n",
"\n",
"Note, you can also explicitly edit the ``default_units.yml`` file within the source code."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d679b05f",
"metadata": {},
"outputs": [],
"source": [
"units.overwrite_defaults_yaml()"
]
},
{
"cell_type": "markdown",
"id": "42e348dd",
"metadata": {},
"source": [
"When the above function is called we also write out the original default units system if it hasn't already been written out. This ensures the unit redefinition is reversible. To revert to the original unit system you can call the ``reset_defaults_yaml`` method."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ac1c2762",
"metadata": {},
"outputs": [],
"source": [
"units.reset_defaults_yaml()\n",
"print(units)"
]
},
{
"cell_type": "markdown",
"id": "66d9fba2",
Expand All @@ -163,7 +210,7 @@
"\n",
"There is no need to work with the `Units` object itself beyond initially defining a modified unit system. Beyond this, all unit operations are handled \"behind the scenes\". This hidden functionality is enabled by the `Quantity` object. \n",
"\n",
"All attributes on synthesizer objects which carry units are in fact `Quantity` objects. `Quantitiy` objects carry a reference to the global unit system, and extract the appropriate units depending on the name of the variable storing the `Quantity`. As such, a user will never instantiate a quantity themselves, but their usage is important. \n",
"All attributes on Synthesizer objects which carry units are in fact `Quantity` objects. `Quantity` objects carry a the unit of the attribute (extracted from the global unit system), and extract the appropriate units depending on the name of the variable storing the `Quantity`. As such, a user will never instantiate a quantity themselves, but their usage is important. \n",
"\n",
"One simple thing to keep in mind is how to return the value with or without units. This is achieved by the application or omission of a leading underscore to a variable name. \n",
"\n",
Expand Down
1 change: 0 additions & 1 deletion src/synthesizer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# Import the various utils submodules to make them available at the top level
# Import the various extensions submodules to make them available
# at the top level
from synthesizer.extensions.openmp_check import check_openmp
Expand Down
12 changes: 6 additions & 6 deletions src/synthesizer/components/blackhole.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ class BlackholesComponent(Component):
"""

# Define class level Quantity attributes
accretion_rate = Quantity()
inclination = Quantity()
bolometric_luminosity = Quantity()
eddington_luminosity = Quantity()
bb_temperature = Quantity()
mass = Quantity()
accretion_rate = Quantity("mass_rate")
inclination = Quantity("angle")
bolometric_luminosity = Quantity("luminosity")
eddington_luminosity = Quantity("luminosity")
bb_temperature = Quantity("temperature")
mass = Quantity("mass")

@accepts(
mass=Msun.in_base("galactic"),
Expand Down
2 changes: 1 addition & 1 deletion src/synthesizer/components/stellar.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class StarsComponent(Component):
"""

# Define quantities
ages = Quantity()
ages = Quantity("time")

@accepts(ages=Myr)
def __init__(
Expand Down
60 changes: 60 additions & 0 deletions src/synthesizer/default_units.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
UnitCategories:
# Spatial lengths and positions
spatial:
unit: Mpc

# Mass
mass:
unit: Msun

# Time (e.g. ages)
time:
unit: yr

# Velocities (e.g. km/s)
velocity:
unit: "km/s"

# Temperature (e.g. blackbody temperature)
temperature:
unit: K

# Angles (e.g. inclinations)
angle:
unit: deg

# Wavelengths
wavelength:
unit: Angstrom

# Frequencies
frequency:
unit: Hz

# Total luminosity (energy per unit time)
luminosity:
unit: "erg / s"

# Luminosity per unit frequency
luminosity_density_frequency:
unit: "erg / s / Hz"

# Luminosity per unit wavelength
luminosity_density_wavelength:
unit: "erg / s / Angstrom"

# Flux (energy per unit time per area)
flux:
unit: "erg / s / cm**2"

# Flux density per unit frequency (e.g. nJy, photo_fnu)
flux_density_frequency:
unit: nJy

# Flux density per unit wavelength
flux_density_wavelength:
unit: "erg / s / Angstrom / cm**2"

# Mass per unit time
mass_rate:
unit: "Msun/yr"
2 changes: 1 addition & 1 deletion src/synthesizer/emission_models/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class EmissionModel(Extraction, Generation, DustAttenuation, Combination):
"""

# Define quantities
lam = Quantity()
lam = Quantity("wavelength")

def __init__(
self,
Expand Down
6 changes: 3 additions & 3 deletions src/synthesizer/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class Grid:
"""

# Define Quantities
lam = Quantity()
lam = Quantity("wavelength")

@accepts(new_lam=angstrom)
def __init__(
Expand Down Expand Up @@ -1317,8 +1317,8 @@ class Template:
"""

# Define Quantities
lam = Quantity()
lnu = Quantity()
lam = Quantity("wavelength")
lnu = Quantity("luminosity_density_frequency")

@accepts(lam=angstrom, lnu=erg / s / Hz)
def __init__(
Expand Down
4 changes: 2 additions & 2 deletions src/synthesizer/imaging/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ class Image:
"""

# Define quantities
resolution = Quantity()
fov = Quantity()
resolution = Quantity("spatial")
fov = Quantity("spatial")

def __init__(
self,
Expand Down
6 changes: 3 additions & 3 deletions src/synthesizer/imaging/image_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ class ImageCollection:
"""

# Define quantities
resolution = Quantity()
fov = Quantity()
orig_resolution = Quantity()
resolution = Quantity("spatial")
fov = Quantity("spatial")
orig_resolution = Quantity("spatial")

def __init__(
self,
Expand Down
6 changes: 3 additions & 3 deletions src/synthesizer/imaging/spectral_cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ class SpectralCube:
"""

# Define quantities
lam = Quantity()
resolution = Quantity()
fov = Quantity()
lam = Quantity("wavelength")
resolution = Quantity("spatial")
fov = Quantity("spatial")

def __init__(
self,
Expand Down
22 changes: 11 additions & 11 deletions src/synthesizer/instruments/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ class FilterCollection:
"""

# Define Quantitys
lam = Quantity()
mean_lams = Quantity()
pivot_lams = Quantity()
lam = Quantity("wavelength")
mean_lams = Quantity("wavelength")
pivot_lams = Quantity("wavelength")

accepts(new_lam=angstrom)

Expand Down Expand Up @@ -1207,14 +1207,14 @@ class Filter:
"""

# Define Quantitys
lam_min = Quantity()
lam_max = Quantity()
lam_eff = Quantity()
lam_fwhm = Quantity()
lam = Quantity()
nu = Quantity()
original_lam = Quantity()
original_nu = Quantity()
lam_min = Quantity("wavelength")
lam_max = Quantity("wavelength")
lam_eff = Quantity("wavelength")
lam_fwhm = Quantity("wavelength")
lam = Quantity("wavelength")
nu = Quantity("frequency")
original_lam = Quantity("wavelength")
original_nu = Quantity("frequency")

@accepts(
lam_min=angstrom,
Expand Down
4 changes: 2 additions & 2 deletions src/synthesizer/instruments/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ class Instrument:
"""

# Define quantities
resoluton = Quantity()
lam = Quantity()
resoluton = Quantity("spatial")
lam = Quantity("wavelength")

@accepts(resolution=kpc, lam=angstrom)
def __init__(
Expand Down
14 changes: 7 additions & 7 deletions src/synthesizer/line.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ class LineCollection:
"""

# Define quantities
wavelengths = Quantity()
wavelengths = Quantity("wavelength")

def __init__(self, lines):
"""
Expand Down Expand Up @@ -773,12 +773,12 @@ class Line:
"""

# Define quantities
wavelength = Quantity()
vacuum_wavelength = Quantity()
continuum = Quantity()
luminosity = Quantity()
flux = Quantity()
obslam = Quantity()
wavelength = Quantity("wavelength")
vacuum_wavelength = Quantity("wavelength")
obslam = Quantity("wavelength")
continuum = Quantity("luminosity_density_frequency")
luminosity = Quantity("luminosity")
flux = Quantity("flux")

@accepts(
wavelength=angstrom,
Expand Down
2 changes: 1 addition & 1 deletion src/synthesizer/parametric/stars.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class Stars(StarsComponent):
"""

# Define quantities
initial_mass = Quantity()
initial_mass = Quantity("mass")

@accepts(initial_mass=Msun.in_base("galactic"))
def __init__(
Expand Down
2 changes: 1 addition & 1 deletion src/synthesizer/particle/blackholes.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class BlackHoles(Particles, BlackholesComponent):
]

# Define quantities
smoothing_lengths = Quantity()
smoothing_lengths = Quantity("spatial")

@accepts(
masses=Msun.in_base("galactic"),
Expand Down
4 changes: 2 additions & 2 deletions src/synthesizer/particle/gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ class Gas(Particles):
]

# Define class level Quantity attributes
smoothing_lengths = Quantity()
dust_masses = Quantity()
smoothing_lengths = Quantity("spatial")
dust_masses = Quantity("mass")

@accepts(
masses=Msun.in_base("galactic"),
Expand Down
Loading

0 comments on commit d4d111d

Please sign in to comment.