2026年五一杯数学建模A题煤矿巷道支护问题求解全过程文档及程序

2026年五一杯数学建模

A题 煤矿巷道支护问题

原题再现:

  煤矿巷道是矿井高效安全生产的重要咽喉工程,巷道围岩支护是防止顶板垮塌以及保障设备正常运行的重要手段。在众多支护技术中,锚杆支护凭借其施工便捷、适应性强和经济高效等优势,在巷道支护中扮演着不可替代的作用,并且在岩土工程、人防工程、桥梁隧道、城市地铁等领域也有着广泛的应用。
  锚杆的支护原理是通过拧紧锚杆尾端螺母施加预紧力矩,经由螺纹传动转化为沿杆体轴向的预紧力,进而使围岩由二向受力状态转化为三向受力状态,实现主动支护(如图1所示)。在工程实践中,一般认为预紧力矩越高,预紧力越大,支护效果越好。然而,预紧力矩与预紧力之间的转换并非简单的线性关系,还受螺纹间隙、接触面状态、围岩性质等多种因素的影响。因此,深入研究预紧力矩与预紧力的转换机制,对于实现巷道科学支护具有重要意义。

  《煤矿预应力锚固施工技术规范》中虽规定初始预紧力矩应不低于150 N·m,但在实际操作中,往往出现预紧力矩达标而预紧力不足的低效支护现象;或者在松软煤体中,因盲目追求高力矩导致托盘及螺母钻入煤体影响支护效果(如图2所示)。如何精准建立预紧力矩与预紧力的转换模型,揭示不同界面条件下的影响规律,并据此确定临界预紧力矩以获得最优施工参数,是实现巷道支护智能化、绿色化的关键科学问题。

  请利用附件中的实验与实测数据,解决以下问题:
  问题1. 在材质均匀、界面平整的标准实验条件下,锚杆的预紧力矩T(单位:N·m)与预紧力P(单位:kN)之间呈现显著的线性相关关系。工程上常用公式T=K⋅P⋅d来描述,其中d为锚杆直径(单位:mm),K为扭矩系数。然而,工程实测发现,预紧力矩与预紧力的关系在全过程中并非始终保持线性相关:在加载初期,由于螺纹间隙、垫圈变形及接触面压实过程的影响,数据的离散性较大,二者关系呈非线性特征;当预紧力矩超过某一临界值后,系统进入稳定工作状态,预紧力矩与预紧力之间才呈现显著的线性相关关系。请完成以下任务:
  问题1.1. 利用附件中表1提供的数据,建立预紧力矩与预紧力关系的数学模型,并给出不同直径的锚杆对应的扭矩系数K。
  问题1.2. 利用附件中表2和表3提供的基于直径20mm锚杆的实测数据,给出临界预紧力矩。

  问题2. 如图1所示,钢带是锚杆支护体系的一种组合构件,其作用是将单根锚杆的集中压力分散到更大面积的围岩表面,有效防止围岩局部压陷。当围岩较松软时,仅靠托盘难以避免压陷,此时须增设钢带以分散压力。在实际采矿工程中,预紧力的施加需要兼顾有效性与安全性------预紧力过小,锚杆无法起到支护作用;预紧力过大,则会导致锚杆屈服、锚固失效或托盘压陷进煤体(如图2©所示)。请解决以下问题:
  问题2.1. 基于附录2所述物理模型和工程约束,建立在给定地质和支护参数下的最大允许预紧力矩 Tmax 的通用参数化模型,并进一步给出钢带使用的必要性。
  问题2.2. 利用附件中表4提供的参数,结合问题2.1构建的数学模型,计算工况A和工况B下的Tmax,并判断是否必须加钢带。

  问题3. 在煤矿现场施工中,由于煤岩体碎裂、孔壁不规则等客观因素,锚杆轴线与围岩表面法线之间存在难以避免的偏心距e(单位:mm)。当拧紧螺母施加预紧力矩T时,预紧力P通过偏心距e在锚杆螺纹段引起附加弯矩M。此时,锚杆螺纹段同时承受多种应力的联合作用,可能导致锚杆在螺纹根部发生齐根断裂,相关力学公式与参数详见附录3。请完成以下任务:
  问题3.1. 基于附录3的力学模型,建立偏心受力状态下最大允许预紧力矩Tmax与偏心距e关系的数学模型。由于该力学公式基于名义截面的简化假设,请结合力学基本原理,分析附录3模型的适用性与局限性。
  问题3.2. 结合偏心受力条件与附录2中给出的工程约束,分析偏心距e的存在是否会影响这些约束的有效性?建立综合考虑多种失效模式的修正模型,给出修正后的最大允许预紧力矩Tmax(e)的数学表达式,并进行数值验证。
  问题 4. 在实际工程中,不同煤矿的围岩性质差异显著。坚硬围岩(如灰岩,普氏系数f ≥6)的弹性模量和强度高,能够承受较大的支护力而不发生压陷或破坏;松软围岩(如煤体,普氏系数 f < 3)的弹性模量低,过大的支护力容易导致锚杆压入围岩,反而造成支护失效;中等强度围岩(如砂岩,3 ≤ f < 6)则介于两者之间。因此,不同围岩等级对预紧力的需求和承载能力截然不同。目前现场锚杆支护施工往往采用统一的预紧力矩标准,既无法充分发挥坚硬围岩的承载潜力,又在松软围岩中面临支护失效风险。
  请基于普氏系数f与各失效约束,建立使支护效果较优而不触发任何失效约束的预紧力矩Topt和f关系的数学模型;分析在不同f值区间内(如软岩、中等、坚硬围岩),导致锚杆支护系统失效的"主控失效模式",即在各种围岩条件下使最大许用预紧力矩最先达到极限的约束是什么,并对模型的有效性和可靠性进行验证。

