Skip to content

Commit

Permalink
Sort vectorActions somewhat logically
Browse files Browse the repository at this point in the history
  • Loading branch information
taranu committed May 17, 2023
1 parent 59423ac commit c598c2e
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 91 deletions.
79 changes: 42 additions & 37 deletions python/lsst/analysis/tools/actions/vector/vectorActions.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

_LOG = logging.getLogger(__name__)

# Basic vector manipulation
# Basic vectorActions


class LoadVector(VectorAction):
Expand Down Expand Up @@ -124,26 +124,7 @@ def __call__(self, data: KeyedData, **kwargs) -> Vector:
dataWithUnit = self.buildAction(data, **kwargs) * u.Unit(self.inUnit)
return dataWithUnit.to(self.outUnit).value


class ConvertFluxToMags(VectorAction):
"""Turn nano janskies into magnitudes."""

vectorKey = Field[str](doc="Key of flux vector to convert to mags")
fluxUnit = Field[str](doc="Astropy unit of flux vector", default="nJy")
returnMillimags = Field[bool](doc="Use millimags or not?", default=False)

def getInputSchema(self) -> KeyedDataSchema:
return ((self.vectorKey, Vector),)

def __call__(self, data: KeyedData, **kwargs) -> Vector:
with np.warnings.catch_warnings(): # type: ignore
np.warnings.filterwarnings("ignore", r"invalid value encountered") # type: ignore
np.warnings.filterwarnings("ignore", r"divide by zero") # type: ignore
vec = cast(Vector, data[self.vectorKey.format(**kwargs)])
mags = (np.array(vec) * u.Unit(self.fluxUnit)).to(u.ABmag).value # type: ignore
if self.returnMillimags:
mags *= 1000
return mags
# Astronomical vectorActions


class CalcSn(VectorAction):
Expand All @@ -166,6 +147,27 @@ def __call__(self, data: KeyedData, **kwargs) -> Vector:
return np.array(sn)


class ConvertFluxToMags(VectorAction):
"""Turn nano janskies into magnitudes."""

vectorKey = Field[str](doc="Key of flux vector to convert to mags")
fluxUnit = Field[str](doc="Astropy unit of flux vector", default="nJy")
returnMillimags = Field[bool](doc="Use millimags or not?", default=False)

def getInputSchema(self) -> KeyedDataSchema:
return ((self.vectorKey, Vector),)

def __call__(self, data: KeyedData, **kwargs) -> Vector:
with np.warnings.catch_warnings(): # type: ignore
np.warnings.filterwarnings("ignore", r"invalid value encountered") # type: ignore
np.warnings.filterwarnings("ignore", r"divide by zero") # type: ignore
vec = cast(Vector, data[self.vectorKey.format(**kwargs)])
mags = (np.array(vec) * u.Unit(self.fluxUnit)).to(u.ABmag).value # type: ignore
if self.returnMillimags:
mags *= 1000
return mags


class MagDiff(VectorAction):
"""Calculate the difference between two magnitudes;
each magnitude is derived from a flux column.
Expand Down Expand Up @@ -274,6 +276,25 @@ def __call__(self, data: KeyedData, **kwargs) -> Vector:
return np.array(diff - correction.value)


class RAcosDec(VectorAction):
"""Construct a vector of RA*cos(Dec) in order to have commensurate values
between RA and Dec."""

raKey = Field[str](doc="RA coordinate", default="coord_ra")
decKey = Field[str](doc="Dec coordinate", default="coord_dec")

def getInputSchema(self) -> KeyedDataSchema:
return ((self.decKey, Vector), (self.raKey, Vector))

def __call__(self, data: KeyedData, **kwargs) -> Vector:
ra = data[self.raKey]
dec = data[self.decKey]
return ra.to_numpy() * np.cos((dec.to_numpy() * u.degree).to(u.radian).value)


# Statistical vectorActions


class PerGroupStatistic(VectorAction):
"""Compute per-group statistic values and return result as a vector with
one element per group. The computed statistic can be any function accepted
Expand Down Expand Up @@ -313,19 +334,3 @@ def __call__(self, data: KeyedData, **kwargs) -> Vector:

result = joinedDf["value_individual"] - joinedDf["value_group"]
return np.array(result)


class RAcosDec(VectorAction):
"""Construct a vector of RA*cos(Dec) in order to have commensurate values
between RA and Dec."""

raKey = Field[str](doc="RA coordinate", default="coord_ra")
decKey = Field[str](doc="Dec coordinate", default="coord_dec")

def getInputSchema(self) -> KeyedDataSchema:
return ((self.decKey, Vector), (self.raKey, Vector))

def __call__(self, data: KeyedData, **kwargs) -> Vector:
ra = data[self.raKey]
dec = data[self.decKey]
return ra.to_numpy() * np.cos((dec.to_numpy() * u.degree).to(u.radian).value)
128 changes: 74 additions & 54 deletions tests/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,72 @@ def _checkSchema(self, action, truth):
schema = sorted([col for col, colType in action.getInputSchema()])
self.assertEqual(schema, truth)

# VectorActions with their own files

def testCalcBinnedStats(self):
selector = RangeSelector(key="r_vector", minimum=0, maximum=self.size + 1)
prefix = "a_"
stats = CalcBinnedStatsAction(name_prefix=prefix, selector_range=selector, key_vector="r_vector")
result = stats(self.data)
median = (1 + self.size) / 2.0
truth = {
stats.name_mask: np.ones(self.size),
stats.name_median: median,
stats.name_sigmaMad: 1.482602218505602 * np.median(np.abs(self.data["r_vector"] - median)),
stats.name_count: self.size,
stats.name_select_maximum: self.size,
stats.name_select_median: median,
stats.name_select_minimum: 1,
"range_maximum": self.size + 1,
"range_minimum": 0,
}
self.assertEqual(list(result.keys()), list(truth.keys()))

