Skip to content

Commit ce2f3fe

Browse files
committed
Refactor solvers package
1 parent 2a330be commit ce2f3fe

19 files changed

+7335
-707
lines changed

src/main/java/uk/co/ryanharrison/mathengine/solvers/BisectionSolver.java

Lines changed: 469 additions & 39 deletions
Large diffs are not rendered by default.

src/main/java/uk/co/ryanharrison/mathengine/solvers/BrentSolver.java

Lines changed: 593 additions & 103 deletions
Large diffs are not rendered by default.
Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,52 @@
11
package uk.co.ryanharrison.mathengine.solvers;
22

33
/**
4-
* Enumeration of convergence criterion which decide whether or not a root
5-
* finding algorithm has converged
4+
* Defines how convergence is determined in root-finding algorithms.
5+
* <p>
6+
* Different convergence criteria are appropriate for different scenarios:
7+
* </p>
8+
* <ul>
9+
* <li><b>{@link #NumberOfIterations}</b>: Use when you need a result within a fixed
10+
* computational budget, regardless of accuracy</li>
11+
* <li><b>{@link #WithinTolerance}</b>: Use when you need a result that meets a specific
12+
* accuracy requirement, regardless of computation time</li>
13+
* </ul>
614
*
7-
* @author Ryan Harrison
15+
* <h2>Tolerance Semantics by Algorithm:</h2>
16+
* The specific meaning of "within tolerance" varies by algorithm:
17+
* <ul>
18+
* <li><b>Bisection:</b> |upper - lower| / 2 < tolerance (half-bracket width)</li>
19+
* <li><b>Brent:</b> |upper - lower| < tolerance (bracket width)</li>
20+
* <li><b>Newton-Raphson:</b> |f(x)| < tolerance (function value magnitude)</li>
21+
* <li><b>Newton-Bisection:</b> |dx| < tolerance (step size magnitude)</li>
22+
* </ul>
823
*
24+
* @see EquationSolver#getConvergenceCriteria()
925
*/
1026
public enum ConvergenceCriteria {
11-
NumberOfIterations, WithinTolerance
27+
28+
/**
29+
* Converge after a fixed number of iterations regardless of accuracy.
30+
* <p>
31+
* The algorithm will stop after completing the specified number of iterations
32+
* and return the current estimate, even if the tolerance criterion has not been met.
33+
* </p>
34+
* <p>
35+
* <b>Use when:</b> Computational budget is limited or approximate solutions are acceptable.
36+
* </p>
37+
*/
38+
NumberOfIterations,
39+
40+
/**
41+
* Converge when the result satisfies the tolerance condition.
42+
* <p>
43+
* The algorithm will continue iterating until the tolerance criterion is met,
44+
* up to the maximum number of iterations. If tolerance is not achieved within
45+
* the iteration limit, a {@link ConvergenceException} is thrown.
46+
* </p>
47+
* <p>
48+
* <b>Use when:</b> Accuracy is critical and you can afford the computational cost.
49+
* </p>
50+
*/
51+
WithinTolerance
1252
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package uk.co.ryanharrison.mathengine.solvers;
2+
3+
/**
4+
* Exception thrown when a root-finding algorithm fails to converge within the specified criteria.
5+
* <p>
6+
* This typically occurs when:
7+
* </p>
8+
* <ul>
9+
* <li>The maximum number of iterations is exceeded without meeting the tolerance requirement</li>
10+
* <li>The tolerance criterion cannot be satisfied due to numerical precision limits</li>
11+
* <li>The algorithm gets stuck in a cyclic pattern</li>
12+
* </ul>
13+
*
14+
* <h2>Example:</h2>
15+
* <pre>{@code
16+
* try {
17+
* double root = solver.solve();
18+
* } catch (ConvergenceException e) {
19+
* System.err.println("Failed to converge after " + e.getIterations() + " iterations");
20+
* System.err.println("Last estimate: " + e.getLastEstimate());
21+
* }
22+
* }</pre>
23+
*
24+
* @see SolverException
25+
*/
26+
public class ConvergenceException extends SolverException {
27+
28+
private final int iterations;
29+
private final double lastEstimate;
30+
private final double tolerance;
31+
32+
/**
33+
* Constructs a new convergence exception with detailed context.
34+
*
35+
* @param message the detail message explaining why convergence failed
36+
* @param iterations the number of iterations completed before failure
37+
* @param lastEstimate the last estimated value before giving up
38+
* @param tolerance the tolerance criterion that could not be met
39+
*/
40+
public ConvergenceException(String message, int iterations, double lastEstimate, double tolerance) {
41+
super(String.format("%s (iterations: %d, last estimate: %.10g, tolerance: %.2e)",
42+
message, iterations, lastEstimate, tolerance));
43+
this.iterations = iterations;
44+
this.lastEstimate = lastEstimate;
45+
this.tolerance = tolerance;
46+
}
47+
48+
/**
49+
* Returns the number of iterations completed before convergence failure.
50+
*
51+
* @return the iteration count at failure
52+
*/
53+
public int getIterations() {
54+
return iterations;
55+
}
56+
57+
/**
58+
* Returns the last estimated root value before the algorithm gave up.
59+
* <p>
60+
* This value may be close to the actual root even though convergence criteria were not met.
61+
* </p>
62+
*
63+
* @return the last estimated root value
64+
*/
65+
public double getLastEstimate() {
66+
return lastEstimate;
67+
}
68+
69+
/**
70+
* Returns the tolerance criterion that could not be satisfied.
71+
*
72+
* @return the required tolerance
73+
*/
74+
public double getTolerance() {
75+
return tolerance;
76+
}
77+
}
Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,120 @@
11
package uk.co.ryanharrison.mathengine.solvers;
22

33
/**
4-
* Enumeration representing different methods of retrieving values of the
5-
* derivative of a target function
4+
* Enumeration representing different methods for obtaining derivative values in Newton-based solvers.
5+
* <p>
6+
* Newton-Raphson and hybrid Newton methods require the derivative f'(x) of the target function.
7+
* This enum specifies how those derivative values should be computed.
8+
* </p>
69
*
7-
* @author Ryan Harrison
10+
* <h2>Method Comparison:</h2>
11+
* <table border="1">
12+
* <tr>
13+
* <th>Method</th>
14+
* <th>Accuracy</th>
15+
* <th>Performance</th>
16+
* <th>Use When</th>
17+
* </tr>
18+
* <tr>
19+
* <td>{@link #Numerical}</td>
20+
* <td>Good (O(h⁴))</td>
21+
* <td>Moderate</td>
22+
* <td>Default choice, no derivative available</td>
23+
* </tr>
24+
* <tr>
25+
* <td>{@link #Symbolic}</td>
26+
* <td>Exact</td>
27+
* <td>Fast</td>
28+
* <td>Analytical derivative can be computed</td>
29+
* </tr>
30+
* <tr>
31+
* <td>{@link #Predefined}</td>
32+
* <td>Exact</td>
33+
* <td>Fastest</td>
34+
* <td>Derivative function is known a priori</td>
35+
* </tr>
36+
* </table>
837
*
38+
* @see NewtonRaphsonSolver
39+
* @see NewtonBisectionSolver
940
*/
1041
public enum DifferentiationMethod {
11-
Numerical, Symbolic, Predefined
42+
43+
/**
44+
* Compute derivatives using numerical differentiation.
45+
* <p>
46+
* Uses the {@link uk.co.ryanharrison.mathengine.differential.ExtendedCentralDifferenceMethod}
47+
* which provides O(h⁴) accuracy through a five-point stencil.
48+
* </p>
49+
* <p>
50+
* This is the default method and works for any function, but is slower than symbolic or
51+
* predefined derivatives.
52+
* </p>
53+
*
54+
* <h3>Advantages:</h3>
55+
* <ul>
56+
* <li>Works for any differentiable function</li>
57+
* <li>No manual derivative specification required</li>
58+
* <li>Good accuracy (better than simple finite differences)</li>
59+
* </ul>
60+
*
61+
* <h3>Disadvantages:</h3>
62+
* <ul>
63+
* <li>Slower than exact derivatives (requires multiple function evaluations)</li>
64+
* <li>May have numerical precision issues near discontinuities</li>
65+
* </ul>
66+
*/
67+
Numerical,
68+
69+
/**
70+
* Compute derivatives using symbolic differentiation.
71+
* <p>
72+
* Uses the {@link uk.co.ryanharrison.mathengine.differential.symbolic.Differentiator}
73+
* to analytically differentiate the target function's expression tree.
74+
* </p>
75+
* <p>
76+
* This provides exact derivatives for functions that can be symbolically differentiated,
77+
* and is faster than numerical differentiation.
78+
* </p>
79+
*
80+
* <h3>Advantages:</h3>
81+
* <ul>
82+
* <li>Exact derivatives (no numerical approximation error)</li>
83+
* <li>Faster than numerical differentiation</li>
84+
* <li>Automatically computed from target function</li>
85+
* </ul>
86+
*
87+
* <h3>Disadvantages:</h3>
88+
* <ul>
89+
* <li>Only works for functions with symbolic expressions</li>
90+
* <li>May produce complex derivative expressions</li>
91+
* </ul>
92+
*/
93+
Symbolic,
94+
95+
/**
96+
* Use a user-provided derivative function.
97+
* <p>
98+
* The user supplies a {@link uk.co.ryanharrison.mathengine.Function} representing
99+
* the derivative of the target function.
100+
* </p>
101+
* <p>
102+
* This is the fastest option as it uses pre-computed derivatives, but requires the
103+
* user to provide the correct derivative function.
104+
* </p>
105+
*
106+
* <h3>Advantages:</h3>
107+
* <ul>
108+
* <li>Fastest evaluation (single function call per iteration)</li>
109+
* <li>Exact derivatives</li>
110+
* <li>Full control over derivative implementation</li>
111+
* </ul>
112+
*
113+
* <h3>Disadvantages:</h3>
114+
* <ul>
115+
* <li>Requires manual specification of derivative</li>
116+
* <li>User responsible for correctness</li>
117+
* </ul>
118+
*/
119+
Predefined
12120
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package uk.co.ryanharrison.mathengine.solvers;
2+
3+
/**
4+
* Exception thrown when an iterative root-finding algorithm diverges.
5+
* <p>
6+
* Divergence occurs when the algorithm produces increasingly worse approximations instead
7+
* of converging to a root. This is most common in polishing methods like Newton-Raphson
8+
* when:
9+
* </p>
10+
* <ul>
11+
* <li>The initial guess is far from the actual root</li>
12+
* <li>The derivative is zero or very small at the current estimate</li>
13+
* <li>The function has a local minimum/maximum near the root</li>
14+
* <li>Numerical overflow occurs during iteration</li>
15+
* </ul>
16+
*
17+
* <h2>Example:</h2>
18+
* <pre>{@code
19+
* Function f = new Function("1/x"); // Has no roots
20+
* NewtonRaphsonSolver solver = NewtonRaphsonSolver.builder()
21+
* .targetFunction(f)
22+
* .initialGuess(1.0)
23+
* .build();
24+
*
25+
* // Throws DivergenceException as the algorithm produces infinite values
26+
* solver.solve();
27+
* }</pre>
28+
*
29+
* @see SolverException
30+
*/
31+
public class DivergenceException extends SolverException {
32+
33+
private final int iteration;
34+
private final double lastFiniteValue;
35+
36+
/**
37+
* Constructs a new divergence exception with detailed context.
38+
*
39+
* @param message the detail message explaining the divergence
40+
* @param iteration the iteration number at which divergence was detected
41+
* @param lastFiniteValue the last finite value before divergence (may be NaN or Infinity)
42+
*/
43+
public DivergenceException(String message, int iteration, double lastFiniteValue) {
44+
super(String.format("%s (iteration: %d, last finite value: %.10g)",
45+
message, iteration, lastFiniteValue));
46+
this.iteration = iteration;
47+
this.lastFiniteValue = lastFiniteValue;
48+
}
49+
50+
/**
51+
* Constructs a new divergence exception with simple message.
52+
*
53+
* @param message the detail message explaining the divergence
54+
*/
55+
public DivergenceException(String message) {
56+
this(message, -1, Double.NaN);
57+
}
58+
59+
/**
60+
* Returns the iteration number at which divergence was detected.
61+
*
62+
* @return the iteration number, or -1 if not available
63+
*/
64+
public int getIteration() {
65+
return iteration;
66+
}
67+
68+
/**
69+
* Returns the last finite value computed before divergence.
70+
* <p>
71+
* This value may be NaN or Infinity if the algorithm diverged catastrophically.
72+
* </p>
73+
*
74+
* @return the last finite value, or NaN if not available
75+
*/
76+
public double getLastFiniteValue() {
77+
return lastFiniteValue;
78+
}
79+
}

0 commit comments

Comments
 (0)