整体求解过程概述(摘要)

  针对问题一,首先对附件中的标准实验数据、岩石工况数据和煤体工况数据进行数据理解与质量核验。经字段统一、直径缺失值组内前向填充、数值型转换、重复值和异常值检查后,共获得标准实验21组、岩石实测60个、煤体实测75个有效观测。描述性统计表明,各直径组预紧力的偏度绝对值均小于0.42、超额峰度均为负,未出现强偏态或尖峰异常;据此分别构建普通最小二乘、Huber稳健回归和过原点工程约束回归。最终得到直径18、20、22 mm锚杆的扭矩系数K分别为0.1620、0.1831、0.1899,工程模型决定系数分别为0.9663、0.9874、0.9938。进一步引入"低载二次阶段---高载线性阶段"的BIC断点识别模型,确定岩石工况和煤体工况的临界预紧力矩分别为150 N·m和225 N·m;Bootstrap 95%区间分别为150,175 N·m与175,275 N·m。
  针对问题二,鉴于最大允许预紧力同时受锚杆屈服、锚固界面剪切破坏和围岩压陷三类机制约束,建立参数化短板模型Pmax=min{Pyield,Pbond,Pindent},并通过Tmax=KdPmax实现力---矩转换。计算表明:工况A的屈服限值、粘结限值和压陷限值分别为170.00、334.27、400.00 kN,故锚杆屈服为主控失效模式,Tmax=622.43 N·m,无须强制增设钢带;工况B三项限值分别为205.00、169.65、123.08 kN,围岩压陷率先达到极限,Tmax=514.10 N·m,因此必须采用钢带扩大承载面积。Monte Carlo不确定性分析进一步表明,工况A由屈服控制的概率接近100%,工况B由围岩压陷控制的概率约为97.32%,结论具有较好的鲁棒性。
  针对问题三,考虑到现场偏心距会在螺纹有效截面产生附加弯矩,首先将轴向拉应力、偏心弯曲应力与施工扭转剪应力统一代入Von Mises等效应力准则,推导出Tmax与e的显式反比例型表达式。随后基于矩形托盘偏心受压的最大压力放大系数1+6e/b,以及锚固界面轴力---弯矩椭圆交互准则,构建包含螺纹联合应力、杆体屈服、锚固粘结和围岩压陷的综合修正模型。数值验证显示,工况A的综合Tmax由e=0时的411.83 N·m下降至e=2、5、10 mm时的213.55、123.83、72.80 N·m;工况B在e=0时由围岩压陷控制,而当e增大到约0.3---0.5 mm后转由螺纹联合应力控制。这表明偏心距虽不改变原三项约束的物理含义,却会显著降低其充分性,并可能导致主控失效模式切换。
  针对问题四,为建立预紧力矩与普氏系数f之间的联系,在保留E(f)可重新标定的前提下,引入幂律映射E(f)=0.56095f^1.83404,并将其代入粘结极限、压陷极限及问题三综合失效约束。由于在给定几何条件下支护效果随安全范围内预紧力单调增强,最优力矩取可行域上界Topt(f)=min Ti(f)。结果表明:软岩区主要受围岩压陷控制,f约小于1.75时甚至无法满足150 N·m最低施工力矩;中等围岩在f≈3.05附近发生"围岩压陷---螺纹联合应力"主控模式切换;坚硬围岩的承载潜力虽继续提升,但Topt被螺纹根部联合应力限制在约411.83 N·m。因此,统一力矩标准会同时造成软岩过载和硬岩潜力利用不足,应采用围岩分级、偏心控制和构件协同的差异化设计。
  综上,本文形成了"数据理解与探索---预处理---特征构造---分段统计建模---多失效约束耦合---不确定性验证---工程决策解释"的完整建模链条。模型既能够从实验数据估计扭矩系数和临界力矩,又能在给定地质、几何及施工参数下计算最大允许预紧力矩、判断钢带必要性,并识别偏心与围岩等级变化下的主控失效模式。模型结构清晰、参数含义明确、便于现场复核和二次标定,可为锚杆支护施工参数的智能化制定提供定量依据。

