You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化含2D列表的Python草地扩散模拟代码?

草地扩散模拟代码优化方案

1. 只跟踪待处理候选格子,避免全网格遍历

每次迭代中,只有与草地相邻的空地块才有可能转化为草地,无需遍历整个网格。可以用集合维护与草地相邻的空坐标:

  • 初始时,将初始草地块的所有空邻居加入集合
  • 每次迭代,仅遍历集合内的坐标,计算转化概率;对转化成功的格子:
    • 标记为草地
    • 将其未被标记的空邻居加入集合(自动去重)
  • 该方式能大幅减少迭代处理的格子数量,模拟后期草地占比越高,效果越显著。

示例代码片段:

import numpy as np

class Biome:
    def __init__(self, size, initial_pos, max_steps):
        self.size = size
        self.grille = np.zeros((size, size), dtype=np.uint8)
        x, y = initial_pos
        self.grille[x, y] = 1
        # 初始化候选集合:初始草地的所有空邻居
        self.candidates = set()
        self._add_neighbors_to_candidates(x, y)
        self.max_steps = max_steps
        self.current_step = 0

    def _add_neighbors_to_candidates(self, x, y):
        for dx in (-1, 0, 1):
            for dy in (-1, 0, 1):
                if dx == 0 and dy == 0:
                    continue
                nx, ny = x + dx, y + dy
                if 0 <= nx < self.size and 0 <= ny < self.size and self.grille[nx, ny] == 0:
                    self.candidates.add((nx, ny))

    def step(self):
        if self.current_step >= self.max_steps or not self.candidates:
            return False
        
        to_convert = []
        new_candidates = set()
        for (x, y) in self.candidates:
            # 统计实际邻居数与草地邻居数
            total_neighbors = 0
            grass_neighbors = 0
            for dx in (-1, 0, 1):
                for dy in (-1, 0, 1):
                    if dx == 0 and dy == 0:
                        continue
                    nx, ny = x + dx, y + dy
                    if 0 <= nx < self.size and 0 <= ny < self.size:
                        total_neighbors += 1
                        grass_neighbors += self.grille[nx, ny]
            proba = grass_neighbors / total_neighbors if total_neighbors > 0 else 0
            if np.random.rand() < proba:
                to_convert.append((x, y))
        
        # 批量更新草地并收集新候选
        for (x, y) in to_convert:
            self.grille[x, y] = 1
            self._add_neighbors_to_candidates(x, y)
        
        # 更新候选集合:移除已转化的,加入新候选
        self.candidates -= set(to_convert)
        self.candidates.update(new_candidates)
        self.current_step += 1
        return True

2. 用Numba JIT编译核心计算逻辑

如果核心循环是纯Python实现,用Numba的@njit装饰器将其编译为机器码,可大幅提速统计邻居、计算概率的逻辑。

示例:

from numba import njit

@njit
def count_neighbors(grille, x, y, size):
    grass = 0
    total = 0
    for dx in (-1, 0, 1):
        for dy in (-1, 0, 1):
            if dx == 0 and dy == 0:
                continue
            nx = x + dx
            ny = y + dy
            if 0 <= nx < size and 0 <= ny < size:
                total += 1
                grass += grille[nx, ny]
    return grass, total

@njit
def process_candidates(grille, candidates, size):
    to_convert = []
    new_candidates = set()
    for (x, y) in candidates:
        grass, total = count_neighbors(grille, x, y, size)
        proba = grass / total if total > 0 else 0.0
        if np.random.rand() < proba:
            to_convert.append((x, y))
    
    # 标记转化格子并收集新候选
    for (x, y) in to_convert:
        grille[x, y] = 1
        for dx in (-1, 0, 1):
            for dy in (-1, 0, 1):
                if dx == 0 and dy == 0:
                    continue
                nx = x + dx
                ny = y + dy
                if 0 <= nx < size and 0 <= ny < size and grille[nx, ny] == 0:
                    new_candidates.add((nx, ny))
    
    remaining_candidates = set(candidates) - set(to_convert)
    return remaining_candidates.union(new_candidates), to_convert

之后在step方法中调用该JIT函数即可,Numba对numpy数组和基础数据类型支持良好。

3. 用Numpy向量化操作批量计算邻居数

若保留全网格遍历,可利用Numpy的滑动窗口工具批量计算每个格子的邻居草地数,替代逐元素循环:

  • 给网格加一圈padding处理边界
  • 生成3x3滑动窗口,批量统计每个窗口内的草地数量
  • 用预先生成的掩码矩阵处理边界格子的实际邻居数

示例:

def compute_neighbor_counts(grille):
    size = grille.shape[0]
    # 加padding处理边界
    padded = np.pad(grille, pad_width=1, mode='constant', constant_values=0)
    # 生成3x3滑动窗口
    windows = np.lib.stride_tricks.sliding_window_view(padded, window_shape=(3,3))
    # 统计邻居草地数(减去自身)
    grass_counts = windows.sum(axis=(2,3)) - grille
    # 预先生成每个格子的总邻居数掩码
    neighbor_mask = np.ones((size, size), dtype=np.int8) * 8
    # 修正角落和边缘格子的邻居数
    neighbor_mask[0,0] = neighbor_mask[0,-1] = neighbor_mask[-1,0] = neighbor_mask[-1,-1] = 3
    neighbor_mask[0,1:-1] = neighbor_mask[-1,1:-1] = 5
    neighbor_mask[1:-1,0] = neighbor_mask[1:-1,-1] = 5
    return grass_counts, neighbor_mask

# 在Biome类中添加向量化迭代方法
def step_vectorized(self):
    if self.current_step >= self.max_steps or (self.grille == 1).all():
        return False
    grass_counts, neighbor_mask = compute_neighbor_counts(self.grille)
    # 仅处理空格子
    empty_mask = self.grille == 0
    proba = np.where(empty_mask, grass_counts / neighbor_mask, 0.0)
    # 批量判断需要转化的格子
    convert_mask = empty_mask & (np.random.rand(self.size, self.size) < proba)
    self.grille[convert_mask] = 1
    self.current_step += 1
    return True

4. 并行化批量模拟

若需大量独立模拟任务,可用多进程绕过Python GIL限制,并行处理不同模拟:

  • 用multiprocessing.Pool或concurrent.futures.ProcessPoolExecutor
  • 每个进程负责一个完整模拟,返回统计数据(如填满网格步数、草地占比等)

示例:

from multiprocessing import Pool

def run_single_simulation(params):
    size, initial_pos, max_steps = params
    biome = Biome(size, initial_pos, max_steps)
    while biome.step():
        pass
    return biome.current_step, (biome.grille == 1).mean()

# 准备100个模拟任务
tasks = [(50, (25,25), 1000) for _ in range(100)]
# 用4个进程并行处理
with Pool(processes=4) as pool:
    results = pool.map(run_single_simulation, tasks)

# 提取统计结果
total_steps = [r[0] for r in results]
mean_coverage = [r[1] for r in results]

5. 优化内存与数据类型

  • 用np.uint8存储网格:每个元素仅占1字节,相比默认int类型内存占用更低,缓存命中率更高
  • 避免不必要的数组复制:优先使用原地修改(如grille[mask] = 1),减少内存分配与拷贝开销

内容的提问来源于stack exchange,提问作者Lava

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 17:06:13