/**
   *
   *
   * <h2>Implementation of Abstract Tracker#doPropagation(IProbe, IElement)</h2>
   *
   * <p>This method is essentially the same implementation as the method <code>
   * EnvelopeTracker#doPropagation()</code>, only here the probe object is back-propagated. The
   * method calls <code>Tracker.retractProbe()</code> rather than <code>Tracker.advanceProbe()
   * </code>, and the implemented method <code>EnvelopeBacktracker.retractState()</code> rather than
   * <code>EnvelopeTracker.advanceState</code>.
   *
   * @author Christopher K. Allen
   * @since Feb 9, 2009
   * @see xal.model.alg.Tracker#propagate(xal.model.IProbe, xal.model.IElement)
   * @see xal.model.alg.EnvelopeTracker#doPropagation(IProbe, IElement)
   */
  @Override
  public void doPropagation(IProbe probe, IElement elem) throws ModelException {

    // sako
    double elemPos = this.getElemPosition();
    double elemLen = elem.getLength();
    double propLen = elemLen - elemPos;

    if (propLen < 0) {
      System.err.println("doPropagation, elemPos, elemLen = " + elemPos + " " + elemLen);
      return;
    }

    // Determine the number of integration steps and the step size
    int cntSteps; // number of steps through element
    double dblStep; // step size through element

    if (this.getUseSpacecharge()) cntSteps = (int) Math.max(Math.ceil(propLen / getStepSize()), 1);
    else cntSteps = 1;

    dblStep = elem.getLength() / cntSteps;
    //        dblStep = propLen / cntSteps;

    for (int i = 0; i < cntSteps; i++) {
      this.retractState(probe, elem, dblStep);
      this.retractProbe(probe, elem, dblStep);
    }
  }
 /**
  * Test for a <code>ChargeExchangeFoil</code> element. If found, the probe represent an
  * H<sup>+</sup> beam, the electrons are added and the beam becomes H<sup>-</sup>.
  *
  * <p>The opposite of {@link EnvelopeTracker#treatChargeExchange(EnvelopeProbe, IElement)}
  *
  * @param probe Propagating beam
  * @param ifcElem Element to tested for <code>ChargeExchangeFoil</code> type
  * @author Hiroyuki Sako
  * @see xal.model.elem.ChargeExchangeFoil
  * @see EnvelopeTracker#treatChargeExchange(EnvelopeProbe, IElement)
  */
 private void treatChargeExchange(EnvelopeProbe probe, IElement ifcElem) {
   if (ifcElem instanceof ChargeExchangeFoil) {
     double q = probe.getSpeciesCharge();
     if (q > 0) {
       System.out.println("charge exchanged at " + ifcElem.getId() + " from " + q + " to " + (-q));
       probe.setSpeciesCharge(-q);
     }
   }
 }
  /**
   *
   *
   * <h2>Compute Transfer Matrix Including Space Charge</h2>
   *
   * <p>Computes the back-propagating transfer matrix over the incremental distance <code>dblLen
   * </code> for the beamline modeling element <code>ifcElem</code>, and for the given <code>probe
   * </code>. We include space charge and emittance growth effects if specified.
   *
   * <p><strong>NOTE</strong>: (CKA) <br>
   * &middot; If space charge is included, the space charge matrix is computed for length <code>
   * dblLen</code>, but at a half-step location behind the current probe position. This method is
   * the same technique used by Trace3D. The space charge matrix is then pre- and post- multiplied
   * by the element transfer matrix for a half-step before and after the mid-step position,
   * respectively. <br>
   * &middot; I do not know if this (leap-frog) technique buys us much more accuracy then full
   * stepping.
   *
   * @param dblLen incremental path length
   * @param probe beam probe under simulation
   * @param ifcElem beamline element propagating probe
   * @return transfer matrix for given element
   * @throws ModelException bubbles up from IElement#transferMap()
   * @see EnvelopeTracker#compScheffMatrix(double, EnvelopeProbe, PhaseMatrix)
   * @see EnvelopeTracker#transferEmitGrowth(EnvelopeProbe, IElement, PhaseMatrix)
   * @see EnvelopeTracker#modTransferMatrixForDisplError(double, double, double, PhaseMatrix)
   */
  private PhaseMatrix compTransferMatrix(double dblLen, EnvelopeProbe probe, IElement ifcElem)
      throws ModelException {

    // Returned value
    PhaseMatrix matPhi; // transfer matrix including all effects

    // Check for exceptional circumstance and modify transfer matrix accordingly
    if (ifcElem instanceof IdealRfGap) {
      IdealRfGap elemRfGap = (IdealRfGap) ifcElem;
      double dW = elemRfGap.energyGain(probe, dblLen);
      double W = probe.getKineticEnergy();
      probe.setKineticEnergy(W - dW);
      PhaseMatrix matPhiI = elemRfGap.transferMap(probe, dblLen).getFirstOrder();

      if (this.getEmittanceGrowth()) {
        double dphi = this.effPhaseSpread(probe, elemRfGap);

        matPhiI = super.modTransferMatrixForEmitGrowth(dphi, matPhiI);
      }
      matPhi = matPhiI.inverse();
      probe.setKineticEnergy(W);

      return matPhi;
    }

    if (dblLen == 0.0) {
      matPhi = ifcElem.transferMap(probe, dblLen).getFirstOrder();

      return matPhi;
    }

    // Check for easy case of no space charge
    if (!this.getUseSpacecharge()) {
      matPhi = ifcElem.transferMap(probe, dblLen).getFirstOrder();

      // we must treat space charge
    } else {

      // Store the current probe state (for rollback)
      EnvelopeProbeState state0 = probe.cloneCurrentProbeState();
      // ProbeState  state0 = probe.createProbeState();

      // Get half-step back-propagation matrix at current probe location
      //  NOTE: invert by computing for negative propagation length
      PhaseMap mapElem0 = ifcElem.transferMap(probe, -dblLen / 2.0);
      PhaseMatrix matPhi0 = mapElem0.getFirstOrder();

      // Get the RMS envelopes at probe location
      CovarianceMatrix covTau0 = probe.getCovariance(); // covariance matrix at entrance

      // Move probe back a half step for position-dependent transfer maps
      double pos = probe.getPosition() - dblLen / 2.0;
      PhaseMatrix matTau1 = covTau0.conjugateTrans(matPhi0);
      CovarianceMatrix covTau1 = new CovarianceMatrix(matTau1);

      probe.setPosition(pos);
      probe.setCovariance(covTau1);

      // space charge transfer matrix
      //  NOTE: invert by computing for negative propagation length
      PhaseMatrix matPhiSc = this.compScheffMatrix(-dblLen, probe, ifcElem);

      // Compute half-step transfer matrix at new probe location
      PhaseMap mapElem1 = ifcElem.transferMap(probe, -dblLen / 2.0);
      PhaseMatrix matPhi1 = mapElem1.getFirstOrder();

      // Restore original probe state
      probe.applyState(state0);

      // Compute the full transfer matrix for the distance dblLen
      matPhi = matPhi1.times(matPhiSc.times(matPhi0));
    }

    if (ifcElem instanceof IdealMagQuad) {
      // sako  put alignment error in sigma matrix
      //  NOTE the use of negative displacements for back-propagation
      IdealMagQuad elemQuad = (IdealMagQuad) ifcElem;

      double delx = -elemQuad.getAlignX();
      double dely = -elemQuad.getAlignY();
      double delz = -elemQuad.getAlignZ();

      matPhi = this.modTransferMatrixForDisplError(delx, dely, delz, matPhi);
    }

    return matPhi;
  }