模型假设:

  • 附件记录的力矩、预紧力和工况参数真实可靠,除表1直径列的合并单元格导致空值外,不存在系统性录入错误。
  • 标准实验中锚杆材料均匀、界面平整,试验范围内不发生宏观屈服和锚固破坏;随机误差均值为零。
  • 公式T=K·P·d中的单位采用N·m、kN和mm,此时1 kN·mm=1 N·m,K为无量纲系数。
  • 实测数据的五个测点代表同一工况下的空间或样本差异;临界力矩在同一围岩工况内取共同断点。
  • Winkler模型中的围岩等效为局部独立弹簧,托盘在小变形范围内保持刚性;偏心受压满足e≤b/6时的线性压力分布。
  • 锚固界面偏心折减采用轴力---弯矩椭圆交互准则;若现场有拉拔---弯矩联合试验,应重新标定交互尺度。
  • 螺纹有效直径取de=0.85d,名义截面模型不直接包含螺纹牙根局部应力集中;修正模型以kσ、kτ表示应力集中影响。
  • 问题四中的E(f)幂律仅用于展示模型求解流程,其两个标定点分别取煤层代表f=2、硬岩分界f=6;不同矿区应使用本地岩样数据重新标定。
  • 在未触发失效约束时,支护效果随预紧力矩单调增强,因此理论最优力矩位于安全可行域上边界。实际施工可再乘0.8---0.9的运行折减系数。

问题分析:

  问题一分析
  针对锚杆预紧力矩与预紧力转换关系、分段临界力矩求解问题,本题属于统计回归与分段断点识别相结合的数据建模问题,分为扭矩系数标定、临界力矩判定两大子任务。题目提供标准、岩石、煤体三类多组实测数据,数据存在单元格空值、低载阶段接触压实非线性干扰,小样本条件下易受异常观测干扰,同时工程公式存在过原点硬性物理约束。建模思路先完成缺失填充、异常核验、分布特征分析,对比普通最小二乘、Huber 稳健回归构建带零点约束的工程转换模型,利用 BIC 信息准则遍历候选力矩断点识别压实阶段与稳定线性阶段分界,搭配 Bootstrap 重采样计算临界力矩置信区间,通过拟合优度、留一交叉验证评估模型可靠性,得到不同直径锚杆扭矩系数与两类围岩临界施工力矩,为后续多失效约束力矩换算提供基础参数。
  问题二分析
  针对多失效模式下最大允许预紧力矩求解与钢带必要性判别问题,本题属于材料力学多约束短板优化问题,依托问题一得到的扭矩系数完成力 - 矩换算,同时耦合锚杆屈服、锚固界面剪切、围岩托盘压陷三类独立失效极限。不同围岩工况下三类承载上限数值差异大,单一约束会成为系统失效短板,且岩土、材料参数存在天然随机扰动,仅静态定值计算结论鲁棒性不足。建模思路分别推导三类失效对应的极限预紧力公式,取最小值作为安全承载上限并换算力矩,通过数值对比判定围岩压陷是否为控制条件以确定钢带是否必备;引入 Monte Carlo 随机扰动开展不确定性仿真,统计各类失效成为主控模式的概率,量化参数波动对施工上限结论的影响,完善工程决策依据。
  问题三分析
  针对偏心安装带来的附加应力修正综合力矩模型问题,本题属于复合应力力学建模问题,在问题二轴向三约束基础上叠加偏心弯矩、施工扭转剪应力耦合作用,核心采用 Von Mises 等效应力准则校核螺纹截面强度。偏心距会同步放大杆体弯曲应力、改变托盘接触压力分布、削弱锚固界面承载能力,会切换原有工况的主控失效模式,同时螺纹应力集中会进一步降低安全力矩阈值。建模思路联立轴向拉力、偏心弯曲、扭转三类应力推导等效应力闭式解,分别修正粘结、压陷两类约束的承载上限,构建包含螺纹强度、杆体屈服、锚固、围岩压陷的四维综合约束模型;遍历多组偏心距计算对应最大允许力矩,对比有无偏心下主控失效类型变化,开展应力集中、偏心参数双维度敏感性分析,量化安装偏差对支护安全的削弱程度。
  问题四分析
  针对围岩普氏系数与最优预紧力矩关联建模问题,本题属于地质参数映射 + 多约束耦合优化问题,需要建立围岩坚固程度与弹性模量的经验映射函数,将围岩分级变量嵌入前三问全套力学约束体系。软、中、硬围岩的承载短板存在明显切换规律,统一固定施工力矩会出现软岩压溃、硬岩锚杆强度浪费两类工程矛盾,最优力矩受围岩属性、安装偏心双重变量共同制约。建模思路基于给定标定点拟合普氏系数 - 弹性模量幂律关系,将弹性模量代入围岩压陷、锚固极限公式,取全部失效约束力矩下界作为各围岩等级最优施工力矩;划分软岩、中等围岩、硬岩三个区间识别各自主控失效模式,绘制二维热力图分析普氏系数与偏心距联合作用下的安全力矩变化,同时校验最低施工力矩在各类围岩的可行性,提出差异化分级支护施工方案。

