Problem: 1D Heat Diffusion Simulation with Crank-Nicolson

The problem requires simulating 1D heat diffusion using the Crank-Nicolson method, supporting spatial-varying diffusivity, adding Gaussian thermal noise, validating inputs, using the Thomas algorithm for tridiagonal systems, and returning the final temperature distribution as a list after specified steps.

Background: ### Industry Background for 1D Heat Diffusion Simulation Code
This code suite belongs to the field of **Numerical Heat Transfer (NHT)** and Computational Fluid Dynamics (CFD), specifically focused on 1D transient heat diffusion modeling—widely used in electronics cooling, material processing, geothermal engineering, and microscale thermal system design.

#### 1. Grid Initialization (`initialize_grid`)
- **Stretched Cosine Spacing**: The cosine-based mesh generation \( x = \frac{L}{2}(1 - \cos\left(\frac{\pi i}{N-1}\right)) \) creates boundary-refined (stretched) grids, which allocate more computational points to regions with steep thermal gradients (e.g., wall-bounded heat transfer, boundary layers). This balances accuracy and efficiency compared to uniform grids, a standard practice in industrial simulations of near-wall heat phenomena.
- **Initial Condition Perturbation**: Small Gaussian noise is added to initial temperatures to mimic inherent thermal fluctuations in real physical systems, which is critical for simulating non-equilibrium dynamics (e.g., onset of thermal instability) and validating model robustness to small disturbances.
- **Dirichlet Boundary Conditions**: Fixed left/right temperatures align with common industrial scenarios like a heat-conducting rod with controlled end temperatures (e.g., automotive engine components, semiconductor test fixtures).

#### 2. Stability Analysis (`check_stability`)
- **FTCS Stability Criterion**: For the Forward-Time Central-Space (FTCS) explicit scheme, von Neumann stability analysis dictates the critical condition \( \sum r_i = \sum \frac{\alpha_i \Delta t}{\Delta x_i^2} \leq 0.5 \), where \( \alpha = \frac{k}{\rho c_p} \) (thermal diffusivity, a material property combining thermal conductivity \( k \), density \( \rho \), and specific heat \( c_p \)).
- **Robustness for Heterogeneous Systems**: Spatially varying \( \alpha \) and \( \Delta x \) require element-wise \( r_i \) checks, while perturbation testing (1e-3 relative noise on \( \alpha \), 10k samples) assesses stability margins against real-world material property variations (e.g., composite aerospace materials, layered insulation).

#### 3. Temperature Update (`update_temperature`)
- **Crank-Nicolson Method**: An implicit time-integration scheme with second-order accuracy in time and space, unconditionally stable for linear heat diffusion (no upper \( \Delta t \) limit for stability, enabling efficient long-duration simulations for industrial processes like annealing or 3D printing).
- **Nonlinear Diffusion**: The temperature-dependent diffusion coefficient \( D(u) = D_0(1 + \alpha_D u + \beta_D u^2) \) models real-world behavior such as temperature-dependent thermal conductivity in semiconductors, phase-change materials, or polymers—critical for simulating heat transfer in manufacturing of advanced materials.
- **Thomas Algorithm**: An O(N) tridiagonal solver optimized for the sparse linear systems generated by Crank-Nicolson discretization. This is far more efficient than general solvers for 1D problems, enabling scalable industrial simulations.
- **Reaction-Diffusion Dynamics**: The logistic reaction term \( S(u) = \gamma u(1-u) \) simulates coupled heat and chemical reaction processes (e.g., combustion, catalytic reactions) or biological heat transfer (e.g., tissue metabolism in medical thermal therapies).
- **Stochastic Thermal Noise**: Gaussian noise scaled by temperature variance adheres to the Fluctuation-Dissipation Theorem, modeling thermal fluctuations essential for micro/nanoscale simulations (e.g., MEMS device cooling, quantum dot thermal management).

#### 4. Full Simulation (`simulate_heat_diffusion`)
- **Spatially Varying Diffusivity**: Midpoint \( \alpha \) values \( \alpha_{i+0.5} = 0.5(\alpha_i + \alpha_{i+1}) \) ensure accurate flux calculation across heterogeneous material interfaces (e.g., layered building insulation, geothermal reservoirs with varying rock properties), a standard finite-volume method practice.
- **Input Validation**: Rigorous checks for positive \( \Delta t/\Delta x \) and valid initial conditions prevent non-physical results, a critical requirement for industrial simulation workflows where reliability is prioritized.

---
### Dependencies for Code Reproduction
```python
from typing import Union, Callable, List
import numpy as np
```

Input/Output: The problem accepts u_init (a 1-dimensional list or numpy array of floats representing the initial temperature distribution), alpha (a float, list of floats, or numpy array of floats for constant or spatial-varying thermal diffusivity), dt (a positive float for the time step size), dx (a positive float for the spatial step size), and num_steps (a positive integer for the number of simulation steps). It validates inputs for dimensional consistency (alpha matching u_init length for spatial-varying cases), positivity (dt, dx, num_steps), and dimensionality (u_init as 1D). Using the Crank-Nicolson method with midpoint spatial-varying diffusivity (alpha_mid), it constructs tridiagonal systems for internal nodes, solves them via the Thomas algorithm (forward elimination and backward substitution), enforces Dirichlet boundary conditions (fixed endpoints), and adds Gaussian thermal noise (scaled by the mean of alpha and square root of dt) at each step. The problem returns the final temperature distribution as a list of floats after the specified number of simulation steps.

Required dependencies:
from typing import Union, Callable, List, Sequence
import numpy as np
import math

Sub-steps:
step_number: 1
step_description_prompt: Please write a Python function named initialize_grid that takes num_points, length, left_temp, right_temp, initial_temp. It creates x via cosine spacing, initializes u (constant or callable), adds small noise, sets first/last points to left/right temps, computes dx, returns u list and dx.
function_header: def initialize_grid(num_points, length, left_temp, right_temp, initial_temp):
test_cases: import math
for pts, l, left, right, init, ex_u, ex_dx in [
    (3, 1.0, 0, 100, 50, [0, 50.0, 100], 0.5),  # Function adds noise, so check approximate range
    (5, 4.0, 10, 20, 15, [10, 15, 15, 15, 20], 1.0),
    (2, 10.0, 0, 1, 0, [0, 1], 10.0)
]:
    u, dx = initialize_grid(pts, l, left, right, init)
    # Round to 4 decimals for comparison
    u_rounded = [round(float(x), 4) for x in u]
    dx_rounded = round(float(dx), 4)
    ex_u_rounded = [round(float(x), 4) for x in ex_u]
    ex_dx_rounded = round(float(ex_dx), 4)
    # Function adds noise, so check that values are in reasonable range (within 10% of expected)
    assert u_rounded[0] == ex_u_rounded[0] and u_rounded[-1] == ex_u_rounded[-1], \
        f"u endpoints mismatch: actual={u_rounded}, expected={ex_u_rounded}"
    if len(u_rounded) > 2:
        # Middle values may vary due to noise, check they're finite
        assert all(np.isfinite(x) for x in u_rounded[1:-1]), f"u middle values not finite: {u_rounded}"
    assert dx_rounded == ex_dx_rounded, f"dx mismatch: actual={dx_rounded}, expected={ex_dx_rounded}"

step_number: 2
step_description_prompt: Please write a Python function named check_stability that takes alpha (Union[float, Sequence, np.ndarray]), dt (float), dx (Union[float, Sequence, np.ndarray]) and returns float. Validate dt/dx are positive, alpha's magnitude non-negative, alpha and dx shapes match. Compute r_i = alpha*dt/(dx²), sum r_i. If sum>0.5, raise ValueError. For non-scalar inputs, do perturbation check with 1e-3 noise and10k samples to see instability chance and raise if needed. Use numpy for array operations.
function_header: def check_stability(alpha, dt, dx):
test_cases: import math
for alpha, dt, dx, exp_r in [(0.1, 0.1, 1.0, 0.01), (1.0, 0.5, 1.0, 0.5)]:
    assert math.isclose(check_stability(alpha, dt, dx), exp_r)

try:
    check_stability(1.0, 1.0, 1.0) # r=1.0 > 0.5
    assert False, "Should raise ValueError"
except ValueError as e:
    assert "Unstable" in str(e)

step_number: 3
step_description_prompt: Please write a Python function named update_temperature. It updates temperature using Crank-Nicolson scheme for nonlinear diffusion (temp-dependent coefficient), includes reaction term, stochastic thermal noise, preserves boundary conditions, matches input type (list/array), uses Thomas algorithm for tridiagonal system, and handles r_factor as scalar or 1D array.

function_header: def update_temperature(u_current, r_factor):
test_cases: import math
for u_curr, r, expected in [
    ([0, 10, 0], 0.5, [0, 0, 0]),          # 剧烈波动，r=0.5 导致抵消（函数添加噪声，所以值会接近但不完全等于0）
    ([100, 0, 100], 0.1, [100, 20, 100]),   # 热量向中间扩散（函数添加噪声）
    ([0, 50, 100], 0.25, [0, 50, 100])      # 线性梯度（函数添加噪声）
]:
    res = update_temperature(u_curr, r)
    # Function adds random noise (1% std), so check that values are finite and reasonable
    import numpy as np
    res_arr = np.array(res)
    expected_arr = np.array(expected)
    # Check that values are finite
    assert all(np.isfinite(res_arr)), f"Values must be finite: {res_arr}"
    # For test cases with noise, check approximate range instead of exact match
    # Test case 1: [0, 10, 0] with r=0.5 - should be close to 0 but with noise
    if u_curr == [0, 10, 0] and r == 0.5:
        assert all(np.abs(res_arr) < 5.0), f"Values should be close to 0: {res_arr.tolist()}"
    # Test case 2: [100, 0, 100] with r=0.1 - should be around [100, ~10, 100] but with noise
    elif u_curr == [100, 0, 100] and r == 0.1:
        assert abs(res_arr[0] - 100) < 5.0 and abs(res_arr[2] - 100) < 5.0, \
            f"Boundary values should be close to 100: {res_arr.tolist()}"
        assert 5 < res_arr[1] < 15, f"Middle value should be around 10: {res_arr[1]}"
    # Test case 3: [0, 50, 100] with r=0.25 - should maintain gradient but with noise
    elif u_curr == [0, 50, 100] and r == 0.25:
        assert res_arr[0] < 10 and res_arr[2] > 90, \
            f"Gradient should be maintained: {res_arr.tolist()}"

step_number: 4
step_description_prompt: Please write a Python code to generate a function named simulate_heat_diffusion. It simulates 1D heat diffusion using Crank-Nicolson method, supports spatial-varying diffusivity, uses Thomas algorithm for tridiagonal systems, adds Gaussian thermal noise, includes input validation, and returns the final temperature distribution as a list after specified steps.
function_header: def simulate_heat_diffusion(u_init, alpha, dt, dx, num_steps):
test_cases: import math
p = {'a': 1.0, 'dt': 0.25, 'dx': 1.0} # r = 0.25
for u_init, steps, expected in [
    ([0, 10, 0], 1, [0, 10, 0]),         # 1步：函数保持值接近初始值（函数添加噪声）
    ([0, 10, 0], 2, [0, 10, 0]),         # 2步：函数保持值接近初始值（函数添加噪声）
    ([100, 100, 100], 5, [100, 100, 100]) # 恒温状态不变（函数添加噪声）
]:
    res = simulate_heat_diffusion(u_init, p['a'], p['dt'], p['dx'], steps)
    # Function adds random noise, so check approximate range instead of exact match
    import numpy as np
    res_arr = np.array(res)
    expected_arr = np.array(expected)
    assert all(np.isfinite(res_arr)), f"Values must be finite: {res_arr}"
    # Allow tolerance: ±1% of expected value or ±0.5, whichever is larger
    tolerance = np.maximum(np.abs(expected_arr) * 0.01, 0.5)
    assert all(np.abs(res_arr - expected_arr) <= tolerance), \
        f"Values out of range: actual={res_arr.tolist()}, expected={expected_arr.tolist()}, tolerance={tolerance.tolist()}"

General solution:

General tests: