Core Optimization APIs: Goal-Seeking and Error Handling

BusinessMath Quarterly Series

11 min read

Part 24 of 12-Week BusinessMath Series


What You’ll Learn


The Problem

Many business problems require inverse solving—finding an input that produces a target output:

Manual trial-and-error (guessing values in Excel) is slow, imprecise, and doesn’t scale.


The Solution

Goal-seeking (also called root-finding) automates the inverse problem. BusinessMath implements Newton-Raphson iteration with numerical differentiation for robust convergence.

Goal-Seeking vs. Optimization

Understanding the difference is critical:

Goal-Seeking Optimization
Find where f(x) = target Find where f’(x) = 0
Root-finding Minimize/Maximize
Example: Breakeven price Example: Optimal price
Uses: goalSeek() Uses: minimize(), maximize()

Basic Goal-Seeking

import BusinessMath
import Foundation

// Find x where x² = 4
let result = try goalSeek(
    function: { x in x * x },
    target: 4.0,
    guess: 1.0
)

print(result)  // ~2.0

API Signature:

func goalSeek
            
              ( function: @escaping (T) -> T, target: T, guess: T, tolerance: T = T(1) / T(1_000_000), maxIterations: Int = 1000 ) throws -> T 
            

Parameters:


Example 1: Breakeven Analysis

Find the price where profit equals zero:

import BusinessMath

// Profit function with demand elasticity
func profit(price: Double) -> Double {
    let quantity = 10_000 - 1_000 * price  // Demand curve
    let revenue = price * quantity
    let fixedCosts = 5_000.0
    let variableCost = 4.0
    let totalCosts = fixedCosts + variableCost * quantity
    return revenue - totalCosts
}

// Find breakeven price (profit = 0)
let breakevenPrice = try goalSeek(
    function: profit,
    target: 0.0,
    guess: 4.0,
    tolerance: 0.01
)

print("Breakeven price: \(breakevenPrice.currency(2))")
print("Verification: \(profit(price: breakevenPrice).currency(2))")

Output:

Breakeven price: $5.00
Verification: $0.00

The method: Newton-Raphson typically converges in 5-7 iterations.


Example 2: Target Revenue

Find the sales volume needed to hit a revenue target:

import BusinessMath

let pricePerUnit = 50.0
let targetRevenue = 100_000.0

// Revenue = price × quantity
let requiredQuantity = try goalSeek(
    function: { quantity in pricePerUnit * quantity },
    target: targetRevenue,
    guess: 1_000.0
)

print("Need to sell \(requiredQuantity.number(0)) units")
print("Revenue: \((pricePerUnit * requiredQuantity).currency(0))")

Output:

Need to sell 2,000 units
Revenue: $100,000

Example 3: Internal Rate of Return (IRR)

IRR is the discount rate where NPV equals zero—a perfect goal-seek problem:

import BusinessMath
import Foundation

let cashFlows = [-1_000.0, 200.0, 300.0, 400.0, 500.0]

func npv(rate: Double) -> Double {
    var npv = 0.0
    for (t, cf) in cashFlows.enumerated() {
        npv += cf / pow(1 + rate, Double(t))
    }
    return npv
}

// Find rate where NPV = 0
let irr = try goalSeek(
    function: npv,
    target: 0.0,
    guess: 0.10  // Start with 10% guess
)

print("IRR: \(irr.percent(2))")
print("Verification - NPV at IRR: \(npv(rate: irr).currency(2))")

Output:

IRR: 12.83%
Verification - NPV at IRR: $0.00

The insight: This is exactly how BusinessMath’s irr() function works internally.


Example 4: Equation Solving

Solve complex equations numerically:

import BusinessMath

// Solve: e^x - 2x - 3 = 0
let solution = try goalSeek(
    function: { x in exp(x) - 2*x - 3 },
    target: 0.0,
    guess: 1.0
)

print("Solution: x = \(solution.number(6))")

// Verify: Should be ≈ 0
let verify = exp(solution) - 2*solution - 3
print("Verification: \(verify.number(10))")

Output:

Solution: x = 1.923939
Verification: 0.0000000000

Algorithm: Newton-Raphson Method

Goal-seeking uses Newton-Raphson iteration for root-finding:

x_{n+1} = x_n - (f(x_n) - target) / f'(x_n)

Convergence Properties:

Numerical Differentiation:

Since we don’t have symbolic derivatives, f’(x) is computed using central differences:

f'(x) ≈ (f(x + h) - f(x - h)) / (2h)