模型的建立与求解整体论文缩略图

全部论文请见下方" 只会建模 QQ名片" 点击QQ名片即可

程序代码:

部分程序如下:
python 复制代码
from __future__ import annotations

import argparse
import json
import math
import warnings
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Dict, Iterable, List, Tuple

import matplotlib as mpl
mpl.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib import font_manager
from matplotlib import text as mtext
from matplotlib.ticker import MaxNLocator
from scipy import stats
from sklearn.linear_model import HuberRegressor, LinearRegression
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.model_selection import LeaveOneOut

# 保留常规数值警告,同时将"中文字符缺失"升级为错误,避免生成方框标签。
warnings.filterwarnings("ignore", category=FutureWarning)
warnings.filterwarnings("error", message=r"Glyph .* missing from font")

def configure_chinese_font():
    """自动定位并注册中文字体;返回可显式应用于全部文本对象的 FontProperties。

    优先使用思源/Noto CJK 字体;在 Windows、macOS 与常见 Linux 环境中依次回退。
    若系统不存在可用中文字体,程序直接给出明确错误,而不是生成方框字符。
    """
    candidates = [
        "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
        "/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc",
        "/usr/share/fonts/truetype/arphic/uming.ttc",
        "C:/Windows/Fonts/msyh.ttc",
        "C:/Windows/Fonts/simhei.ttf",
        "/System/Library/Fonts/PingFang.ttc",
        "/System/Library/Fonts/STHeiti Medium.ttc",
    ]
    discovered = font_manager.findSystemFonts(fontext="ttf") + font_manager.findSystemFonts(fontext="ttc")
    priorities = ("NotoSansCJK", "SourceHanSans", "PingFang", "msyh", "simhei", "uming")
    for token in priorities:
        candidates.extend([p for p in discovered if token.lower() in Path(p).name.lower()])
    seen = set()
    required_chars = "煤矿巷道锚杆预紧力矩偏心围岩钢带约束"
    for candidate in candidates:
        if not candidate or candidate in seen or not Path(candidate).exists():
            continue
        seen.add(candidate)
        try:
            font_manager.fontManager.addfont(candidate)
            prop = font_manager.FontProperties(fname=candidate)
            ft = mpl.ft2font.FT2Font(candidate)
            charmap = ft.get_charmap()
            if not all(ord(ch) in charmap for ch in required_chars):
                continue
            family = prop.get_name()
            mpl.rcParams.update({
                "font.family": "sans-serif",
                "font.sans-serif": [family, "DejaVu Sans"],
                "axes.unicode_minus": False,
                "figure.dpi": 150,
                "savefig.dpi": 260,
                "axes.titlesize": 12,
                "axes.labelsize": 10.5,
                "legend.fontsize": 8.5,
                "xtick.labelsize": 9,
                "ytick.labelsize": 9,
            })
            return prop, candidate
        except Exception:
            continue
    raise RuntimeError(
        "未检测到可用中文字体。请安装 Noto Sans CJK SC、思源黑体、微软雅黑或黑体后重新运行。"
    )

ZH_FONT, ZH_FONT_PATH = configure_chinese_font()

def apply_chinese_font(fig):
    """对图中标题、坐标轴、图例、注释和色标文本逐一显式指定中文字体。"""
    for obj in fig.findobj(match=lambda x: isinstance(x, mtext.Text)):
        obj.set_fontproperties(ZH_FONT)

@dataclass
class Condition:
    name: str
    d: float
    p_yield: float
    d_hole: float
    l_bond: float
    elastic_modulus: float
    poisson: float
    tray_width: float
    delta_max: float
    h0: float
    k: float


def ensure_dir(path: Path) -> Path:
    path.mkdir(parents=True, exist_ok=True)
    return path


def savefig(path: Path) -> None:
    fig = plt.gcf()
    apply_chinese_font(fig)
    try:
        fig.tight_layout()
    except Exception:
        pass
    fig.savefig(path, bbox_inches="tight", facecolor="white")
    plt.close(fig)


