File indexing completed on 2026-09-26 08:04:21
0001 """Pure-Python coverage for the Examples-module EDM bindings (measurements,
0002 tracks/track states, track containers) that needs no detector or simulation.
0003 Mirrors Python/Core/tests/test_event_data.py, but for acts.examples types.
0004 """
0005
0006 import numpy as np
0007 import pytest
0008
0009 import acts
0010 import acts.examples as ae
0011
0012
0013 def test_measurement_creation():
0014 meas_properties = [
0015 {
0016 "geometryId": acts.GeometryIdentifier(798),
0017 "indices": [0],
0018 "parameters": [1.0],
0019 "covariance": [0.1],
0020 },
0021 {
0022 "geometryId": acts.GeometryIdentifier(123),
0023 "indices": [0, 1],
0024 "parameters": [1.0, 2.0],
0025 "covariance": [0.1, 0.1],
0026 },
0027 {
0028 "geometryId": acts.GeometryIdentifier(456),
0029 "indices": [0, 1, 4],
0030 "parameters": [3.0, 4.0, 5.0],
0031 "covariance": [0.2, 0.2, 0.2],
0032 },
0033 ]
0034
0035 container = acts.examples.MeasurementContainer()
0036 container.reserve(3)
0037 for meas_prop in meas_properties:
0038 meas = container.emplaceMeasurement(**meas_prop)
0039
0040 for i in range(len(meas_properties)):
0041 meas = container[i]
0042 meas_prop = meas_properties[i]
0043
0044 dim = len(meas_prop["indices"])
0045 assert meas.geometryId.value == meas_prop["geometryId"].value
0046 assert [meas_prop["indices"][i] == meas.subspaceIndices[i] for i in range(dim)]
0047 indices = meas_prop["indices"]
0048 assert [
0049 meas_prop["parameters"][i] == meas.fullParameters[indices[i]]
0050 for i in range(dim)
0051 ]
0052 assert [
0053 meas_prop["covariance"][i] == meas.fullCovariance[indices[i], indices[i]]
0054 for i in range(dim)
0055 ]
0056
0057 assert len(container) == 3
0058
0059
0060 subset = acts.examples.MeasurementSubset(container, [0, 2])
0061 assert len(subset) == 2
0062
0063
0064 subset_list = list(subset)
0065 assert len(subset_list) == 2
0066 assert subset_list[0].index == 0
0067 assert subset_list[1].index == 2
0068
0069
0070 assert subset[0].index == 0
0071 assert subset[1].index == 2
0072
0073
0074 assert (
0075 subset.getMeasurement(0).geometryId.value
0076 == meas_properties[0]["geometryId"].value
0077 )
0078 assert (
0079 subset.getMeasurement(2).geometryId.value
0080 == meas_properties[2]["geometryId"].value
0081 )
0082
0083
0084 for pos, orig_idx in enumerate([0, 2]):
0085 meas = subset[pos]
0086 meas_prop = meas_properties[orig_idx]
0087 dim = len(meas_prop["indices"])
0088 assert meas.geometryId.value == meas_prop["geometryId"].value
0089 indices = meas_prop["indices"]
0090 assert [meas.subspaceIndices[i] == meas_prop["indices"][i] for i in range(dim)]
0091 assert [
0092 meas.fullParameters[indices[i]] == meas_prop["parameters"][i]
0093 for i in range(dim)
0094 ]
0095
0096
0097 def test_measurement_map_creation():
0098 from acts.examples import (
0099 MeasurementParticlesMap,
0100 MeasurementSimHitsMap,
0101 ParticleMeasurementsMap,
0102 SimBarcode,
0103 SimHitMeasurementsMap,
0104 )
0105
0106
0107 m = MeasurementSimHitsMap()
0108 m.insert(0, 10)
0109 m.insert(0, 11)
0110 m.insert(1, 20)
0111 assert len(m) == 3
0112
0113 assert 0 in m
0114 assert 2 not in m
0115
0116 vals = m.valuesFor(0)
0117 assert sorted(vals) == [10, 11]
0118 assert m.valuesFor(1) == [20]
0119 assert m.valuesFor(99) == []
0120
0121 pairs = list(m)
0122 assert len(pairs) == 3
0123 assert all(isinstance(k, int) and isinstance(v, int) for k, v in pairs)
0124
0125 inv = m.invert()
0126 assert isinstance(inv, SimHitMeasurementsMap)
0127 assert len(inv) == 3
0128 assert inv.valuesFor(10) == [0]
0129 assert inv.valuesFor(11) == [0]
0130 assert inv.valuesFor(20) == [1]
0131
0132
0133 bc0 = SimBarcode()
0134 bc0.particle = 1
0135 bc1 = SimBarcode()
0136 bc1.particle = 2
0137 mp = MeasurementParticlesMap()
0138 mp.insert(0, bc0)
0139 mp.insert(0, bc1)
0140 mp.insert(1, bc0)
0141 assert len(mp) == 3
0142
0143 assert mp.valuesFor(0) == [bc0, bc1]
0144
0145 inv_p = mp.invert()
0146 assert isinstance(inv_p, ParticleMeasurementsMap)
0147 assert len(inv_p) == 3
0148
0149
0150
0151
0152
0153
0154
0155
0156
0157
0158 def _fill_track(track):
0159 surface = acts.Surface.createPerigee(acts.Vector3(0, 0, 0))
0160 geo_id = acts.GeometryIdentifier()
0161
0162 track.referenceSurface = surface
0163 track.parameters = acts.BoundVector(0.1, 0.2, 0.3, 1.4, 0.01, 0.0)
0164 track.covariance = acts.BoundMatrix.Identity()
0165 track.particleHypothesis = acts.ParticleHypothesis.muon
0166 track.nMeasurements = 3
0167 track.nHoles = 1
0168 track.nOutliers = 2
0169 track.nSharedHits = 1
0170 track.chi2 = 4.5
0171 track.nDoF = 5
0172
0173 state = track.appendTrackState(ae.TrackStatePropMask.All)
0174 state.typeFlags.setIsMeasurement()
0175 state.referenceSurface = surface
0176 state.uncalibratedSourceLink = ae.IndexSourceLink(geo_id, 0).toSourceLink()
0177 state.predicted = acts.BoundVector(0.1, 0.2, 0.3, 1.4, 0.01, 0.0)
0178 state.predictedCovariance = acts.BoundMatrix.Identity()
0179 state.filtered = acts.BoundVector(0.15, 0.25, 0.3, 1.4, 0.01, 0.0)
0180 state.filteredCovariance = acts.BoundMatrix.Identity()
0181 state.smoothed = acts.BoundVector(0.12, 0.22, 0.3, 1.4, 0.01, 0.0)
0182 state.smoothedCovariance = acts.BoundMatrix.Identity()
0183 state.jacobian = acts.BoundMatrix.Identity()
0184 state.chi2 = 1.5
0185 state.pathLength = 12.0
0186 state.allocateCalibrated(2)
0187 state.effectiveCalibrated = [1.0, 2.0]
0188 state.effectiveCalibratedCovariance = [[1.0, 0.0], [0.0, 1.0]]
0189 state.setProjectorSubspaceIndices([0, 1])
0190
0191 return geo_id, state
0192
0193
0194 def _check_track(track, state, *, linked):
0195 assert track.index == 0
0196 assert track.tipIndex == state.index
0197 assert track.stemIndex == (state.index if linked else ae.kTrackIndexInvalid)
0198 assert track.hasReferenceSurface
0199 assert track.parameters[0] == pytest.approx(0.1)
0200 assert track.covariance[0, 0] == pytest.approx(1.0)
0201 assert track.particleHypothesis.absolutePdg == acts.PdgParticle.eMuon
0202 assert track.nMeasurements == 3
0203 assert track.nHoles == 1
0204 assert track.nOutliers == 2
0205 assert track.nSharedHits == 1
0206 assert track.chi2 == pytest.approx(4.5)
0207 assert track.nDoF == 5
0208 assert track.nTrackStates == 1
0209 assert track.isForwardLinked is linked
0210
0211
0212 def _check_state(state, geo_id, *, is_const):
0213 assert state.hasReferenceSurface
0214 assert state.referenceSurface is not None
0215 assert state.hasPredicted
0216 assert state.hasFiltered
0217 assert state.hasSmoothed
0218 assert state.hasJacobian
0219 assert state.hasProjector
0220 assert state.hasUncalibratedSourceLink
0221 assert state.hasCalibrated
0222 assert state.calibratedSize == 2
0223 assert state.effectiveCalibrated == pytest.approx([1.0, 2.0])
0224 assert np.allclose(state.effectiveCalibratedCovariance, [[1.0, 0.0], [0.0, 1.0]])
0225 assert list(state.projectorSubspaceIndices) == [0, 1]
0226 assert state.chi2 == pytest.approx(1.5)
0227 assert state.pathLength == pytest.approx(12.0)
0228 assert state.predicted[0] == pytest.approx(0.1)
0229 assert state.filtered[0] == pytest.approx(0.15)
0230 assert state.smoothed[0] == pytest.approx(0.12)
0231 assert all(
0232 state.jacobian[i, j] == pytest.approx(1.0 if i == j else 0.0)
0233 for i in range(6)
0234 for j in range(6)
0235 )
0236 assert state.parameters[0] == pytest.approx(state.smoothed[0])
0237 assert state.typeFlags.isMeasurement
0238
0239
0240
0241 isl = ae.IndexSourceLink.fromSourceLink(state.uncalibratedSourceLink)
0242 assert isl.geometryId() == geo_id
0243
0244 proxy_type = ae.ConstTrackStateProxy if is_const else ae.TrackStateProxy
0245 assert isinstance(state, proxy_type)
0246
0247
0248 def test_mutable_track_proxy():
0249 tc = ae.TrackContainer()
0250 track = tc.makeTrack()
0251 geo_id, state = _fill_track(track)
0252
0253 _check_track(track, state, linked=False)
0254 _check_state(state, geo_id, is_const=False)
0255
0256
0257 track.linkForward()
0258 fwd = list(track.trackStates)
0259 rev = list(track.trackStatesReversed)
0260 assert len(fwd) == len(rev) == 1
0261 assert fwd[0].index == rev[0].index == state.index
0262
0263 assert track.hasColumn("doesNotExist") is False
0264
0265
0266 def test_const_mutable_parity():
0267 """The const and mutable track/track-state proxies must read back
0268 identical values for every property they share -- the parity the
0269 templated binder is supposed to guarantee by construction."""
0270 tc = ae.TrackContainer()
0271 track = tc.makeTrack()
0272 geo_id, _ = _fill_track(track)
0273 track.linkForward()
0274
0275 const_tc = tc.makeConst()
0276 const_track = const_tc[0]
0277 const_state = next(iter(const_track.trackStatesReversed))
0278
0279 _check_track(const_track, const_state, linked=True)
0280 _check_state(const_state, geo_id, is_const=True)
0281
0282 assert isinstance(const_track, ae.ConstTrackProxy)
0283 assert const_track.hasColumn("doesNotExist") is False
0284
0285
0286 def test_const_to_mutable_round_trip():
0287 tc = ae.TrackContainer()
0288 track = tc.makeTrack()
0289 _fill_track(track)
0290 track.linkForward()
0291
0292 const_tc = tc.makeConst()
0293
0294
0295
0296 for mutable_copy in (ae.TrackContainer(const_tc), const_tc.makeMutable()):
0297 assert len(mutable_copy) == 1
0298 copy_track = mutable_copy[0]
0299 assert copy_track.chi2 == pytest.approx(4.5)
0300 assert copy_track.nMeasurements == 3
0301
0302 copy_track.chi2 = 99.0
0303 assert copy_track.chi2 == pytest.approx(99.0)
0304
0305 assert const_tc[0].chi2 == pytest.approx(4.5)
0306
0307
0308 def test_track_container_iteration_and_getitem():
0309 tc = ae.TrackContainer()
0310 for i in range(3):
0311 t = tc.makeTrack()
0312 t.chi2 = float(i)
0313
0314 assert len(tc) == 3
0315 assert [t.chi2 for t in tc] == [0.0, 1.0, 2.0]
0316 assert tc[1].chi2 == pytest.approx(1.0)
0317 assert tc.getTrack(2).chi2 == pytest.approx(2.0)
0318
0319 const_tc = tc.makeConst()
0320 assert len(const_tc) == 3
0321 assert [t.chi2 for t in const_tc] == [0.0, 1.0, 2.0]
0322 assert const_tc[1].chi2 == pytest.approx(1.0)
0323 assert const_tc.getTrack(2).chi2 == pytest.approx(2.0)
0324
0325
0326 def test_track_container_soa_numpy_views():
0327 tc = ae.TrackContainer()
0328 for i in range(3):
0329 t = tc.makeTrack()
0330 t.parameters = acts.BoundVector(float(i), 0.0, 0.0, 1.4, 0.01, 0.0)
0331 t.chi2 = float(i) + 0.5
0332 t.nOutliers = i
0333 t.nSharedHits = 2 * i
0334
0335 const_tc = tc.makeConst()
0336
0337 for i, track in enumerate(const_tc):
0338 assert const_tc.parameters[i, 0] == pytest.approx(track.parameters[0])
0339 assert const_tc.chi2[i] == pytest.approx(track.chi2)
0340 assert const_tc.nOutliers[i] == track.nOutliers
0341 assert const_tc.nSharedHits[i] == track.nSharedHits
0342
0343
0344 def test_ensure_dynamic_columns_and_copy_from():
0345 tc = ae.TrackContainer()
0346 track = tc.makeTrack()
0347 _fill_track(track)
0348 track.linkForward()
0349 const_tc = tc.makeConst()
0350
0351 dst = ae.TrackContainer()
0352 dst.ensureDynamicColumns(const_tc)
0353 dst_track = dst.makeTrack()
0354 dst_track.copyFrom(const_tc[0])
0355
0356 assert dst_track.chi2 == pytest.approx(4.5)
0357 assert dst_track.nMeasurements == 3
0358 assert len(list(dst_track.trackStates)) == 1
0359
0360
0361 def test_any_proxy():
0362 """AnyMutableTrackProxy/AnyConstTrackProxy/AnyMutableTrackStateProxy/
0363 AnyConstTrackStateProxy are type-erased views onto the same underlying
0364 storage as the concrete proxy they were constructed from -- reading them
0365 must agree, and mutating through the Any* handle must be visible back on
0366 the original proxy.
0367
0368 Note: an Any*Proxy constructed from a mutable TrackProxy/TrackStateProxy
0369 must not outlive a TrackContainer.makeConst() call on its container --
0370 that moves the backing storage out, and the Any*Proxy would dangle just
0371 like the original proxy would.
0372 """
0373 tc = ae.TrackContainer()
0374 track = tc.makeTrack()
0375 geo_id, state = _fill_track(track)
0376
0377 any_mut_track = ae.AnyMutableTrackProxy(track)
0378 assert any_mut_track.chi2 == pytest.approx(4.5)
0379 assert any_mut_track.tipIndex == track.tipIndex
0380 assert any_mut_track.parameters[0] == pytest.approx(track.parameters[0])
0381 any_mut_track.chi2 = 99.0
0382 assert track.chi2 == pytest.approx(99.0)
0383 track.chi2 = 4.5
0384
0385 any_mut_state = ae.AnyMutableTrackStateProxy(state)
0386 assert any_mut_state.chi2 == pytest.approx(1.5)
0387 assert any_mut_state.predicted[0] == pytest.approx(state.predicted[0])
0388 isl = ae.IndexSourceLink.fromSourceLink(any_mut_state.uncalibratedSourceLink)
0389 assert isl.geometryId() == geo_id
0390 assert any_mut_state.effectiveCalibrated == pytest.approx([1.0, 2.0])
0391 any_mut_state.chi2 = 88.0
0392 assert state.chi2 == pytest.approx(88.0)
0393 state.chi2 = 1.5
0394
0395 const_tc = tc.makeConst()
0396 const_track = const_tc[0]
0397 const_state = next(iter(const_track.trackStatesReversed))
0398
0399 any_const_track = ae.AnyConstTrackProxy(const_track)
0400 assert any_const_track.chi2 == pytest.approx(4.5)
0401
0402 any_const_state = ae.AnyConstTrackStateProxy(const_state)
0403 assert any_const_state.chi2 == pytest.approx(1.5)