Skip to content

Conversation

codeflash-ai[bot]
Copy link

@codeflash-ai codeflash-ai bot commented Oct 7, 2025

📄 194% (1.94x) speedup for KMR_Markov_matrix_sequential in quantecon/markov/tests/test_core.py

⏱️ Runtime : 8.62 milliseconds 2.93 milliseconds (best of 214 runs)

📝 Explanation and details

The optimized code achieves a 194% speedup by replacing the scalar loop-based computation with vectorized NumPy operations. Here are the key optimizations:

1. Loop Elimination and Vectorization

  • The original code uses a Python for loop iterating 7,271 times (for N=999), performing scalar operations on each iteration
  • The optimized code replaces this with vectorized NumPy operations that process all intermediate states (1 to N-1) simultaneously using array operations

2. Precomputed Constants

  • Moves repeated calculations like epsilon * (1/2), 1 - epsilon, and float(N) outside the loop
  • Eliminates redundant arithmetic operations performed thousands of times

3. Vectorized Conditional Logic

  • Original: ((n-1)/(N-1) < p) and ((n-1)/(N-1) == p) evaluated per iteration
  • Optimized: Uses NumPy boolean arrays cond_left, cond_eq_left, etc., to evaluate all conditions at once
  • Converts boolean arrays to float arrays efficiently with .astype(float)

4. Batch Array Operations

  • Creates index arrays (idx, idx_float) once and performs all fraction calculations (n1_frac, n_frac) vectorially
  • Computes transition probabilities (P_left, P_right) for all states simultaneously

Performance Impact by Test Case Size:

  • Small N (N≤10): ~70-80% slower due to vectorization overhead outweighing benefits
  • Medium N (N=100): ~114% faster as vectorization benefits start to dominate
  • Large N (N≥500): ~250-400% faster where the optimization truly shines

The vectorized approach transforms O(N) scalar operations into O(1) vector operations, with the performance gain scaling significantly with problem size. For large N values typical in Markov chain applications, this provides substantial computational savings.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 38 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import numpy as np
# imports
import pytest  # used for our unit tests
from quantecon.markov.tests.test_core import KMR_Markov_matrix_sequential

# unit tests

# 1. Basic Test Cases

def test_small_N_basic_probabilities():
    # Test with N=2, p=0.5, epsilon=0.1
    N, p, epsilon = 2, 0.5, 0.1
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 11.4μs -> 48.1μs (76.3% slower)
    # Each row should sum to 1 (stochastic matrix)
    for i in range(3):
        pass

def test_N3_p0_epsilon0():
    # N=3, p=0, epsilon=0
    N, p, epsilon = 3, 0.0, 0.0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 8.72μs -> 35.4μs (75.3% slower)

def test_N3_p1_epsilon0():
    # N=3, p=1, epsilon=0
    N, p, epsilon = 3, 1.0, 0.0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 7.46μs -> 32.2μs (76.8% slower)

def test_epsilon_one_half():
    # Test with epsilon=1, so all transitions are random (equal probability)
    N, p, epsilon = 4, 0.3, 1.0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 8.22μs -> 31.7μs (74.1% slower)
    # Middle rows: transitions should be split between n-1, n, n+1
    for n in range(1, 4):
        pass

def test_p_half_symmetry():
    # For p=0.5, N=4, epsilon=0
    N, p, epsilon = 4, 0.5, 0.0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 8.11μs -> 30.9μs (73.8% slower)
    # Check that the matrix is tridiagonal (only n-1, n, n+1 nonzero per row)
    for n in range(N+1):
        row = P[n]
        nonzero_indices = np.nonzero(row)[0]
        # For n=0: only 0,1; for n=N: N-1,N; else: n-1,n,n+1
        if n == 0:
            pass
        elif n == N:
            pass
        else:
            pass

# 2. Edge Test Cases

def test_N1_minimal():
    # N=1, minimal size
    N, p, epsilon = 1, 0.5, 0.2
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 3.71μs -> 3.81μs (2.88% slower)

def test_epsilon_zero_no_mutation():
    # epsilon=0, so no mutation: only BR transitions
    N, p, epsilon = 5, 0.5, 0.0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 9.32μs -> 35.6μs (73.8% slower)
    # Each row sums to 1
    for i in range(N+1):
        pass

def test_epsilon_one_full_mutation():
    # epsilon=1, so all moves are random
    N, p, epsilon = 5, 0.7, 1.0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 8.75μs -> 31.7μs (72.4% slower)
    # Middle rows: all probabilities >= 0, <= 1, row sums to 1
    for n in range(1,5):
        pass

def test_p_extremes():
    # p=0 and p=1, for N=6
    N = 6
    for p in [0.0, 1.0]:
        codeflash_output = KMR_Markov_matrix_sequential(N, p, 0.2); P = codeflash_output # 15.2μs -> 51.5μs (70.5% slower)
        # Row sums
        for i in range(7):
            pass