def read_data(path: Path):
    raw_sheets = pd.read_excel(path, sheet_name=None)
    sheets = {str(k).strip(): v for k, v in raw_sheets.items()}
    required = [
        "Sheet1_标准实验数据",
        "Sheet2_岩石工况数据",
        "Sheet3_煤体工况数据",
        "Sheet4_工况参数设置",
    ]
    missing = [s for s in required if s not in sheets]
    if missing:
        raise ValueError(f"缺少工作表:{missing}")

    standard = sheets[required[0]].copy()
    standard.columns = ["d", "T", "P"]
    standard["d"] = pd.to_numeric(standard["d"], errors="coerce").ffill()
    standard["T"] = pd.to_numeric(standard["T"], errors="coerce")
    standard["P"] = pd.to_numeric(standard["P"], errors="coerce")
    standard = standard.dropna().reset_index(drop=True)

    rock = sheets[required[1]].copy()
    coal = sheets[required[2]].copy()
    for frame in (rock, coal):
        frame.columns = ["T"] + [f"P{i}" for i in range(1, 6)]
        for c in frame.columns:
            frame[c] = pd.to_numeric(frame[c], errors="coerce")
        frame.dropna(how="all", inplace=True)
        frame.reset_index(drop=True, inplace=True)

    parameter = sheets[required[3]].copy()
    parameter.columns = ["parameter", "A", "B"]
    parameter = parameter.dropna(how="all").reset_index(drop=True)
    return standard, rock, coal, parameter


def quality_report(frames: Dict[str, pd.DataFrame]) -> pd.DataFrame:
    rows = []
    for name, df in frames.items():
        numeric = df.select_dtypes(include=[np.number])
        rows.append(
            {
                "数据表": name,
                "行数": len(df),
                "列数": len(df.columns),
                "缺失单元格数": int(df.isna().sum().sum()),
                "重复行数": int(df.duplicated().sum()),
                "数值列数": len(numeric.columns),
                "无穷值数": int(np.isinf(numeric.to_numpy()).sum()) if len(numeric.columns) else 0,
            }
        )
    return pd.DataFrame(rows)


def descriptive_table(df: pd.DataFrame, group_col: str | None = None) -> pd.DataFrame:
    rows = []
    groups = [("全部", df)] if group_col is None else list(df.groupby(group_col))
    for group_name, g in groups:
        for col in g.select_dtypes(include=[np.number]).columns:
            if col == group_col:
                continue
            x = g[col].dropna().to_numpy(float)
            if len(x) == 0:
                continue
            rows.append(
                {
                    "分组": group_name,
                    "变量": col,
                    "样本量": len(x),
                    "均值": np.mean(x),
                    "标准差": np.std(x, ddof=1) if len(x) > 1 else 0,
                    "变异系数": np.std(x, ddof=1) / np.mean(x) if len(x) > 1 and np.mean(x) != 0 else np.nan,
                    "最小值": np.min(x),
                    "中位数": np.median(x),
                    "最大值": np.max(x),
                    "偏度": stats.skew(x, bias=False) if len(x) >= 3 else np.nan,
                    "峰度": stats.kurtosis(x, fisher=True, bias=False) if len(x) >= 4 else np.nan,
                    "Shapiro_p": stats.shapiro(x).pvalue if 3 <= len(x) <= 5000 else np.nan,
                }
            )
    return pd.DataFrame(rows)


def loocv_metrics(x: np.ndarray, y: np.ndarray, model_kind: str = "ols") -> Dict[str, float]:
    loo = LeaveOneOut()
    pred, true = [], []
    for tr, te in loo.split(x):
        if model_kind == "ols":
            model = LinearRegression().fit(x[tr, None], y[tr])
        elif model_kind == "huber":
            model = HuberRegressor(epsilon=1.35, alpha=0.0).fit(x[tr, None], y[tr])
        else:
            raise ValueError(model_kind)
        pred.append(float(model.predict(x[te, None])[0]))
        true.append(float(y[te][0]))
    return {
        "LOOCV_RMSE": math.sqrt(mean_squared_error(true, pred)),
        "LOOCV_MAE": mean_absolute_error(true, pred),
    }