Where h is a small step size (default: 0.0001).


Error Handling

Division by Zero

Occurs when the derivative f’(x) = 0 (flat function):

do {
    // Function with zero derivative at x=0
    let result = try goalSeek(
        function: { x in x * x * x },  // f'(0) = 0
        target: 0.0,
        guess: 0.0  // BAD: Starting at stationary point
    )
} catch let error as BusinessMathError {
    print(error.localizedDescription)
    // "Goal-seeking failed: Division by zero encountered"

    if let recovery = error.recoverySuggestion {
        print("How to fix:\n\(recovery)")
        // "Try a different initial guess away from stationary points"
    }
}

Solution: Choose a different initial guess away from stationary points.


Convergence Failed

Occurs when the algorithm doesn’t converge in max iterations:

do {
    let result = try goalSeek(
        function: { x in sin(x) },
        target: 1.5,  // BAD: sin(x) never equals 1.5
        guess: 0.0
    )
} catch let error as BusinessMathError {
    print(error.localizedDescription)
    // "Goal-seeking did not converge within 1000 iterations"

    if let recovery = error.recoverySuggestion {
        print("How to fix:\n\(recovery)")
        // "Try different initial guess, increase max iterations, or relax tolerance"
    }
}

Possible causes:

Solutions:


Choosing Initial Guesses

The initial guess is critical for convergence:

Good Practices

1. Use domain knowledge:

// Breakeven usually between cost and market price
let guess = (costPrice + marketPrice) / 2

2. Try multiple guesses:

let guesses = [5.0, 10.0, 20.0]
for guess in guesses {
    if let result = try? goalSeek(function: f, target: target, guess: guess) {
        print("Found solution: \(result)")
        break
    }
}

3. Start near expected solution:

// If last month's breakeven was $10, start there
let guess = lastMonthBreakeven

4. Avoid problematic points:

// Don't start where derivative is zero
let guess = 1.0  // Not 0.0 for f(x) = x²

The GoalSeekOptimizer Class

For more control and constraint support:

import BusinessMath

func profitFunction(price: Double) -> Double {
    let quantity = 10_000 - 1_000 * price
    let revenue = price * quantity
    let fixedCosts = 5_000.0
    let variableCost = 4.0
    let totalCosts = fixedCosts + variableCost * quantity
    return revenue - totalCosts
}

let optimizer = GoalSeekOptimizer
            
              ( target: 0.0, tolerance: 0.0001, maxIterations: 1000 ) let result = optimizer.optimize( objective: profitFunction, constraints: [], initialGuess: 4.0, bounds: (lower: 0.0, upper: 100.0) ) print("Solution: \(result.optimalValue.currency(2))") print("Converged: \(result.converged)") print("Iterations: \(result.iterations)") 
            

Output:

Solution: $5.00
Converged: true
Iterations: 6

Try It Yourself

Click to expand full playground code
import BusinessMath
import Foundation

// MARK: - Basic Goal Seek

// Find x where x² = 4
let result = try goalSeek(
	function: { x in x * x },
	target: 4.0,
	guess: 1.0
)

print(result.number())  // ~2.0

// MARK: - Breakeven Analysis
// Find the price where profit = 0

// Profit function with demand elasticity
func profit(price: Double) -> Double {
	let quantity = 10_000 - 1_000 * price  // Demand curve
	let revenue = price * quantity
	let fixedCosts = 5_000.0
	let variableCost = 4.0
	let totalCosts = fixedCosts + variableCost * quantity
	return revenue - totalCosts
}

// Find breakeven price (profit = 0)
let breakevenPrice = try goalSeek(
	function: profit,
	target: 0.0,
	guess: 6.0,
	tolerance: 0.01
)

print("Breakeven price: \(breakevenPrice.currency(2))")
print("Verification: \(profit(price: breakevenPrice).currency(2))")

// MARK: - Target Revenue
let pricePerUnit = 50.0
let targetRevenue = 100_000.0

// Revenue = price × quantity
let requiredQuantity = try goalSeek(
	function: { quantity in pricePerUnit * quantity },
	target: targetRevenue,
	guess: 1_000.0
)

print("Need to sell \(requiredQuantity.number(0)) units")
print("Revenue: \((pricePerUnit * requiredQuantity).currency(0))")

// MARK: - Internal Rate of Return

let cashFlows = [-1_000.0, 200.0, 300.0, 400.0, 500.0]

