/**
   *
   *
   * <h2>Back-propagates the Defining State of the Probe Object</h2>
   *
   * <p>This method uses the same basic algorithm as in <code>EnvelopeTracker#advanceState()</code>,
   * only the probe object is back-propagated. The method utilizes all the space charge mechanisms
   * of the base class <code>EnvelopeTracker</code>.
   *
   * @param ifcElem interface to the beam element
   * @param ifcProbe interface to the probe
   * @param dblLen length of element subsection to retract
   * @throws ModelException bad element transfer matrix/corrupt probe state
   * @author Christopher K. Allen
   * @since Feb 9, 2009
   * @see xal.model.alg.EnvelopeTracker#advanceState(xal.model.IProbe, xal.model.IElement, double)
   */
  protected void retractState(IProbe ifcProbe, IElement ifcElem, double dblLen)
      throws ModelException {

    // Identify probe
    EnvelopeProbe probe = (EnvelopeProbe) ifcProbe;

    // Get initial conditions of probe
    //        R3                  vecPhs0  = probe.getBetatronPhase();
    //        Twiss[]             twiss0   = probe.getCovariance().computeTwiss();
    PhaseMatrix matResp0 = probe.getResponseMatrix();
    PhaseMatrix matTau0 = probe.getCovariance();

    // Remove the emittance growth
    if (this.getEmittanceGrowth()) matTau0 = this.removeEmittanceGrowth(probe, ifcElem, matTau0);

    // Compute the transfer matrix
    // def PhaseMatrix matPhi = compTransferMatrix(dblLen, probe, ifcElem);
    PhaseMatrix matPhi = compTransferMatrix(dblLen, probe, ifcElem);

    // Advance the probe states
    PhaseMatrix matResp1 = matPhi.times(matResp0);
    PhaseMatrix matTau1 = matTau0.conjugateTrans(matPhi);

    // Save the new state variables in the probe
    probe.setResponseMatrix(matResp1);
    probe.setCurrentResponseMatrix(matPhi);
    probe.setCovariance(new CovarianceMatrix(matTau1));
    //        probe.advanceTwiss(matPhi, ifcElem.energyGain(probe, dblLen) );

    // phase update:
    //        Twiss []    twiss1  = probe.getCovariance().computeTwiss();
    //        R3          vecPhs1 = vecPhs0.plus( matPhi.compPhaseAdvance(twiss0, twiss1) );
    //        probe.setBetatronPhase(vecPhs1);

    /** sako treatment of ChargeExchangeFoil */
    treatChargeExchange(probe, ifcElem);
  }
  /**
   *
   *
   * <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;
  }