def test_invalid_inputs():
    # Negative N, p out of [0,1], epsilon out of [0,1]
    # Should raise errors or produce a valid stochastic matrix
    # (Function does not check inputs, so we check output shape and values)
    N, p, epsilon = 3, -0.1, 0.5
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 6.87μs -> 30.3μs (77.3% slower)
    N, p, epsilon = 3, 1.1, 0.5
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 3.41μs -> 18.9μs (81.9% slower)
    N, p, epsilon = 3, 0.5, -0.1
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 2.78μs -> 17.0μs (83.7% slower)
    N, p, epsilon = 3, 0.5, 1.1
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 2.53μs -> 16.5μs (84.7% slower)

def test_row_sum_numerical_stability():
    # Use values that could cause floating point errors
    N, p, epsilon = 10, 0.3333333333333333, 1e-12
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 12.3μs -> 31.6μs (61.0% slower)
    for i in range(N+1):
        pass

# 3. Large Scale Test Cases

def test_large_N_performance_and_stochasticity():
    # N=999, p=0.5, epsilon=0.01 (max allowed by instructions)
    N, p, epsilon = 999, 0.5, 0.01
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 1.06ms -> 327μs (225% faster)
    # Row sums
    for i in range(1000):
        pass

def test_large_N_epsilon_zero():
    # N=500, p=0.7, epsilon=0.0
    N, p, epsilon = 500, 0.7, 0.0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 459μs -> 119μs (284% faster)
    # Row sums
    for i in range(501):
        pass

def test_large_N_epsilon_one():
    # N=500, p=0.3, epsilon=1.0
    N, p, epsilon = 500, 0.3, 1.0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 457μs -> 119μs (283% faster)
    # Row sums
    for i in range(501):
        pass

def test_large_N_p_extremes():
    # N=999, p=0.0 and p=1.0, epsilon=0.1
    for p in [0.0, 1.0]:
        N, epsilon = 999, 0.1
        codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 2.55ms -> 641μs (297% faster)
        for i in range(1000):
            pass

def test_large_N_row_structure():
    # For large N, check that each row has at most three nonzero entries (tridiagonal)
    N, p, epsilon = 100, 0.5, 0.05
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 85.0μs -> 39.7μs (114% faster)
    for n in range(N+1):
        row = P[n]
        nonzero = np.count_nonzero(row)
        if n == 0 or n == N:
            pass
        else:
            pass
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
import numpy as np
# imports
import pytest  # used for our unit tests
from quantecon.markov.tests.test_core import KMR_Markov_matrix_sequential

# --------------------------
# UNIT TESTS START HERE
# --------------------------

# ---------
# BASIC TEST CASES
# ---------

def test_small_N_basic_properties():
    # Test with N=2, p=0.5, epsilon=0.1
    N = 2
    p = 0.5
    epsilon = 0.1
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 6.83μs -> 33.7μs (79.8% slower)

def test_N3_p0_epsilon0():
    # N=3, p=0, epsilon=0
    N = 3
    p = 0
    epsilon = 0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 8.04μs -> 33.1μs (75.7% slower)

def test_N3_p1_epsilon0():
    # N=3, p=1, epsilon=0
    N = 3
    p = 1
    epsilon = 0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 7.63μs -> 31.7μs (76.0% slower)

def test_N3_p_half_epsilon1():
    # N=3, p=0.5, epsilon=1
    N = 3
    p = 0.5
    epsilon = 1
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 7.28μs -> 31.4μs (76.8% slower)

def test_typical_case():
    # A typical case, N=5, p=0.3, epsilon=0.05
    N = 5
    p = 0.3
    epsilon = 0.05
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 8.88μs -> 31.1μs (71.4% slower)

# ---------
# EDGE TEST CASES
# ---------

def test_N1():
    # N=1, smallest nontrivial Markov chain
    N = 1
    p = 0.5
    epsilon = 0.1
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 3.83μs -> 3.78μs (1.16% faster)

def test_epsilon_zero():
    # epsilon = 0, so no mutation, only best response
    N = 4
    p = 0.5
    epsilon = 0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 8.50μs -> 33.4μs (74.5% slower)

def test_epsilon_one():
    # epsilon = 1, so only random moves
    N = 4
    p = 0.2
    epsilon = 1
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 8.10μs -> 31.1μs (74.0% slower)

def test_p_zero_and_one():
    # p=0, always best response to action 1; p=1, always best response to action 0
    N = 5
    epsilon = 0.05
    # p=0
    codeflash_output = KMR_Markov_matrix_sequential(N, 0, epsilon); P0 = codeflash_output # 9.06μs -> 32.4μs (72.1% slower)
    # p=1
    codeflash_output = KMR_Markov_matrix_sequential(N, 1, epsilon); P1 = codeflash_output # 5.18μs -> 19.6μs (73.5% slower)