def fit_standard_models(standard: pd.DataFrame) -> Tuple[pd.DataFrame, Dict[int, dict]]:
    rows, details = [], {}
    for d, g in standard.groupby("d"):
        T = g["T"].to_numpy(float)
        P = g["P"].to_numpy(float)

        ols = LinearRegression().fit(T[:, None], P)
        pred = ols.predict(T[:, None])
        huber = HuberRegressor(epsilon=1.35, alpha=0.0).fit(T[:, None], P)
        pred_h = huber.predict(T[:, None])

        # 工程约束回归:T = K*d*P,过原点最小二乘
        kd = float(np.dot(P, T) / np.dot(P, P))
        k_zero = kd / d
        pred_t = kd * P
        r2_zero = 1 - np.sum((T - pred_t) ** 2) / np.sum((T - T.mean()) ** 2)

        # 含截距模型由 P=a+bT 推回 K=1/(bd)
        k_slope = 1.0 / (ols.coef_[0] * d)
        cv_ols = loocv_metrics(T, P, "ols")
        cv_huber = loocv_metrics(T, P, "huber")

        # Bootstrap K 置信区间
        rng = np.random.default_rng(2026 + int(d))
        k_boot = []
        for _ in range(5000):
            idx = rng.integers(0, len(T), len(T))
            pb, tb = P[idx], T[idx]
            k_boot.append(float(np.dot(pb, tb) / (d * np.dot(pb, pb))))
        k_ci = np.percentile(k_boot, [2.5, 97.5])

        rows.append(
            {
                "直径_mm": d,
                "OLS截距_kN": ols.intercept_,
                "OLS斜率_kN_per_Nm": ols.coef_[0],
                "OLS_R2": r2_score(P, pred),
                "OLS_RMSE_kN": math.sqrt(mean_squared_error(P, pred)),
                "OLS_LOOCV_RMSE_kN": cv_ols["LOOCV_RMSE"],
                "Huber截距_kN": huber.intercept_,
                "Huber斜率_kN_per_Nm": huber.coef_[0],
                "Huber_R2": r2_score(P, pred_h),
                "Huber_LOOCV_RMSE_kN": cv_huber["LOOCV_RMSE"],
                "K_工程过原点": k_zero,
                "K_由OLS斜率反推": k_slope,
                "K_Bootstrap下限": k_ci[0],
                "K_Bootstrap上限": k_ci[1],
                "工程模型R2": r2_zero,
            }
        )
        details[int(d)] = {
            "T": T,
            "P": P,
            "ols": ols,
            "huber": huber,
            "k": k_zero,
            "k_ci": k_ci,
            "pred": pred,
            "resid": P - pred,
        }
    return pd.DataFrame(rows), details


def candidate_breakpoints(T: np.ndarray, Y: np.ndarray) -> pd.DataFrame:
    """低载阶段二次回归 + 稳定阶段线性回归的 BIC 断点搜索。

    临界观测同时参与两侧拟合,使其作为接触压实结束、稳定传力开始的过渡点。
    """
    mean_y = Y.mean(axis=1)
    rows = []
    for c in T[2:-2]:
        low = T <= c
        high = T >= c
        X_low = np.column_stack([np.ones(low.sum()), T[low], T[low] ** 2])
        X_high = np.column_stack([np.ones(high.sum()), T[high]])
        coef_low = np.linalg.lstsq(X_low, mean_y[low], rcond=None)[0]
        coef_high = np.linalg.lstsq(X_high, mean_y[high], rcond=None)[0]
        resid = np.r_[mean_y[low] - X_low @ coef_low, mean_y[high] - X_high @ coef_high]
        sse = float(np.dot(resid, resid))
        n, k = len(resid), 5
        bic = n * np.log(max(sse / n, 1e-12)) + k * np.log(n)
        pred_high = X_high @ coef_high
        r2_high = r2_score(mean_y[high], pred_high)
        rows.append(
            {
                "临界力矩候选": c,
                "BIC": bic,
                "SSE": sse,
                "稳定段R2": r2_high,
                "稳定段截距": coef_high[0],
                "稳定段斜率": coef_high[1],
                "低载二次项": coef_low[2],
            }
        )
    return pd.DataFrame(rows).sort_values("BIC").reset_index(drop=True)


def bootstrap_breakpoint(T: np.ndarray, Y: np.ndarray, n_boot: int = 3000, seed: int = 2026):
    rng = np.random.default_rng(seed)
    selected = []
    for _ in range(n_boot):
        idx = rng.integers(0, Y.shape[1], Y.shape[1])
        result = candidate_breakpoints(T, Y[:, idx])
        selected.append(float(result.iloc[0]["临界力矩候选"]))
    values, counts = np.unique(selected, return_counts=True)
    return {
        "samples": np.asarray(selected),
        "mode": float(values[np.argmax(counts)]),
        "median": float(np.median(selected)),
        "ci95": np.percentile(selected, [2.5, 97.5]).tolist(),
        "frequency": {str(int(v)): int(c) for v, c in zip(values, counts)},
    }


def process_field_condition(df: pd.DataFrame, name: str) -> Dict[str, object]:
    T = df["T"].to_numpy(float)
    Y = df[[f"P{i}" for i in range(1, 6)]].to_numpy(float)
    mean = Y.mean(axis=1)
    sd = Y.std(axis=1, ddof=1)
    cv = sd / mean
    candidates = candidate_breakpoints(T, Y)
    best = candidates.iloc[0]
    boot = bootstrap_breakpoint(T, Y, seed=2026 if name == "岩石" else 2027)
    return {
        "T": T,
        "Y": Y,
        "mean": mean,
        "sd": sd,
        "cv": cv,
        "candidates": candidates,
        "critical": float(best["临界力矩候选"]),
        "stable_intercept": float(best["稳定段截距"]),
        "stable_slope": float(best["稳定段斜率"]),
        "stable_r2": float(best["稳定段R2"]),
        "bootstrap": boot,
    }


