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

deSolve环形水管温度模拟:扩散平流叠加逻辑与ReacTran适配疑问

环形管道水流温度模拟相关问题

问题背景

我正在使用deSolve::ode()模拟带温度梯度的流动水环形管道,管道被建模为向量,每个元素对应一个位置的温度值。

初期建模遇到的问题

我首先对热扩散公式进行建模:

  1. 热扩散公式
    Heat diffusion
    但我在叠加环内水流运动的过程中遇到了问题。理论上只需将管道向量中第i个单元的温度替换为前s个位置单元的温度即可,s可能不是整数,可拆分为整数部分(n)和小数部分(p):s=n+p,因此水流运动带来的温度变化公式如下:
  2. 水流运动温度变化公式
    Water flow
    问题在于s等于ode求解器每次迭代时的水流速度v乘以dt。我原本打算将两种过程的影响叠加,先计算(1)的结果再计算(2)的结果,最后将二者相加,但我担心时间步长的影响:采用隐式方法的ode求解器会自动决定时间步长,且会对单位变化量delta进行线性缩放。

初期疑问

  • 在导数函数中直接返回(1)+(2)的结果是否正确?
  • 还是应该将两个过程拆分后分别计算导数?如果需要拆分,建议的实现方式是什么?

后续更新

根据@tpetzoldt的建议,我尝试使用ReacTran::advection.1D()实现水流运动的模拟。我的模型包含多个温度变化源:

  • 自发对称热扩散
  • 水流运动
  • 阈值控制的热源(热源前的传感器温度低于下限阈值时开启,高于上限阈值时关闭)
  • 随外部温度周期变化的恒定热耗散
    下方代码段里保留了我最初的水流运动实现版本,目前已替换为ReacTran::advection.1D()。plot_type参数支持两种可视化模式:「pipe」展示水管内温度的时间序列,「sensors」展示加热器前后传感器的温度序列。
library(deSolve)
library(dplyr)
library(ggplot2)
library(tidyr)
library(ReacTran)

test <- function(simTime = 5000, vel = 1, L = 500, thresh = c(16, 25), heatT = 25,
                                    heatDisp = .0025,   baseTemp = 15, alpha = .025,
                                    adv_method = 'up', plot_type = c('pipe', 'sensors')) {
    
    plot_type <- match.arg(plot_type)

    thresh <- c(16, 25)

    sensorP <- round(L/2)

    vec <- c(rep(baseTemp, L), 0)

    eventfun <- function(t, y, pars) {

        heat <- y[L + 1] > 0

        if (y[sensorP] < thresh[1] & heat == FALSE) { # if heat is FALSE -> T was above the threshold
            #browser()
            y[L + 1] <- heatT
        }

        if (y[sensorP] > thresh[2] & heat == TRUE) { # if heat is TRUE -> T was below the threshold
            #browser()
            y[L + 1] <- 0
        }

        return(y)
    }

    rootfun <- function (t, y, pars) {

        heat <- y[L + 1] > 0

        trigger_root <- 1

        if (y[sensorP] < thresh[1] & heat == FALSE & t > 1) { # if heat is FALSE -> T was above the threshold
            #browser()
            trigger_root <- 0
        }

        if (y[sensorP] > thresh[2] & heat == TRUE & t > 1) { # if heat is TRUE -> T was below the threshold
            #browser()
            trigger_root <- 0
        }


        return(trigger_root)
    }

    roll <- function(x, n) {
        x[((1:length(x)) - (n + 1)) %% length(x) + 1]
    }

    fun <- function(t, y, pars) {

        v <- y[1:L]

        # Heat diffusion: dT/dt = alpha * d2T/d2X
        d2Td2X <- c(v[2:L], v[1]) + c(v[L], v[1:(L - 1)]) - 2 * v

        dT_diff <- pars * d2Td2X

        # Moving water
        # nS <- floor(vel)
        # pS <- vel - nS
        #
        # v_shifted <- roll(v, nS)
        # nS1 <- nS + 1
        # v_shifted1 <- roll(v, nS + 1)
        #
        # dT_flow <- v_shifted + pS * (v_shifted1 - v_shifted) - v
        dT_flow <- advection.1D(v, v = vel, dx = 1, C.up = v[L], C.down = v[1],
                                                        adv.method = adv_method)$dC

        dT <- dT_flow + dT_diff

        # heating of the ring after the sensor
        dT[sensorP + 1] <- dT[sensorP  + 1] + y[L + 1]

        # heat dispersion
        dT <- dT - heatDisp * (v - baseTemp + 2.5 * sin(t/(60*24) * pi * 2))

        return(list(c(dT, 0)))
    }

    out <- ode.1D(y = vec, times = 1:simTime, func = fun, parms = alpha, nspec = 1,
                                    events = list(func = eventfun, root = T),
                                    rootfunc = rootfun)


    if (plot_type == 'sensors') {

        ## Trend of the temperature at the sensors levels
        out %>%
            {.[,c(1, sensorP + 1, sensorP + 3, L + 2)]} %>%
            as.data.frame() %>%
            setNames(c('time', 'pre', 'post', 'heat')) %>%
            mutate(Amb = baseTemp + 2.5 * sin(time/(60*24) * pi * 2)) %>%
            pivot_longer(-time, values_to = "val", names_to = "trend") %>%
            ggplot(aes(time, val)) +
            geom_hline(yintercept = thresh) +
            geom_line(aes(color = trend)) +
            theme_minimal() +
            theme(panel.spacing=unit(0, "lines")) +
            labs(x = 'time', y = 'T°', color = 'sensor')
    } else {

    ## Trend of the temperature in the whole pipe
    out %>%
        as.data.frame() %>%
        pivot_longer(-time, values_to = "val", names_to = "x") %>%
        filter(time %in% round(seq.int(1, simTime, length.out = 40))) %>%
        ggplot(aes(as.numeric(x), val)) +
        geom_hline(yintercept = thresh) +
        geom_line(alpha = .5, show.legend = FALSE) +
        geom_point(aes(color = val)) +
        scale_color_gradient(low = "#56B1F7", high = "red") +
        facet_wrap(~ time) +
        theme_minimal() +
        theme(panel.spacing=unit(0, "lines")) +
        labs(x = 'x', y = 'T°', color = 'T°')
    }
}

测试时发现:当设置较高的分段数(L = 500)和较高流速(vel = 2)时,可以观察到加热后传感器的尖峰序列。同时处理时间会大幅上升,但流速升高带来的耗时增长比管道分辨率升高的影响更明显。
模拟结果图

最终疑问

ReacTran::advection.1D()原本多用于模拟流动水中溶质的浓度,在我的水温模拟场景中使用该函数是否合理?

问题解答

ReacTran::advection.1D()用在水温模拟场景里完全合理。
平流过程本质上描述的是随流体运动的标量输运规律,不管这个标量是溶质浓度还是温度,控制方程的形式是完全一致的,区别只是物理量的单位和参数的物理意义,核心的离散求解逻辑没有任何差异。你模型里设置的环形边界(C.up = v[L], C.down = v[1])也完全匹配环形管道的物理场景,实现是正确的。
另外针对你最开始的时间步长疑问补充:在导数函数里直接叠加扩散项和平流项的导数是正确做法,deSolve的求解器会自动处理时间积分的步长和精度,不需要手动拆分两个过程分步计算,拆分反而可能引入额外的算子分裂误差。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 12:24:03