Repository: spacetelescope/synphot_refactor

Issue:
Redefine VEGAMAG and OBMAG so astropy can tell them apart
https://github.com/spacetelescope/synphot_refactor/blob/e51d7605349eebea0921c7d72da674b0af7d6db9/synphot/units.py#L52-L55

As stated in https://github.com/astropy/astropy/pull/13158#discussion_r858962549 , the new `astropy.modeling.models.Schechter1D` takes magnitude as parameter but currently it cannot tell VEGAMAG and OBMAG apart. @mhvk advised the following:

<hr/>

[The] problem is that they now are both just aliases to `u.mag`, so the units system cannot know what to do. I think best might be to treat it a bit like ST and AB, and define units `VEGA` and `OB` - which both being units without a known number, and then define `VEGAMAG = u.mag(VEGA)` and `OBMAB = u.mag(OB)`. Then one can no longer just mix and match those magnitude systems:

```python
>>> from astropy import units as u
>>> VEGA = u.def_unit('VEGA')
>>> OB = u.def_unit('OB')
>>> VEGAMAG = u.mag(VEGA)
>>> OBMAG = u.mag(OB)
>>> (5 * VEGAMAG - 2.5 * VEGAMAG).to(u.one)
<Quantity 0.1>
>>> (5 * VEGAMAG - 2.5 * OBMAG).to(u.one)
UnitConversionError: ("'VEGA / OB' and '' (dimensionless) are not convertible",
'Did you perhaps subtract magnitudes so the unit got lost?')
```

Note that of course the user can still shoot themselves in the foot by forgetting what band a magnitude is in...

<hr/>

To go with this patch, relevant documentation and tests might also need updating. And this might be a breaking change, so should not be in a bugfix release.

Comments:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index fe2844c..50ecb8f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,6 +4,8 @@
 - Dropped support for Python 3.6 and 3.7. Minimum supported Python
   version is now 3.8. [#330]
 
+- OBMAG and VEGAMAG are no longer interchangeable. [#331]
+
 1.1.1 (2021-11-18)
 ==================
 
diff --git a/docs/synphot/spectrum.rst b/docs/synphot/spectrum.rst
index 661aa70..43645a6 100644
--- a/docs/synphot/spectrum.rst
+++ b/docs/synphot/spectrum.rst
@@ -48,7 +48,7 @@ for flux conversion)::
     <Quantity [6.62607015e-24, 6.62607015e-23] FNU>
     >>> area = 45238.93416 * units.AREA  # HST
     >>> sp(wave, flux_unit=units.OBMAG, area=area)  # doctest: +FLOAT_CMP
-    <Quantity [-21.52438718,-21.52438718] OBMAG>
+    <Magnitude [-21.52438718,-21.52438718] mag(OB)>
 
 .. _synphot_reddening:
 
diff --git a/synphot/observation.py b/synphot/observation.py
index 97f8281..5514b0e 100644
--- a/synphot/observation.py
+++ b/synphot/observation.py
@@ -458,7 +458,7 @@ class Observation(BaseSourceSpectrum):
         if flux_unit == u.count or flux_unit_name == units.OBMAG.to_string():
             val = self.countrate(area, binned=False, wavelengths=wavelengths)
 
-            if flux_unit.decompose() == u.mag:
+            if flux_unit == units.OBMAG:
                 eff_stim = (-2.5 * np.log10(val.value)) * flux_unit
             else:
                 eff_stim = val
diff --git a/synphot/units.py b/synphot/units.py
index fc129ee..8dbc68f 100644
--- a/synphot/units.py
+++ b/synphot/units.py
@@ -1,9 +1,6 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 """This module handles photometry units that are not in `astropy.units`."""
 
-# THIRD-PARTY
-import numpy as np
-
 # ASTROPY
 from astropy import constants as const
 from astropy import units as u
@@ -49,10 +46,10 @@ FLAM = u.def_unit(
 FNU = u.def_unit(
     'fnu', u.erg / (u.cm**2 * u.s * u.Hz),
     format={'generic': 'FNU', 'console': 'FNU'})
-OBMAG = u.def_unit(
-    'obmag', u.mag, format={'generic': 'OBMAG', 'console': 'OBMAG'})
-VEGAMAG = u.def_unit(
-    'vegamag', u.mag, format={'generic': 'VEGAMAG', 'console': 'VEGAMAG'})
+_u_ob = u.def_unit('OB')
+OBMAG = u.mag(_u_ob)
+_u_vega = u.def_unit('VEGA')
+VEGAMAG = u.mag(_u_vega)
 
 # Register with astropy units
 u.add_enabled_units([PHOTLAM, PHOTNU, FLAM, FNU, OBMAG, VEGAMAG])
@@ -109,20 +106,12 @@ def spectral_density_vega(wav, vegaflux):
         PHOTLAM, equivalencies=u.spectral_density(wav)).value
 
     def converter(x):
-        """Set nan/inf to -99 mag."""
-        val = -2.5 * np.log10(x / vega_photlam)
-        result = np.zeros(val.shape, dtype=np.float64) - 99
-        mask = np.isfinite(val)
-        if result.ndim > 0:
-            result[mask] = val[mask]
-        elif mask:
-            result = np.asarray(val)
-        return result
+        return x / vega_photlam
 
     def iconverter(x):
-        return vega_photlam * 10**(-0.4 * x)
+        return x * vega_photlam
 
-    return [(PHOTLAM, VEGAMAG, converter, iconverter)]
+    return [(PHOTLAM, VEGAMAG.physical_unit, converter, iconverter)]
 
 
 def spectral_density_count(wav, area):
@@ -156,14 +145,8 @@ def spectral_density_count(wav, area):
     def iconverter_count(x):
         return x / factor
 
-    def converter_obmag(x):
-        return -2.5 * np.log10(x * factor)
-
-    def iconverter_obmag(x):
-        return 10**(-0.4 * x) / factor
-
     return [(PHOTLAM, u.count, converter_count, iconverter_count),
-            (PHOTLAM, OBMAG, converter_obmag, iconverter_obmag)]
+            (PHOTLAM, OBMAG.physical_unit, converter_count, iconverter_count)]
 
 
 def convert_flux(wavelengths, fluxes, out_flux_unit, **kwargs):