def parse_conditions(parameter: pd.DataFrame, k_table: pd.DataFrame) -> Dict[str, Condition]:
    values = {str(row["parameter"]).strip(): row for _, row in parameter.iterrows()}
    k_by_d = dict(zip(k_table["直径_mm"].astype(int), k_table["K_工程过原点"]))

    def get(label: str, col: str) -> float:
        return float(values[label][col])

    result = {}
    for col, name in [("A", "工况A:岩层锚杆"), ("B", "工况B:煤层锚杆")]:
        d = get("锚杆直径 / mm", col)
        result[col] = Condition(
            name=name,
            d=d,
            p_yield=get("锚杆屈服载荷 / kN", col),
            d_hole=get("钻孔直径 / mm", col),
            l_bond=get("有效锚固长度 / mm", col),
            elastic_modulus=get("围岩弹性模量 / Gpa", col),
            poisson=get("泊松比", col),
            tray_width=get("托盘边长 / mm", col),
            delta_max=get("托盘允许压入深度 / mm", col),
            h0=get("围岩载荷影响深度 / mm", col),
            k=float(k_by_d[int(d)]),
        )
    return result


def q2_limits(c: Condition) -> Dict[str, float | str | bool]:
    tau = 0.25 * c.elastic_modulus + 1.0
    p_bond = math.pi * c.d_hole * c.l_bond * tau * 1e-3
    p_indent = c.delta_max * c.elastic_modulus * c.tray_width**2 / (
        (1 - c.poisson**2) * c.h0
    )
    limits = {"锚杆屈服": c.p_yield, "锚固粘结": p_bond, "围岩压陷": p_indent}
    mode = min(limits, key=limits.get)
    p_max = limits[mode]
    t_max = c.k * c.d * p_max
    steel_required = mode == "围岩压陷"
    return {
        "工况": c.name,
        "K": c.k,
        "界面剪切强度_MPa": tau,
        "屈服限值_kN": c.p_yield,
        "粘结限值_kN": p_bond,
        "压陷限值_kN": p_indent,
        "主控失效模式": mode,
        "Pmax_kN": p_max,
        "Tmax_Nm": t_max,
        "是否必须加钢带": bool(steel_required),
    }


def thread_geometry(d: float) -> Tuple[float, float, float, float]:
    de = 0.85 * d
    ae = math.pi * de**2 / 4
    wb = math.pi * de**3 / 32
    wt = math.pi * de**3 / 16
    return de, ae, wb, wt


def thread_tmax(
    e: float,
    d: float,
    k: float,
    ks: float = 0.09,
    sigma_allow: float = 500.0,
    k_sigma: float = 1.0,
    k_tau: float = 1.0,
) -> float:
    _, ae, wb, wt = thread_geometry(d)
    a = 1000.0 / (k * d * ae)
    b = 1000.0 / (k * d * wb)
    c = 1000.0 * ks / wt
    denominator = math.sqrt((k_sigma * (a + b * e)) ** 2 + 3 * (k_tau * c) ** 2)
    return sigma_allow / denominator


def corrected_limits(c: Condition, e: float, k_sigma: float = 1.0, k_tau: float = 1.0):
    # 1) 螺纹根部 Von Mises 约束
    t_thread = thread_tmax(e, c.d, c.k, k_sigma=k_sigma, k_tau=k_tau)
    # 2) 杆体屈服约束
    t_yield = c.k * c.d * c.p_yield
    # 3) 偏心粘结的椭圆交互折减
    tau = 0.25 * c.elastic_modulus + 1.0
    p_bond0 = math.pi * c.d_hole * c.l_bond * tau * 1e-3
    p_bond_e = p_bond0 / math.sqrt(1 + (2 * e / c.d_hole) ** 2)
    t_bond = c.k * c.d * p_bond_e
    # 4) 托盘偏心受压:qmax/qavg = 1 + 6e/b,适用于 e<=b/6
    p_indent0 = c.delta_max * c.elastic_modulus * c.tray_width**2 / (
        (1 - c.poisson**2) * c.h0
    )
    p_indent_e = p_indent0 / (1 + 6 * e / c.tray_width)
    t_indent = c.k * c.d * p_indent_e
    values = {
        "螺纹联合应力": t_thread,
        "杆体屈服": t_yield,
        "锚固粘结": t_bond,
        "围岩压陷": t_indent,
    }
    mode = min(values, key=values.get)
    return values, mode, values[mode]


