trajectory/ballistics/calculators: Docs + reorganization

- BCResult: Define a static no solution record
- Refactor evalSingleCandidate into BallisticCalculator
    - getCandidatePenalty() made private
This commit is contained in:
Ray Healy 2026-03-14 12:08:27 -04:00
parent 138dfc5d7f
commit 93aa5b7430
4 changed files with 74 additions and 28 deletions

View File

@ -7,6 +7,8 @@ import org.fairportrobotics.frc.robolib.trajectory.ballistics.constraints.BCEval
*/ */
public record BCResult(Double penalty, BCEvalParams evalParams) { public record BCResult(Double penalty, BCEvalParams evalParams) {
public static final BCResult kNoSolution = new BCResult(null, null);
/** /**
* Checks if the result is valid (i.e. checks if penalty != null) * Checks if the result is valid (i.e. checks if penalty != null)
* @return True if the evalParams evaluates as valid, false otherwise * @return True if the evalParams evaluates as valid, false otherwise

View File

@ -10,24 +10,43 @@ import org.fairportrobotics.frc.robolib.trajectory.ballistics.models.BallisticMo
import edu.wpi.first.units.measure.Angle; import edu.wpi.first.units.measure.Angle;
/**
* Base cass for ballistics calculators
*/
public abstract class BallisticCalculator { public abstract class BallisticCalculator {
/** The list of constraints used to evaluate solutions */
private ArrayList<BallisticConstraint> constraints; private ArrayList<BallisticConstraint> constraints;
/** The weight to assign to difference in velocity while evaluating */
private double velocityDiffWeight; private double velocityDiffWeight;
// //
// Setup code // Setup code
// //
/**
* The default constructor. Sets velocity diff weight to 1.0
*/
public BallisticCalculator() { public BallisticCalculator() {
constraints = new ArrayList<>(); constraints = new ArrayList<>();
this.velocityDiffWeight = 1.0; this.velocityDiffWeight = 1.0;
} }
/**
* Add a constraint to the calculator
* @param constraint The constraint to add to the calculator
* @return this
*/
public BallisticCalculator addConstraint(BallisticConstraint constraint) { public BallisticCalculator addConstraint(BallisticConstraint constraint) {
constraints.add(constraint); constraints.add(constraint);
return this; return this;
} }
/**
* Set the weight for the difference in velocity
* @param weight The weight to assign to difference in velocity
* @return this
*/
public BallisticCalculator setVelocityDiffWeight(double weight) { public BallisticCalculator setVelocityDiffWeight(double weight) {
this.velocityDiffWeight = weight; this.velocityDiffWeight = weight;
return this; return this;
@ -62,7 +81,9 @@ public abstract class BallisticCalculator {
/** /**
* Compute the best solution for given initial conditions * Compute the best solution for given initial conditions.
*
* NOTE: May reset seen candidates
* *
* @param <M> The type of model to compute against * @param <M> The type of model to compute against
* @param <P> The candidate parameter type of the model * @param <P> The candidate parameter type of the model
@ -81,6 +102,30 @@ public abstract class BallisticCalculator {
Velocity3d shooterVelocity Velocity3d shooterVelocity
) throws BallisticException; ) throws BallisticException;
/**
* Generate and evaluate a solution for a given model, param, and robot state
*
* @param <M> The type of model to evaluate for
* @param <P> The type of param to model M
* @param model The model to evaluate for
* @param modelParam The parameter to evaluate for
* @param robotVelocity The robot's current velocity
* @param robotAngle The robot's current angle
* @param shooterVelocity The current velocity provided by the shooter
* @return The BCResult instance for the given params
* @throws BallisticException If thrown by registered constraints
*/
protected <M extends BallisticModel<P>, P> BCResult evaluateCandidate(
M model,
P modelParam,
Velocity3d robotVelocity,
Angle robotAngle,
Velocity3d shooterVelocity
) throws BallisticException {
Velocity3d candidateVelocity = model.getCandidateVelocity(modelParam);
BCEvalParams evalParams = new BCEvalParams(robotVelocity, robotAngle, shooterVelocity, candidateVelocity);
return new BCResult(this.getCandidatePenalty(model, evalParams), evalParams);
}
/** /**
* Get the average penalty for a given set of params * Get the average penalty for a given set of params
@ -91,8 +136,9 @@ public abstract class BallisticCalculator {
* @param evalParams The eval function param block * @param evalParams The eval function param block
* @return The weighted average of penalties from velocity diferences and * @return The weighted average of penalties from velocity diferences and
* constraints (null if evalParams fail any constraints) * constraints (null if evalParams fail any constraints)
* @throws BallisicsException If thrown by registered constraints
*/ */
protected <M extends BallisticModel<P>, P> Double evaluateCandidate( private <M extends BallisticModel<P>, P> Double getCandidatePenalty(
M model, BCEvalParams evalParams M model, BCEvalParams evalParams
) throws BallisticException { ) throws BallisticException {
// Get the weight component for velocity difference // Get the weight component for velocity difference

View File

@ -4,18 +4,23 @@ import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
import org.fairportrobotics.frc.robolib.trajectory.Velocity3d; import org.fairportrobotics.frc.robolib.trajectory.Velocity3d;
import org.fairportrobotics.frc.robolib.trajectory.ballistics.constraints.BCEvalParams;
import org.fairportrobotics.frc.robolib.trajectory.ballistics.exceptions.BallisticException; import org.fairportrobotics.frc.robolib.trajectory.ballistics.exceptions.BallisticException;
import org.fairportrobotics.frc.robolib.trajectory.ballistics.models.BallisticModel; import org.fairportrobotics.frc.robolib.trajectory.ballistics.models.BallisticModel;
import edu.wpi.first.units.measure.Angle; import edu.wpi.first.units.measure.Angle;
/**
* Simple greedy calculator (i.e. returns the first valid result)
*/
public class GreedyBallisticCalculator extends BallisticCalculator { public class GreedyBallisticCalculator extends BallisticCalculator {
/**
* Default constructor for the calculator
*/
public GreedyBallisticCalculator() { public GreedyBallisticCalculator() {
super(); super();
} }
@Override
public <M extends BallisticModel<P>, P> BCResult computeSolution( public <M extends BallisticModel<P>, P> BCResult computeSolution(
M model, M model,
P initParam, P initParam,
@ -33,18 +38,16 @@ public class GreedyBallisticCalculator extends BallisticCalculator {
P param = candidate_params.removeFirst(); P param = candidate_params.removeFirst();
if(model.paramComputed(param)) continue; if(model.paramComputed(param)) continue;
Velocity3d candidate = model.getCandidateVelocity(param); BCResult candidate = this.evaluateCandidate(
if(candidate != null) { model, param, robotVelocity, robotAngle, shooterVelocity
BCEvalParams evalParams = new BCEvalParams(robotVelocity, robotAngle, shooterVelocity, candidate); );
Double penalty = this.evaluateCandidate(model, evalParams); if(candidate.isValid()) {
if(penalty != null) { return candidate;
return new BCResult(penalty, evalParams);
}
} }
// Update the candidate list // Update the candidate list
candidate_params.addAll(Arrays.asList(model.getNeighborParams(param))); candidate_params.addAll(Arrays.asList(model.getNeighborParams(param)));
} }
return new BCResult(null, null); return BCResult.kNoSolution;
} }
} }

View File

@ -1,30 +1,25 @@
package org.fairportrobotics.frc.robolib.trajectory.ballistics.calculators; package org.fairportrobotics.frc.robolib.trajectory.ballistics.calculators;
import org.fairportrobotics.frc.robolib.trajectory.Velocity3d; import org.fairportrobotics.frc.robolib.trajectory.Velocity3d;
import org.fairportrobotics.frc.robolib.trajectory.ballistics.constraints.BCEvalParams;
import org.fairportrobotics.frc.robolib.trajectory.ballistics.exceptions.BallisticException; import org.fairportrobotics.frc.robolib.trajectory.ballistics.exceptions.BallisticException;
import org.fairportrobotics.frc.robolib.trajectory.ballistics.models.BallisticModel; import org.fairportrobotics.frc.robolib.trajectory.ballistics.models.BallisticModel;
import edu.wpi.first.units.measure.Angle; import edu.wpi.first.units.measure.Angle;
/**
* Solution calculator implementing a gradient descent algorithm to find a
* solution with a local minimum penalty (not guaranteed to find a global
* minimum penalty)
*/
public class HillDescentBallisticCalculator extends BallisticCalculator { public class HillDescentBallisticCalculator extends BallisticCalculator {
/**
* The default consttructor for the calculator
*/
public HillDescentBallisticCalculator() { public HillDescentBallisticCalculator() {
super(); super();
} }
private <M extends BallisticModel<P>, P> BCResult evalSingleCandidate(
M model,
P modelParam,
Velocity3d robotVelocity,
Angle robotAngle,
Velocity3d shooterVelocity
) throws BallisticException {
Velocity3d candidateVelocity = model.getCandidateVelocity(modelParam);
BCEvalParams evalParams = new BCEvalParams(robotVelocity, robotAngle, shooterVelocity, candidateVelocity);
return new BCResult(this.evaluateCandidate(model, evalParams), evalParams);
}
public <M extends BallisticModel<P>, P> BCResult computeSolution( public <M extends BallisticModel<P>, P> BCResult computeSolution(
M model, M model,
P initParam, P initParam,
@ -35,7 +30,7 @@ public class HillDescentBallisticCalculator extends BallisticCalculator {
model.paramComputedReset(); model.paramComputedReset();
P bestParam = initParam; P bestParam = initParam;
BCResult bestResult = this.evalSingleCandidate(model, initParam, robotVelocity, robotAngle, shooterVelocity); BCResult bestResult = this.evaluateCandidate(model, initParam, robotVelocity, robotAngle, shooterVelocity);
boolean foundImprovement = true; boolean foundImprovement = true;
while(bestResult.isValid() && foundImprovement) { while(bestResult.isValid() && foundImprovement) {
@ -44,7 +39,7 @@ public class HillDescentBallisticCalculator extends BallisticCalculator {
for (P param : model.getNeighborParams(bestParam)) { for (P param : model.getNeighborParams(bestParam)) {
if(model.paramComputed(param)) continue; if(model.paramComputed(param)) continue;
BCResult result = this.evalSingleCandidate(model, param, robotVelocity, robotAngle, shooterVelocity); BCResult result = this.evaluateCandidate(model, param, robotVelocity, robotAngle, shooterVelocity);
if(result.isValid() && result.penalty() < bestResult.penalty()) { if(result.isValid() && result.penalty() < bestResult.penalty()) {
bestParam = param; bestParam = param;
bestResult = result; bestResult = result;