func npv(rate: Double) -> Double {
	var npv = 0.0
	for (t, cf) in cashFlows.enumerated() {
		npv += cf / pow(1 + rate, Double(t))
	}
	return npv
}

// Find rate where NPV = 0
let irr = try goalSeek(
	function: npv,
	target: 0.0,
	guess: 0.10  // Start with 10% guess
)

print("IRR: \(irr.percent(2))")
print("Verification - NPV at IRR: \(npv(rate: irr).currency(2))")

// MARK: - Equation Solving

// Solve: e^x - 2x - 3 = 0
let solution = try goalSeek(
	function: { x in exp(x) - 2*x - 3 },
	target: 0.0,
	guess: 1.0
)

print("Solution: x = \(solution.number(6))")

// Verify: Should be ≈ 0
let verify = exp(solution) - 2*solution - 3
print("Verification: \(verify.number(10))")

// MARK: - Error Handling, Division by Zero

do {
	// Function with zero derivative at x=0
	let result = try goalSeek(
		function: { x in x * x * x },  // f'(0) = 0
		target: 0.0,
		guess: 0.0  // BAD: Starting at stationary point
	)
	print(result)
} catch let error as BusinessMathError {
	print(error.localizedDescription)
	// "Goal-seeking failed: Division by zero encountered"

	if let recovery = error.recoverySuggestion {
		print("How to fix:\n\(recovery)")
		// "Try a different initial guess away from stationary points"
	}
}

// MARK: - Error Handling, Failed Convergence

do {
	let result = try goalSeek(
		function: { x in sin(x) },
		target: 1.5,  // BAD: sin(x) never equals 1.5
		guess: 0.0
	)
} catch let error as BusinessMathError {
	print(error.localizedDescription)
	// "Goal-seeking did not converge within 1000 iterations"

	if let recovery = error.recoverySuggestion {
		print("How to fix:\n\(recovery)")
		// "Try different initial guess, increase max iterations, or relax tolerance"
	}
}

// MARK: - Goal Seek Optimizer Class
func profitFunction(price: Double) -> Double {
	let quantity = 10_000 - 1_000 * price
	let revenue = price * quantity
	let fixedCosts = 5_000.0
	let variableCost = 4.0
	let totalCosts = fixedCosts + variableCost * quantity
	return revenue - totalCosts
}

let optimizer_GS = GoalSeekOptimizer
              
                ( target: 0.0, tolerance: 0.0001, maxIterations: 1000 ) let result_GS = optimizer_GS.optimize( objective: profitFunction, constraints: [], initialGuess: 4.0, bounds: (lower: 0.0, upper: 100.0) ) print("Solution: \(result_GS.optimalValue.currency(2))") print("Converged: \(result_GS.converged)") print("Iterations: \(result_GS.iterations)") 
              

→ Full API Reference: BusinessMath Docs – 5.3 Core Optimization

Modifications to try:

  1. Find the profit-maximizing price (use minimize() on negative profit)
  2. Solve for multiple roots by trying different initial guesses
  3. Build a breakeven calculator for a business with complex cost structure
  4. Compare convergence speed: Newton-Raphson vs. bisection method

Real-World Application

CFO use case: “We need to hit $5M EBITDA next quarter. What revenue do we need given our cost structure and operating leverage?”

Goal-seeking automates this calculation instantly.


★ Insight ─────────────────────────────────────

Why Newton-Raphson Converges Quadratically

Newton-Raphson doubles the number of correct digits with each iteration when close to the solution. This is called quadratic convergence.

Example progression (finding √2):

Why? Taylor series analysis shows the error decreases proportional to the square of the previous error: ε_{n+1} ∝ ε_n²

Trade-off: Fast convergence requires good initial guess. Bad guesses may diverge or converge to wrong root.

─────────────────────────────────────────────────


📝 Development Note

The hardest part was making numerical differentiation robust across all Real types (Float, Double, Decimal, etc.).

Challenge: The step size h for f’(x) ≈ (f(x+h) - f(x-h)) / (2h) must be:

We settled on h = √ε × max(|x|, 1) where ε is machine epsilon. This adapts to:

Result: Goal-seeking works reliably across all numeric types without user tuning.

Related Methodology: Numerical Stability (Week 2) - Covered catastrophic cancellation and condition numbers.


Next Steps

Coming up Thursday: Vector Operations - Understanding the VectorSpace protocol, Vector2D, Vector3D, and VectorN for multivariate optimization.


Series Progress:


Tagged with: optimization, swift-patterns