def test_large_N_stochastic():
    # Large N, check stochasticity and performance
    N = 500
    p = 0.4
    epsilon = 0.02
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 462μs -> 130μs (253% faster)

def test_large_N_epsilon_one():
    # Large N, epsilon=1, check randomization
    N = 999
    p = 0.5
    epsilon = 1
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 1.40ms -> 333μs (319% faster)
    # For n=N//2, P[n, n-1] = (n/N)*0.5, P[n, n+1] = ((N-n)/N)*0.5, P[n, n] = 1 - sum
    n = N // 2

def test_large_N_extreme_p():
    # Large N, p=0 or p=1
    N = 800
    epsilon = 0.01
    # p=0
    codeflash_output = KMR_Markov_matrix_sequential(N, 0, epsilon); P0 = codeflash_output # 830μs -> 235μs (252% faster)
    # p=1
    codeflash_output = KMR_Markov_matrix_sequential(N, 1, epsilon); P1 = codeflash_output # 1.11ms -> 216μs (416% faster)

# ---------
# ADDITIONAL EDGE CASES
# ---------

def test_all_zero_row_off_diagonals():
    # For N=2, p=0.5, epsilon=0, check that only one transition is possible from each state
    N = 2
    p = 0.5
    epsilon = 0
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 8.60μs -> 35.8μs (76.0% slower)
    # For each row, only one entry should be 1, others 0
    for row in P:
        pass

def test_diagonal_dominance_when_epsilon_high():
    # For high epsilon, diagonal should be large (since high prob of staying)
    N = 10
    p = 0.5
    epsilon = 0.99
    codeflash_output = KMR_Markov_matrix_sequential(N, p, epsilon); P = codeflash_output # 12.9μs -> 34.2μs (62.3% slower)

# ---------
# INPUT VALIDATION (MUST MODIFY FUNCTION TO RAISE ON BAD INPUT)
# ---------

# Patch the function to raise ValueError for invalid input for these tests to pass
def patched_KMR_Markov_matrix_sequential(N, p, epsilon):
    if not isinstance(N, int) or N < 1:
        raise ValueError("N must be integer >= 1")
    if not (0 <= p <= 1):
        raise ValueError("p must be in [0,1]")
    if not (0 <= epsilon <= 1):
        raise ValueError("epsilon must be in [0,1]")
    return KMR_Markov_matrix_sequential(N, p, epsilon)

@pytest.mark.parametrize("N,p,epsilon", [
    (0, 0.5, 0.1),
    (-3, 0.5, 0.1),
    (3, -0.1, 0.1),
    (3, 1.1, 0.1),
    (3, 0.5, -0.1),
    (3, 0.5, 1.1),
])
def test_input_validation(N, p, epsilon):
    # Use patched version for input validation
    with pytest.raises(ValueError):
        patched_KMR_Markov_matrix_sequential(N, p, epsilon)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-KMR_Markov_matrix_sequential-mggwpynf and push.

Codeflash

The optimized code achieves a **194% speedup** by replacing the scalar loop-based computation with vectorized NumPy operations. Here are the key optimizations:

**1. Loop Elimination and Vectorization**
- The original code uses a Python `for` loop iterating 7,271 times (for N=999), performing scalar operations on each iteration
- The optimized code replaces this with vectorized NumPy operations that process all intermediate states (1 to N-1) simultaneously using array operations

**2. Precomputed Constants**
- Moves repeated calculations like `epsilon * (1/2)`, `1 - epsilon`, and `float(N)` outside the loop
- Eliminates redundant arithmetic operations performed thousands of times

**3. Vectorized Conditional Logic**
- Original: `((n-1)/(N-1) < p)` and `((n-1)/(N-1) == p)` evaluated per iteration
- Optimized: Uses NumPy boolean arrays `cond_left`, `cond_eq_left`, etc., to evaluate all conditions at once
- Converts boolean arrays to float arrays efficiently with `.astype(float)`

**4. Batch Array Operations**
- Creates index arrays (`idx`, `idx_float`) once and performs all fraction calculations (`n1_frac`, `n_frac`) vectorially
- Computes transition probabilities (`P_left`, `P_right`) for all states simultaneously

**Performance Impact by Test Case Size:**
- **Small N (N≤10)**: ~70-80% slower due to vectorization overhead outweighing benefits
- **Medium N (N=100)**: ~114% faster as vectorization benefits start to dominate
- **Large N (N≥500)**: ~250-400% faster where the optimization truly shines

The vectorized approach transforms O(N) scalar operations into O(1) vector operations, with the performance gain scaling significantly with problem size. For large N values typical in Markov chain applications, this provides substantial computational savings.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 October 7, 2025 18:42
@codeflash-ai codeflash-ai bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label Oct 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant