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

基于R语言deSolve包构建多单元格一维河流模型的技术咨询

多单元格河流模型适配deSolve的优雅实现方案

问题背景

我想用R语言的deSolve包构建一个演示用的简易河流模型,核心需求是:

  • 为每种浓度类型创建按河流位置索引的向量(比如NH[2]在NH[1]下游)
  • 微分关系遵循:dNH[2] <- (内部动力学过程) + inflow * NH[1] - outflow * NH[2]
  • 所有单元格的速率系数和流速一致,无需单独设置参数

当前遇到的问题是:deSolve的lsoda要求传入命名向量作为状态变量,虽然可以把向量拆成NH.1、NH.2这类命名变量,但想找更优雅的实现方式。现有待修正的模型框架如下:

Model <- function(t, state, parameters){
   for (i in 1:num_cells){
       dA[i] <- (A[i-1] - A[i]) * flow_rate + B[i] * k_b - A[i] * k_a
       dB[i] <- (B[i-1] - B[i]) * flow_rate - B[i] * k_b + A[i] * k_a
       list(c(dA, dB))
   }
}

同时提供了单单元格模型代码(参数取自Hamilton等人2001年研究,DOI: 10.1023/A:1010635524108):

library(deSolve)
library(ggplot2)

# Stocks
NH4 <- 20 # micrograms N per L
NO3 <- 25 # micrograms N per L
biota <- 36.8 # g N per square meter of stream surface
NH_inflow_c <- 40 # micrograms N per L inflow concentration
NO_inflow_c <- 40 # micrograms N per L inflow concentration

# Constants and coefficients
cell_depth <- 0.2 # Average depth in meters
cell_length <- 65 # Length of cell in meters
flow <- 40 # flow in L/s into cell
k_n <-  0.019 # Nitrification rate of ammonia in s^-1
k_d <- 2.751E-6 # Uptake of ammonia by biota in m^2 (gN s)^-1
k_r <- 3.3E-7 # Release of ammonia by biota in s^-1

run_length <- 1000 # Run length in seconds
time_step <- 1 # Report frequency

# Convert all units to g N/m^2
conv <- 1000 * cell_depth * 1E-6 # micrograms N per L * 1000 L/m^2 * depth * g/microg
NH4 <- conv * NH4
NO3 <- conv * NO3
total_liters <- cell_depth * cell_length * 1 * 1000
NH_inflow <- NH_inflow_c * conv * flow / total_liters
NO_inflow <- NO_inflow_c * conv * flow / total_liters
total_liters <- cell_depth * cell_length * 1 * 1000
outflow_frac <- flow/total_liters

params <- c(k_n = k_n, k_d = k_d, k_r = k_r, NH_inflow = NH_inflow,
            NO_inflow = NO_inflow, outflow_frac = outflow_frac)
initial_values <- c(NH = NH4, NO = NO3, B = biota)
time_reports <- seq(0, run_length, by = time_step)

Stream_model <- function(t, state, parameters) {
    with(as.list(c(state, parameters)), {
        dNH <- k_r * B + NH_inflow - 
            NH * (k_n + k_d * B + outflow_frac)
        dNO <- NO_inflow + NH * k_n - NO * outflow_frac
        dB <- B * (k_d * NH - k_r)
        list(c(dNH, dNO, dB))
    })
}

优雅实现思路

核心思路是在模型函数内部将命名向量重新组织为向量组,既满足deSolve对命名向量的要求,又能保持代码的可读性和运算效率:

  1. 批量生成命名状态变量:用paste0自动生成NH_1, NH_2,...这类规范命名的状态向量,避免手动命名的繁琐。
  2. 模型内重构状态结构:在模型函数开头,把扁平的命名向量拆分回按物质类型分组的单元格向量,方便进行上下游传输计算。
  3. 分情况处理微分:单独处理第一个单元格的外部入流,后续单元格通过上游状态计算传输项,最后将微分结果重新拼接为命名向量返回。

完整示例代码

library(deSolve)
library(ggplot2)
library(reshape2)

# --------------------------
# 参数设置与单位转换
# --------------------------
num_cells <- 5  # 设置河流单元格数量

# 单单元格初始值(批量扩展到多单元格)
NH4_init <- 20  # micrograms N per L
NO3_init <- 25  # micrograms N per L
biota_init <- 36.8  # g N per square meter of stream surface
NH_inflow_c <- 40  # 仅第一个单元格的外部入流浓度(micrograms N per L)
NO_inflow_c <- 40  # 仅第一个单元格的外部入流浓度(micrograms N per L)

