diff --git a/common-tools/cnuphys/clas12-swimmer/pom.xml b/common-tools/cnuphys/clas12-swimmer/pom.xml new file mode 100644 index 0000000000..0590a59e59 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + + cnuphys + clas12 + 14.1.2-SNAPSHOT + + + clas12-swimmer + Experimental Commons Math CLAS12 Swimmer + + + false + + + + + cnuphys + swimmer + ${project.version} + + + + cnuphys + magfield + ${project.version} + + + + org.apache.commons + commons-math3 + + + + junit + junit + test + + + diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12BeamlineListener.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12BeamlineListener.java new file mode 100644 index 0000000000..6da8f17ff3 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12BeamlineListener.java @@ -0,0 +1,21 @@ +package cnuphys.CLAS12Swim; + +public class CLAS12BeamlineListener extends CLAS12DOCAListener { + + /** + * Create a CLAS12 beamline crossing listener + * + * @param ivals the initial values of the swim + * @param accuracy the accuracy (cm) (on on difference in successive docas) + * @param sMax the final or max path length (cm) + */ + public CLAS12BeamlineListener(CLAS12Values ivals, double accuracy, double sMax) { + super(ivals, accuracy, sMax); + } + + @Override + public double doca(double newS, double[] newU) { + return Math.hypot(newU[0], newU[1]); + } + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12BoundaryListener.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12BoundaryListener.java new file mode 100644 index 0000000000..6fa985a559 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12BoundaryListener.java @@ -0,0 +1,91 @@ +package cnuphys.CLAS12Swim; + +/** + * This is an abstract class to be extended by classes that swim to a boundary. + */ + +public abstract class CLAS12BoundaryListener extends CLAS12Listener { + + // the requested accuracy in cm + protected final double _accuracy; + + /** + * Create a CLAS12 boundary crossing listener + * + * @param ivals the initial values of the swim + * @param accuracy the accuracy (cm) + * @param sMax the final or max path length (cm) + */ + public CLAS12BoundaryListener(CLAS12Values ivals, double accuracy, double sMax) { + super(ivals, sMax); + _accuracy = accuracy; + } + + /** + * Get the requested accuracy + * + * @return the requested accuracy + */ + public double getAccuracy() { + return _accuracy; + } + + /** + * Called when a new step is taken in the ODE solving process. + * + * @param newS The new path length after the step. + * @param newU The new state vector after the step. + * @return A boolean indicating whether to continue (true) or stop (false) the + * integration. + */ + @Override + public boolean newStep(double newS, double[] newU) { + accept(newS, newU); + + // have we crossed the boundary? If so the CrossedBoundary method should handle + // the crossing + // and return with the "intersection" set as the last point. The status should + // be set to SWIM_SUCCESS. + if (crossedBoundary(newS, newU)) { + _status = CLAS12Swimmer.SWIM_SUCCESS; + return false; + } + + // have we reached the max path length? + if (newS >= _sMax) { + _status = CLAS12Swimmer.SWIM_TARGET_MISSED; + return false; + } + + return true; + } + + /** + * Get the absolute distance to the target (boundary) in cm. + * + * @param newS the new path length + * @param newU the new state vector + * @return the distance to the target (boundary) in cm. + */ + public abstract double distanceToTarget(double newS, double[] newU); + + /** + * Called when a new step is taken in the ODE solving process. + * + * @param newS The new path length after the step. + * @param newU The new state vector after the step. + * @return A boolean indicating whether the requested accuracy has been reached + */ + public abstract boolean accuracyReached(double newS, double[] newU); + + /** + * Have we crossed the boundary? + * + * @param newS The new path length after the step. + * @param newU The new state vector after the step. + * @return true if we crossed the boundary, in which case we should + * terminate and interpolate to the intersection. + */ + public abstract boolean crossedBoundary(double newS, double[] newU); + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12CylinderListener.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12CylinderListener.java new file mode 100644 index 0000000000..0ac98004bd --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12CylinderListener.java @@ -0,0 +1,57 @@ +package cnuphys.CLAS12Swim; + +import cnuphys.CLAS12Swim.geometry.Cylinder; + +/** + * A listener for swimming to the surface of a fixed infinite cylinder + */ + +public class CLAS12CylinderListener extends CLAS12BoundaryListener { + + // the target cylinder + private Cylinder _targetCylinder; + + // starting inside or outside + private boolean _inside; + + /** + * Create a CLAS12 boundary target cylinder listener, for swimming to a fixed + * infinite cylinder + * + * @param ivals the initial values of the swim + * @param targetCylinder the target infinite cylinder + * @param accuracy the desired accuracy (cm) + * @param sMax the final or max path length (cm) + */ + public CLAS12CylinderListener(CLAS12Values ivals, Cylinder targetCylinder, double accuracy, double sMax) { + super(ivals, accuracy, sMax); + _targetCylinder = targetCylinder; + _inside = _targetCylinder.isInside(ivals.x, ivals.y, ivals.z); + _canMakeStraightLine = false; + } + + @Override + public boolean accuracyReached(double newS, double[] newU) { + double dist = _targetCylinder.distance(newU[0], newU[1], newU[2]); + return dist < _accuracy; + } + + @Override + public boolean crossedBoundary(double newS, double[] newU) { + boolean newInside = _targetCylinder.isInside(newU[0], newU[1], newU[2]); + return newInside != _inside; + } + + /** + * Get the absolute distance to the target (boundary) in cm. + * + * @param newS the new path length + * @param newU the new state vector + * @return the distance to the target (boundary) in cm. + */ + @Override + public double distanceToTarget(double newS, double[] newU) { + return _targetCylinder.distance(newU[0], newU[1], newU[2]); + } + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12DOCAListener.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12DOCAListener.java new file mode 100644 index 0000000000..7cf8b264ee --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12DOCAListener.java @@ -0,0 +1,94 @@ +package cnuphys.CLAS12Swim; + +/** + * This is an abstract class to be extended by classes that to a distance of + * closest approach. The assumption is that the first doca is the only one. i.e. + * we are not dealing with low energy particles looping about. + */ + +public abstract class CLAS12DOCAListener extends CLAS12Listener { + + // the requested accuracy in cm + protected final double _accuracy; + + // current doca + protected double _currentDOCA = Double.POSITIVE_INFINITY; + + /** + * Create a CLAS12 boundary crossing listener + * + * @param ivals the initial values of the swim + * @param accuracy the accuracy (cm) + * @param sMax the final or max path length (cm) + */ + public CLAS12DOCAListener(CLAS12Values ivals, double accuracy, double sMax) { + super(ivals, sMax); + _accuracy = accuracy; + } + + /** + * Reset the current DOCA to infinity + */ + @Override + public void reset() { + super.reset(); + _currentDOCA = Double.POSITIVE_INFINITY; + } + + /** + * Get the requested accuracy (on on difference in successive docas)in cm. + * + * @return the requested accuracy + */ + public double getAccuracy() { + return _accuracy; + } + + /** + * Get the current estimate of the doca + * + * @return the current doca + */ + public double getCurrentDOCA() { + return _currentDOCA; + } + + /** + * Called when a new step is taken in the ODE solving process. + * + * @param newS The new path length after the step. + * @param newU The new state vector after the step. + * @return A boolean indicating whether to continue (true) or stop (false) the + * integration. + */ + @Override + public boolean newStep(double newS, double[] newU) { + accept(newS, newU); + + double doca = doca(newS, newU); + + if (doca > _currentDOCA) { // getting farther + _status = CLAS12Swimmer.SWIM_SUCCESS; + return false; + } + + // have we reached the max path length? + if (newS >= _sMax) { + _status = CLAS12Swimmer.SWIM_TARGET_MISSED; + return false; + } + + _currentDOCA = doca; + return true; + } + + /** + * Get the absolute distance to the target (boundary) in cm. + * + * @param newS the new path length + * @param newU the new state vector + * @return the distance to the target (boundary) in cm. + */ + public abstract double doca(double newS, double[] newU); + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Listener.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Listener.java new file mode 100644 index 0000000000..00874d54c8 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Listener.java @@ -0,0 +1,254 @@ +package cnuphys.CLAS12Swim; + +/** + * Base ODE step listener used by {@link CLAS12Swimmer} to observe integration progress + * and optionally terminate the swim. + *

+ * This “basic” listener never requests termination on its own; the integrator will + * stop when the configured maximum path length {@code sMax} is reached. Specialized + * subclasses (e.g., z, rho, plane, cylinder) override termination checks to stop when + * a target condition is met within a requested accuracy. + *

+ * + *

State vector convention

+ * The state vector {@code u} passed to and stored by this listener has length 6: + * + * + *

Neutral particles

+ * For {@code q = 0} the swimmer can optionally bypass ODE integration and propagate in a + * straight line. Whether a listener can compute a straight-line intersection depends on the + * target geometry; this is controlled by {@link #_canMakeStraightLine}. + */ + +public class CLAS12Listener implements ODEStepListener { + + /** + * This parameter controls whether the listener can make a straight line to the + * target for a neutral particle. Some can, like the basic, z, and rho + * listeners. Some, like the cylinder, cannot-- in which case we let the full + * swim occur. + */ + protected boolean _canMakeStraightLine = true; + + protected static final double TINY = 1.0e-8; // cm + + // the trajectory if cached + protected CLAS12Trajectory _trajectory; // the cached trajectory + + // the initial values + protected CLAS12Values _initialVaues; // initial values + + // a status, one of the CLAS12Swimmer class constants + protected int _status = CLAS12Swimmer.SWIM_SWIMMING; + + // the final (target) or maximum path length in cm + protected final double _sMax; + + /** + * Create a CLAS12 listener + * + * @param ivals the initial values of the swim + * @param sMax the final or max path length (cm) + */ + public CLAS12Listener(CLAS12Values ivals, double sMax) { + _initialVaues = ivals; + _sMax = sMax; + + _trajectory = new CLAS12Trajectory(ivals); + reset(); + } + + /** + * This parameter controls whether the listener can make a straight line to the + * target for a neutral particle. Some can, like the basic, z, and rho + * listeners. Some, like the cylinder, cannot-- in which case we let the full + * swim occur. + * + * @return true if the listener can make a straight line to the + * target for a neutral particle. + */ + public boolean canMakeStraightLine() { + return _canMakeStraightLine; + } + + /** + * Get the final (target) or maximum path length in cm + * + * @return the final (target) or maximum path length in cm + */ + public double getSMax() { + return _sMax; + } + + /* + * Basic initialization and reset + */ + public void reset() { + _status = CLAS12Swimmer.SWIM_SWIMMING; + _trajectory.clear(); + _trajectory.add(0., _initialVaues.getU()); + } + + /** + * Called when a new step is taken in the ODE solving process. + * + * @param newS The new path length after the step. + * @param newU The new state vector after the step. + * @return A boolean indicating whether to continue (true) or stop (false) the + * integration. + */ + @Override + public boolean newStep(double newS, double[] newU) { + + accept(newS, newU); + + // if we are done, set the status + if (Math.abs(newS - _sMax) < TINY) { + _status = CLAS12Swimmer.SWIM_SUCCESS; + } + + // base always continues, the solve with integrate to sMax and stop + return true; + } + + /** + * Accept the next step. + * + * @param newS The new path length after the step. + * @param newU The new state vector after the step. + */ + protected void accept(double newS, double[] newU) { + _trajectory.add(newS, newU); + } + + /** + * Get the trajectory + * + * @return the trajectory + */ + public CLAS12Trajectory getTrajectory() { + return _trajectory; + } + + /** + * Get the initial values + * + * @return the initial values + */ + public CLAS12Values getIvals() { + return _initialVaues; + } + + /** + * Get the current state vector + * + * @return the current state vector + */ + public double[] getU() { + return _trajectory.get(_trajectory.size() - 1); + } + + /** + * Get the state vector at the given index + * + * @param index the index + * @return the state vector + */ + public double[] getU(int index) { + return _trajectory.get(index); + } + + /** + * Get the number of integration steps + * + * @return the number of integration steps + */ + public int getNumStep() { + return _trajectory.size(); + } + + /** + * Get the current path length + * + * @return the current path length in cm + */ + public double getS() { + return _trajectory.getS(_trajectory.size() - 1); + } + + /** + * Get the path length at the given index + * + * @param index the index + * @return the path length in cm + */ + public double getS(int index) { + return _trajectory.getS(index); + } + + /** + * Get the status of the swim. The values are the CLAS12Swimmer constants: + * SWIM_SUCCESS or SWIM_TARGET_MISSED. + * + * @return the status + */ + public int getStatus() { + return _status; + } + + /** + * Set the status + * + * @param status the status. The values are the CLAS12Swimmer constants: + * SWIM_SUCCESS or SWIM_TARGET_MISSED. + * @see CLAS12Swimmer + */ + public void setStatus(int status) { + _status = status; + } + + /** + * Get the status of the swim as a string + * + * @return the status of the swim as a string + */ + public String statusString() { + String s = CLAS12Swimmer.resultNames.get(_status); + if (s == null) { + s = "Unknown (" + _status + ")"; + } + return s; + } + + /** + * Add a second point creating a straight line. This is only used when + * "swimming" neutral particles. This can be overridden to stop the straight + * line at a target. + */ + public void straightLine() { + + double xo = _initialVaues.x; + double yo = _initialVaues.y; + double zo = _initialVaues.z; + double theta = _initialVaues.theta; + double phi = _initialVaues.phi; + double sf = _sMax; + + double sintheta = Math.sin(Math.toRadians(theta)); + double costheta = Math.cos(Math.toRadians(theta)); + double sinphi = Math.sin(Math.toRadians(phi)); + double cosphi = Math.cos(Math.toRadians(phi)); + + double xf = xo + sf * sintheta * cosphi; + double yf = yo + sf * sintheta * sinphi; + double zf = zo + sf * costheta; + + _trajectory.addPoint(xf, yf, zf, theta, phi, sf); + _status = CLAS12Swimmer.SWIM_SUCCESS; + + } + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12PlaneListener.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12PlaneListener.java new file mode 100644 index 0000000000..4810c69f6c --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12PlaneListener.java @@ -0,0 +1,67 @@ +package cnuphys.CLAS12Swim; + +import java.io.File; +import java.io.FileNotFoundException; + +import cnuphys.CLAS12Swim.geometry.Plane; +import cnuphys.magfield.MagneticFieldInitializationException; +import cnuphys.magfield.MagneticFields; +import cnuphys.magfield.MagneticFields.FieldType; + +/** + * A listener for swimming to the surface of a fixed infinite plane + */ + +public class CLAS12PlaneListener extends CLAS12BoundaryListener { + + // the target plane + private Plane _targetPlane; + + // the starting sign. When this changes we have crossed. + private double _startSign; + + /** + * Create a CLAS12 boundary target plane listener, for swimming to a fixed + * infinite plane + * + * @param ivals the initial values of the swim + * @param targetPlane the target infinite plane + * @param accuracy the desired accuracy (cm) + * @param sMax the final or max path length (cm) + */ + public CLAS12PlaneListener(CLAS12Values ivals, Plane targetPlane, double accuracy, double sMax) { + super(ivals, accuracy, sMax); + _targetPlane = targetPlane; + _startSign = _targetPlane.sign(ivals.x, ivals.y, ivals.z); + _canMakeStraightLine = false; + } + + @Override + public boolean crossedBoundary(double newS, double[] newU) { + int sign = _targetPlane.sign(newU[0], newU[1], newU[2]); + + if (sign != _startSign) { + return true; + } + return false; + } + + @Override + public boolean accuracyReached(double newS, double[] newU) { + double distance = _targetPlane.distance(newU[0], newU[1], newU[2]); + return distance < _accuracy; + } + + /** + * Get the absolute distance to the target (boundary) in cm. + * + * @param newS the new path length + * @param newU the new state vector + * @return the distance to the target (boundary) in cm. + */ + @Override + public double distanceToTarget(double newS, double[] newU) { + return _targetPlane.distance(newU[0], newU[1], newU[2]); + } + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12RhoListener.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12RhoListener.java new file mode 100644 index 0000000000..b524fb35cf --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12RhoListener.java @@ -0,0 +1,133 @@ +package cnuphys.CLAS12Swim; + +import cnuphys.magfield.FastMath; + +/** + * A listener for swimming to a fixed cylindrical radius (rho). + */ +public class CLAS12RhoListener extends CLAS12BoundaryListener { + + // the target rho (cm) + private double _rhoTarget; + + // the starting sign. When this changes we have crossed. + private double _startSign; + + /** + * Create a CLAS12 boundary target Z listener, for swimming to a fixed z + * + * @param ivals the initial values of the swim + * @param rhoTarget the target rho (cylindrical r) (cm) + * @param accuracy the desired accuracy (cm) + * @param sMax the final or max path length (cm) + */ + public CLAS12RhoListener(CLAS12Values ivals, double rhoTarget, double accuracy, double sMax) { + super(ivals, accuracy, sMax); + _rhoTarget = rhoTarget; + + double x = ivals.x; + double y = ivals.y; + _startSign = sign(Math.hypot(x, y)); + } + + @Override + public boolean crossedBoundary(double newS, double[] newU) { + int sign = sign(rho(newU)); + + if (sign != _startSign) { + return true; + } + return false; + } + + // the rho (cylindrical r) of the state vector in cm + private double rho(double u[]) { + double x = u[0]; + double y = u[1]; + return FastMath.hypot(x, y); + } + + @Override + public boolean accuracyReached(double newS, double[] newU) { + double dRho = Math.abs(rho(newU) - _rhoTarget); + return dRho < _accuracy; + } + + // left or right of the target rho? + private int sign(double rho) { + return (rho < _rhoTarget) ? -1 : 1; + } + + /** + * Get the absolute distance to the target (boundary) in cm. + * + * @param newS the new path length + * @param newU the new state vector + * @return the distance to the target (boundary) in cm. + */ + @Override + public double distanceToTarget(double newS, double[] newU) { + return Math.abs(rho(newU) - _rhoTarget); + } + + /** + * Add a second point creating a straight line to the target rho + */ + @Override + public void straightLine() { + + double u[] = _trajectory.get(_trajectory.size() - 1); + double s = _trajectory.getS(_trajectory.size() - 1); + + double u2[] = findPoint(u[0], u[1], u[2], u[3], u[4], u[5], _rhoTarget); + + double dx = u2[0] - u[0]; + double dy = u2[1] - u[1]; + double dz = u2[2] - u[2]; + double ds = Math.sqrt(dx * dx + dy * dy + dz * dz); + + _trajectory.add(s + ds, u2); + _status = CLAS12Swimmer.SWIM_SUCCESS; + + } + + private double[] findPoint(double x0, double y0, double z0, double tx, double ty, double tz, double rTarget) { + // Calculate the coefficients of the quadratic equation + double a = tx * tx + ty * ty; + double b = 2 * (x0 * tx + y0 * ty); + double c = x0 * x0 + y0 * y0 - rTarget * rTarget; + + // Solve the quadratic equation + double discriminant = b * b - 4 * a * c; + if (discriminant < 0) { + return null; // No real solutions, rTarget cannot be reached + } + + // Find the two possible values of s + double t1 = (-b + Math.sqrt(discriminant)) / (2 * a); + double t2 = (-b - Math.sqrt(discriminant)) / (2 * a); + + // Choose the appropriate t (the one with the smaller positive value) + + if (t1 < 0 && t2 < 0) { + return null; + } + + double t; + if (t1 < 0) { + t = t2; + } else if (t2 < 0) { + t = t1; + } else { + t = Math.min(t1, t2); + } + + // Calculate the resulting point + double x = x0 + tx * t; + double y = y0 + ty * t; + double z = z0 + tz * t; + + return new double[] { x, y, z, tx, ty, tz }; + } + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12SphereListener.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12SphereListener.java new file mode 100644 index 0000000000..eb939adcb9 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12SphereListener.java @@ -0,0 +1,57 @@ +package cnuphys.CLAS12Swim; + +import cnuphys.CLAS12Swim.geometry.Sphere; + +/** + * A listener for swimming to the surface of a fixed sphere + */ + +public class CLAS12SphereListener extends CLAS12BoundaryListener { + + // the target sphere + private Sphere _targetSphere; + + // starting inside or outside + private boolean _inside; + + /** + * Create a CLAS12 boundary target sphere listener, for swimming to a fixed + * sphere + * + * @param ivals the initial values of the swim + * @param targetSphere the target infinite sphere + * @param accuracy the desired accuracy (cm) + * @param sMax the final or max path length (cm) + */ + public CLAS12SphereListener(CLAS12Values ivals, Sphere targetSphere, double accuracy, double sMax) { + super(ivals, accuracy, sMax); + _targetSphere = targetSphere; + _inside = targetSphere.isInside(ivals.x, ivals.y, ivals.z); + _canMakeStraightLine = false; + } + + @Override + public boolean accuracyReached(double newS, double[] newU) { + double dist = _targetSphere.distance(newU[0], newU[1], newU[2]); + return dist < _accuracy; + } + + @Override + public boolean crossedBoundary(double newS, double[] newU) { + boolean newInside = _targetSphere.isInside(newU[0], newU[1], newU[2]); + return newInside != _inside; + } + + /** + * Get the absolute distance to the target (boundary) in cm. + * + * @param newS the new path length + * @param newU the new state vector + * @return the distance to the target (boundary) in cm. + */ + @Override + public double distanceToTarget(double newS, double[] newU) { + return _targetSphere.distance(newU[0], newU[1], newU[2]); + } + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12SwimResult.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12SwimResult.java new file mode 100644 index 0000000000..63fc1ecfca --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12SwimResult.java @@ -0,0 +1,209 @@ +package cnuphys.CLAS12Swim; + +/** + * Result container returned by all {@code CLAS12Swimmer} swim operations. + *

+ * A {@code CLAS12SwimResult} encapsulates: + *

+ * + *

Termination semantics

+ * A swim may terminate because: + * + * + * The specific termination condition is encoded in {@link #getStatus()}, + * and convenience predicates such as {@link #isSuccess()} are provided. + * + *

State representation

+ * The final state is stored internally as: + * + * + *

Trajectory storage

+ * Depending on how the swim was configured, the result may contain a sampled + * trajectory. Trajectories are typically used for visualization or debugging + * and may be {@code null} if not requested. + * + *

+ * This class is a passive data container and is not thread-safe for mutation. + *

+ */ + +public class CLAS12SwimResult { + + private CLAS12Listener _listener; + + public CLAS12SwimResult(CLAS12Listener listener) { + _listener = listener; + } + + /** + * Get the trajectory + * + * @return the trajectory + */ + public CLAS12Trajectory getTrajectory() { + return _listener.getTrajectory(); + } + + /** + * Get the initial values of the swim + * + * @return the initial values + */ + public CLAS12Values getInitialValues() { + return _listener.getIvals(); + } + + /** + * Get the final values of the swim + * + * @return the final values + */ + public CLAS12Values getFinalValues() { + double u[] = _listener.getU(); + int q = _listener.getIvals().q; + double p = _listener.getIvals().p; + return new CLAS12Values(q, p, u); + } + + /** + * Get the path length in cm + * + * @return the path length in cm + */ + public double getPathLength() { + return _listener.getS(); + } + + /** + * Get the termination status code for the swim. + *

+ * The value is one of the {@link CLAS12Swimmer} status constants, e.g. + * {@link CLAS12Swimmer#SWIM_SUCCESS}, {@link CLAS12Swimmer#SWIM_TARGET_MISSED}, + * or {@link CLAS12Swimmer#BELOW_MIN_MOMENTUM}. + *

+ * + * @return integer status code indicating how the swim terminated + */ + public int getStatus() { + return _listener.getStatus(); + } + + /** + * Get a copy of the final state vector. + *

+ * The state vector {@code u} has length 6 and is interpreted as: + *

+ *

+ *

+ * The returned array is a defensive copy and may be modified by the caller. + *

+ * + * @return a copy of the final state vector + */ + public double[] getFinalU() { + double[] u = _listener.getU(); + return (u == null) ? null : u.clone(); + } + + /** + * Determine whether the swim terminated successfully. + *

+ * A successful termination indicates that the swimmer reached the requested target condition + * (such as a surface, target {@code z}, target {@code ρ}, distance of closest approach, + * or maximum path length) within the specified accuracy, and without encountering an internal + * failure condition. + *

+ *

+ * If this method returns {@code false}, the final state stored in this result still represents + * the particle state at the point where the swim terminated (for example, due to exceeding + * {@code sMax} or failing to converge). + *

+ * + * @return {@code true} if the swim terminated successfully; {@code false} otherwise + */ + public boolean isSuccess() { + return getStatus() == CLAS12Swimmer.SWIM_SUCCESS; + } + + /** + * Get the final rho in cm + * + * @return the final rho in cm + */ + public double getFinalRho() { + return Math.hypot(_listener.getU()[0], _listener.getU()[1]); + } + + /** + * Get the status of the swim as a string + * + * @return the status of the swim as a string + */ + public String statusString() { + int status = getStatus(); + String s = CLAS12Swimmer.resultNames.get(status); + if (s == null) { + s = "Unknown (" + status + ")"; + } + return s; + } + + /** + * Get the number of integration steps + * + * @return the number of integration steps + */ + public int getNStep() { + return _listener.getNumStep(); + } + + /** + * Get a summary of the results of the swim + */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(2000); + CLAS12Values ivalues = getInitialValues(); + CLAS12Values fvalues = getFinalValues(); + + double norm = ivalues.p / fvalues.p; // should be 1.0 + + sb.append("Swim results:\n"); + sb.append("Status: " + statusString() + "\n"); + sb.append("Initial values:\n"); + sb.append("charge = " + ivalues.q + "\n"); + sb.append(String.format("vertex = (%.4f, %.4f, %.4f) cm\n", ivalues.x, ivalues.y, ivalues.z)); + + sb.append(String.format("momentum = %.4f GeV/c\n", ivalues.p)); + sb.append(String.format("theta = %.4f deg\n", ivalues.theta)); + sb.append(String.format("phi = %.4f deg\n", ivalues.phi)); + sb.append("--------\nFinal values:\n"); + sb.append(String.format("location = (%.4f, %.4f, %.4f) cm\n", fvalues.x, fvalues.y, fvalues.z)); + sb.append(String.format("momentum = %.4f GeV/c\n", fvalues.p)); + sb.append(String.format("norm = %.4f (should be 1)\n", norm)); + sb.append(String.format("theta = %.4f deg\n", fvalues.theta)); + sb.append(String.format("phi = %.4f deg\n", fvalues.phi)); + sb.append(String.format("rho = %.4f cm\n", Math.hypot(fvalues.x, fvalues.y))); + sb.append(String.format("path length = %.4f cm\n", getPathLength())); + sb.append(String.format("number of steps = %d\n", getNStep())); + + return sb.toString(); + } + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Swimmer.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Swimmer.java new file mode 100644 index 0000000000..97e86ced83 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Swimmer.java @@ -0,0 +1,1105 @@ +package cnuphys.CLAS12Swim; + +import java.util.Hashtable; + +import cnuphys.CLAS12Swim.geometry.Cylinder; +import cnuphys.CLAS12Swim.geometry.Plane; +import cnuphys.CLAS12Swim.geometry.Sphere; +import cnuphys.magfield.FieldProbe; +import cnuphys.magfield.RotatedCompositeProbe; + +import org.apache.commons.math3.ode.FirstOrderDifferentialEquations; +import org.apache.commons.math3.ode.events.EventHandler; +import org.apache.commons.math3.ode.sampling.StepHandler; +import org.apache.commons.math3.ode.sampling.StepInterpolator; +import org.apache.commons.math3.ode.nonstiff.ClassicalRungeKuttaIntegrator; +import org.apache.commons.math3.ode.nonstiff.DormandPrince54Integrator; + +/** + * The CLAS12 swimmer implementation, based on the Apache Commons Math 3.6.1 + * ODE solvers. + * + *

Independent variable

+ * The ODE is integrated with independent variable {@code s} = path length in cm. + * + *

State vector convention

+ * The integrated state is {@code y = [x, y, z, tx, ty, tz]} where: + * + * + *

Magnetic field

+ * The field is obtained from {@link FieldProbe#field(float, float, float, float[])} + * with position in cm; field units must match the curvature constant defined + * by this class. + */ +public final class CLAS12Swimmer implements ICLAS12Swimmer { + + public static final double C = 2.99792458e10; + public static final int SWIM_SWIMMING = 88; + public static final int SWIM_SUCCESS = 0; + public static final int SWIM_TARGET_MISSED = -1; + public static final int BELOW_MIN_MOMENTUM = -2; + + public static final Hashtable resultNames = new Hashtable<>(); + + static { + resultNames.put(SWIM_SWIMMING, "SWIMMING"); + resultNames.put(SWIM_SUCCESS, "SWIM_SUCCESS"); + resultNames.put(SWIM_TARGET_MISSED, "SWIM_TARGET_MISSED"); + resultNames.put(BELOW_MIN_MOMENTUM, "BELOW_MIN_MOMENTUM"); + } + + /** Minimum momentum threshold in GeV/c. */ + private double minMomentum = 5e-05; + + /** Minimum step size (cm). */ + private double minStepSize = 1.0e-6; + + /** Maximum step size (cm). */ + private double maxStepSize = Double.POSITIVE_INFINITY; + + + private final FieldProbe probe; + + /** + * Create a Commons-Math based swimmer using the current active field probe. + */ + public CLAS12Swimmer() { + this(FieldProbe.factory()); + } + + /** + * Create a Commons-Math based swimmer using the given field probe. + * + * @param probe field probe (must not be {@code null}) + */ + public CLAS12Swimmer(FieldProbe probe) { + if (probe == null) { + throw new NullPointerException("probe"); + } + this.probe = probe; + } + + @Override + public FieldProbe getProbe() { + return probe; + } + + + /** + * Swim to a fixed target z (cm), stopping when the trajectory reaches {@code zTarget} + * (event detection) or when the path length {@code sMax} is reached. + * + *

Units: positions in cm, momentum in GeV/c, angles in degrees, path length in cm.

+ */ + @Override + public CLAS12SwimResult swimZ(int q, + double xo, double yo, double zo, + double p, double theta, double phi, + double zTarget, double accuracy, + double sMax, double h, double tolerance) { + + final CLAS12Values ivals = new CLAS12Values(q, xo, yo, zo, p, theta, phi); + final CLAS12ZListener listener = new CLAS12ZListener(ivals, zTarget, accuracy, sMax); + + // Momentum guard + if (p < minMomentum) { + listener.setStatus(CLAS12Swimmer.BELOW_MIN_MOMENTUM); + return new CLAS12SwimResult(listener); + } + + // Neutral shortcut + if (q == 0 && listener.canMakeStraightLine()) { + listener.straightLine(); + return new CLAS12SwimResult(listener); + } + + // Initial state y = [x,y,z, tx,ty,tz] + final double[] y = ivals.getU().clone(); + final SwimEquations ode = new SwimEquations(q, p, probe); + + final double targetMiss = accuracy; // cm + final double successTol = accuracy; // cm + + // Commons Math requires per-component tolerances. Interpret the provided "tolerance" knob + // as a position absolute tolerance in cm. + final double absPos = Math.max(1.0e-12, tolerance); // cm + final double absDir = 1.0e-10; // dimensionless + final double rel = 1.0e-12; + + final double[] absTol = new double[] { absPos, absPos, absPos, absDir, absDir, absDir }; + final double[] relTol = new double[] { rel, rel, rel, rel, rel, rel }; + + // Adaptive integrator: allow step-size growth up to maxStepSize (do NOT cap at h) + final DormandPrince54Integrator integrator = + new DormandPrince54Integrator( + Math.max(minStepSize, 1e-12), + Math.max(maxStepSize, minStepSize), + absTol, + relTol + ); + + // Use h only as an initial step-size guess + final double h0 = Math.max(minStepSize, Math.min(Math.abs(h), maxStepSize)); + integrator.setInitialStepSize(h0); + + // Record the trajectory at each accepted step + integrator.addStepHandler(new StepHandler() { + @Override + public void init(double s0, double[] y0, double sEnd) { + // no-op + } + + @Override + public void handleStep(StepInterpolator interpolator, boolean isLast) { + final double s = interpolator.getCurrentTime(); + final double[] state = interpolator.getInterpolatedState().clone(); + listener.accept(s, state); + } + }); + + // Event: stop at zTarget + final HitFlag hit = new HitFlag(); + + final EventHandler zEvent = new EventHandler() { + @Override + public void init(double s0, double[] y0, double sEnd) { } + + @Override + public double g(double s, double[] y) { + return y[2] - zTarget; + } + + @Override + public Action eventOccurred(double s, double[] y, boolean increasing) { + hit.hit = true; + return Action.STOP; + } + + @Override + public void resetState(double s, double[] y) { } + }; + + final double maxCheckInterval = Math.max(0.5, h0); + final double eventConv = Math.max(1.0e-12, targetMiss); + integrator.addEventHandler(zEvent, maxCheckInterval, eventConv, 200); + + // Integrate + double sFinal; + try { + sFinal = integrator.integrate(ode, 0.0, y, sMax, y); + } catch (Exception ex) { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + return new CLAS12SwimResult(listener); + } + + // Ensure final point captured even if the last step handler didn't run as expected + listener.accept(sFinal, y.clone()); + + // Status + if (hit.hit && Math.abs(listener.getU()[2] - zTarget) <= successTol) { + listener.setStatus(CLAS12Swimmer.SWIM_SUCCESS); + } else { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + } + + return new CLAS12SwimResult(listener); + } + + + // ------------------------------------------------------------------------- + // The rest of the interface: skeleton placeholders + // ------------------------------------------------------------------------- + + @Override + public CLAS12SwimResult swim(int q, double xo, double yo, double zo, double p, double theta, double phi, + double sMax, double h, double tolerance) { + + final CLAS12Values ivals = new CLAS12Values(q, xo, yo, zo, p, theta, phi); + final CLAS12Listener listener = new CLAS12Listener(ivals, sMax); + + // Momentum guard + if (p < minMomentum) { + listener.setStatus(CLAS12Swimmer.BELOW_MIN_MOMENTUM); + return new CLAS12SwimResult(listener); + } + + // Neutral shortcut: if permitted, do an exact straight-line propagation to sMax + if (q == 0 && listener.canMakeStraightLine()) { + listener.straightLine(); + return new CLAS12SwimResult(listener); + } + + // Initial state y0 = [x,y,z, tx,ty,tz] + final double[] y0 = ivals.getU(); + final double[] y = y0.clone(); + + final FirstOrderDifferentialEquations ode = new SwimEquations(q, p, probe); + + // Per-component tolerances + final double absPos = Math.max(1.0e-12, tolerance); // cm + final double absDir = 1.0e-10; // dimensionless + final double rel = 1.0e-12; + + final double[] absTol = new double[] { absPos, absPos, absPos, absDir, absDir, absDir }; + final double[] relTol = new double[] { rel, rel, rel, rel, rel, rel }; + + // Adaptive integrator: allow step-size growth up to maxStepSize; use h only as initial guess. + final DormandPrince54Integrator integrator = + new DormandPrince54Integrator( + Math.max(minStepSize, 1e-12), + Math.max(maxStepSize, minStepSize), + absTol, + relTol + ); + + final double h0 = Math.max(minStepSize, Math.min(Math.abs(h), maxStepSize)); + integrator.setInitialStepSize(h0); + + // Record trajectory at accepted steps + integrator.addStepHandler(new StepHandler() { + @Override + public void init(double t0, double[] y0, double t) { + // listener.reset() already added the initial point + } + + @Override + public void handleStep(StepInterpolator interpolator, boolean isLast) { + final double s = interpolator.getCurrentTime(); + final double[] state = interpolator.getInterpolatedState().clone(); + listener.accept(s, state); + } + }); + + try { + integrator.integrate(ode, 0.0, y, sMax, y); + } catch (Exception ex) { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + return new CLAS12SwimResult(listener); + } + + // For the basic swim, reaching sMax is considered success. + listener.setStatus(CLAS12Swimmer.SWIM_SUCCESS); + return new CLAS12SwimResult(listener); + } + + @Override + public CLAS12SwimResult swimFixed(int q, double xo, double yo, double zo, double p, double theta, double phi, + double sMax, double h) { + if (h <= 0.0) { + throw new IllegalArgumentException("Fixed step size must be positive"); + } + + final CLAS12Values ivals = new CLAS12Values(q, xo, yo, zo, p, theta, phi); + final CLAS12Listener listener = new CLAS12Listener(ivals, sMax); + + if (p < minMomentum) { + listener.setStatus(CLAS12Swimmer.BELOW_MIN_MOMENTUM); + return new CLAS12SwimResult(listener); + } + + if (q == 0 && listener.canMakeStraightLine()) { + listener.straightLine(); + return new CLAS12SwimResult(listener); + } + + final double[] y = ivals.getU().clone(); + final FirstOrderDifferentialEquations ode = new SwimEquations(q, p, probe); + final ClassicalRungeKuttaIntegrator integrator = new ClassicalRungeKuttaIntegrator(h); + + integrator.addStepHandler(new StepHandler() { + @Override + public void init(double s0, double[] y0, double sEnd) { + // listener reset already recorded the initial point + } + + @Override + public void handleStep(StepInterpolator interpolator, boolean isLast) { + listener.accept(interpolator.getCurrentTime(), interpolator.getInterpolatedState().clone()); + } + }); + + try { + integrator.integrate(ode, 0.0, y, sMax, y); + listener.setStatus(CLAS12Swimmer.SWIM_SUCCESS); + } catch (Exception ex) { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + } + + return new CLAS12SwimResult(listener); + } + + @Override + public CLAS12SwimResult swimCylinder(int q, double xo, double yo, double zo, double p, double theta, double phi, + double[] p1, double[] p2, double r, double accuracy, double sMax, double h, + double tolerance) { + Cylinder targetCylinder = new Cylinder(p1, p2, r); + return swimCylinder(q, xo, yo, zo, p, theta, phi, targetCylinder, accuracy, sMax, h, tolerance); + } + + @Override + public CLAS12SwimResult swimCylinder(int q, double xo, double yo, double zo, double p, double theta, double phi, + Cylinder targetCylinder, double accuracy, double sMax, double h, + double tolerance) { + + // If the target cylinder is centered on the z axis, this is exactly a rho swim. + if (targetCylinder.centeredOnZ()) { + return swimRho(q, xo, yo, zo, p, theta, phi, targetCylinder.radius, accuracy, sMax, h, tolerance); + } + + final CLAS12Values ivals = new CLAS12Values(q, xo, yo, zo, p, theta, phi); + final CLAS12CylinderListener listener = + new CLAS12CylinderListener(ivals, targetCylinder, accuracy, sMax); + + // Momentum guard + if (p < minMomentum) { + listener.setStatus(CLAS12Swimmer.BELOW_MIN_MOMENTUM); + return new CLAS12SwimResult(listener); + } + + // Neutral shortcut is intentionally not used here unless the cylinder reduces to rho. + // CLAS12CylinderListener disables exact straight-line handling. + + final double[] y = ivals.getU().clone(); + final SwimEquations ode = new SwimEquations(q, p, probe); + + final double targetMiss = accuracy; + final double successTol = accuracy; + + final double absPos = Math.max(1.0e-12, tolerance); + final double absDir = 1.0e-10; + final double rel = 1.0e-12; + + final double[] absTol = { absPos, absPos, absPos, absDir, absDir, absDir }; + final double[] relTol = { rel, rel, rel, rel, rel, rel }; + + final DormandPrince54Integrator integrator = + new DormandPrince54Integrator( + Math.max(minStepSize, 1.0e-12), + Math.max(maxStepSize, minStepSize), + absTol, + relTol + ); + + final double h0 = Math.max(minStepSize, Math.min(Math.abs(h), maxStepSize)); + integrator.setInitialStepSize(h0); + + integrator.addStepHandler(new StepHandler() { + @Override + public void init(double s0, double[] y0, double sEnd) { } + + @Override + public void handleStep(StepInterpolator interpolator, boolean isLast) { + final double s = interpolator.getCurrentTime(); + final double[] state = interpolator.getInterpolatedState().clone(); + listener.accept(s, state); + } + }); + + // Cylinder event: signed distance to surface = 0. + // Keep checks reasonably frequent to reduce any chance of stepping over the cylinder. + final HitFlag hit = new HitFlag(); + + final EventHandler cylinderEvent = new EventHandler() { + @Override + public void init(double s0, double[] y0, double sEnd) { } + + @Override + public double g(double s, double[] y) { + return targetCylinder.signedDistance(y[0], y[1], y[2]); + } + + @Override + public Action eventOccurred(double s, double[] y, boolean increasing) { + hit.hit = true; + return Action.STOP; + } + + @Override + public void resetState(double s, double[] y) { } + }; + + final double maxCheckInterval = Math.min(Math.max(0.5, h0), 5.0); + final double eventConv = Math.max(1.0e-12, targetMiss); + + integrator.addEventHandler(cylinderEvent, maxCheckInterval, eventConv, 200); + + double sFinal; + try { + sFinal = integrator.integrate(ode, 0.0, y, sMax, y); + } catch (Exception ex) { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + return new CLAS12SwimResult(listener); + } + + // Ensure final point is recorded + listener.accept(sFinal, y.clone()); + + // Status + double dist = targetCylinder.distance(listener.getU()[0], + listener.getU()[1], + listener.getU()[2]); + + if (hit.hit && dist <= successTol) { + listener.setStatus(CLAS12Swimmer.SWIM_SUCCESS); + } else { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + } + + return new CLAS12SwimResult(listener); + } + + @Override + public CLAS12SwimResult swimSphere(int q, double xo, double yo, double zo, double p, double theta, double phi, + double[] center, double r, double accuracy, double sMax, double h, + double tolerance) { + return swimSphere(q, xo, yo, zo, p, theta, phi, new Sphere(center, r), accuracy, sMax, h, tolerance); + } + + @Override + public CLAS12SwimResult swimSphere(int q, double xo, double yo, double zo, double p, double theta, double phi, + Sphere targetSphere, double accuracy, double sMax, double h, + double tolerance) { + final CLAS12Values ivals = new CLAS12Values(q, xo, yo, zo, p, theta, phi); + final CLAS12SphereListener listener = new CLAS12SphereListener(ivals, targetSphere, accuracy, sMax); + + if (p < minMomentum) { + listener.setStatus(BELOW_MIN_MOMENTUM); + return new CLAS12SwimResult(listener); + } + + final double[] y = ivals.getU().clone(); + final SwimEquations ode = new SwimEquations(q, p, probe); + final DormandPrince54Integrator integrator = createAdaptiveIntegrator(h, tolerance); + final HitFlag hit = new HitFlag(); + + integrator.addStepHandler(recordSteps(listener)); + integrator.addEventHandler(new EventHandler() { + @Override public void init(double s0, double[] y0, double sEnd) { } + @Override public double g(double s, double[] state) { + return targetSphere.signedDistance(state[0], state[1], state[2]); + } + @Override public Action eventOccurred(double s, double[] state, boolean increasing) { + hit.hit = true; + return Action.STOP; + } + @Override public void resetState(double s, double[] state) { } + }, Math.max(0.5, initialStep(h)), Math.max(1.0e-12, accuracy), 200); + + try { + double sFinal = integrator.integrate(ode, 0.0, y, sMax, y); + listener.accept(sFinal, y.clone()); + } catch (Exception ex) { + listener.setStatus(SWIM_TARGET_MISSED); + return new CLAS12SwimResult(listener); + } + + listener.setStatus(hit.hit && targetSphere.distance(y[0], y[1], y[2]) <= accuracy + ? SWIM_SUCCESS : SWIM_TARGET_MISSED); + return new CLAS12SwimResult(listener); + } + + @Override + public CLAS12SwimResult swimPlane(int q, double xo, double yo, double zo, double p, double theta, double phi, + double nx, double ny, double nz, double px, double py, double pz, + double accuracy, double sMax, double h, double tolerance) { + return swimPlane(q, xo, yo, zo, p, theta, phi, new Plane(nx, ny, nz, px, py, pz), accuracy, sMax, h, + tolerance); + } + + @Override + public CLAS12SwimResult swimPlane(int q, double xo, double yo, double zo, double p, double theta, double phi, + double[] norm, double[] point, double accuracy, double sMax, double h, + double tolerance) { + return swimPlane(q, xo, yo, zo, p, theta, phi, new Plane(norm, point), accuracy, sMax, h, tolerance); + } + + /** + * Swim to a fixed plane, stopping when the trajectory intersects the plane + * or when the path length {@code sMax} is reached. + * + *

Units: positions in cm, momentum in GeV/c, angles in degrees.

+ */ + @Override + public CLAS12SwimResult swimPlane(int q, + double xo, double yo, double zo, + double p, double theta, double phi, + Plane plane, + double accuracy, + double sMax, + double h, + double tolerance) { + + final CLAS12Values ivals = new CLAS12Values(q, xo, yo, zo, p, theta, phi); + final CLAS12PlaneListener listener = + new CLAS12PlaneListener(ivals, plane, accuracy, sMax); + + // Momentum guard + if (p < minMomentum) { + listener.setStatus(CLAS12Swimmer.BELOW_MIN_MOMENTUM); + return new CLAS12SwimResult(listener); + } + + // Neutral shortcut + if (q == 0 && listener.canMakeStraightLine()) { + listener.straightLine(); + return new CLAS12SwimResult(listener); + } + + // Initial state y = [x,y,z, tx,ty,tz] + final double[] y = ivals.getU().clone(); + final SwimEquations ode = new SwimEquations(q, p, probe); + + final double targetMiss = accuracy; + final double successTol = accuracy; + final double absPos = Math.max(1.0e-12, tolerance); + final double absDir = 1.0e-10; + final double rel = 1.0e-12; + + final double[] absTol = { + absPos, absPos, absPos, + absDir, absDir, absDir + }; + final double[] relTol = { + rel, rel, rel, rel, rel, rel + }; + + final DormandPrince54Integrator integrator = + new DormandPrince54Integrator( + Math.max(minStepSize, 1.0e-12), + Math.max(maxStepSize, minStepSize), + absTol, + relTol + ); + + // Initial step-size guess + final double h0 = Math.max(minStepSize, Math.min(Math.abs(h), maxStepSize)); + integrator.setInitialStepSize(h0); + + // Record every accepted step (matches swimZ behavior) + integrator.addStepHandler(new StepHandler() { + @Override + public void init(double s0, double[] y0, double sEnd) { } + + @Override + public void handleStep(StepInterpolator interpolator, boolean isLast) { + final double s = interpolator.getCurrentTime(); + final double[] state = interpolator.getInterpolatedState().clone(); + listener.accept(s, state); + } + }); + + // ------------------------------------------------------------ + // Plane event: signed distance = 0 + // ------------------------------------------------------------ + final HitFlag hit = new HitFlag(); + + final EventHandler planeEvent = new EventHandler() { + + @Override + public void init(double s0, double[] y0, double sEnd) { } + + @Override + public double g(double s, double[] y) { + return plane.signedDistance(y[0], y[1], y[2]); + } + + @Override + public Action eventOccurred(double s, double[] y, boolean increasing) { + hit.hit = true; + return Action.STOP; + } + + @Override + public void resetState(double s, double[] y) { } + }; + + final double maxCheckInterval = Math.max(0.5, h0); + final double eventConv = Math.max(1.0e-12, targetMiss); + + integrator.addEventHandler(planeEvent, maxCheckInterval, eventConv, 200); + + // ------------------------------------------------------------ + // Integrate + // ------------------------------------------------------------ + double sFinal; + try { + sFinal = integrator.integrate(ode, 0.0, y, sMax, y); + } catch (Exception ex) { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + return new CLAS12SwimResult(listener); + } + + // Ensure final point is recorded + listener.accept(sFinal, y.clone()); + + // ------------------------------------------------------------ + // Status + // ------------------------------------------------------------ + double dist = plane.distance(listener.getU()[0], + listener.getU()[1], + listener.getU()[2]); + + if (hit.hit && dist <= successTol) { + listener.setStatus(CLAS12Swimmer.SWIM_SUCCESS); + } else { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + } + + return new CLAS12SwimResult(listener); + } + + /** + * Swim to a fixed target z (cm) in a given CLAS12 sector using the RotatedComposite field. + *

+ * This operation is only valid when the active probe is a + * {@link RotatedCompositeProbe}. If not, it prints an error and returns {@code null}. + *

+ */ + @Override + public CLAS12SwimResult sectorSwimZ(int sector, int q, + double xo, double yo, double zo, + double p, double theta, double phi, + double zTarget, double accuracy, + double sMax, double h, double tolerance) { + + // Must use the rotated field. + if (!(probe instanceof RotatedCompositeProbe)) { + System.err.println("sectorSwimZ only valid with RotatedCompositeProbe."); + return null; + } + + final CLAS12Values ivals = new CLAS12Values(q, xo, yo, zo, p, theta, phi); + final CLAS12ZListener listener = new CLAS12ZListener(ivals, zTarget, accuracy, sMax); + + if (p < minMomentum) { + listener.setStatus(CLAS12Swimmer.BELOW_MIN_MOMENTUM); + return new CLAS12SwimResult(listener); + } + + if (q == 0 && listener.canMakeStraightLine()) { + listener.straightLine(); + return new CLAS12SwimResult(listener); + } + + final double[] y = ivals.getU().clone(); + + final SectorSwimEquations ode = + new SectorSwimEquations(sector, q, p, probe); + + final double targetMiss = accuracy; + final double successTol = accuracy; + final double absPos = Math.max(1.0e-12, tolerance); + final double absDir = 1.0e-10; + final double rel = 1.0e-12; + + final double[] absTol = { absPos, absPos, absPos, absDir, absDir, absDir }; + final double[] relTol = { rel, rel, rel, rel, rel, rel }; + + final DormandPrince54Integrator integrator = + new DormandPrince54Integrator( + Math.max(minStepSize, 1.0e-12), + Math.max(maxStepSize, minStepSize), + absTol, + relTol + ); + + final double h0 = Math.max(minStepSize, Math.min(Math.abs(h), maxStepSize)); + integrator.setInitialStepSize(h0); + + integrator.addStepHandler(new StepHandler() { + @Override + public void init(double s0, double[] y0, double sEnd) { } + + @Override + public void handleStep(StepInterpolator interpolator, boolean isLast) { + double s = interpolator.getCurrentTime(); + double[] state = interpolator.getInterpolatedState().clone(); + listener.accept(s, state); + } + }); + + final HitFlag hit = new HitFlag(); + + EventHandler zEvent = new EventHandler() { + + @Override + public void init(double s0, double[] y0, double sEnd) { } + + @Override + public double g(double s, double[] y) { + return y[2] - zTarget; + } + + @Override + public Action eventOccurred(double s, double[] y, boolean increasing) { + hit.hit = true; + return Action.STOP; + } + + @Override + public void resetState(double s, double[] y) { } + }; + + double maxCheckInterval = Math.max(0.5, h0); + double eventConv = Math.max(1.0e-12, targetMiss); + + integrator.addEventHandler(zEvent, maxCheckInterval, eventConv, 200); + + double sFinal; + + try { + sFinal = integrator.integrate(ode, 0.0, y, sMax, y); + } catch (Exception ex) { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + return new CLAS12SwimResult(listener); + } + + listener.accept(sFinal, y.clone()); + + if (hit.hit && Math.abs(listener.getU()[2] - zTarget) <= successTol) { + listener.setStatus(CLAS12Swimmer.SWIM_SUCCESS); + } else { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + } + + return new CLAS12SwimResult(listener); + } + + + @Override + public CLAS12SwimResult swimRho(int q, double xo, double yo, double zo, double p, double theta, double phi, + double rhoTarget, double accuracy, double sMax, double h, double tolerance) { + + final CLAS12Values ivals = new CLAS12Values(q, xo, yo, zo, p, theta, phi); + final CLAS12RhoListener listener = new CLAS12RhoListener(ivals, rhoTarget, accuracy, sMax); + + // Momentum guard + if (p < minMomentum) { + listener.setStatus(CLAS12Swimmer.BELOW_MIN_MOMENTUM); + return new CLAS12SwimResult(listener); + } + + // Neutral shortcut: listener can compute exact straight-line intersection with rho target + if (q == 0 && listener.canMakeStraightLine()) { + listener.straightLine(); + return new CLAS12SwimResult(listener); + } + + // Initial state y0 = [x,y,z, tx,ty,tz] + final double[] y0 = ivals.getU(); + final double[] y = y0.clone(); + + final FirstOrderDifferentialEquations ode = new SwimEquations(q, p, probe); + + final double targetMiss = accuracy; + final double absPos = Math.max(1e-12, tolerance); + final double absDir = 1e-10; + final double rel = 1e-12; + + final double[] absTol = new double[] { absPos, absPos, absPos, absDir, absDir, absDir }; + final double[] relTol = new double[] { rel, rel, rel, rel, rel, rel }; + + final DormandPrince54Integrator integrator = + new DormandPrince54Integrator( + Math.max(minStepSize, 1e-12), + Math.max(maxStepSize, minStepSize), + absTol, + relTol + ); + + final double h0 = Math.max(minStepSize, Math.min(Math.abs(h), maxStepSize)); + integrator.setInitialStepSize(h0); + + // Record trajectory at accepted steps + integrator.addStepHandler(new StepHandler() { + @Override + public void init(double t0, double[] y0, double t) { + // listener.reset() already added the initial point + } + + @Override + public void handleStep(StepInterpolator interpolator, boolean isLast) { + final double s = interpolator.getCurrentTime(); + final double[] state = interpolator.getInterpolatedState().clone(); + listener.accept(s, state); + } + }); + + // Event: rho(s) - rhoTarget = 0, where rho = sqrt(x^2 + y^2) + final HitFlag hit = new HitFlag(); + + final EventHandler rhoEventHandler = new EventHandler() { + @Override + public void init(double t0, double[] y0, double t) { + // nothing + } + + @Override + public double g(double s, double[] y) { + final double rho = Math.hypot(y[0], y[1]); + return rho - rhoTarget; + } + + @Override + public Action eventOccurred(double s, double[] y, boolean increasing) { + hit.hit = true; + return Action.STOP; + } + + @Override + public void resetState(double s, double[] y) { + // no reset + } + }; + + integrator.addEventHandler( + rhoEventHandler, + Math.max(0.5, h0), + Math.max(1e-12, targetMiss), + 200 + ); + + try { + integrator.integrate(ode, 0.0, y, sMax, y); + } catch (Exception ex) { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + return new CLAS12SwimResult(listener); + } + + // Decide final status + final double rhoFinal = Math.hypot(listener.getU()[0], listener.getU()[1]); + if (hit.hit && Math.abs(rhoFinal - rhoTarget) <= targetMiss) { + listener.setStatus(CLAS12Swimmer.SWIM_SUCCESS); + } else { + listener.setStatus(CLAS12Swimmer.SWIM_TARGET_MISSED); + } + + return new CLAS12SwimResult(listener); + } + + @Override + public CLAS12SwimResult swimZLine(int q, double xo, double yo, double zo, double p, double theta, double phi, + double xb, double yb, double accuracy, double sMax, double h, double tolerance) { + CLAS12Values ivals = new CLAS12Values(q, xo, yo, zo, p, theta, phi); + return swimToClosestApproach(ivals, new CLAS12ZLineListener(ivals, xb, yb, accuracy, sMax), + xb, yb, accuracy, sMax, h, tolerance); + } + + @Override + public CLAS12SwimResult swimBeamline(int q, double xo, double yo, double zo, double p, double theta, double phi, + double accuracy, double sMax, double h, double tolerance) { + CLAS12Values ivals = new CLAS12Values(q, xo, yo, zo, p, theta, phi); + return swimToClosestApproach(ivals, new CLAS12BeamlineListener(ivals, accuracy, sMax), + 0.0, 0.0, accuracy, sMax, h, tolerance); + } + + // ------------------------------------------------------------------------- + // Internal helpers + // ------------------------------------------------------------------------- + + private CLAS12SwimResult swimToClosestApproach(CLAS12Values ivals, CLAS12DOCAListener listener, + double xb, double yb, double accuracy, double sMax, + double h, double tolerance) { + if (ivals.p < minMomentum) { + listener.setStatus(BELOW_MIN_MOMENTUM); + return new CLAS12SwimResult(listener); + } + + final double[] y = ivals.getU().clone(); + if ((y[0] - xb) * y[3] + (y[1] - yb) * y[4] >= 0.0) { + listener.setStatus(SWIM_SUCCESS); + return new CLAS12SwimResult(listener); + } + + final DormandPrince54Integrator integrator = createAdaptiveIntegrator(h, tolerance); + final HitFlag hit = new HitFlag(); + integrator.addStepHandler(recordSteps(listener)); + integrator.addEventHandler(new EventHandler() { + @Override public void init(double s0, double[] y0, double sEnd) { } + @Override public double g(double s, double[] state) { + return (state[0] - xb) * state[3] + (state[1] - yb) * state[4]; + } + @Override public Action eventOccurred(double s, double[] state, boolean increasing) { + hit.hit = true; + return Action.STOP; + } + @Override public void resetState(double s, double[] state) { } + }, Math.max(0.5, initialStep(h)), Math.max(1.0e-12, accuracy), 200); + + try { + double sFinal = integrator.integrate(new SwimEquations(ivals.q, ivals.p, probe), 0.0, y, sMax, y); + listener.accept(sFinal, y.clone()); + } catch (Exception ex) { + listener.setStatus(SWIM_TARGET_MISSED); + return new CLAS12SwimResult(listener); + } + + listener.setStatus(hit.hit ? SWIM_SUCCESS : SWIM_TARGET_MISSED); + return new CLAS12SwimResult(listener); + } + + private DormandPrince54Integrator createAdaptiveIntegrator(double h, double tolerance) { + double absPos = Math.max(1.0e-12, tolerance); + double[] absTol = {absPos, absPos, absPos, 1.0e-10, 1.0e-10, 1.0e-10}; + double[] relTol = {1.0e-12, 1.0e-12, 1.0e-12, 1.0e-12, 1.0e-12, 1.0e-12}; + DormandPrince54Integrator integrator = new DormandPrince54Integrator( + Math.max(minStepSize, 1.0e-12), Math.max(maxStepSize, minStepSize), absTol, relTol); + integrator.setInitialStepSize(initialStep(h)); + return integrator; + } + + private double initialStep(double h) { + return Math.max(minStepSize, Math.min(Math.abs(h), maxStepSize)); + } + + private static StepHandler recordSteps(final CLAS12Listener listener) { + return new StepHandler() { + @Override public void init(double s0, double[] y0, double sEnd) { } + @Override public void handleStep(StepInterpolator interpolator, boolean isLast) { + listener.accept(interpolator.getCurrentTime(), interpolator.getInterpolatedState().clone()); + } + }; + } + + /** + * ODE system for Cartesian CLAS12 swimming. + * Independent variable is path length s in cm. + */ + private static final class SwimEquations implements FirstOrderDifferentialEquations { + + private final FieldProbe probe; + private final double alpha; // 1/(kG*cm) + private final float[] b = new float[3]; + + SwimEquations(int q, double p, FieldProbe probe) { + this.probe = probe; + // Curvature scale for field in kG and distance in cm. + // alpha = 1.0e-14 * q * C / p (units: 1/(kG*cm)) + this.alpha = 1.0e-14 * q * CLAS12Swimmer.C / p; + } + + @Override + public int getDimension() { + return 6; + } + + @Override + public void computeDerivatives(double s, double[] y, double[] yDot) { + double Bx = 0.0, By = 0.0, Bz = 0.0; + + if (probe != null) { + probe.field((float) y[0], (float) y[1], (float) y[2], b); + Bx = b[0]; + By = b[1]; + Bz = b[2]; + } + + // dr/ds = t + yDot[0] = y[3]; + yDot[1] = y[4]; + yDot[2] = y[5]; + + // dt/ds = alpha * (t x B) + yDot[3] = alpha * (y[4] * Bz - y[5] * By); + yDot[4] = alpha * (y[5] * Bx - y[3] * Bz); + yDot[5] = alpha * (y[3] * By - y[4] * Bx); + } + } + + /** + * Sector-aware ODE system for sector-dependent swimming with a {@link RotatedCompositeProbe}. + *

+ * The independent variable is the path length {@code s} in cm. + *

+ * + *

+ * This implementation tries to call a sector-aware method on the probe via reflection: + * {@code field(int sector, float x, float y, float z, float[] b)}. + * If not found (or invocation fails), it falls back to {@code probe.field(x,y,z,b)}. + *

+ */ + private static final class SectorSwimEquations implements FirstOrderDifferentialEquations { + + private final int sector; + private final FieldProbe probe; + private final double alpha; // 1/(kG*cm) + private final float[] b = new float[3]; + + + // Cached reflective call (lazy init) + private transient java.lang.reflect.Method sectorFieldMethod; + private transient boolean searched = false; + + SectorSwimEquations(int sector, int q, double p, FieldProbe probe) { + this.sector = sector; + this.probe = probe; + this.alpha = 1.0e-14 * q * CLAS12Swimmer.C / p; + } + + @Override + public int getDimension() { + return 6; + } + + @Override + public void computeDerivatives(double s, double[] y, double[] yDot) { + + double Bx = 0.0, By = 0.0, Bz = 0.0; + + if (probe != null) { + if (!searched) { + searched = true; + sectorFieldMethod = findSectorFieldMethod(probe.getClass()); + } + + boolean ok = false; + + if (sectorFieldMethod != null) { + try { + // signature: (int, float, float, float, float[]) + sectorFieldMethod.invoke(probe, sector, (float) y[0], (float) y[1], (float) y[2], b); + ok = true; + } catch (Throwable t) { + // Disable and fall back for remainder of this swim + sectorFieldMethod = null; + } + } + + if (!ok) { + probe.field((float) y[0], (float) y[1], (float) y[2], b); + } + + + Bx = b[0]; + By = b[1]; + Bz = b[2]; + } + + // dr/ds = t + yDot[0] = y[3]; + yDot[1] = y[4]; + yDot[2] = y[5]; + + // dt/ds = alpha * (t x B) + yDot[3] = alpha * (y[4] * Bz - y[5] * By); + yDot[4] = alpha * (y[5] * Bx - y[3] * Bz); + yDot[5] = alpha * (y[3] * By - y[4] * Bx); + } + + private static java.lang.reflect.Method findSectorFieldMethod(Class cls) { + try { + return cls.getMethod("field", int.class, float.class, float.class, float.class, float[].class); + } catch (NoSuchMethodException e) { + return null; + } + } + } + + + private static final class HitFlag { + boolean hit = false; + } +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Trajectory.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Trajectory.java new file mode 100644 index 0000000000..56c7de172c --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Trajectory.java @@ -0,0 +1,191 @@ +package cnuphys.CLAS12Swim; + +import java.util.Arrays; + +import cnuphys.magfield.FieldProbe; +import cnuphys.magfield.RotatedCompositeProbe; +import cnuphys.swim.SwimTrajectory; + +@SuppressWarnings("serial") +public class CLAS12Trajectory extends SwimTrajectory { + + private double[] _s = new double[200]; + private int _sSize = 0; + + private double _bdlValue = Double.NaN; + + public CLAS12Trajectory(CLAS12Values initialValues) { + super(initialValues.toGeneratedParticleRecord(), 200); + } + + public void add(double s, double[] u) { + addS(s); + super.add(u); + _bdlValue = Double.NaN; + } + + public void replaceLastPoint(double s, double[] u) { + if (_sSize > 0) { + int index = _sSize - 1; + removePoint(index); + add(s, u); + } + } + + public void removeLastPoint() { + if (_sSize > 0) { + removePoint(_sSize - 1); + _bdlValue = Double.NaN; + } + } + + public void removePoint(int index) { + if (index >= 0 && index < _sSize) { + System.arraycopy(_s, index + 1, _s, index, _sSize - index - 1); + _sSize--; + remove(index); + _bdlValue = Double.NaN; + } + } + + public double getS(int index) { + return _s[index]; + } + + public int getSSize() { + return _sSize; + } + + public String sizeReport() { + return String.format("State vector size: %d Pathlength size: %d", size(), _sSize); + } + + @Override + public void clear() { + super.clear(); + _sSize = 0; + _bdlValue = Double.NaN; + } + + @Override + public boolean add(double[] u) { + throw new UnsupportedOperationException("Use add(s, u) instead."); + } + + @Override + public boolean add(double[] u, double s) { + throw new UnsupportedOperationException("Use add(s, u) instead."); + } + + @Override + public void add(double xo, double yo, double zo, double p, double theta, double phi) { + throw new UnsupportedOperationException("Use addPoint instead."); + } + + public void addPoint(double x, double y, double z, double theta, double phi, double s) { + double thetaRad = Math.toRadians(theta); + double phiRad = Math.toRadians(phi); + double sinTheta = Math.sin(thetaRad); + + double[] u = new double[6]; + u[0] = x; + u[1] = y; + u[2] = z; + u[3] = sinTheta * Math.cos(phiRad); + u[4] = sinTheta * Math.sin(phiRad); + u[5] = Math.cos(thetaRad); + + add(s, u); + } + + @Override + public double getR(int index) { + if ((index < 0) || (index >= size())) { + return Double.NaN; + } + + double[] v = get(index); + return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + } + + @Override + public double getComputedBDL() { + if (Double.isNaN(_bdlValue)) { + computeBDL(FieldProbe.factory()); + } + return _bdlValue; + } + + @Override + public void computeBDL(FieldProbe probe) { + if (!(probe instanceof RotatedCompositeProbe) && Double.isNaN(_bdlValue) && size() >= 2) { + _bdlValue = 0.0; + int n = size(); + double[] dr = new double[3]; + float[] b = new float[3]; + double[] bxdl = new double[3]; + + for (int i = 0; i < n - 1; i++) { + double[] p0 = get(i); + double[] p1 = get(i + 1); + + dr[0] = p1[0] - p0[0]; + dr[1] = p1[1] - p0[1]; + dr[2] = p1[2] - p0[2]; + + float xavg = (float) ((p0[0] + p1[0]) * 0.5); + float yavg = (float) ((p0[1] + p1[1]) * 0.5); + float zavg = (float) ((p0[2] + p1[2]) * 0.5); + + probe.field(xavg, yavg, zavg, b); + cross(b, dr, bxdl); + _bdlValue += vecmag(bxdl); + } + } + } + + @Override + public void sectorComputeBDL(int sector, RotatedCompositeProbe probe) { + if (Double.isNaN(_bdlValue) && size() >= 2) { + _bdlValue = 0.0; + int n = size(); + double[] dr = new double[3]; + float[] b = new float[3]; + double[] bxdl = new double[3]; + + for (int i = 0; i < n - 1; i++) { + double[] p0 = get(i); + double[] p1 = get(i + 1); + + dr[0] = p1[0] - p0[0]; + dr[1] = p1[1] - p0[1]; + dr[2] = p1[2] - p0[2]; + + float xavg = (float) ((p0[0] + p1[0]) * 0.5); + float yavg = (float) ((p0[1] + p1[1]) * 0.5); + float zavg = (float) ((p0[2] + p1[2]) * 0.5); + + probe.field(sector, xavg, yavg, zavg, b); + cross(b, dr, bxdl); + _bdlValue += vecmag(bxdl); + } + } + } + + private void addS(double s) { + if (_sSize >= _s.length) { + _s = Arrays.copyOf(_s, _s.length * 2); + } + _s[_sSize++] = s; + } + + private static void cross(float[] a, double[] b, double[] out) { + out[0] = a[1] * b[2] - a[2] * b[1]; + out[1] = a[2] * b[0] - a[0] * b[2]; + out[2] = a[0] * b[1] - a[1] * b[0]; + } + + private static double vecmag(double[] a) { + return Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]); + } +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Values.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Values.java new file mode 100644 index 0000000000..a7713bef39 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12Values.java @@ -0,0 +1,155 @@ +package cnuphys.CLAS12Swim; + +import cnuphys.lund.GeneratedParticleRecord; +import cnuphys.magfield.FastMath; + +/** + * A class to hold the initial or final values for a swim. + */ +public class CLAS12Values { + + /** The integer charge */ + public final int q; + + /** The coordinate x in cm */ + public final double x; + + /** The y coordinate in cm */ + public final double y; + + /** The z coordinate of in cm */ + public final double z; + + /** The momentum in GeV/c */ + public final double p; + + /** The DIRECTIONAL polar angle in degrees, i.e. theta component of p */ + public final double theta; + + /** The azimuthal angle in degrees */ + public final double phi; + + public final double tx; + public final double ty; + public final double tz; + + /** + * Store the initial conditions of a swim + * + * @param q The integer charge + * @param xo The x coordinate of the vertex in cm + * @param yo The y coordinate of the vertex in cm + * @param zo The z coordinate of the vertex in cm + * @param p The momentum in GeV/c + * @param theta The DITECTIONAL polar angle in degrees + * @param phi The DIRECTIONAL azimuthal angle in degrees + */ + public CLAS12Values(int q, double xo, double yo, double zo, double p, double theta, double phi) { + this.q = q; + this.x = xo; + this.y = yo; + this.z = zo; + this.p = p; + this.theta = theta; + this.phi = phi; + double thetaRad = Math.toRadians(theta); + double phiRad = Math.toRadians(phi); + double sinTheta = Math.sin(thetaRad); + tx = sinTheta * Math.cos(phiRad); + ty = sinTheta * Math.sin(phiRad); + tz = Math.cos(thetaRad); + } + + /** + * Get the POSITIONAL values from a state vector. The state vector is the vector + * of that is the dependent variable in the integration. The anlges are + * positional, not the directional angles for the momentum. + * + * @param q the integer charge. Must be supplied, not part of the state vector. + * It shouldn't change, but we assume this is the original momentum, so + * we mutliply by the state vector norm of the t components, which + * should be 1 since we have magnetic field only. + * @param p the momentum in GeV/c + * @param u the state vector + */ + public CLAS12Values(int q, double p, double[] u) { + this.q = q; + x = u[0]; + y = u[1]; + z = u[2]; + + tx = u[3]; + ty = u[4]; + tz = u[5]; + + // norm should be 1 + double norm = Math.sqrt(u[3] * u[3] + u[4] * u[4] + u[5] * u[5]); + + this.p = norm * p; + + // directional theta and phi + theta = FastMath.acos2Deg(u[5]); + phi = FastMath.atan2Deg(u[4], u[3]); + } + + /** + * Get the values as a state vector used in integration + * + * @return the values as a state vector + */ + public double[] getU() { + double uo[] = new double[6]; + + double thetaRad = Math.toRadians(theta); + double phiRad = Math.toRadians(phi); + double sinTheta = Math.sin(thetaRad); + + double tx = sinTheta * Math.cos(phiRad); // px/p + double ty = sinTheta * Math.sin(phiRad); // py/p + double tz = Math.cos(thetaRad); // pz/p + + // set uf to the starting state vector + uo[0] = x; + uo[1] = y; + uo[2] = z; + uo[3] = tx; + uo[4] = ty; + uo[5] = tz; + return uo; + } + + /** + * Copy constructor + * + * @param src the source initial values + */ + public CLAS12Values(CLAS12Values src) { + this(src.q, src.x, src.y, src.z, src.p, src.theta, src.phi); + } + + @Override + public String toString() { + return String.format("Q: %d\n", q) + String.format("xo: %10.7e cm\n", x) + String.format("yo: %10.7e cm\n", y) + + String.format("zo: %10.7e cm\n", z) + String.format("p: %10.7e GeV/c\n", p) + + String.format("theta: %10.7f deg\n", theta) + String.format("phi: %10.7f deg", phi); + } + + /** + * Convert to a GeneratedParticleRecord for backwards compatibility + * + * @return a GeneratedParticleRecord corresponding to this data + */ + public GeneratedParticleRecord toGeneratedParticleRecord() { + return new GeneratedParticleRecord(q, x, y, z, p, theta, phi); + } + + /** + * A raw string for output, just numbers no units + * + * @return a raw string for output + */ + public String toStringRaw() { + return String.format("%-7.4f %-7.4f %-7.4f %-6.3f %-6.3f %-6.3f", x, y, z, p, theta, phi); + } + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12ZLineListener.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12ZLineListener.java new file mode 100644 index 0000000000..914183e653 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12ZLineListener.java @@ -0,0 +1,33 @@ +package cnuphys.CLAS12Swim; + +public class CLAS12ZLineListener extends CLAS12DOCAListener { + + private double _xb; //x offset in cm + private double _yb; //y offset in cm + + /** + * Create a CLAS12 swim to an "offest beamline" listener. The offset + * beamline is a line parallel to the z-axis, but offset in the x and y + * directions by _xb and _yb. + * + * @param ivals the initial values of the swim + * @param xb the x offset (cm) + * @param yb the y offset (cm) + * @param accuracy the accuracy (cm) (on on difference in successive docas) + * @param sMax the final or max path length (cm) + */ + public CLAS12ZLineListener(CLAS12Values ivals, double xb, double yb, double accuracy, double sMax) { + super(ivals, accuracy, sMax); + _xb = xb; + _yb = yb; + } + + + @Override + public double doca(double newS, double[] newU) { + double dx = newU[0] - _xb; + double dy = newU[1] - _yb; + return Math.hypot(dx, dy); + } + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12ZListener.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12ZListener.java new file mode 100644 index 0000000000..5565ee5c6a --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/CLAS12ZListener.java @@ -0,0 +1,118 @@ +package cnuphys.CLAS12Swim; + +/** + * A listener for swimming to a fixed value of z + */ +public class CLAS12ZListener extends CLAS12BoundaryListener { + + // the target z (cm) + private double _zTarget; + + // the starting sign. When this changes we have crossed. + private double _startSign; + + /** + * Create a CLAS12 boundary target Z listener, for swimming to a fixed z + * + * @param ivals the initial values of the swim + * @param zTarget the target z (cm) + * @param accuracy the desired accuracy (cm) + * @param sMax the final or max path length (cm) + */ + public CLAS12ZListener(CLAS12Values ivals, double zTarget, double accuracy, double sMax) { + super(ivals, accuracy, sMax); + _zTarget = zTarget; + _startSign = sign(ivals.z); + } + + @Override + public boolean crossedBoundary(double newS, double[] newU) { + double newZ = newU[2]; + int sign = sign(newZ); + + if (sign != _startSign) { + return true; + } + return false; + } + + @Override + public boolean accuracyReached(double newS, double[] newU) { + double dZ = Math.abs(newU[2] - _zTarget); + return dZ < _accuracy; + } + + // left or right of the target Z? + private int sign(double z) { + return (z < _zTarget) ? -1 : 1; + } + + /** + * Get the absolute distance to the target (boundary) in cm. + * + * @param newS the new path length + * @param newU the new state vector + * @return the distance to the target (boundary) in cm. + */ + @Override + public double distanceToTarget(double newS, double[] newU) { + return Math.abs(newU[2] - _zTarget); + } + + /** + * Add a second point creating a straight line to the target z + */ + @Override + public void straightLine() { + + double u[] = _trajectory.get(_trajectory.size() - 1); + double s = _trajectory.getS(_trajectory.size() - 1); + + double u2[] = findPoint(u[0], u[1], u[2], u[3], u[4], u[5], _zTarget); + + double dx = u2[0] - u[0]; + double dy = u2[1] - u[1]; + double dz = u2[2] - u[2]; + double ds = Math.sqrt(dx * dx + dy * dy + dz * dz); + + _trajectory.add(s + ds, u2); + _status = CLAS12Swimmer.SWIM_SUCCESS; + + } + + /** + * Finds the point along the line of velocity where the z coordinate reaches + * zTarget. + * + * @param x0 Starting x coordinate + * @param y0 Starting y coordinate + * @param z0 Starting z coordinate + * @param tx x component of the unit direction vector + * @param ty y component of the unit direction vector + * @param tz z component of the unit direction vector + * @param zTarget The target z coordinate to reach + * @return The point [x, y, z] where the z coordinate reaches zTarget, or null + * if it never reaches. + */ + private double[] findPoint(double x0, double y0, double z0, double tx, double ty, double tz, double zTarget) { + // Check if the line is parallel to the z-plane (tz = 0) + if (tz == 0) { + if (z0 == zTarget) { + // The entire line is on the plane where z = zTarget + return new double[] { x0, y0, zTarget }; + } else { + // The line will never reach zTarget + return null; + } + } + + // Calculate the parameter (s) at which z coordinate reaches zTarget + double t = (zTarget - z0) / tz; + + // Calculate the x and y coordinates at this point + double x = x0 + tx * t; + double y = y0 + ty * t; + + return new double[] { x, y, zTarget, tx, ty, tz }; + } +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/ICLAS12Swimmer.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/ICLAS12Swimmer.java new file mode 100644 index 0000000000..fb7593ad5b --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/ICLAS12Swimmer.java @@ -0,0 +1,369 @@ +package cnuphys.CLAS12Swim; + +import cnuphys.CLAS12Swim.geometry.Cylinder; +import cnuphys.CLAS12Swim.geometry.Plane; +import cnuphys.CLAS12Swim.geometry.Sphere; +import cnuphys.magfield.FieldProbe; + +/** + * Public API for the CLAS12 charged-particle swimmer. + *

+ * A “swim” numerically propagates a charged particle through the magnetic field from an + * initial vertex and direction/momentum until a termination condition is reached + * (path length, target surface, target z, target rho/beamline, etc.). + *

+ * + *

Conventions

+ * + * + *

+ *

Results

+ * All swim methods return a {@link CLAS12SwimResult} describing the final particle + * state and the reason for termination. Callers should always check + * {@link CLAS12SwimResult#isSuccess()} or {@link CLAS12SwimResult#getStatus()} + * before using the final state. + *

+ */ +public interface ICLAS12Swimmer { + + /** + * Get the magnetic field probe being used to swim. + * + * @return the magnetic field probe + */ + FieldProbe getProbe(); + + /** + * The basic adaptive-step swim method. + * The swim is terminated when the particle reaches path length {@code sMax}. + * If not terminated by an internal termination condition, it will swim exactly to {@code sMax}. + * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param sMax final (max) path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swim(int q, double xo, double yo, double zo, double p, double theta, double phi, + double sMax, double h, double tolerance); + + /** + * The basic fixed-step swim method. + * The swim is terminated when the particle reaches path length {@code sMax}. + * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param sMax final (max) path length in cm + * @param h fixed step size in cm + * @return the result of the swim + */ + CLAS12SwimResult swimFixed(int q, double xo, double yo, double zo, double p, double theta, double phi, + double sMax, double h); + + /** + * Swim a particle to the surface of a target cylinder. + * The cylinder is defined by two points (each a {@code double[3]} in {x,y,z} cm) + * that define the centerline, and by a radius {@code r} in cm. + *

+ * If the swim starts inside the cylinder, it will terminate immediately (subject to the + * implementation’s handling of that case in {@link CLAS12SwimResult}). + *

+ * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param p1 first point on the cylinder centerline: {x,y,z} in cm + * @param p2 second point on the cylinder centerline: {x,y,z} in cm + * @param r cylinder radius in cm + * @param accuracy desired accuracy in cm for reaching the surface + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swimCylinder(int q, double xo, double yo, double zo, double p, double theta, double phi, + double p1[], double p2[], double r, double accuracy, double sMax, double h, + double tolerance); + + /** + * Swim a particle to the surface of a target cylinder. + * The cylinder is specified by a {@link Cylinder} object (typically treated as infinite in length). + * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param targetCylinder target cylinder + * @param accuracy desired accuracy in cm for reaching the surface + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swimCylinder(int q, double xo, double yo, double zo, double p, double theta, double phi, + Cylinder targetCylinder, double accuracy, double sMax, double h, + double tolerance); + + /** + * Swim a particle to the surface of a target sphere. + * The sphere is defined by a center point {@code center} as a {@code double[3]} in {x,y,z} cm, + * and radius {@code r} in cm. + *

+ * If the swim starts inside the sphere, it will terminate immediately (subject to the + * implementation’s handling of that case in {@link CLAS12SwimResult}). + *

+ * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param center sphere center: {x,y,z} in cm + * @param r sphere radius in cm + * @param accuracy desired accuracy in cm for reaching the surface + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swimSphere(int q, double xo, double yo, double zo, double p, double theta, double phi, + double center[], double r, double accuracy, double sMax, double h, + double tolerance); + + /** + * Swim a particle to the surface of a target sphere. + * The sphere is specified by a {@link Sphere} object. + * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param targetSphere target sphere + * @param accuracy desired accuracy in cm for reaching the surface + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swimSphere(int q, double xo, double yo, double zo, double p, double theta, double phi, + Sphere targetSphere, double accuracy, double sMax, double h, + double tolerance); + + /** + * Swim a particle until it intersects a target plane or until {@code sMax} is reached. + * The plane is defined by the components of a normal vector and the components of a point on the plane. + * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param nx plane normal x-component + * @param ny plane normal y-component + * @param nz plane normal z-component + * @param px x-component of a point on the plane (cm) + * @param py y-component of a point on the plane (cm) + * @param pz z-component of a point on the plane (cm) + * @param accuracy desired accuracy in cm for reaching the plane + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swimPlane(int q, double xo, double yo, double zo, double p, double theta, double phi, + double nx, double ny, double nz, double px, double py, double pz, + double accuracy, double sMax, double h, double tolerance); + + /** + * Swim a particle until it intersects a target plane or until {@code sMax} is reached. + * The plane is defined by a normal vector and a point on the plane. + * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param norm plane normal vector {nx, ny, nz} + * @param point a point on the plane {px, py, pz} in cm + * @param accuracy desired accuracy in cm for reaching the plane + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swimPlane(int q, double xo, double yo, double zo, double p, double theta, double phi, + double norm[], double point[], double accuracy, double sMax, double h, + double tolerance); + + /** + * Swim a particle until it intersects a target plane or until {@code sMax} is reached. + * The plane is specified by a {@link Plane} object. + * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param targetPlane target plane + * @param accuracy desired accuracy in cm for reaching the plane + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swimPlane(int q, double xo, double yo, double zo, double p, double theta, double phi, + Plane targetPlane, double accuracy, double sMax, double h, + double tolerance); + + /** + * Swim to a target {@code z} (cm) in a sector coordinate system. + *

+ * Important: this is only valid if the underlying field/probe is a rotated composite + * field implementation (your {@code CLAS12Swimmer} uses {@link cnuphys.magfield.RotatedCompositeProbe} + * internally for sector coordinate transforms). + *

+ * The swim is terminated when the particle reaches {@code zTarget} or if {@code sMax} is reached. + * + * @param sector sector number in [1..6] + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param zTarget target z position in cm + * @param accuracy desired accuracy in cm + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult sectorSwimZ(int sector, int q, double xo, double yo, double zo, double p, double theta, + double phi, double zTarget, double accuracy, double sMax, double h, + double tolerance); + + /** + * Swim to a target {@code z} (cm). + * The swim is terminated when the particle reaches {@code zTarget} or if {@code sMax} is reached. + * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param zTarget target z position in cm + * @param accuracy desired accuracy in cm + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swimZ(int q, double xo, double yo, double zo, double p, double theta, double phi, + double zTarget, double accuracy, double sMax, double h, double tolerance); + + /** + * Swim to a target cylindrical radius {@code rho} (cm), i.e. to the surface of an infinite cylinder + * about the z-axis. + * The swim is terminated when the particle reaches {@code rhoTarget} or if {@code sMax} is reached. + * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param rhoTarget target rho (radius) in cm + * @param accuracy desired accuracy in cm + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swimRho(int q, double xo, double yo, double zo, double p, double theta, double phi, + double rhoTarget, double accuracy, double sMax, double h, double tolerance); + + /** + * Swim to an "offset beamline" listener. + * The offset beamline is a line parallel to the z-axis, offset in x and y by {@code xb} and {@code yb}. + * The goal is to swim to the distance of closest approach (DOCA) to this offset line. + * Swim terminates when successive DOCA estimates differ by less than {@code accuracy}. + * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param xb beamline x offset in cm + * @param yb beamline y offset in cm + * @param accuracy desired DOCA convergence accuracy in cm + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swimZLine(int q, double xo, double yo, double zo, double p, double theta, double phi, + double xb, double yb, double accuracy, double sMax, double h, double tolerance); + + /** + * Swim to the beamline (defined by {@code rho = 0}), i.e. find the distance of closest approach (DOCA) + * to the z-axis. + * Swim terminates when successive DOCA estimates differ by less than {@code accuracy}. + * + * @param q particle charge in integer units of e + * @param xo initial x vertex position in cm + * @param yo initial y vertex position in cm + * @param zo initial z vertex position in cm + * @param p initial momentum magnitude in GeV/c + * @param theta initial polar angle in degrees + * @param phi initial azimuthal angle in degrees + * @param accuracy desired DOCA convergence accuracy in cm + * @param sMax maximum path length in cm + * @param h initial step size in cm + * @param tolerance desired tolerance; the integrator adapts step size to meet this tolerance + * @return the result of the swim + */ + CLAS12SwimResult swimBeamline(int q, double xo, double yo, double zo, double p, double theta, double phi, + double accuracy, double sMax, double h, double tolerance); +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/ODEStepListener.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/ODEStepListener.java new file mode 100644 index 0000000000..6cbc0d0c0d --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/ODEStepListener.java @@ -0,0 +1,16 @@ +package cnuphys.CLAS12Swim; + +/** + * Interface for listening to steps taken by an ODE solver. + */ +public interface ODEStepListener { + /** + * Called when a new step is taken in the ODE solving process. + * + * @param newT The new independent variable after the step. + * @param newY The new state vector after the step. + * @return A boolean indicating whether to continue (true) or stop (false) the + * integration. + */ + boolean newStep(double newT, double[] newY); +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Cylinder.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Cylinder.java new file mode 100644 index 0000000000..0f89c9d215 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Cylinder.java @@ -0,0 +1,109 @@ +package cnuphys.CLAS12Swim.geometry; + + +/** + * An INFINITE cylinder is defined by a centerline and a radius + * @author heddle + * + */ +public class Cylinder { + + //the centerline + private Line _centerLine; + + //the radius + public double radius; + + /** + * Create a cylinder + * @param centerLine the center line + * @param radius the radius + */ + public Cylinder(Line centerLine, double radius) { + _centerLine = new Line(centerLine); + this.radius = radius; + } + + /** + * Create a cylinder + * @param p1 one point of center line as an xyz array + * @param p2 another point of center line as an xyz array + * @param radius + */ + public Cylinder(double[] p1, double[] p2, double radius) { + this(new Line(p1, p2), radius); + } + + + /** + * Get the shortest distance between the surface of this infinite cylinder and a point. + * If the value is negative, we are inside the cylinder. + * @param p a point + * @return the perpendicular distance + */ + public double signedDistance(Point p) { + double lineDist = _centerLine.distance(p); + return lineDist - radius; + } + + /** + * Set the path length of the swim + * @deprecated Use {@link Cylinder#signedDistance} instead. + * @param p a point + * @return the perpendicular distance + */ + @Deprecated + public double distance(Point p) { + double lineDist = _centerLine.distance(p); + return lineDist - radius; + } + + /** + * Get the shortest distance between the surface of this infinite cylinder and a point. + * If the value is negative, we are inside the cylinder. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the perpendicular distance + */ + public double signedDistance(double x, double y, double z) { + Point p = new Point(x, y, z); + return signedDistance(p); + } + + /** + * Get the shortest absolute distance between the surface of this infinite cylinder and a point. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the perpendicular distance + */ + public double distance(double x, double y, double z) { + Point p = new Point(x, y, z); + return Math.abs(signedDistance(p)); + } + + /** + * Is the point inside the cylinder? + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return true if the point is inside the cylinder. + */ + public boolean isInside(double x, double y, double z) { + return signedDistance(x, y, z) < 0; + } + + /** + * Is the cylinder centered on the z axis? + * @return true if the cylinder is centered on the z axis. + */ + public boolean centeredOnZ() { + double x0 = _centerLine.getP0().x; + double y0 = _centerLine.getP0().y; + double x1 = _centerLine.getP1().x; + double y1 = _centerLine.getP1().y; + return (x0 == 0) && (y0 == 0) && (x1 == 0) && (y1 == 0); + } + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Line.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Line.java new file mode 100644 index 0000000000..ebb96780c8 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Line.java @@ -0,0 +1,157 @@ +package cnuphys.CLAS12Swim.geometry; + +/** + * 3D line of the form p(t) = po + t*dp where p(t) is a point on the line, po is + * one point and dp = (p1-po) where p1 is another point. If this is an infinite + * line, the t = [-infinity, infinity]. If this is a directed segment, t = [0, + * 1] + * + * @author heddle + * + */ +public class Line { + + private Point _po; // "start" point on the line + private Vector _dp; // segment from start to end + private double _dpLen; // length of start to end segment + + /** + * Create a line from two points on the line. If this is a directed line + * segment, the line will go from po to p1. + * + * @param po one point + * @param p1 the other point + */ + public Line(Point po, Point p1) { + _po = new Point(po); + _dp = new Vector(Point.difference(p1, po)); + _dpLen = _dp.length(); + } + + /** + * Create a line from two ponts passed as arrays + * @param p1 one point as an xyz array + * @param p2 another point as an xyz array + */ + public Line(double[] p1, double[] p2) { + this(new Point(p1), new Point(p2)); + } + + /** + * Copy constructor + * @param line the line to copy + */ + public Line(Line line) { + _po = new Point(line._po); + _dp = new Vector(line._dp); + _dpLen = line._dpLen; + } + + /** + * Create a line from the origing in the direction of a vector + * @param v the vector + */ + public Line(Vector v) { + this(new Point(0,0,0), new Point(v.x, v.y, v.z)); + } + + /** + * Get the po "start" point. This is just an arbitrary point on an infinite + * line, but the starting point if this is a directed line segment + * + * @return the "starting" point. + */ + public Point getP0() { + return _po; + } + + /** + * Get the p1-po "dP" segment + * + * @return dP = p1 - po + */ + public Vector getDelP() { + return _dp; + } + + /** + * Get the p1 "end" point. This is just an arbitrary point on an infinite line, + * but the end point if this is a directed line segment + * + * @return the "end" point. + */ + public Point getP1() { + return new Point(_po.x + _dp.x, _po.y + _dp.y, _po.z + _dp.z); + } + + /** + * Get a point on the line + * + * @param t the t parameter. If this is a directed line segment, t should be + * restricted to [0, 1] + * @return a point on the line + */ + public Point getP(double t) { + Point p = new Point(); + getP(t, p); + return p; + } + + /** + * Get a point on the line (in place) + * + * @param t the t parameter. If this is a directed line segment, t should be + * restricted to [0, 1] + * @param p upon return, a point on the line + */ + public void getP(double t, Point p) { + p.x = _po.x + t * _dp.x; + p.y = _po.y + t * _dp.y; + p.z = _po.z + t * _dp.z; + } + + /** + * Get the shortest distance between this line (as an infinite line) and a point + * + * @param p a point + * @return the perpendicular distance + */ + public double distance(Point p) { + Vector ap = new Vector(Point.difference(p, _po)); + Vector c = Vector.cross(ap, _dp); + return c.length() / _dpLen; + } + + /** + * Find the point on the line closest to the given point + * @param p the given point + * @return the point on the line closest to the given point + */ + public Point closestPointOnLine(Point p) { + Point pointVec = p.subtract(_po); + double t = pointVec.dot(_dp) / _dp.dot(_dp); + return _po.add(_dp.scale(t)); + } + + + + /** + * Get a String representation + * + * @return a String representation of the Line + */ + @Override + public String toString() { + return "Line from " + getP0() + " to " + getP1(); + } + + /** + * Get the center of the line + * + * @return the center of the line + */ + public Point getCenter() { + return getP(0.5); + } + +} \ No newline at end of file diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Plane.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Plane.java new file mode 100644 index 0000000000..623ec5873b --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Plane.java @@ -0,0 +1,325 @@ +package cnuphys.CLAS12Swim.geometry; + +/** + * A plane is defined by the equation (r - ro).norm = 0 Where r is an arbitrary + * point on the plane, ro is a given point on the plane and norm is the normal + * to the plane + * + * @author heddle + * + */ +public class Plane { + + /** Effectively zero */ + private static final double TINY = 1.0e-20; + + // for the form ax + by + cz = d; + public final double a; + public final double b; + public final double c; + public final double d; + + private double _denom = Double.NaN; + + /** + * Create a plane from a normal vector and a point on the plane + * + * @param norm the normal vector + * @param p0 a point in the plane + * @return the plane that contains p and its normal is norm + */ + public Plane(Vector anorm, Point p0) { + // lets make it a unit vector + Vector norm = anorm.unitVector(); + a = norm.x; // A + b = norm.y; // B + c = norm.z; // C + d = a * p0.x + b * p0.y + c * p0.z; // D + } + + /** + * Create a plane from the coefficients of the equation ax + by + cz = d + * + * @param a the a coefficient + * @param b the b coefficient + * @param c the c coefficient + * @param d the d coefficient + */ + public Plane(double a, double b, double c, double d) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + + /** + * Create a plane from the normal vector in an array of doubles and a point in + * the plane in an array, both (x, y, z) + * + * @param norm the normal + * @param point the point in the plane + */ + public Plane(double norm[], double point[]) { + + this(new Vector(norm[0], norm[1], norm[2]), new Point(point[0], point[1], point[2])); + } + + /** + * Create a plane from a normal vector and a point on the plane + * + * @param nx x component of normal vector + * @param ny y component of normal vector + * @param nz z component of normal vector + * @param px x component of point on plane + * @param py y component of point on plane + * @param pz z component of point on plane + */ + public Plane(double nx, double ny, double nz, double px, double py, double pz) { + + this(new Vector(nx, ny, nz), new Point(px, py, pz)); + } + + /** + * Create a line from two points and then get the intersection with the plane + * + * @param p1 one point + * @param p2 another point + * @param p will hold the intersection, NaNs if no intersection + * @return the t parameter. If NaN it means the line is parallel to the plane. + * If t [0,1] then the segment intersects the plane. If t outside [0, 1] + * the infinite line intersects the plane, but not the segment + */ + public double interpolate(Point p1, Point p2, Point p) { + Line line = new Line(p1, p2); + return lineIntersection(line, p); + } + + /** + * Distance from a point to the plane + * + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the signed distance (indicates which side you are on where norm + * defines positive side) + */ + public double distance(double x, double y, double z) { + return Math.abs(signedDistance(x, y, z)); + } + + /** + * Signed distance from a point to the plane + * + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the signed distance (indicates which side you are on where norm + * defines positive side) + */ + public double signedDistance(double x, double y, double z) { + if (Double.isNaN(_denom)) { + _denom = Math.sqrt(a * a + b * b + c * c); + } + return (a * x + b * y + c * z - d) / _denom; + } + + /** + * Compute the intersection of an infinite line with the plane + * + * @param line the line + * @param intersection will hold the point of intersection + * @return the t parameter. If NaN it means the line is parallel to the plane. + * If t [0,1] then the segment intersects the plane. If t outside [0, 1] + * the infinite line intersects the plane, but not the segment + */ + public double lineIntersection(Line line, Point intersection) { + // Direction vector of the line + Vector lineDir = line.getDelP(); + + Point p0 = line.getP0(); + + // Check if the line is parallel to the plane + double dotProduct = a * lineDir.x + b * lineDir.y + c * lineDir.z; + if (Math.abs(dotProduct) < TINY) { + System.err.println("The line is parallel to the plane in Plane.findLinePlaneIntersection."); + return Double.NaN; + } + + // Parameter t in the parametric line equation + double t = (d - a * p0.x - b * p0.y - c * p0.z) / dotProduct; + + line.getP(t, intersection); + return t; + } + + /** + * Get whether the point is to the left, right or (exactly) on the plane + * + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return +1 if to the left, -1 if to the right, 0 if on the plane + */ + public int sign(double x, double y, double z) { + double result = a * x + b * y + c * z; + + if (result > d) { + return +1; + } else if (result < d) { + return -1; + } else { + return 0; + } + } + + /** + * Create a plane of constant azimuthal angle phi + * + * @param phi the azimuthal angle in degrees + * @return the plane of constant phi + */ + public static Plane constantPhiPlane(double phi) { + phi = Math.toRadians(phi); + + double cphi = Math.cos(phi); + double sphi = Math.sin(phi); + + // point in the plane + Point p = new Point(cphi, sphi, 0); + + // normal + Vector norm = new Vector(sphi, -cphi, 0); + + return new Plane(norm, p); + } + + @Override + public String toString() { + String pstr = String.format("abcd = [%10.6G, %10.6G, %10.6G, %10.6G]", a, b, c, d); + return pstr; + } + + // is the value essentially 0? + private boolean tiny(double v) { + return Math.abs(v) < TINY; + } + + /** + * Find some coordinates suitable for drawing the plane as a Quad in 3D + * + * @param scale an arbitrary big number, a couple times bigger than the drawing + * extent + * @return the jogl coordinates for drawing a Quad + */ + public float[] planeQuadCoordinates(float scale) { + + int[] i1 = { -1, -1, 1, 1 }; + int[] i2 = { -1, 1, 1, -1 }; + + if (tiny(a) && tiny(b) && tiny(c)) { + return null; + } + + float[] coords = new float[12]; + + if (tiny(b) && tiny(c)) { // constant x plane + float fx = (float) (d / a); + for (int k = 0; k < 4; k++) { + int j = 3 * k; + + float y = scale * i1[k]; + float z = scale * i2[k]; + + coords[j] = fx; + coords[j + 1] = y; + coords[j + 2] = z; + } + + } else if (tiny(a) && tiny(c)) { // constant y plane + float fy = (float) (d / b); + for (int k = 0; k < 4; k++) { + int j = 3 * k; + + float x = scale * i1[k]; + float z = scale * i2[k]; + + coords[j] = x; + coords[j + 1] = fy; + coords[j + 2] = z; + } + } else if (tiny(a) && tiny(b)) { // constant z plane + float fz = (float) (d / c); + for (int k = 0; k < 4; k++) { + int j = 3 * k; + + float x = scale * i1[k]; + float y = scale * i2[k]; + + coords[j] = x; + coords[j + 1] = y; + coords[j + 2] = fz; + } + } + + else if (tiny(a)) { + for (int k = 0; k < 4; k++) { + int j = 3 * k; + + float x = scale * i1[k]; + float y = scale * i2[k]; + float z = (float) ((d - b * y) / c); + + coords[j] = x; + coords[j + 1] = y; + coords[j + 2] = z; + } + } + + else if (tiny(b)) { + for (int k = 0; k < 4; k++) { + int j = 3 * k; + + float x = scale * i1[k]; + float y = scale * i2[k]; + float z = (float) ((d - a * x) / c); + + coords[j] = x; + coords[j + 1] = y; + coords[j + 2] = z; + } + + } + + else if (tiny(c)) { + for (int k = 0; k < 4; k++) { + int j = 3 * k; + + float x = scale * i1[k]; + float z = scale * i2[k]; + float y = (float) ((d - a * x) / b); + + coords[j] = x; + coords[j + 1] = y; + coords[j + 2] = z; + } + + } + + else { // general case, no small constants + for (int k = 0; k < 4; k++) { + int j = 3 * k; + + float x = scale * i1[k]; + float y = scale * i2[k]; + float z = (float) ((d - a * x - b * y) / c); + + coords[j] = x; + coords[j + 1] = y; + coords[j + 2] = z; + } + } + + return coords; + } + +} \ No newline at end of file diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Point.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Point.java new file mode 100644 index 0000000000..00bad9c3f7 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Point.java @@ -0,0 +1,186 @@ +package cnuphys.CLAS12Swim.geometry; + +public class Point { + + /** x component */ + public double x; + /** y component */ + public double y; + /** z component */ + public double z; + + /** + * Create a point at the origin + */ + public Point() { + this(0, 0, 0); + } + + /** + * Copy constructor + * + * @param p the point to copy + */ + public Point(Point p) { + this(p.x, p.y, p.z); + } + + /** + * Create a point + * + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + */ + public Point(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Create a point from an xyz array + * @param p the point as an xyz array + */ + public Point(double[] p) { + this(p[0], p[1], p[2]); + } + + /** + * Set the components of the point (vector) + * + * @param x the x component + * @param y the y component + * @param z the z component + */ + public void set(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Set the components of the point (vector) + * @param p the point to use to set (e.g., copy) + */ + public void set(Point p) { + this.x = p.x; + this.y = p.y; + this.z = p.z; + } + + /** + * Get the difference between two points + * + * @param a one point + * @param b the other point + * @return the difference between two points a - b + */ + public static Point difference(Point a, Point b) { + return new Point(a.x - b.x, a.y - b.y, a.z - b.z); + } + + /** + * Method to subtract another point from this point + * + * @param other the point to subtract + * @return the difference between this point and the other point + */ + public Point subtract(Point other) { + return new Point(x - other.x, y - other.y, z - other.z); + } + + /** + * Get the in-place difference between two points + * + * @param a one point + * @param b the other point + * @param c upon return the difference between two points a - b + */ + public static void difference(Point a, Point b, Point c) { + c.set(a.x - b.x, a.y - b.y, a.z - b.z); + } + + /** + * The dot product of this "vector" with another vector + * + * @param v the other vector or point + * @return the dot product + */ + public double dot(Point v) { + return x * v.x + y * v.y + z * v.z; + } + + /** + * The dot product of two vectors or points + * + * @param a one vector or point + * @param b the other vector or point + * @return the dot product + */ + public static double dot(Point a, Point b) { + return a.dot(b); + } + + /** + * Get a string representation of the Point + * + * @return a String representation + */ + @Override + public String toString() { + return String.format("(%-10.6f, %-10.6f, %-10.6f)", x, y, z); + } + + /** + * Compute the distance to another point + * @param x the x coordinate of the other point + * @param y the y coordinate of the other point + * @param z the z coordinate of the other point + * @return the distance between the points + */ + public double distance(double x, double y, double z) { + double dx = x - this.x; + double dy = y - this.y; + double dz = z - this.z; + return Math.sqrt(dx*dx + dy*dy + dz*dz); + } + + /** + * Compute the distance to another point + * @param p the other point + * @return the distance between the points + */ + public double distance(Point p) { + return distance(p.x, p.y, p.z); + } + + /** Method to calculate distance between two points + * + * @param p1 one point + * @param p2 the other point + * @return the distance between the two points + */ + public static double distance(Point p1, Point p2) { + return p1.distance(p2); + } + + /** + * Add another point to this point (i.e., vector addition) + * @param other the other point + * @return the sum of the two points + */ + public Point add(Point other) { + return new Point(x + other.x, y + other.y, z + other.z); + } + + /** + * Scale this point by a scalar + * @param scalar the scalar multiplier + * @return the scaled point + */ + public Point scale(double scalar) { + return new Point(x * scalar, y * scalar, z * scalar); + } + +} \ No newline at end of file diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Sphere.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Sphere.java new file mode 100644 index 0000000000..c7ecff95ab --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Sphere.java @@ -0,0 +1,155 @@ +package cnuphys.CLAS12Swim.geometry; + +/** + * A sphere centered at an arbitrary point + * + * @author heddle + * + */ +public class Sphere { + + // the center + private Point _center; + + // the radius + private double _radius; + + /** + * Create a sphere + * + * @param center the center of the sphere + * @param radius the radius of the sphere + */ + public Sphere(Point center, double radius) { + _center = new Point(center); + _radius = radius; + } + + /** + * Create a sphere + * + * @param center the center of the sphere as an xyz array + * @param radius the radius of the sphere + */ + public Sphere(double[] center, double radius) { + this(new Point(center[0], center[1], center[2]), radius); + } + + /** + * Create a sphere centered on the origin + * + * @param radius the radius of the sphere + */ + public Sphere(double radius) { + this(new Point(0, 0, 0), radius); + } + + /** + * Get the radius of the sphere + * + * @return the radius of the sphere + */ + public double getRadius() { + return _radius; + } + + /** + * Get the shortest distance between the surface of this sphere and a point. If + * the value is negative, we are inside the sphere. + * + * @param p a point + * @return the distance to the sphere + */ + public double signedDistance(Point p) { + double centDist = _center.distance(p); + return centDist - _radius; + } + + /** + * Get the shortest distance between the surface of this sphere and a point. If + * the value is negative, we are inside the sphere. + * + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the distance to the sphere + */ + public double signedDistance(double x, double y, double z) { + Point p = new Point(x, y, z); + return signedDistance(p); + } + + /** + * Get the shortest absolute distance between the surface of this infinite cylinder and a point. + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the perpendicular distance + */ + public double distance(double x, double y, double z) { + Point p = new Point(x, y, z); + return Math.abs(signedDistance(p)); + } + + /** + * Is the point inside the sphere? + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return true if the point is inside the sphere. + */ + public boolean isInside(double x, double y, double z) { + return signedDistance(x, y, z) < 0; + } + + /** + * Check whether a segment intersects the sphere + * + * @param x1 x coordinate of one end of segment + * @param y1 y coordinate of one end of segment + * @param z1 z coordinate of one end of segment + * @param x2 x coordinate of other end of segment + * @param y2 y coordinate of other end of segment + * @param z2 z coordinate of other end of segment + * @return true if the segment intersects the sphere + */ + public boolean segmentIntersects(double x1, double y1, double z1, double x2, double y2, double z2) { + return (distToSegment(0, 0, 0, x1, y1, z1, x2, y2, z2) < _radius); + } + + /** + * The closest distance of a line segment to a point + * + * @param px x coordinate of point + * @param py y coordinate of point + * @param pz z coordinate of point + * @param x1 x coordinate of one end of segment + * @param y1 y coordinate of one end of segment + * @param z1 z coordinate of one end of segment + * @param x2 x coordinate of other end of segment + * @param y2 y coordinate of other end of segment + * @param z2 z coordinate of other end of segment + * @return the closest distance of the segment to point p + */ + private double distToSegment(double px, double py, double pz, double x1, double y1, double z1, double x2, double y2, + double z2) { + + double line_dist = distSq(x1, y1, z1, x2, y2, z2); + if (line_dist == 0) { + return distSq(px, py, pz, x1, y1, z1); + } + double t = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1) + (pz - z1) * (z2 - z1)) / line_dist; + t = Math.max(0, Math.min(1, t)); + return Math.sqrt(distSq(px, py, pz, x1 + t * (x2 - x1), y1 + t * (y2 - y1), z1 + t * (z2 - z1))); + } + + // the square of the distance between two points + private double distSq(double x1, double y1, double z1, double x2, double y2, double z2) { + double dx = x2 - x1; + double dy = y2 - y1; + double dz = z2 - z1; + return dx * dx + dy * dy + dz * dz; + + } + +} diff --git a/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Vector.java b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Vector.java new file mode 100644 index 0000000000..8c00dfbd4d --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/main/java/cnuphys/CLAS12Swim/geometry/Vector.java @@ -0,0 +1,142 @@ +package cnuphys.CLAS12Swim.geometry; + +/** + * Ordinary 3D vector + * + * @author heddle + * + */ +public class Vector extends Point { + + /** Effectively zero */ + private static final double TINY = 1.0e-20; + + + /** + * Create a new vector with a zero components + */ + public Vector() { + } + + /** + * Create a Vector from a point + * + * @param p the point + */ + public Vector(Point p) { + this(p.x, p.y, p.z); + } + + /** + * Create a vector + * + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + */ + public Vector(double x, double y, double z) { + super(x, y, z); + } + + /** + * The square of the length of the vector + * + * @return the square of the length of the vector + */ + public double lengthSquared() { + return x * x + y * y + z * z; + } + + /** + * The length of the vector + * + * @return the length of the vector + */ + public double length() { + return Math.sqrt(lengthSquared()); + } + + /** + * The cross product of two vectors + * + * @param a one vector + * @param b other vector + * @return c = a x b + */ + public static Vector cross(Vector a, Vector b) { + Vector c = new Vector(); + cross(a, b, c); + return c; + } + + /** + * The in-place cross product of two vectors + * + * @param a one vector + * @param b other vector + * @param c on return c = a x b + */ + public static void cross(Vector a, Vector b, Vector c) { + c.x = a.y * b.z - a.z * b.y; + c.y = a.z * b.x - a.x * b.z; + c.z = a.x * b.y - a.y * b.x; + } + + + /** + * Get a unit vector in the same direction as this + * + * @return a unit vector + */ + public Vector unitVector() { + double len = length(); + if (len < TINY) { + return null; + } + + return new Vector(x / len, y / len, z / len); + } + + /** + * Multiplies each element of a vector by a scalar. + * + * @param vector The vector to be multiplied. + * @param scalar The scalar value for multiplication. + * @return The resulting vector after multiplication. + */ + public static double[] scalarMultiply(double[] vector, double scalar) { + double[] result = new double[vector.length]; + for (int i = 0; i < vector.length; i++) { + result[i] = vector[i] * scalar; + } + return result; + } + + /** + * Adds multiple vectors together element-wise. + * + * @param vectors An array of vectors to be added. + * @return The resulting vector after addition. + */ + public static double[] addVectors(double[]... vectors) { + if (vectors.length == 0) { + throw new IllegalArgumentException("At least one vector is required for addition."); + } + + int length = vectors[0].length; + for (double[] vector : vectors) { + if (vector.length != length) { + throw new IllegalArgumentException("All vectors must be of the same length."); + } + } + + double[] result = new double[length]; + for (double[] vector : vectors) { + for (int i = 0; i < length; i++) { + result[i] += vector[i]; + } + } + return result; + } + +} \ No newline at end of file diff --git a/common-tools/cnuphys/clas12-swimmer/src/test/java/cnuphys/CLAS12Swim/CLAS12SwimmerTest.java b/common-tools/cnuphys/clas12-swimmer/src/test/java/cnuphys/CLAS12Swim/CLAS12SwimmerTest.java new file mode 100644 index 0000000000..d5076a3dd0 --- /dev/null +++ b/common-tools/cnuphys/clas12-swimmer/src/test/java/cnuphys/CLAS12Swim/CLAS12SwimmerTest.java @@ -0,0 +1,121 @@ +package cnuphys.CLAS12Swim; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import cnuphys.magfield.ZeroProbe; +import cnuphys.CLAS12Swim.geometry.Sphere; + +public class CLAS12SwimmerTest { + + private static final double POSITION_TOLERANCE = 1.0e-7; + + @Test + public void chargedParticleFollowsStraightLineInZeroField() { + CLAS12Swimmer swimmer = new CLAS12Swimmer(new ZeroProbe()); + double pathLength = 100.0; + double theta = 60.0; + double phi = 30.0; + + CLAS12SwimResult result = swimmer.swim(1, 1.0, 2.0, 3.0, 1.0, theta, phi, + pathLength, 0.01, 1.0e-9); + + assertTrue(result.statusString(), result.isSuccess()); + assertEquals(pathLength, result.getPathLength(), POSITION_TOLERANCE); + + double sinTheta = Math.sin(Math.toRadians(theta)); + double[] finalState = result.getFinalU(); + assertEquals(1.0 + pathLength * sinTheta * Math.cos(Math.toRadians(phi)), finalState[0], + POSITION_TOLERANCE); + assertEquals(2.0 + pathLength * sinTheta * Math.sin(Math.toRadians(phi)), finalState[1], + POSITION_TOLERANCE); + assertEquals(3.0 + pathLength * Math.cos(Math.toRadians(theta)), finalState[2], POSITION_TOLERANCE); + } + + @Test + public void neutralParticleReachesTargetZExactly() { + CLAS12Swimmer swimmer = new CLAS12Swimmer(new ZeroProbe()); + + CLAS12SwimResult result = swimmer.swimZ(0, 0.0, 0.0, 0.0, 1.0, 60.0, 0.0, + 50.0, 1.0e-8, 200.0, 0.01, 1.0e-9); + + assertTrue(result.statusString(), result.isSuccess()); + assertEquals(50.0, result.getFinalU()[2], POSITION_TOLERANCE); + assertEquals(100.0, result.getPathLength(), POSITION_TOLERANCE); + } + + @Test + public void momentumBelowThresholdIsRejected() { + CLAS12Swimmer swimmer = new CLAS12Swimmer(new ZeroProbe()); + + CLAS12SwimResult result = swimmer.swim(1, 0.0, 0.0, 0.0, 1.0e-6, 45.0, 0.0, + 100.0, 0.01, 1.0e-9); + + assertFalse(result.isSuccess()); + assertEquals(CLAS12Swimmer.BELOW_MIN_MOMENTUM, result.getStatus()); + assertEquals("BELOW_MIN_MOMENTUM", result.statusString()); + } + + @Test + public void fixedStepSwimUsesRequestedStepSize() { + CLAS12Swimmer swimmer = new CLAS12Swimmer(new ZeroProbe()); + + CLAS12SwimResult result = swimmer.swimFixed(1, 0.0, 0.0, 0.0, 1.0, 90.0, 0.0, + 10.0, 2.0); + + assertTrue(result.statusString(), result.isSuccess()); + assertEquals(10.0, result.getPathLength(), POSITION_TOLERANCE); + assertEquals(10.0, result.getFinalU()[0], POSITION_TOLERANCE); + assertEquals(6, result.getNStep()); + } + + @Test + public void planeArrayOverloadDelegatesToPlaneSwim() { + CLAS12Swimmer swimmer = new CLAS12Swimmer(new ZeroProbe()); + + CLAS12SwimResult result = swimmer.swimPlane(1, 0.0, 0.0, 0.0, 1.0, 60.0, 0.0, + new double[] {0.0, 0.0, 1.0}, new double[] {0.0, 0.0, 25.0}, + 1.0e-7, 100.0, 0.01, 1.0e-9); + + assertTrue(result.statusString(), result.isSuccess()); + assertEquals(25.0, result.getFinalU()[2], POSITION_TOLERANCE); + } + + @Test + public void sphereSwimStopsAtSurface() { + CLAS12Swimmer swimmer = new CLAS12Swimmer(new ZeroProbe()); + CLAS12SwimResult result = swimmer.swimSphere(1, 0.0, 0.0, 0.0, 1.0, 90.0, 0.0, + new Sphere(25.0), 1.0e-7, 100.0, 0.01, 1.0e-9); + + assertTrue(result.statusString(), result.isSuccess()); + assertEquals(25.0, result.getFinalU()[0], POSITION_TOLERANCE); + assertEquals(25.0, result.getPathLength(), POSITION_TOLERANCE); + } + + @Test + public void zLineSwimFindsClosestApproach() { + CLAS12Swimmer swimmer = new CLAS12Swimmer(new ZeroProbe()); + CLAS12SwimResult result = swimmer.swimZLine(1, 12.0, 7.0, 0.0, 1.0, 90.0, 180.0, + 2.0, 3.0, 1.0e-7, 100.0, 0.01, 1.0e-9); + + assertTrue(result.statusString(), result.isSuccess()); + assertEquals(2.0, result.getFinalU()[0], POSITION_TOLERANCE); + assertEquals(7.0, result.getFinalU()[1], POSITION_TOLERANCE); + assertEquals(10.0, result.getPathLength(), POSITION_TOLERANCE); + } + + @Test + public void beamlineSwimFindsClosestApproach() { + CLAS12Swimmer swimmer = new CLAS12Swimmer(new ZeroProbe()); + CLAS12SwimResult result = swimmer.swimBeamline(1, 10.0, 5.0, 0.0, 1.0, 90.0, 180.0, + 1.0e-7, 100.0, 0.01, 1.0e-9); + + assertTrue(result.statusString(), result.isSuccess()); + assertEquals(0.0, result.getFinalU()[0], POSITION_TOLERANCE); + assertEquals(5.0, result.getFinalU()[1], POSITION_TOLERANCE); + assertEquals(10.0, result.getPathLength(), POSITION_TOLERANCE); + } +} diff --git a/common-tools/cnuphys/pom.xml b/common-tools/cnuphys/pom.xml index e3a0dc1eb1..34ce8d1d5c 100644 --- a/common-tools/cnuphys/pom.xml +++ b/common-tools/cnuphys/pom.xml @@ -18,6 +18,7 @@ snr magfield swimmer + clas12-swimmer