Developer Documentation
This section contains documentation for internal methods and developer-focused functionality that may be useful for advanced development and extending RootSolvers.jl.
Internal Methods
These functions are used internally by the solvers but are exported and may be useful for advanced development.
RootSolvers.method_args
— Functionmethod_args(method::RootSolvingMethod)
Extract the initial guess(es) for a root-solving method for internal dispatch.
This function is used internally to unpack method parameters for passing to the appropriate find_zero
implementation.
Arguments
method::RootSolvingMethod
: The root-solving method instance
Returns
Tuple
: Initial guess(es) specific to the method type
Example
method = SecantMethod{Float64}(0.0, 1.0)
args = method_args(method) # Returns (0.0, 1.0)
RootSolvers.value_deriv
— Functionvalue_deriv(f, x)
Compute both the function value and its derivative at point x
using automatic differentiation.
This function uses ForwardDiff.jl to simultaneously compute f(x)
and f'(x)
, which is more efficient than computing them separately when both are needed (as in Newton's method). It is used internally by NewtonsMethodAD
.
Arguments
f
: Function to evaluatex::FT
: Point at which to evaluate the function and derivative
Returns
Tuple{FT, FT}
:(f(x), f'(x))
where the second element is the derivative
Example
f(x) = x^3 - 2x + 1
val, deriv = value_deriv(f, 1.5)
# val ≈ 1.375, deriv ≈ 4.75
RootSolvers.default_tol
— Functiondefault_tol(FT)
Returns the default tolerance for a given type FT
. This is a helper function to provide a consistent default tolerance for different numerical types.
Arguments
FT
: The type of the numerical value (e.g.,Float64
,ComplexF64
).
Returns
AbstractTolerance
: A default tolerance object.
Example
using RootSolvers
# Find the default tolerance for Float64
tol = default_tol(Float64)
println("Default tolerance for Float64: ", tol)
# Default tolerance for Float64: SolutionTolerance{Float64}(1e-4)
Core Types
RootSolvingMethod
RootSolvers.RootSolvingMethod
— TypeRootSolvingMethod{FT} <: AbstractType
Abstract type for root-finding methods in RootSolvers.jl.
This is the base type for all numerical methods used to find roots of scalar functions. Each concrete method type should implement the method_args
function to extract initial guesses and parameters for the solver.
Type Parameters
FT
: The floating-point type for computations (e.g.,Float64
,Float32
)
Concrete Implementations
SecantMethod{FT}
: Linear interpolation between two pointsRegulaFalsiMethod{FT}
: Bracketing method with linear interpolationBisectionMethod{FT}
: Simple bracketing method with guaranteed convergenceBrentsMethod{FT}
: Robust bracketing method combining multiple techniquesNewtonsMethodAD{FT}
: Newton's method with automatic differentiationNewtonsMethod{FT}
: Newton's method with user-provided derivatives
Interface Requirements
All concrete subtypes must implement:
method_args(method)
: Return initial guesses as a tuplefind_zero(f, method, args..., soltype, tol, maxiters)
: Main solver implementation
Example
# Define a custom method
struct MyCustomMethod{FT} <: RootSolvingMethod{FT}
x0::FT
end
# Implement required interface
method_args(method::MyCustomMethod) = (method.x0,)
# Use with find_zero
sol = find_zero(x -> x^2 - 4, MyCustomMethod{Float64}(1.0))
SolutionType
RootSolvers.SolutionType
— TypeSolutionType <: AbstractType
Abstract type for solution formats in RootSolvers.jl.
This is the base type for all solution types that control the level of detail returned by find_zero
. Each concrete solution type should implement the SolutionResults
constructor and history management functions.
Concrete Implementations
CompactSolution
: Memory-efficient, GPU-compatible solution with minimal outputVerboseSolution
: CPU-only solution with detailed diagnostics and iteration history
Interface Requirements
All concrete subtypes must implement:
SolutionResults(soltype, args...)
: Constructor for the corresponding results typeinit_history(soltype, x)
: Initialize history storage for the solution typepush_history!(history, x, soltype)
: Add a value to the history
Example
# Define a custom solution type
struct MySolution <: SolutionType end
# Implement required interface
SolutionResults(soltype::MySolution, args...) = MySolutionResults(args...)
init_history(::MySolution, x) = [x]
push_history!(history, x, ::MySolution) = push!(history, x)
# Use with find_zero
sol = find_zero(x -> x^2 - 4, SecantMethod(1.0, 3.0), MySolution())
Extending RootSolvers.jl
Adding New Root-Finding Methods
To add a new root-finding method, you need to:
- Define the method struct:
struct MyNewMethod{FT} <: RootSolvingMethod{FT}
x0::FT
# Add other fields as needed
end
- Implement
method_args
:
method_args(method::MyNewMethod) = (method.x0,)
- Implement the main
find_zero
method:
function find_zero(
f::F,
::MyNewMethod,
x0::FT,
soltype::SolutionType,
tol::AbstractTolerance,
maxiters::Int,
) where {F <: Function, FT}
# Your implementation here
return _find_zero_my_method(f, x0, soltype, tol, maxiters)
end
- Implement the core algorithm:
function _find_zero_my_method(f, x0, soltype, tol, maxiters)
# Your root-finding algorithm implementation
# Return a SolutionResults object
end
Adding New Tolerance Types
To add a new tolerance type:
- Define the tolerance struct:
struct MyTolerance{FT} <: AbstractTolerance{FT}
tol::FT
end
- Implement the callable interface:
(tol::MyTolerance)(x1, x2, y) = # your convergence criterion
Adding New Solution Types
To add a new solution type:
- Define the solution type:
struct MySolution <: SolutionType end
- Define the results struct:
struct MySolutionResults{FT} <: AbstractSolutionResults{FT}
root::FT
converged::Bool
# Add other fields as needed
end
- Implement the constructor:
SolutionResults(soltype::MySolution, args...) = MySolutionResults(args...)
- Implement history functions:
init_history(::MySolution, x::FT) where {FT <: Real} = # your initialization
push_history!(history, x, ::MySolution) = # your push logic
Performance Considerations
GPU Compatibility
When extending RootSolvers.jl for GPU compatibility:
- Use
ifelse
instead ofif-else
blocks where possible - Avoid dynamic dispatch in hot loops
- Ensure all functions are type-stable
- Use
CompactSolution
for GPU operations (avoidVerboseSolution
)
Memory Management
CompactSolution
is memory-efficient and GPU-compatibleVerboseSolution
stores iteration history and is CPU-only- Consider memory usage when implementing new solution types
Type Stability
- Ensure all functions return consistent types
- Use
Base.Fix1
for function composition instead of anonymous functions - Avoid type instability in hot loops
Testing Guidelines
Test Structure
The tests are located in the test/
directory:
test/runtests.jl
: Main test suite with comprehensive tests for all methods (including GPU tests)test/test_helper.jl
: Helper functions and test utilitiestest/test_printing.jl
: Tests for solution printing and formatting
What Tests Cover
test/runtests.jl
- All root-finding methods: Secant, Regula Falsi, Brent's, Newton's (AD and manual)
- All tolerance types: Solution, Residual, Relative, and combined tolerances
- All solution types: Compact and Verbose solutions
- Edge cases: Non-finite inputs, convergence failures, high-multiplicity roots
- Broadcasting: Array and GPU compatibility
- Type stability: Different floating-point types (Float32, Float64)
GPU Tests (integrated in test/runtests.jl
)
- GPU kernel tests: CUDA array compatibility
- Broadcasting on GPU: Parallel root-finding on GPU arrays
test/test_helper.jl
- Test utilities: Helper functions for generating test problems
- Method type definitions: Test-specific method types
- Problem generators: Functions to create test cases
test/test_printing.jl
- Solution formatting: Compact and verbose solution display
- Color output: Terminal color coding for convergence status
- History display: Iteration history formatting
Running Tests
Basic Test Suite
# From the project root
julia --project=. -e "using Pkg; Pkg.test()"
# Or using the test script directly
julia --project=. test/runtests.jl
Specific Test Files
# Run the main test suite (CPU only)
julia --project=. test/runtests.jl
# Run tests with GPU arrays (requires CUDA.jl and compatible GPU)
julia --project=. test/runtests.jl CuArray
# Run printing tests
julia --project=. test/test_printing.jl
GPU Tests
# Run all tests including GPU tests (only run when CUDA.jl and compatible GPU is available)
julia --project=. test/runtests.jl CuArray
Test Coverage
The test suite aims for comprehensive coverage:
- Unit tests: Test individual functions and methods
- GPU tests: Test CUDA compatibility and performance (integrated in main test suite)
- Edge case tests: Handle difficult convergence scenarios
- Type stability tests: Ensure GPU compatibility
- Broadcasting tests: Test array and GPU operations
Continuous Integration
Tests are automatically run on:
- GitHub Actions: Multiple Julia versions and operating systems
- Code coverage: Tracked via CodeCov
- GPU tests: Run on CUDA-compatible runners