@@ -348,6 +331,10 @@ def validate_unit(input_unit):
             output_unit = u.STmag
         elif input_unit_lowcase in ('abmag', 'mag(ab)'):
             output_unit = u.ABmag
+        elif input_unit_lowcase in ('obmag', 'mag(ob)'):
+            output_unit = OBMAG
+        elif input_unit_lowcase in ('vegamag', 'mag(vega)'):
+            output_unit = VEGAMAG
 
         else:
             try:  # astropy.units is case-sensitive


Test patch:
diff --git a/synphot/tests/test_units.py b/synphot/tests/test_units.py
index 95aef73..3ffb064 100644
--- a/synphot/tests/test_units.py
+++ b/synphot/tests/test_units.py
@@ -1,7 +1,7 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 """Test units.py module.
 
-.. note:: VEGAMAG conversion is tested in test_spectrum.py.
+.. note:: VEGAMAG conversion is tested in test_spectrum_source.py.
 
 .. note:: spectral_density_integrated is tested in astropy>=4.1.
 
@@ -13,6 +13,7 @@ import pytest
 
 # ASTROPY
 from astropy import units as u
+from astropy.tests.helper import assert_quantity_allclose
 
 # LOCAL
 from synphot import exceptions, units
@@ -176,3 +177,18 @@ def test_flux_conversion_exceptions():
         units.convert_flux(_wave, _flux_photlam, u.count, area=None)
     with pytest.raises(exceptions.SynphotError):
         units.convert_flux(_wave, _flux_obmag, units.PHOTLAM, area=None)
+
+
+def test_vegamag_obmag_calculations():
+    assert_quantity_allclose(
+        5 * units.VEGAMAG - 2.5 * units.VEGAMAG, u.Magnitude(2.5))
+    assert_quantity_allclose(
+        (5 * units.VEGAMAG - 2.5 * units.VEGAMAG).to(u.one), 0.1)
+
+    # Should not be interchangeable with astropy mag unit or with another
+    # custom mag unit, but error is only raised if .to(u.one) is called.
+    msg = 'subtract magnitudes so the unit got lost'
+    with pytest.raises(u.UnitConversionError, match=msg):
+        (5 * units.VEGAMAG - 2.5 * u.STmag).to(u.one)
+    with pytest.raises(u.UnitConversionError, match=msg):
+        5 * units.VEGAMAG - 2.5 * units.OBMAG.to(u.one)