LSSTApplications  17.0+11,17.0+34,17.0+56,17.0+57,17.0+59,17.0+7,17.0-1-g377950a+33,17.0.1-1-g114240f+2,17.0.1-1-g4d4fbc4+28,17.0.1-1-g55520dc+49,17.0.1-1-g5f4ed7e+52,17.0.1-1-g6dd7d69+17,17.0.1-1-g8de6c91+11,17.0.1-1-gb9095d2+7,17.0.1-1-ge9fec5e+5,17.0.1-1-gf4e0155+55,17.0.1-1-gfc65f5f+50,17.0.1-1-gfc6fb1f+20,17.0.1-10-g87f9f3f+1,17.0.1-11-ge9de802+16,17.0.1-16-ga14f7d5c+4,17.0.1-17-gc79d625+1,17.0.1-17-gdae4c4a+8,17.0.1-2-g26618f5+29,17.0.1-2-g54f2ebc+9,17.0.1-2-gf403422+1,17.0.1-20-g2ca2f74+6,17.0.1-23-gf3eadeb7+1,17.0.1-3-g7e86b59+39,17.0.1-3-gb5ca14a,17.0.1-3-gd08d533+40,17.0.1-30-g596af8797,17.0.1-4-g59d126d+4,17.0.1-4-gc69c472+5,17.0.1-6-g5afd9b9+4,17.0.1-7-g35889ee+1,17.0.1-7-gc7c8782+18,17.0.1-9-gc4bbfb2+3,w.2019.22
LSSTDataManagementBasePackage
test.py
Go to the documentation of this file.
1 import pickle
2 import unittest
3 
4 import numpy as np
5 from numpy.testing import assert_allclose, assert_array_equal
6 
7 from .channel import Channel
8 from .fitsChan import FitsChan
9 from .polyMap import PolyMap
10 from .xmlChan import XmlChan
11 from .stream import StringStream
12 
13 
14 class ObjectTestCase(unittest.TestCase):
15  """Base class for unit tests of objects
16  """
17 
18  def assertObjectsIdentical(self, obj1, obj2, checkType=True):
19  """Assert that two astshim objects are identical.
20 
21  Identical means the objects are of the same class (if checkType)
22  and all properties are identical (including whether set or defaulted).
23  """
24  if checkType:
25  self.assertIs(type(obj1), type(obj2))
26  self.assertEqual(obj1.show(), obj2.show())
27  self.assertEqual(str(obj1), str(obj2))
28  self.assertEqual(repr(obj1), repr(obj2))
29 
30  def checkCopy(self, obj):
31  """Check that an astshim object can be deep-copied
32  """
33  nobj = obj.getNObject()
34  nref = obj.getRefCount()
35 
36  def copyIter(obj):
37  yield obj.copy()
38  yield type(obj)(obj)
39 
40  for cp in copyIter(obj):
41  self.assertObjectsIdentical(obj, cp)
42  self.assertEqual(obj.getNObject(), nobj + 1)
43  # Object.copy makes a new pointer instead of copying the old one,
44  # so the reference count of the old one does not increase
45  self.assertEqual(obj.getRefCount(), nref)
46  self.assertFalse(obj.same(cp))
47  self.assertEqual(cp.getNObject(), nobj + 1)
48  self.assertEqual(cp.getRefCount(), 1)
49  # changing an attribute of the copy does not affect the original
50  originalIdent = obj.ident
51  cp.ident = obj.ident + " modified"
52  self.assertEqual(obj.ident, originalIdent)
53 
54  del cp
55  self.assertEqual(obj.getNObject(), nobj)
56  self.assertEqual(obj.getRefCount(), nref)
57 
58  def checkPersistence(self, obj, typeFromChannel=None):
59  """Check that an astshim object can be persisted and unpersisted
60 
61  @param[in] obj Object to be checked
62  @param[in] typeFromChannel Type of object expected to be read from
63  a channel (since some thin wrapper types are read
64  as the underlying type); None if the original type
65 
66  Check persistence using Channel, FitsChan (with native encoding,
67  as the only encoding compatible with all AST objects), XmlChan
68  and pickle.
69  """
70  for channelType, options in (
71  (Channel, ""),
72  (FitsChan, "Encoding=Native"),
73  (XmlChan, ""),
74  ):
75  ss = StringStream()
76  chan = channelType(ss, options)
77  chan.write(obj)
78  ss.sinkToSource()
79  if channelType is FitsChan:
80  chan.clearCard()
81  obj_copy = chan.read()
82  if typeFromChannel is not None:
83  self.assertIs(type(obj_copy), typeFromChannel)
84  self.assertObjectsIdentical(obj, obj_copy, checkType=False)
85  else:
86  self.assertObjectsIdentical(obj, obj_copy)
87 
88  obj_copy = pickle.loads(pickle.dumps(obj))
89  self.assertObjectsIdentical(obj, obj_copy)
90 
91 
93 
94  """Base class for unit tests of mappings
95  """
96 
97  def checkRoundTrip(self, amap, poslist, rtol=1e-05, atol=1e-08):
98  """Check that a mapping's reverse transform is the opposite of forward
99 
100  amap is the mapping to test
101  poslist is a list of input position for a forward transform;
102  a numpy array with shape [nin, num points]
103  or collection that can be cast to same
104  rtol is the relative tolerance for numpy.testing.assert_allclose
105  atol is the absolute tolerance for numpy.testing.assert_allclose
106  """
107  poslist = np.array(poslist, dtype=float)
108  if len(poslist.shape) == 1:
109  # supplied data was a single list of points
110  poslist.shape = (1, len(poslist))
111  # forward with applyForward, inverse with applyInverse
112  to_poslist = amap.applyForward(poslist)
113  rt_poslist = amap.applyInverse(to_poslist)
114  assert_allclose(poslist, rt_poslist, rtol=rtol, atol=atol)
115 
116  # forward with applyForward, inverse with inverted().applyForward
117  amapinv = amap.inverted()
118  rt2_poslist = amapinv.applyForward(to_poslist)
119  assert_allclose(poslist, rt2_poslist, rtol=rtol, atol=atol)
120 
121  # forward and inverse with a compound map of amap.then(amap.inverted())
122  acmp = amap.then(amapinv)
123  assert_allclose(poslist, acmp.applyForward(poslist), rtol=rtol, atol=atol)
124 
125  # test vector versions of forward and inverse
126  posvec = list(poslist.flat)
127  to_posvec = amap.applyForward(posvec)
128  # cast to_poslist to np.array because if poslist has 1 axis then
129  # a list is returned, which has no `flat` attribute
130  assert_allclose(to_posvec, list(to_poslist.flat), rtol=rtol, atol=atol)
131 
132  rt_posvec = amap.applyInverse(to_posvec)
133  assert_allclose(posvec, rt_posvec, rtol=rtol, atol=atol)
134 
135  def checkBasicSimplify(self, amap):
136  """Check basic simplfication for a reversible mapping
137 
138  Check the following:
139  - A compound mapping of a amap and its inverse simplifies to a unit amap
140  - A compound mapping of a amap and a unit amap simplifies to the original amap
141  """
142  amapinv = amap.inverted()
143  cmp1 = amap.then(amapinv)
144  unit1 = cmp1.simplified()
145  self.assertEqual(unit1.className, "UnitMap")
146  self.assertEqual(amap.nIn, cmp1.nIn)
147  self.assertEqual(amap.nIn, cmp1.nOut)
148  self.assertEqual(cmp1.nIn, unit1.nIn)
149  self.assertEqual(cmp1.nOut, unit1.nOut)
150 
151  cmp2 = amapinv.then(amap)
152  unit2 = cmp2.simplified()
153  self.assertEqual(unit2.className, "UnitMap")
154  self.assertEqual(amapinv.nIn, cmp2.nIn)
155  self.assertEqual(amapinv.nIn, cmp2.nOut)
156  self.assertEqual(cmp2.nIn, unit2.nIn)
157  self.assertEqual(cmp2.nOut, unit2.nOut)
158 
159  for ma, mb, desmap3 in (
160  (unit1, amap, amap),
161  (amap, unit2, amap),
162  (unit2, amapinv, amapinv),
163  (amapinv, unit1, amapinv),
164  ):
165  cmp3 = ma.then(mb)
166  cmp3simp = cmp3.simplified()
167  self.assertEqual(cmp3simp.className, amap.simplified().className)
168  self.assertEqual(ma.nIn, cmp3.nIn)
169  self.assertEqual(mb.nOut, cmp3.nOut)
170  self.assertEqual(cmp3.nIn, cmp3simp.nIn)
171  self.assertEqual(cmp3.nOut, cmp3simp.nOut)
172 
173  def checkMappingPersistence(self, amap, poslist):
174  """Check that a mapping gives identical answers to unpersisted copy
175 
176  poslist is a list of input position for a forward transform
177  (if it exists), or the inverse transform (if not).
178  A numpy array with shape [nAxes, num points]
179  or collection that can be cast to same
180 
181  Checks each direction, if present. However, for generality,
182  does not check that the two directions are inverses of each other;
183  call checkRoundTrip for that.
184 
185  Does everything checkPersistence does, so no need to call both.
186  """
187  for channelType, options in (
188  (Channel, ""),
189  (FitsChan, "Encoding=Native"),
190  (XmlChan, ""),
191  ):
192  ss = StringStream()
193  chan = Channel(ss)
194  chan.write(amap)
195  ss.sinkToSource()
196  amap_copy = chan.read()
197  self.assertEqual(amap.className, amap_copy.className)
198  self.assertEqual(amap.show(), amap_copy.show())
199  self.assertEqual(str(amap), str(amap_copy))
200  self.assertEqual(repr(amap), repr(amap_copy))
201 
202  if amap.hasForward:
203  outPoslist = amap.applyForward(poslist)
204  assert_array_equal(outPoslist, amap_copy.applyForward(poslist))
205 
206  if amap.hasInverse:
207  assert_array_equal(amap.applyInverse(outPoslist),
208  amap_copy.applyInverse(outPoslist))
209 
210  elif amap.hasInverse:
211  assert_array_equal(amap.applyInverse(poslist),
212  amap_copy.applyInverse(poslist))
213 
214  else:
215  raise RuntimeError("mapping has neither forward nor inverse transform")
216 
217  def checkMemoryForCompoundObject(self, obj1, obj2, cmpObj, isSeries):
218  """Check the memory usage for a compoundObject
219 
220  obj1: first object in compound object
221  obj2: second object in compound object
222  cmpObj: compound object (SeriesMap, ParallelMap, CmpMap or CmpFrame)
223  isSeries: is compound object in series? None to not test (e.g. CmpFrame)
224  """
225  # if obj1 and obj2 are the same type then copying the compound object
226  # will increase the NObject of each by 2, otherwise 1
227  deltaObj = 2 if type(obj1) == type(obj2) else 1
228 
229  initialNumObj1 = obj1.getNObject()
230  initialNumObj2 = obj2.getNObject()
231  initialNumCmpObj = cmpObj.getNObject()
232  initialRefCountObj1 = obj1.getRefCount()
233  initialRefCountObj2 = obj2.getRefCount()
234  initialRefCountCmpObj = cmpObj.getRefCount()
235  self.assertEqual(obj1.getNObject(), initialNumObj1)
236  self.assertEqual(obj2.getNObject(), initialNumObj2)
237  if isSeries is not None:
238  if isSeries is True:
239  self.assertTrue(cmpObj.series)
240  elif isSeries is False:
241  self.assertFalse(cmpObj.series)
242 
243  # making a deep copy should increase the object count of the contained objects
244  # but should not affect the reference count
245  cp = cmpObj.copy()
246  self.assertEqual(cmpObj.getRefCount(), initialRefCountCmpObj)
247  self.assertEqual(cmpObj.getNObject(), initialNumCmpObj + 1)
248  self.assertEqual(obj1.getRefCount(), initialRefCountObj1)
249  self.assertEqual(obj2.getRefCount(), initialRefCountObj2)
250  self.assertEqual(obj1.getNObject(), initialNumObj1 + deltaObj)
251  self.assertEqual(obj2.getNObject(), initialNumObj2 + deltaObj)
252 
253  # deleting the deep copy should restore ref count and nobject
254  del cp
255  self.assertEqual(cmpObj.getRefCount(), initialRefCountCmpObj)
256  self.assertEqual(cmpObj.getNObject(), initialNumCmpObj)
257  self.assertEqual(obj1.getRefCount(), initialRefCountObj1)
258  self.assertEqual(obj1.getNObject(), initialNumObj1)
259  self.assertEqual(obj2.getRefCount(), initialRefCountObj2)
260  self.assertEqual(obj2.getNObject(), initialNumObj2)
261 
262 
263 def makePolyMapCoeffs(nIn, nOut):
264  """Make an array of coefficients for astshim.PolyMap for the following equation:
265 
266  fj(x) = C0j x0^2 + C1j x1^2 + C2j x2^2 + ... + CNj xN^2
267  where:
268  * i ranges from 0 to N=nIn-1
269  * j ranges from 0 to nOut-1,
270  * Cij = 0.001 (i+j+1)
271  """
272  baseCoeff = 0.001
273  forwardCoeffs = []
274  for out_ind in range(nOut):
275  coeffOffset = baseCoeff * out_ind
276  for in_ind in range(nIn):
277  coeff = baseCoeff * (in_ind + 1) + coeffOffset
278  coeffArr = [coeff, out_ind + 1] + [2 if i == in_ind else 0 for i in range(nIn)]
279  forwardCoeffs.append(coeffArr)
280  return np.array(forwardCoeffs, dtype=float)
281 
282 
283 def makeTwoWayPolyMap(nIn, nOut):
284  """Make an astshim.PolyMap suitable for testing
285 
286  The forward transform is as follows:
287  fj(x) = C0j x0^2 + C1j x1^2 + C2j x2^2 + ... + CNj xN^2 where Cij = 0.001 (i+j+1)
288 
289  The reverse transform is the same equation with i and j reversed
290  thus it is NOT the inverse of the forward direction,
291  but is something that can be easily evaluated.
292 
293  The equation is chosen for the following reasons:
294  - It is well defined for any positive value of nIn, nOut
295  - It stays small for small x, to avoid wraparound of angles for SpherePoint endpoints
296  """
297  forwardCoeffs = makePolyMapCoeffs(nIn, nOut)
298  reverseCoeffs = makePolyMapCoeffs(nOut, nIn)
299  polyMap = PolyMap(forwardCoeffs, reverseCoeffs)
300  assert polyMap.nIn == nIn
301  assert polyMap.nOut == nOut
302  assert polyMap.hasForward
303  assert polyMap.hasInverse
304  return polyMap
305 
306 
307 def makeForwardPolyMap(nIn, nOut):
308  """Make an astshim.PolyMap suitable for testing
309 
310  The forward transform is the same as for `makeTwoWayPolyMap`.
311  This map does not have a reverse transform.
312 
313  The equation is chosen for the following reasons:
314  - It is well defined for any positive value of nIn, nOut
315  - It stays small for small x, to avoid wraparound of angles for SpherePoint endpoints
316  """
317  forwardCoeffs = makePolyMapCoeffs(nIn, nOut)
318  polyMap = PolyMap(forwardCoeffs, nOut, "IterInverse=0")
319  assert polyMap.nIn == nIn
320  assert polyMap.nOut == nOut
321  assert polyMap.hasForward
322  assert not polyMap.hasInverse
323  return polyMap
def checkMappingPersistence(self, amap, poslist)
Definition: test.py:173
def checkCopy(self, obj)
Definition: test.py:30
table::Key< int > type
Definition: Detector.cc:167
def assertObjectsIdentical(self, obj1, obj2, checkType=True)
Definition: test.py:18
def makeTwoWayPolyMap(nIn, nOut)
Definition: test.py:283
def checkMemoryForCompoundObject(self, obj1, obj2, cmpObj, isSeries)
Definition: test.py:217
def checkBasicSimplify(self, amap)
Definition: test.py:135
def makePolyMapCoeffs(nIn, nOut)
Definition: test.py:263
def checkRoundTrip(self, amap, poslist, rtol=1e-05, atol=1e-08)
Definition: test.py:97
daf::base::PropertyList * list
Definition: fits.cc:885
def makeForwardPolyMap(nIn, nOut)
Definition: test.py:307
def checkPersistence(self, obj, typeFromChannel=None)
Definition: test.py:58