self.assertAlmostEqual(result[stats.name_sigmaMad], truth[stats.name_sigmaMad])
del truth[stats.name_sigmaMad]

np.testing.assert_array_equal(result[stats.name_mask], truth[stats.name_mask])
del truth[stats.name_mask]

for key, value in truth.items():
self.assertEqual(result[key], value, key)

# def testCalcRhoStatistics(self): TODO: implement

def testCalcShapeSize(self):
xx = self.data["r_ixx"]
yy = self.data["r_iyy"]
xy = self.data["r_ixy"]

# Test determinant with defaults
action = CalcShapeSize()
result = action(self.data, band="r")
schema = [col for col, colType in action.getInputSchema()]
self.assertEqual(sorted(schema), ["{band}_ixx", "{band}_ixy", "{band}_iyy"])
truth = 0.25 * (xx * yy - xy**2)
np.testing.assert_array_almost_equal(result, truth)

# Test trace with columns specified
action = CalcShapeSize(
colXx="{band}_iixx",
colYy="{band}_iiyy",
colXy="{band}_iixy",
sizeType="trace",
)
result = action(self.data, band="g")
schema = [col for col, colType in action.getInputSchema()]
self.assertEqual(sorted(schema), ["{band}_iixx", "{band}_iiyy"])
truth = np.sqrt(0.5 * (xx + yy))
np.testing.assert_array_almost_equal(result, truth)

# def testCalcE(self): TODO: implement

# def testCalcEDiff(self): TODO: implement

# def testCalcE1(self): TODO: implement

# def testCalcE2(self): TODO: implement

# MathActions

def _testMath(self, ActionType, truth, compare_exact: bool = False):
Expand Down Expand Up @@ -183,18 +249,18 @@ def testFractionalDifference(self):

# def testLoadVector(self): TODO: implement

# def MultiCriteriaDownselectVector(self): TODO: implement

def testDownselectVector(self):
selector = FlagSelector(selectWhenTrue=["{band}_flag"])
action = DownselectVector(vectorKey="{band}_vector", selector=selector)
result = action(self.data, band="r")
self._checkSchema(action, ["{band}_flag", "{band}_vector"])
np.testing.assert_array_equal(result, np.array([1, 3, 4, 5]))

# def MultiCriteriaDownselectVector(self): TODO: implement
# def testMultiCriteriaDownselectVector(self): TODO: implement

# def testConvertUnits(self): TODO: implement

# def ConvertUnits(self): TODO: implement
# def testCalcSn(self): TODO: implement

def testConvertFluxToMags(self):
truth = [
Expand Down Expand Up @@ -277,59 +343,13 @@ def testExtinctionCorrectedMagDiff(self):
self._checkSchema(action, ["E(B-V)", "g_vector", "r_vector"])
np.testing.assert_array_almost_equal(result, truth.value)

def testCalcBinnedStats(self):
selector = RangeSelector(key="r_vector", minimum=0, maximum=self.size + 1)
prefix = "a_"
stats = CalcBinnedStatsAction(name_prefix=prefix, selector_range=selector, key_vector="r_vector")
result = stats(self.data)
median = (1 + self.size) / 2.0
truth = {
stats.name_mask: np.ones(self.size),
stats.name_median: median,
stats.name_sigmaMad: 1.482602218505602 * np.median(np.abs(self.data["r_vector"] - median)),
stats.name_count: self.size,
stats.name_select_maximum: self.size,
stats.name_select_median: median,
stats.name_select_minimum: 1,
"range_maximum": self.size + 1,
"range_minimum": 0,
}
self.assertEqual(list(result.keys()), list(truth.keys()))

self.assertAlmostEqual(result[stats.name_sigmaMad], truth[stats.name_sigmaMad])
del truth[stats.name_sigmaMad]

np.testing.assert_array_equal(result[stats.name_mask], truth[stats.name_mask])
del truth[stats.name_mask]
# def testRAcosDec(self): TODO: implement

for key, value in truth.items():
self.assertEqual(result[key], value, key)

def testCalcShapeSize(self):
xx = self.data["r_ixx"]
yy = self.data["r_iyy"]
xy = self.data["r_ixy"]
# Statistical vectorActions

# Test determinant with defaults
action = CalcShapeSize()
result = action(self.data, band="r")
schema = [col for col, colType in action.getInputSchema()]
self.assertEqual(sorted(schema), ["{band}_ixx", "{band}_ixy", "{band}_iyy"])
truth = 0.25 * (xx * yy - xy**2)
np.testing.assert_array_almost_equal(result, truth)
# def testPerGroupStatistic(self): TODO: implement

# Test trace with columns specified
action = CalcShapeSize(
colXx="{band}_iixx",
colYy="{band}_iiyy",
colXy="{band}_iixy",
sizeType="trace",
)
result = action(self.data, band="g")
schema = [col for col, colType in action.getInputSchema()]
self.assertEqual(sorted(schema), ["{band}_iixx", "{band}_iiyy"])
truth = np.sqrt(0.5 * (xx + yy))
np.testing.assert_array_almost_equal(result, truth)
# def testResidualWithPerGroupStatistic(self): TODO: implement


class TestVectorSelectors(unittest.TestCase):
Expand Down

0 comments on commit c598c2e

Please sign in to comment.