|
|
|||
Warning, file /acts/Core/include/Acts/Propagator/GenericRungeKuttaStepper.hpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 // This file is part of the ACTS project. 0002 // 0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project 0004 // 0005 // This Source Code Form is subject to the terms of the Mozilla Public 0006 // License, v. 2.0. If a copy of the MPL was not distributed with this 0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/. 0008 0009 #pragma once 0010 0011 #include "Acts/Definitions/Algebra.hpp" 0012 #include "Acts/Definitions/Direction.hpp" 0013 #include "Acts/Definitions/Units.hpp" 0014 #include "Acts/EventData/BoundTrackParameters.hpp" 0015 #include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp" 0016 #include "Acts/MagneticField/MagneticFieldProvider.hpp" 0017 #include "Acts/Propagator/ButcherTableau.hpp" 0018 #include "Acts/Propagator/ConstrainedStep.hpp" 0019 #include "Acts/Propagator/NavigationTarget.hpp" 0020 #include "Acts/Propagator/PropagatorTraits.hpp" 0021 #include "Acts/Propagator/StepperOptions.hpp" 0022 #include "Acts/Propagator/StepperStatistics.hpp" 0023 #include "Acts/Propagator/detail/SteppingHelper.hpp" 0024 0025 #include <optional> 0026 #include <stdexcept> 0027 #include <string> 0028 0029 namespace Acts { 0030 0031 class IVolumeMaterial; 0032 0033 /// @brief Reference stepper with a configurable Runge-Kutta tableau 0034 /// 0035 /// The stepper integrates the equation of motion in vacuum for the free 0036 /// parameters y = (r, t, T, q/p), 0037 /// 0038 /// dy/ds = f(y) = (T, 1/beta, (q/p) T x B(r), 0), 0039 /// 0040 /// with any explicit @ref ButcherTableau. It is not optimised. It is meant as a 0041 /// reference to compare other steppers against. 0042 /// 0043 /// The transport jacobian is the exact derivative of the discrete step. The 0044 /// stepper differentiates each stage with the chain rule, 0045 /// 0046 /// dY_i = I + h sum_j a_ij dK_j, dK_i = J_f(Y_i) dY_i, 0047 /// D = I + h sum_i b_i dK_i, 0048 /// 0049 /// so it works for every tableau. Here dY_i, dK_i and D are the derivatives 0050 /// of the stage, of the stage slope and of the step by the free parameters at 0051 /// the start of the step. J_f = df/dy has the non-zero blocks 0052 /// 0053 /// d(r)/dT = I, d(t)/d(q/p) = d(1/beta)/d(q/p), 0054 /// d(T)/dr = (q/p) [T x] dB/dr, d(T)/dT = -(q/p) [B x], 0055 /// d(T)/d(q/p) = T x B. 0056 /// 0057 /// The stepper takes dB/dr from the field provider. If the provider does not 0058 /// implement the gradient, the stepper computes it with central finite 0059 /// differences of the field, so the jacobian is exact up to the O(epsilon^2) 0060 /// error of the gradient, see @ref Options::fieldGradientEpsilon. Without the 0061 /// gradient term (@ref Options::includeFieldGradient) the jacobian is not 0062 /// exact. 0063 /// 0064 /// After the step, the stepper normalises the direction to unit length. This 0065 /// is part of the discrete step, so the stepper also applies its derivative 0066 /// (I - T T^T / |T|^2) / |T| to the direction rows of D. 0067 /// 0068 /// The stepper adapts the step size to @ref StepperPlainOptions::stepTolerance 0069 /// with one of two error estimates, see @ref ErrorEstimation. The error 0070 /// estimate is the maximum norm of the position (mm), the time (mm) and the 0071 /// dimensionless direction components, with the same absolute tolerance. In 0072 /// vacuum q/p does not change, so it does not contribute. The step size 0073 /// control follows Hairer, Nørsett, Wanner, Solving Ordinary Differential 0074 /// Equations I, 2nd ed., Section II.4. 0075 /// 0076 /// As for @ref HelixStepper, a step to the straight-line distance of a surface 0077 /// can pass it, and the next step goes back. A target aborter must accept an 0078 /// intersection that far behind the track. 0079 /// 0080 /// @note The order of a tableau only holds in a smooth field. An interpolated 0081 /// field map is continuous but not differentiable at the cell faces. 0082 /// A step that crosses a face has a local error of order h^2 for every 0083 /// tableau. The number of crossings does not decrease with h, so in a 0084 /// field map every tableau is only accurate to about second order. 0085 /// @note The stepper propagates in vacuum only. It ignores volume material. 0086 class GenericRungeKuttaStepper final { 0087 public: 0088 /// Type alias for bound track parameters 0089 using BoundParameters = BoundTrackParameters; 0090 /// Type alias for jacobian matrix 0091 using Jacobian = BoundMatrix; 0092 /// Type alias for covariance matrix 0093 using Covariance = BoundMatrix; 0094 0095 /// Type alias for the magnetic field and its gradient 0096 using FieldAndGradient = MagneticFieldProvider::FieldAndGradient; 0097 0098 /// Error estimate for the adaptive step size 0099 /// 0100 /// Both use the same tolerance. The embedded estimate is for the lower-order 0101 /// solution, so the propagated solution is usually 100 to 1000 times more 0102 /// accurate than the tolerance. Step doubling estimates the propagated 0103 /// solution itself, so its local error is about the tolerance. 0104 enum class ErrorEstimation { 0105 /// Fixed step size 0106 None, 0107 /// Solution minus embedded solution. Needs embedded weights. 0108 Embedded, 0109 /// One step of h against two steps of h/2, which are propagated. Works 0110 /// for every tableau, at about three times the cost. 0111 StepDoubling, 0112 }; 0113 0114 /// Configuration for the Runge-Kutta stepper. 0115 struct Config { 0116 /// Magnetic field provider 0117 std::shared_ptr<const MagneticFieldProvider> bField; 0118 0119 /// Runge-Kutta tableau. It is shared, so that all steppers can use the 0120 /// static built-in tableaus without a copy. 0121 std::shared_ptr<const ButcherTableau> tableau = 0122 ButcherTableau::dormandPrince54(); 0123 }; 0124 0125 /// Runtime options for Runge-Kutta propagation. 0126 struct Options : public StepperPlainOptions { 0127 /// Constructor 0128 /// @param gctx Geometry context 0129 /// @param mctx Magnetic field context 0130 Options(const GeometryContext& gctx, const MagneticFieldContext& mctx) 0131 : StepperPlainOptions(gctx, mctx) {} 0132 0133 /// Error estimate for the adaptive step size 0134 ErrorEstimation errorEstimation = ErrorEstimation::Embedded; 0135 0136 /// Include the field gradient in the transport jacobian 0137 bool includeFieldGradient = true; 0138 0139 /// Distance of the field lookups for the finite-difference gradient, if 0140 /// the field provider does not implement the gradient. 0141 /// 0142 /// The truncation error of the central difference is of order epsilon^2. 0143 /// The round-off error is of order eps_machine |B| / epsilon, which is 0144 /// about 1e-14 |B| per mm for 10 um. This is far below the gradient of a 0145 /// real field. 10 um is also small compared with a field-map cell, so the 0146 /// lookups usually stay in one cell. 0147 double fieldGradientEpsilon = 10 * UnitConstants::um; 0148 0149 /// Include the jacobian in the error estimate. Its entries have mixed 0150 /// units, and the stepper uses the same tolerance for all of them. 0151 bool jacobianInErrorEstimate = false; 0152 0153 /// Set plain stepper options 0154 /// @param options Plain stepper options 0155 void setPlainOptions(const StepperPlainOptions& options) { 0156 static_cast<StepperPlainOptions&>(*this) = options; 0157 } 0158 }; 0159 0160 /// @brief State for track parameter propagation 0161 /// 0162 /// It contains the stepping information and is provided thread local 0163 /// by the propagator 0164 struct State { 0165 /// Constructor from the options and the field cache 0166 /// 0167 /// @param [in] optionsIn is the configuration of the stepper 0168 /// @param [in] fieldCacheIn is the cache object for the magnetic field 0169 State(const Options& optionsIn, MagneticFieldProvider::Cache fieldCacheIn) 0170 : options(optionsIn), fieldCache(std::move(fieldCacheIn)) {} 0171 0172 /// Configuration options for the stepper 0173 Options options; 0174 0175 /// Internal free vector parameters 0176 FreeVector pars = FreeVector::Zero(); 0177 0178 /// The propagation derivative 0179 FreeVector derivative = FreeVector::Zero(); 0180 0181 /// Jacobian from local to the global frame 0182 BoundToFreeMatrix jacToGlobal = BoundToFreeMatrix::Zero(); 0183 0184 /// Pure transport jacobian part from the Runge-Kutta steps 0185 FreeMatrix jacTransport = FreeMatrix::Identity(); 0186 0187 /// Covariance matrix for track parameter uncertainties, set if the 0188 /// covariance is transported 0189 std::optional<Covariance> cov; 0190 0191 /// Particle hypothesis 0192 ParticleHypothesis particleHypothesis = ParticleHypothesis::pion(); 0193 0194 /// Adaptive step size of the Runge-Kutta steps 0195 ConstrainedStep stepSize; 0196 0197 /// Last performed step (for overstep limit calculation) 0198 double previousStepSize = 0.; 0199 0200 /// Magnetic field at the current position, reused as the first stage of 0201 /// the next step. Reset whenever the position is set from outside. 0202 std::optional<FieldAndGradient> field; 0203 0204 /// Whether @ref field contains the gradient 0205 bool fieldHasGradient = false; 0206 0207 /// Accumulated path length state 0208 double pathAccumulated = 0.; 0209 0210 /// Total number of performed steps 0211 std::size_t nSteps = 0; 0212 0213 /// Statistics of the stepper 0214 StepperStatistics statistics; 0215 0216 /// This caches the current magnetic field cell and stays 0217 /// (and interpolates) within it as long as this is valid. 0218 MagneticFieldProvider::Cache fieldCache; 0219 }; 0220 0221 /// Constructor requires knowledge of the detector's magnetic field 0222 /// @param bField The magnetic field provider 0223 explicit GenericRungeKuttaStepper( 0224 std::shared_ptr<const MagneticFieldProvider> bField); 0225 0226 /// @brief Constructor with configuration 0227 /// @param config The configuration of the stepper 0228 explicit GenericRungeKuttaStepper(const Config& config); 0229 0230 /// Create a state object 0231 /// @param options Stepper options 0232 /// @return State object 0233 /// @throws std::invalid_argument for an embedded error estimate without 0234 /// embedded weights 0235 State makeState(const Options& options) const; 0236 0237 /// Initialize the state from bound track parameters 0238 /// @param state The state to initialize 0239 /// @param par The bound track parameters 0240 void initialize(State& state, const BoundParameters& par) const; 0241 0242 /// Initialize the state from bound parameters 0243 /// @param state The state to initialize 0244 /// @param boundParams Bound track parameters vector 0245 /// @param cov Covariance matrix 0246 /// @param particleHypothesis Particle hypothesis 0247 /// @param surface Reference surface 0248 void initialize(State& state, const BoundVector& boundParams, 0249 const std::optional<BoundMatrix>& cov, 0250 ParticleHypothesis particleHypothesis, 0251 const Surface& surface) const; 0252 0253 /// Get the field for the stepping, it checks first if the access is still 0254 /// within the Cell, and updates the cell if necessary. 0255 /// 0256 /// @param [in,out] state is the propagation state associated with the track 0257 /// the magnetic field cell is used (and potentially updated) 0258 /// @param [in] pos is the field position 0259 /// @return Magnetic field vector 0260 Result<Vector3> getField(State& state, const Vector3& pos) const { 0261 return m_bField->getField(pos, state.fieldCache); 0262 } 0263 0264 /// Current global geometric curvature d(direction)/ds in inverse length. 0265 /// Unchanged under propagation reversal; reflects current momentum and field. 0266 /// @param state Stepping state; the field cache may be updated 0267 /// @return Curvature vector, or a field lookup error 0268 Result<Vector3> curvature(State& state) const { 0269 return detail::magneticCurvature(*this, state); 0270 } 0271 0272 /// Global particle position accessor 0273 /// 0274 /// @param state [in] The stepping state (thread-local cache) 0275 /// @return Position vector 0276 Vector3 position(const State& state) const { 0277 return state.pars.template segment<3>(eFreePos0); 0278 } 0279 0280 /// Momentum direction accessor 0281 /// 0282 /// @param state [in] The stepping state (thread-local cache) 0283 /// @return Direction vector 0284 Vector3 direction(const State& state) const { 0285 return state.pars.template segment<3>(eFreeDir0); 0286 } 0287 0288 /// QoP direction accessor 0289 /// 0290 /// @param state [in] The stepping state (thread-local cache) 0291 /// @return Charge over momentum 0292 double qOverP(const State& state) const { return state.pars[eFreeQOverP]; } 0293 0294 /// Absolute momentum accessor 0295 /// 0296 /// @param state [in] The stepping state (thread-local cache) 0297 /// @return Absolute momentum 0298 double absoluteMomentum(const State& state) const { 0299 return particleHypothesis(state).extractMomentum(qOverP(state)); 0300 } 0301 0302 /// Momentum accessor 0303 /// 0304 /// @param state [in] The stepping state (thread-local cache) 0305 /// @return Momentum vector 0306 Vector3 momentum(const State& state) const { 0307 return absoluteMomentum(state) * direction(state); 0308 } 0309 0310 /// Charge access 0311 /// 0312 /// @param state [in] The stepping state (thread-local cache) 0313 /// @return Particle charge 0314 double charge(const State& state) const { 0315 return particleHypothesis(state).extractCharge(qOverP(state)); 0316 } 0317 0318 /// Particle hypothesis 0319 /// 0320 /// @param state [in] The stepping state (thread-local cache) 0321 /// @return Particle hypothesis 0322 const ParticleHypothesis& particleHypothesis(const State& state) const { 0323 return state.particleHypothesis; 0324 } 0325 0326 /// Time access 0327 /// 0328 /// @param state [in] The stepping state (thread-local cache) 0329 /// @return Time 0330 double time(const State& state) const { return state.pars[eFreeTime]; } 0331 0332 /// Update surface status 0333 /// 0334 /// It checks the status to the reference surface & updates 0335 /// the step size accordingly 0336 /// 0337 /// @param [in,out] state The stepping state (thread-local cache) 0338 /// @param [in] surface The surface provided 0339 /// @param [in] index The surface intersection index 0340 /// @param [in] navDir The navigation direction 0341 /// @param [in] boundaryTolerance The boundary check for this status update 0342 /// @param [in] surfaceTolerance Surface tolerance used for intersection 0343 /// @param [in] stype The step size type to be set 0344 /// @param [in] logger A @c Logger instance 0345 /// @param curvature Global geometric curvature for the crossing estimate 0346 /// @return Intersection status 0347 IntersectionStatus updateSurfaceStatus( 0348 State& state, const Surface& surface, std::uint8_t index, 0349 Direction navDir, const BoundaryTolerance& boundaryTolerance, 0350 double surfaceTolerance, ConstrainedStep::Type stype, 0351 const Logger& logger = getDummyLogger(), 0352 const Vector3& curvature = Vector3::Zero()) const { 0353 return detail::updateSingleSurfaceStatus<GenericRungeKuttaStepper>( 0354 *this, state, surface, index, navDir, boundaryTolerance, 0355 surfaceTolerance, stype, logger, curvature); 0356 } 0357 0358 /// Update step size 0359 /// 0360 /// This method intersects the provided surface and update the navigation 0361 /// step estimation accordingly (hence it changes the state). It also 0362 /// returns the status of the intersection to trigger onSurface in case 0363 /// the surface is reached. 0364 /// 0365 /// @param state [in,out] The stepping state (thread-local cache) 0366 /// @param target [in] The NavigationTarget 0367 /// @param direction [in] The propagation direction 0368 /// @param stype [in] The step size type to be set 0369 void updateStepSize(State& state, const NavigationTarget& target, 0370 Direction direction, ConstrainedStep::Type stype) const { 0371 static_cast<void>(direction); 0372 double stepSize = target.pathLength(); 0373 updateStepSize(state, stepSize, stype); 0374 } 0375 0376 /// Update step size - explicitly with a double 0377 /// 0378 /// @param state [in,out] The stepping state (thread-local cache) 0379 /// @param stepSize [in] The step size value 0380 /// @param stype [in] The step size type to be set 0381 void updateStepSize(State& state, double stepSize, 0382 ConstrainedStep::Type stype) const { 0383 state.previousStepSize = state.stepSize.value(); 0384 state.stepSize.update(stepSize, stype); 0385 } 0386 0387 /// Get the step size 0388 /// 0389 /// @param state [in] The stepping state (thread-local cache) 0390 /// @param stype [in] The step size type to be returned 0391 /// @return Step size 0392 double getStepSize(const State& state, ConstrainedStep::Type stype) const { 0393 return state.stepSize.value(stype); 0394 } 0395 0396 /// Release the Step size 0397 /// 0398 /// @param state [in,out] The stepping state (thread-local cache) 0399 /// @param [in] stype The step size type to be released 0400 void releaseStepSize(State& state, ConstrainedStep::Type stype) const { 0401 state.stepSize.release(stype); 0402 } 0403 0404 /// Output the Step Size - single component 0405 /// 0406 /// @param state [in,out] The stepping state (thread-local cache) 0407 /// @return String representation of step size 0408 std::string outputStepSize(const State& state) const { 0409 return state.stepSize.toString(); 0410 } 0411 0412 /// Get the step size constraints 0413 /// 0414 /// @param state [in] The stepping state (thread-local cache) 0415 /// @return The step size constraints 0416 const ConstrainedStep& stepSize(const State& state) const { 0417 return state.stepSize; 0418 } 0419 0420 /// Get the stepper statistics 0421 /// 0422 /// @param state [in] The stepping state (thread-local cache) 0423 /// @return The statistics since the last initialization 0424 const StepperStatistics& statistics(const State& state) const { 0425 return state.statistics; 0426 } 0427 0428 /// Get the path length 0429 /// 0430 /// @param state [in] The stepping state (thread-local cache) 0431 /// @return The path length since the last initialization 0432 double pathLength(const State& state) const { return state.pathAccumulated; } 0433 0434 /// Check if the state carries a covariance 0435 /// 0436 /// @param state [in] The stepping state (thread-local cache) 0437 /// @return True if the covariance is transported 0438 bool hasCovariance(const State& state) const { return state.cov.has_value(); } 0439 0440 /// Get the covariance at the anchor 0441 /// 0442 /// The anchor is the frame of the last initialization, transport or update. 0443 /// 0444 /// @param state [in] The stepping state (thread-local cache) 0445 /// @return The covariance at the anchor, or no value if the state does not 0446 /// carry a covariance 0447 const std::optional<Covariance>& covariance(const State& state) const { 0448 return state.cov; 0449 } 0450 0451 /// Set the covariance at the anchor 0452 /// 0453 /// The state must already carry a covariance, because the stepper only 0454 /// transports the Jacobian while it has one. 0455 /// 0456 /// @param [in,out] state The stepping state (thread-local cache) 0457 /// @param [in] covariance The new covariance at the anchor 0458 /// @throws std::logic_error if the state does not carry a covariance 0459 void setCovariance(State& state, const Covariance& covariance) const { 0460 if (!state.cov.has_value()) { 0461 throw std::logic_error( 0462 "Cannot set the covariance of a state without a covariance"); 0463 } 0464 state.cov = covariance; 0465 } 0466 0467 /// Get the bound parameters at the current position 0468 /// 0469 /// The parameters carry the covariance at the anchor if the state has one. 0470 /// 0471 /// @note It does not check if the state is on @p surface or anchored on it 0472 /// 0473 /// @param [in] state The stepping state (thread-local cache) 0474 /// @param [in] surface The surface of the parameters 0475 /// @return The bound parameters, or a failure if the position cannot be 0476 /// expressed on @p surface 0477 Result<BoundParameters> boundParameters(const State& state, 0478 const Surface& surface) const; 0479 0480 /// @brief If necessary fill additional members needed for 0481 /// transportToCurvilinear 0482 /// 0483 /// Compute path length derivatives in case they have not been computed 0484 /// yet, which is the case if no step has been executed yet. 0485 /// 0486 /// @param [in, out] state State of the stepper 0487 /// @return true if nothing is missing after this call, false otherwise. 0488 bool prepareCurvilinearState(State& state) const; 0489 0490 /// Get the curvilinear parameters at the current position 0491 /// 0492 /// The parameters carry the covariance at the anchor if the state has one. 0493 /// 0494 /// @param [in] state The stepping state (thread-local cache) 0495 /// @return The curvilinear parameters 0496 BoundParameters curvilinearParameters(const State& state) const; 0497 0498 /// Method to update a stepper state to the some parameters 0499 /// 0500 /// This anchors the state on @p surface. 0501 /// 0502 /// @param [in,out] state State object that will be updated 0503 /// @param [in] freeParams Free parameters that will be written into @p state 0504 /// @param [in] boundParams Corresponding bound parameters used to update jacToGlobal in @p state 0505 /// @param [in] covariance The covariance that will be written into @p state 0506 /// if the state carries a covariance 0507 /// @param [in] surface The surface used to update the jacToGlobal 0508 void update(State& state, const FreeVector& freeParams, 0509 const BoundVector& boundParams, const Covariance& covariance, 0510 const Surface& surface) const; 0511 0512 /// Method to update the stepper state 0513 /// 0514 /// @param [in,out] state State object that will be updated 0515 /// @param [in] uposition the updated position 0516 /// @param [in] udirection the updated direction 0517 /// @param [in] qop the updated qop value 0518 /// @param [in] time the updated time value 0519 void update(State& state, const Vector3& uposition, const Vector3& udirection, 0520 double qop, double time) const; 0521 0522 /// Transport the covariance to the curvilinear frame at the current position 0523 /// 0524 /// This anchors the state on the curvilinear frame. Without a covariance 0525 /// the state does not change. 0526 /// 0527 /// @param [in,out] state State of the stepper 0528 /// @return The jacobian from the previous anchor to the curvilinear frame 0529 Jacobian transportToCurvilinear(State& state) const; 0530 0531 /// Transport the covariance to a surface at the current position 0532 /// 0533 /// This anchors the state on @p surface. Without a covariance the state 0534 /// does not change. The state must be on @p surface, and it stays unchanged 0535 /// if it is not. 0536 /// 0537 /// @param [in,out] state State of the stepper 0538 /// @param [in] surface The surface to transport the covariance to 0539 /// @param [in] freeToBoundCorrection Correction for non-linearity effect during transform from free to bound 0540 /// @return The jacobian from the previous anchor to @p surface, or a failure 0541 /// if the state is not on @p surface 0542 Result<Jacobian> transportToBound( 0543 State& state, const Surface& surface, 0544 const FreeToBoundCorrection& freeToBoundCorrection = 0545 FreeToBoundCorrection(false)) const; 0546 0547 /// Perform a Runge-Kutta track parameter propagation step 0548 /// 0549 /// @param [in,out] state State of the stepper 0550 /// @param propDir is the direction of propagation 0551 /// @param material is ignored, the stepper propagates in vacuum only 0552 /// @return the result of the step 0553 /// @throws std::invalid_argument for an embedded error estimate without 0554 /// embedded weights 0555 /// 0556 /// @note The state contains the desired step size. It can be negative during 0557 /// backwards track propagation, and since the step size is adaptive, 0558 /// it can be modified by the stepper class during propagation. 0559 Result<double> step(State& state, Direction propDir, 0560 const IVolumeMaterial* material) const; 0561 0562 /// Get the field and optionally its gradient at a position. 0563 /// 0564 /// @param [in,out] state The stepper state with the field cache 0565 /// @param [in] pos The position of the lookup 0566 /// @param [in] withGradient Whether the gradient is needed 0567 /// @return The field and the gradient, which is zero without @p withGradient 0568 Result<FieldAndGradient> getFieldAndGradient(State& state, const Vector3& pos, 0569 bool withGradient) const; 0570 0571 private: 0572 /// @param options Stepper options 0573 /// @throws std::invalid_argument for an embedded error estimate without 0574 /// embedded weights 0575 void checkErrorEstimation(const Options& options) const; 0576 0577 /// Magnetic field inside of the detector 0578 std::shared_ptr<const MagneticFieldProvider> m_bField; 0579 0580 /// Runge-Kutta tableau 0581 std::shared_ptr<const ButcherTableau> m_tableau; 0582 }; 0583 0584 template <> 0585 struct SupportsBoundParameters<GenericRungeKuttaStepper> 0586 : public std::true_type {}; 0587 0588 } // namespace Acts
| [ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
|
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
|