def q3_table(conditions: Dict[str, Condition], e_grid: Iterable[float], k_sigma=1.0, k_tau=1.0):
    rows = []
    for key, c in conditions.items():
        for e in e_grid:
            vals, mode, tmax = corrected_limits(c, float(e), k_sigma, k_tau)
            rows.append(
                {
                    "工况": key,
                    "偏心距_mm": e,
                    **{f"{k}_Nm": v for k, v in vals.items()},
                    "综合Tmax_Nm": tmax,
                    "主控模式": mode,
                    "应力集中系数_正应力": k_sigma,
                    "应力集中系数_剪应力": k_tau,
                }
            )
    return pd.DataFrame(rows)


def calibrate_E_f() -> Tuple[float, float]:
    """演示性幂律标定:将附件中的煤层 E=2 GPa 对应代表性 f=2,
    岩层 E=15 GPa 对应硬岩分界 f=6。该假设只用于数值展示,实际应用应重标定。
    """
    exponent = math.log(15.0 / 2.0) / math.log(6.0 / 2.0)
    coefficient = 2.0 / (2.0**exponent)
    return coefficient, exponent


def q4_curve(base: Condition, f_grid: np.ndarray, e: float = 0.0) -> pd.DataFrame:
    a, b = calibrate_E_f()
    rows = []
    for f in f_grid:
        E = a * f**b
        c = Condition(**{**asdict(base), "elastic_modulus": E})
        vals, mode, tmax = corrected_limits(c, e)
        rows.append(
            {
                "f": f,
                "E_GPa": E,
                **{f"{k}_Nm": v for k, v in vals.items()},
                "Topt_Nm": tmax,
                "主控模式": mode,
                "是否满足150Nm最低施工力矩": tmax >= 150,
            }
        )
    return pd.DataFrame(rows)


def monte_carlo_q2(c: Condition, n: int = 20000, seed: int = 2026) -> pd.DataFrame:
    """参数不确定性:E、L、tau关系、托盘尺寸、压入阈值等采用小变异正态/对数正态扰动。"""
    rng = np.random.default_rng(seed)
    E = np.clip(rng.normal(c.elastic_modulus, 0.10 * c.elastic_modulus, n), 0.05, None)
    L = np.clip(rng.normal(c.l_bond, 0.05 * c.l_bond, n), 1, None)
    D = np.clip(rng.normal(c.d_hole, 0.02 * c.d_hole, n), 1, None)
    b = np.clip(rng.normal(c.tray_width, 0.03 * c.tray_width, n), 1, None)
    delta = np.clip(rng.normal(c.delta_max, 0.08 * c.delta_max, n), 0.01, None)
    h0 = np.clip(rng.normal(c.h0, 0.08 * c.h0, n), 1, None)
    tau = np.clip(0.25 * E + 1 + rng.normal(0, 0.10, n), 0.01, None)
    p_bond = math.pi * D * L * tau * 1e-3
    p_indent = delta * E * b**2 / ((1 - c.poisson**2) * h0)
    p_yield = np.clip(rng.normal(c.p_yield, 0.03 * c.p_yield, n), 1, None)
    matrix = np.column_stack([p_yield, p_bond, p_indent])
    idx = np.argmin(matrix, axis=1)
    labels = np.array(["锚杆屈服", "锚固粘结", "围岩压陷"])
    pmax = matrix[np.arange(n), idx]
    tmax = c.k * c.d * pmax
    return pd.DataFrame({"Tmax_Nm": tmax, "主控模式": labels[idx]})
全部论文请见下方" 只会建模 QQ名片" 点击QQ名片即可
相关推荐
饼干哥哥3 小时前
十年数据分析经验,被我打包成了4个跨境VOC Skill
前端·数据分析·数据可视化
数模竞赛Paid answer5 小时前
2026年五一杯数学建模C题边坡预警问题求解全过程文档及程序
算法·数学建模·五一杯
数模竞赛Paid answer7 小时前
2021年深圳杯数学建模A题火星探测器着陆控制方案解题全过程论文及程序
算法·数学建模·深圳杯
smppbzyc10 小时前
2026华数杯数学建模A题原创论文分享
数学建模·数学建模竞赛·华数杯·全国大学生数学建模竞赛·华数杯数学建模·2026华数杯
Asa1213810 小时前
Nature Microbiology|陆域地下水微生物群落与深层生物圈的“微生物节俭“框架
数据分析
数模竞赛Paid answer21 小时前
2024年五一杯数学建模A题钢板最优切割路径问题求解全过程论文及程序
数学建模·数据分析·五一杯
茜茜数模21 小时前
2026华数杯A题完整思路+论文+代码:微构体中填充导电介质的仿真优化
数学建模·华数杯
数据智研1 天前
【数据分享】中国民政统计年鉴(1949-2025)
大数据·人工智能·信息可视化·数据分析
雪山青木1 天前
全国微博签到数据201912-202004
大数据·爬虫·python·数据挖掘·数据分析·新浪微博