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_argsFunction
method_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)
source
RootSolvers.value_derivFunction
value_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 evaluate
  • x::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
source
RootSolvers.default_tolFunction
default_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)
source

Core Types

RootSolvingMethod

RootSolvers.RootSolvingMethodType
RootSolvingMethod{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

Interface Requirements

All concrete subtypes must implement:

  • method_args(method): Return initial guesses as a tuple
  • find_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))
source

SolutionType

RootSolvers.SolutionTypeType
SolutionType <: 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 output
  • VerboseSolution: CPU-only solution with detailed diagnostics and iteration history

Interface Requirements

All concrete subtypes must implement:

  • SolutionResults(soltype, args...): Constructor for the corresponding results type
  • init_history(soltype, x): Initialize history storage for the solution type
  • push_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())
source

Extending RootSolvers.jl

Adding New Root-Finding Methods

To add a new root-finding method, you need to:

  1. Define the method struct:
struct MyNewMethod{FT} <: RootSolvingMethod{FT}
    x0::FT
    # Add other fields as needed
end
  1. Implement method_args:
method_args(method::MyNewMethod) = (method.x0,)
  1. 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
  1. 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:

  1. Define the tolerance struct:
struct MyTolerance{FT} <: AbstractTolerance{FT}
    tol::FT
end
  1. Implement the callable interface:
(tol::MyTolerance)(x1, x2, y) = # your convergence criterion

Adding New Solution Types

To add a new solution type:

  1. Define the solution type:
struct MySolution <: SolutionType end
  1. Define the results struct:
struct MySolutionResults{FT} <: AbstractSolutionResults{FT}
    root::FT
    converged::Bool
    # Add other fields as needed
end
  1. Implement the constructor:
SolutionResults(soltype::MySolution, args...) = MySolutionResults(args...)
  1. 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 of if-else blocks where possible
  • Avoid dynamic dispatch in hot loops
  • Ensure all functions are type-stable
  • Use CompactSolution for GPU operations (avoid VerboseSolution)

Memory Management

  • CompactSolution is memory-efficient and GPU-compatible
  • VerboseSolution 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 utilities
  • test/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:

  1. Unit tests: Test individual functions and methods
  2. GPU tests: Test CUDA compatibility and performance (integrated in main test suite)
  3. Edge case tests: Handle difficult convergence scenarios
  4. Type stability tests: Ensure GPU compatibility
  5. 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