# 常量参数
cell_depth <- 0.2  # 单元格平均水深(米)
cell_length <- 65  # 单元格长度(米)
flow <- 40  # 流量(L/s)
k_n <- 0.019  # 氨硝化速率(s^-1)
k_d <- 2.751E-6  # 生物吸收氨速率(m^2/(gN·s))
k_r <- 3.3E-7  # 生物释放氨速率(s^-1)

run_length <- 1000  # 模拟时长(秒)
time_step <- 1  # 结果输出间隔(秒)

# 单位转换:统一为g N/m^2
conv <- 1000 * cell_depth * 1E-6  # micrograms N/L -> g N/m^2的转换系数
NH4_init <- conv * NH4_init
NO3_init <- conv * NO3_init
total_liters <- cell_depth * cell_length * 1 * 1000  # 单单元格体积(L)
outflow_frac <- flow / total_liters  # 单位时间流出比例

# 外部入流项(仅第一个单元格)
NH_inflow <- NH_inflow_c * conv * flow / total_liters
NO_inflow <- NO_inflow_c * conv * flow / total_liters

params <- c(
  k_n = k_n, k_d = k_d, k_r = k_r,
  NH_inflow = NH_inflow, NO_inflow = NO_inflow,
  outflow_frac = outflow_frac, num_cells = num_cells
)

# 生成多单元格初始状态的命名向量
state_names <- c(
  paste0("NH_", 1:num_cells),
  paste0("NO_", 1:num_cells),
  paste0("B_", 1:num_cells)
)
initial_values <- c(
  rep(NH4_init, num_cells),
  rep(NO3_init, num_cells),
  rep(biota_init, num_cells)
)
names(initial_values) <- state_names

time_reports <- seq(0, run_length, by = time_step)

# --------------------------
# 多单元格河流模型函数
# --------------------------
Stream_model_multi <- function(t, state, parameters) {
  with(as.list(c(state, parameters)), {
    # 1. 将扁平命名向量拆分为按物质分组的单元格向量
    NH <- state[paste0("NH_", 1:num_cells)]
    NO <- state[paste0("NO_", 1:num_cells)]
    B <- state[paste0("B_", 1:num_cells)]
    
    # 2. 初始化微分数组
    dNH <- numeric(num_cells)
    dNO <- numeric(num_cells)
    dB <- numeric(num_cells)
    
    # 3. 计算微分:第一个单元格(含外部入流)
    dNH[1] <- k_r * B[1] + NH_inflow - NH[1] * (k_n + k_d * B[1] + outflow_frac)
    dNO[1] <- NO_inflow + NH[1] * k_n - NO[1] * outflow_frac
    dB[1] <- B[1] * (k_d * NH[1] - k_r)
    
    # 4. 后续单元格(入流来自上游流出)
    if (num_cells > 1) {
      for (i in 2:num_cells) {
        # 上下游传输项
        transport_NH <- outflow_frac * (NH[i-1] - NH[i])
        transport_NO <- outflow_frac * (NO[i-1] - NO[i])
        
        # 内部动力学 + 传输项
        dNH[i] <- k_r * B[i] + transport_NH - NH[i] * (k_n + k_d * B[i])
        dNO[i] <- NH[i] * k_n + transport_NO - NO[i] * outflow_frac
        dB[i] <- B[i] * (k_d * NH[i] - k_r)
      }
    }
    
    # 5. 将微分结果拼接为命名向量返回
    d_state <- c(dNH, dNO, dB)
    names(d_state) <- state_names
    list(d_state)
  })
}

# --------------------------
# 运行模型并可视化
# --------------------------
model_output <- ode(y = initial_values, times = time_reports, func = Stream_model_multi, parms = params)

# 转换为长格式数据框方便绘图
output_df <- as.data.frame(model_output)
output_df_long <- melt(output_df, id.vars = "time")
output_df_long$variable_type <- substr(output_df_long$variable, 1, 2)
output_df_long$cell <- as.integer(substr(output_df_long$variable, 4, nchar(output_df_long$variable)))

# 绘制NH浓度随时间和位置的变化
ggplot(output_df_long[output_df_long$variable_type == "NH", ], aes(x = time, y = value, color = factor(cell))) +
  geom_line(linewidth = 0.8) +
  labs(title = "NH浓度随时间变化(不同河流单元格)", 
       x = "时间(s)", y = "NH浓度(g N/m²)", 
       color = "单元格编号") +
  theme_minimal()

关键优化点

  • 结构化状态管理:用paste0批量生成状态变量名,既满足deSolve的命名要求,又便于在模型内部快速拆分出各物质的单元格向量。
  • 边界逻辑清晰:单独处理首单元格的外部入流,后续单元格的传输项直接通过上游状态计算,贴合河流流动的物理规律。
  • 效率与可读性平衡:演示级模型用循环足够高效,若需大规模扩展,可替换为diff等向量式函数进一步提升运算速度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 08:29:55