Skip to content

BlackBody model: fix handling of scale units#12318

Merged
nden merged 5 commits intoastropy:mainfrom
kecnry:modeling-bb-unit-fixes
Mar 9, 2022
Merged

BlackBody model: fix handling of scale units#12318
nden merged 5 commits intoastropy:mainfrom
kecnry:modeling-bb-unit-fixes

Conversation

@kecnry
Copy link
Member

@kecnry kecnry commented Oct 28, 2021

Description

This pull request (which is a stripped down version of #12304 that only includes minimal bug fixes and not deprecating support for units in scale or introduction of support for flux units) fixes the case where dimensionless (but not unscaled) units are passed to scale (#11547) and also changes the expected input_units based on the units passed to scale (which control the returned units). This second change is necessary in order to handle fitting BlackBody(..) + Linear1D(...) with respect to wavelength, as BlackBody was previously forcing the unitless array to Hz which then failed unit consistency checks with Linear1D.

Note that this does not address the second example in 11547 and that case remains ambiguous as scale is both providing a unitless scale factor and optionally responsible for determining the returned units. See #12304 for a possible solution to this by requiring scale to be dimensionless and handling the request for output units as a separate argument.

Revisiting the first case in #11547:

from astropy.modeling.models import BlackBody
from astropy import units as u
import numpy as np

T = 3000 * u.K
r = 1e14 * u.cm
DL = 100 * u.Mpc
scale = np.pi * (r / DL)**2

print(BlackBody(temperature=T, scale=scale).bolometric_flux)
print(BlackBody(temperature=T, scale=scale.to_value(u.dimensionless_unscaled)).bolometric_flux)

now returns the expected results (the passed scale with units of cm ** 2 / Mpc **2 are converted to dimensionless_unscaled before being applied):

4.823870774433646e-16 erg / (cm2 s)
4.823870774433646e-16 erg / (cm2 s)

The following case demonstrates the new treatment of input_units:

import astropy.units as u
from astropy.modeling import models
from astropy.modeling.fitting import LevMarLSQFitter
import numpy as np

SLAM = u.erg / (u.cm ** 2 * u.s * u.AA * u.sr)
SNU = u.erg / (u.cm ** 2 * u.s * u.Hz * u.sr)

fitter = LevMarLSQFitter()

x_lam = np.linspace(1000, 10000, 1000) * u.AA
x_nu = x_lam.to(u.Hz, u.spectral())

y_lam = np.random.random(x_lam.shape) * SLAM
y_nu = np.random.random(x_nu.shape) * SNU

b_nu = models.BlackBody(3000*u.K, scale=1*SNU)
l_nu = models.Linear1D(intercept=1*SNU, slope=0*SNU/u.Hz)
c_nu = b_nu + l_nu

print(b_nu.input_units, l_nu.input_units, c_nu.input_units)

fitter(c_nu, x_nu, y_nu, maxiter=1000)

which shows all models have expected Hz input units and the fitter runs successfully (as previously). However, the wavelength case:

b_lam = models.BlackBody(3000*u.K, scale=1*SLAM)
l_lam = models.Linear1D(intercept=1*SLAM, slope=0*SLAM/u.AA)
c_lam = b_lam + l_lam

print(b_lam.input_units, l_lam.input_units, c_lam.input_units)

fitter(c_lam, x_lam, y_lam, maxiter=1000)

used to have x input_units of Hz for the BlackBody model (but Angstrom for the other models), causing the following error during fitting: UnitConversionError: 'erg / (Angstrom2 cm2 s sr)' and 'erg / (Angstrom cm2 Hz s sr)' are not convertible, but now has expected input units for Angstrom for all models and fitting succeeds without errors.

Fixes #11547

Checklist for package maintainer(s)

This checklist is meant to remind the package maintainer(s) who will review this pull request of some common things to look for. This list is not exhaustive.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see "When to rebase and squash commits".
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the Extra CI label.
  • Is a change log needed? If yes, did the change log check pass? If no, add the no-changelog-entry-needed label. If this is a manual backport, use the skip-changelog-checks label unless special changelog handling is necessary.
  • Is a milestone set? Milestone must be set but astropy-bot check might be missing; do not let the green checkmark fool you.
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate backport-X.Y.x label(s) before merge.

scale : float or `~astropy.units.Quantity` ['dimensionless']
Scale factor
Scale factor. If dimensionless, input units will assumed
to be in Hz and output units in (erg / (cm ** 2 * s * Hz * sr).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hz? Why?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this just needs rewording for clarity. Let me explain the behavior and if everyone agrees on that, then let's revise the wording to make sure its clear: the input units (of x when calling evaluate, which are dictated by the input_units property) are assumed to be in Hz, as they were previously, if scale is either dimensionless, not provided, or equivalent to SNU units. The change is that now they will be assumed to be in Angstroms if the units on scale were equivalent to SLAM units to be consistent with the returned units. Otherwise the case I show in the description fails with ugly error messages during fitting with a Compound model (and I suspect might even give incorrect results when used alone unless you really mean to pass in frequencies without units and return wrt wavelength).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see... I misread it as "the scale factor, if dimensionless, is assumed to be in Hz" 😛

@kecnry kecnry marked this pull request as ready for review October 28, 2021 17:35
Copy link
Contributor

@lpsinger lpsinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a deprecation warning for non-dimensionless scale.

Comment on lines +219 to +222
if self.scale.unit is not None:
# Will be dimensionless at this point, but may not be dimensionless_unscaled
scale = self.scale.quantity.to(u.dimensionless_unscaled)
else:
scale = self.scale.value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.scale.unit is not None:
# Will be dimensionless at this point, but may not be dimensionless_unscaled
scale = self.scale.quantity.to(u.dimensionless_unscaled)
else:
scale = self.scale.value
if self.scale.unit is not None:
# Will be dimensionless at this point, but may not be dimensionless_unscaled
scale = self.scale.quantity.to(u.dimensionless_unscaled)

The else: statement does nothing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR only fixes the two bugs mentioned in the description above, but does not deprecate non-dimensionless scale (although it does internally split off the units as that is needed for the logic to fix the second bug). The idea was to try to get these bugfixes independently of any longer-term decisions on full general handling of return units.

The else statement is needed to fallback on scale = self.scale.value. self.scale is stored as either a float or as a quantity with dimensionless units. The if block addresses the first bug (passing scale with units of cm **2 / Mpc **2) by converting dimensionless to dimensionless_unscaled, but the else is needed to handle the case where it is passed and stored as a float.

@eteq eteq added the Bug label Oct 29, 2021
@kecnry kecnry force-pushed the modeling-bb-unit-fixes branch 3 times, most recently from 99735b8 to 3a7632b Compare November 16, 2021 20:02
@pep8speaks
Copy link

pep8speaks commented Nov 16, 2021

Hello @kecnry 👋! It looks like you've made some changes in your pull request, so I've checked the code again for style.

There are no PEP8 style issues with this pull request - thanks! 🎉

Comment last updated at 2022-03-08 21:16:27 UTC

@WilliamJamieson
Copy link
Contributor

Please you rebase your PR onto main.

@lpsinger
Copy link
Contributor

Is there a PR coming to implement the strategy that we discussed in the telecon?

@kecnry kecnry force-pushed the modeling-bb-unit-fixes branch from 9621f06 to 4bdb638 Compare January 6, 2022 21:29
kecnry added 5 commits March 8, 2022 16:16
* dimensionless (but non-unscaled) scale is converted to dimensionless_unscaled before being applied
* when scale is passed with units, the units are checked for validity and then stripped and stored in self._output_units.  self.scale always contains the dimensionless factor that will be multiplied in both evaluate and bolometric flux.
* expected input units (units on x if not provided when evaluating) still default to Hz, but will change to AA if the unit on scale is equivalent to erg / (cm ** 2 * s * AA *sr).  The input array is still converted to Hz using the spectral equivalency for internal computation.  This fixes the case where using BlackBody+Linear1D wrt BlackBody failed to fit due to unit consistency checks.
* both with and without internal units set
@kecnry kecnry force-pushed the modeling-bb-unit-fixes branch from 4bdb638 to 930ea38 Compare March 8, 2022 21:16
@WilliamJamieson WilliamJamieson added this to the v5.0.2 milestone Mar 8, 2022
Copy link
Contributor

@WilliamJamieson WilliamJamieson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nden nden merged commit 3abaa4f into astropy:main Mar 9, 2022
meeseeksmachine pushed a commit to meeseeksmachine/astropy that referenced this pull request Mar 9, 2022
pllim added a commit that referenced this pull request Mar 9, 2022
…318-on-v5.0.x

Backport PR #12318 on branch v5.0.x (BlackBody model: fix handling of scale units)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BlackBody bolometric flux is wrong if scale has units of dimensionless_unscaled

6 participants