Part 14: Goal Seek

Kerry Back, Rice University

What is Goal Seek?

Excel’s Goal Seek changes one input cell to achieve a desired value in a formula cell. Python’s fsolve does that, but it can also solve for multiple input values simultaneously.

Python fsolve:

  • Define an equation that equals zero at your target
  • fsolve finds the input(s) that makes it zero

fsolve returns a numpy array. The elements of that array are extracted as arr[0], arr[1], etc.

Import Libraries

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.optimize import minimize, fsolve
from scipy.linalg import solve
import warnings
warnings.filterwarnings('ignore')

Simple Example: Quadratic Equation

Find the values of x that make x² - 5x + 6 = 0

We know from algebra (factoring or quadratic formula) that there are two solutions: x = 2 and x = 3.

# STEP 1: Define a function that is zero at the value we want to find
def function_to_find_zero(x):
    return x**2 - 5*x + 6

# STEP 2: Find solution(s). Vary initial values to look for multiple solutions.
initial_value1 = 0
solution1 = fsolve(function_to_find_zero, initial_value1)
initial_value2 = 5
solution2 = fsolve(function_to_find_zero, initial_value2)

print(f"Starting near 0: solution = {solution1}")
print(f"Starting near 5: solution = {solution2}")
Starting near 0: solution = [2.]
Starting near 5: solution = [3.]

Practice: Using fsolve

Exercise 1 (with Gemini): Ask Gemini to “use fsolve to find the value of x where x^2 - 9 = 0”

Exercise 2 (on your own): Type from scipy.optimize import fsolve then fsolve(lambda x: x - 5, 0) and run it to find where x - 5 = 0.

Verify the Solutions

print(f"2**2 - 5*2 + 6 = {function_to_find_zero(2)}")
print(f"3**2 - 5*3 + 6 = {function_to_find_zero(3)}")
2**2 - 5*2 + 6 = 0
3**2 - 5*3 + 6 = 0

Business Example: NPV Break-Even Analysis

What annual sales volume gives NPV = 0?

Project assumptions:

  • Initial CapEx: $100,000
  • Project Life: 5 years
  • Discount Rate: 10%
  • Unit Price: $50
  • COGS per Unit: $25
  • Annual SG&A: $5,000
  • Tax Rate: 30%
  • NWC: $9 per unit
  • Depreciation: Straight-line

NPV Calculation Function

# Project assumptions
initial_capex = 100000
project_life = 5
discount_rate = 0.10
unit_price = 50
cogs_per_unit = 25
sga_annual = 5000
tax_rate = 0.30
nwc_per_unit = 9
annual_depreciation = initial_capex / project_life

NPV Calculation Function (continued)

def calculate_npv(annual_qty):
    """Calculate NPV for a given annual sales quantity"""
    cash_flows = [-initial_capex]  # Year 0: Initial investment
    nwc_balances = []

    for year in range(1, project_life + 1):
        # Annual financial calculations
        sales = annual_qty * unit_price
        cogs = annual_qty * cogs_per_unit
        gross_profit = sales - cogs
        ebitda = gross_profit - sga_annual
        ebit = ebitda - annual_depreciation
        net_income = ebit * (1 - tax_rate)

        # Net Working Capital
        nwc = annual_qty * nwc_per_unit
        nwc_balances.append(nwc)

        if year == 1:
            change_nwc = nwc
        else:
            change_nwc = nwc - nwc_balances[year-2]

        # Cash Flow
        if year < project_life:
            cash_flow = net_income + annual_depreciation - change_nwc
        else:
            cash_flow = net_income + annual_depreciation + nwc

        cash_flows.append(cash_flow)

    # Calculate NPV
    npv = 0
    for year, cf in enumerate(cash_flows):
        npv += cf / (1 + discount_rate)**year

    return npv

Test NPV Function

# Demonstrate NPV calculation with example quantity
example_qty = 5000
example_npv = calculate_npv(example_qty)
print(f"At {example_qty:,} units/year, NPV = ${example_npv:,.0f}")
At 5,000 units/year, NPV = $228,203

Find Break-Even Volume

# We want to set NPV = 0, so we can use calculate_npv as function_to_find_zero
initial_guess = 4000
breakeven_qty = fsolve(calculate_npv, initial_guess)[0]
print(f"Required Annual Sales Volume: {breakeven_qty:,.0f} units")

# Verify NPV = 0
verification_npv = calculate_npv(breakeven_qty)
print(f"NPV at break-even volume: ${verification_npv:,.2f}")
Required Annual Sales Volume: 1,420 units
NPV at break-even volume: $0.00

scipy.optimize.minimize

While fsolve finds where functions equal zero, minimize finds where functions reach their lowest value.

Important attributes of the result object:

  • result.success: was the minimization successful (True or False)
  • result.x: the value of x that minimizes the function
  • result.fun: the value of the function at the minimum

Minimize Example

Find the value of x that minimizes f(x) = x² - 4x + 7

def function_to_minimize(x):
    return x**2 - 4*x + 7

initial_guess = 0
result = minimize(function_to_minimize, initial_guess)

print(f'Was the minimization successful? {result.success}')
print(f'What is the minimizing value of x? {result.x}')
print(f'What is the value of the function at the minimum? {result.fun}')
Was the minimization successful? True
What is the minimizing value of x? [2.00000002]
What is the value of the function at the minimum? 3.0

Practice: Minimize

Exercise 1 (with Gemini): Ask Gemini to “use scipy.optimize.minimize to find the minimum of the function f(x) = x^2 + 3x + 2”

Exercise 2 (on your own): Type from scipy.optimize import minimize then result = minimize(lambda x: x**2, 5) then print(result.x) and run it.

Maximization via Minimization

To solve a maximize problem, we minimize the negative of the function.

Key Concept: The x value that minimizes -f(x) is the SAME x value that maximizes f(x)

Example:

  • To maximize profit π(Q) = 80Q - 2Q² - 500
  • We minimize -π(Q) = -80Q + 2Q² + 500