2026年华数杯全国大学生数学建模
A题 微构体中填充导电介质的仿真优化
原题再现:
一种导电体系由多种填充介质组成,研究其导电性能与填充介质之间的关系,对新能源、新材料及半导体领域的研究具有重要意义。为便于研究此种导电体系的导通问题,研究人员将研究对象理想化成一个边长10000nm(nm:纳米)正方体的微型结构,以下简称为"微构体",如图1所示。研究微构体的导通情况即可反映该导电体系的导电性能。微构体内部默认充满绝缘溶剂和若干导电介质。在微构体中,所有导电介质可以悬浮于微构体内部的任意位置,任意方向。不考虑导电介质的重力作用,即导电介质所受的合力为0。

现有的导电介质存在两种形状。第一种导电介质为直圆柱体,直圆柱体高为5000nm,底面半径为30nm,以下简称介质A;第二种导电介质为半径200nm的正球体,以下简称介质B。所有导电介质均位于微构体范围内,不允许任何部分超出微构体边界。在计算机仿真中,导电介质超出微构体边界的部分默认统一采用边界截断规则(具体规则见相关说明)进行处理。
选择微构体两个相对的面作为带电表面,默认这两个面的所有位置均带电,剩余四个面则默认不带电且绝缘,也不导电。同时,在计算机仿真中,微构体中的导电介质位置一旦固定,就不会移动,介质之间不存在碰撞关系,允许介质因随机生成而产生相互贯穿、重叠等情况。一个导电介质是否能带电取决于该导电介质与带电表面或者其余已经带电的导电介质之间的最短距离。当最短距离小于等于1.8nm时,导电介质和带电表面之间或者两个导电介质之间视为导通;大于1.8 nm 时,视为不导通。假设导电介质m开始时不带电,若现在周围存在已经带电的导电介质n且m与n之间的最短距离小于等于1.8nm,则m由于极化作用也全部带电,m与n视为导通。若微构体两个相对的带电表面之间存在至少一条由导电介质构成的完整通路,则判定该微构体导通。
需要额外注意的是,微构体的导通性仅在两个带电表面之间进行判定。在本研究中,为方便计算,微构体的中心点为坐标轴原点,且长宽高分别平行于XYZ轴,并选取垂直于X轴的两个表面为带电表面,即微构体的带电表面仅为正方体的左面和右面。
请建立数学模型解决以下问题:
问题1:若微构体中的导电介质只有介质A。附件1给出了三个微构体中所有介质A的坐标,试分析三个微构体是否导通,分别给出三个微构体的导通情况。
问题2:若向微构体中仅添加介质A,添加介质A的体积分数(名词解释见相关说明)为 0.50%,计算在当前条件下微构体的导通概率。若将介质A的体积分数依次调整为 0.60%、0.70%和1.00%,请分别计算对应的微构体导通概率。如果体积分数对应的介质A的数量不是整数,四舍五入即可。
问题3:若微构体中的导电介质只包含介质A。试计算在微构体导通的概率不低于90%的前提下,介质A的最低填充量(结果使用体积分数表示,并精确到百分号下小数点后两位)。
问题4:若在微构体中同时填充介质A和介质B。介质A成本为1.05元/μm3(μm:微米),介质B的成本为0.05元/μm3。试计算在微构体导通的概率不低于90%的前提下,两种介质的填充量为多少时,使得微构体填充导电介质的总成本最低。
整体求解过程概述(摘要)
针对问题一,本文首先对附件三个工作表进行数据理解与探索,将每一行六维端点坐标重构为介质A的三维有限轴段,并通过缺失值、坐标范围、轴段长度与边界端点比例进行一致性检查。在此基础上,将半径30 nm的直圆柱近似为轴段的Minkowski膨胀体,把"两介质表面最短距离不超过1.8 nm"转化为"轴线段最短距离不超过61.8 nm",进而建立无向接触图,并以左、右带电表面为超级源点和超级汇点进行可达性判定。结果表明:组1不导通;组2导通,存在2→12→24→39的短导通链;组3导通,存在63→264→216→351的短导通链。
针对问题二,为探究介质A体积分数对导通概率的非线性影响,本文在三维立方体内随机生成中心位置与各向同性方向,按照附件数据形式对越界轴段做有效截断,并用并查集实时判定左、右带电表面的连通性。对0.50%、0.60%、0.70%和1.00%四种体积分数分别实施800次Monte Carlo重复试验,同时给出Wilson 95%置信区间。得到导通概率点估计依次为6.00%、21.13%、43.00%和99.50%,呈现典型的渗流相变式S形跃迁。
针对问题三,鉴于90%目标概率位于问题二相变区间的高侧,本文在0.80%~0.90%范围内进行局部精细搜索,并以0.01个百分点为决策分辨率。仿真结果显示,0.88%时导通概率约为88.86%,0.89%时上升至92.14%,0.90%时约为93.14%。因此,在本文随机生成与边界解释下,满足导通概率不低于90%的最低A填充体积分数为0.89%,对应约630根介质A。
针对问题四,本文引入半径200 nm的球形介质B,分别构建A-A、A-B和B-B三类几何接触判据,其轴线/球心等效阈值分别为61.8 nm、231.8 nm和401.8 nm;以导通概率不低于90%为约束,以总材料成本最小为目标建立离散随机优化模型。采用共同随机数的嵌套扫描,在A约0.869%(615根)固定时逐步增加B。名义最优可行点为A实际体积分数约0.8694%、B实际体积分数约0.3016%(90个球),导通概率约90.38%,总成本约9.280元;若更强调置信下限意义下的稳健性,则应提高填充量并重新按保守概率约束筛选。
总体而言,本文把微观几何相交问题统一转化为"随机几何图上的跨面渗流问题",形成"数据理解与探索→预处理→几何特征工程→接触图构建→随机仿真→概率估计→成本优化→稳健性评价"的完整技术链。模型具有结构清晰、计算复杂度可控、可自然推广到更多形状与材料体系等优点;同时,本文明确指出圆柱胶囊近似、边界处理解释和有限Monte Carlo样本量是主要误差来源,并通过临界邻域复核与置信区间给出可解释的不确定性描述。
模型假设:
假设1:附件坐标为有效轴段。对问题一,严格使用附件两端点作为每个介质A的有效轴线段,不对被边界处理后可能存在的不可观测片段进行反演。
假设2:介质A采用胶囊近似。鉴于A的长径比较大,将有限直圆柱用轴段半径膨胀体近似。该近似在侧面---侧面接触占主导时与真实圆柱高度一致,主要误差集中在平端面附近。
假设3:随机位置均匀、方向各向同性。问题二至问题四未给出空间偏置与取向分布,因此中心点在立方体内独立均匀生成,方向向量在单位球面上均匀分布。
假设4:几何位置固定且允许重叠。遵循题意,不考虑重力、碰撞和动力学演化;随机生成后位置不变,允许不同介质贯穿或重叠。
假设5:电学行为只由连通性决定。一旦两个导电实体满足距离阈值即视为理想导通,不进一步考虑接触电阻、隧穿电阻差异和电流分配。
假设6:问题四成本与实际填充体积线性。总成本等于各类导电介质总体积乘对应单位体积成本,忽略混合、加工和界面附加成本。
问题分析:
问题一分析
本题属于确定性三维计算几何结合无向图连通判定问题,核心是判断正方体微构体内给定多根圆柱介质能否形成贯通左右带电表面的导电通路。建模核心难点为将圆柱表面导通距离约束转化为轴线段距离判据,同时处理边界截断后的不规则有效轴段;建模思路先完成附件坐标清洗与几何特征分析,利用 Minkowski 膨胀等效推导介质间临界距离阈值,构建介质节点接触图,增设左右电极超级节点,采用并查集 + BFS 双重算法判定连通性并输出完整导通介质链路,分层核验线段距离、电极接触、通路完整三重硬约束,直观三维可视化展示介质空间分布与导通链,最终区分三组样本导通状态,为后序随机渗流仿真搭建统一几何判定底层算子。
问题二分析
本题属于三维连续渗流 Monte Carlo 随机仿真统计建模问题,在问题一几何接触框架基础上引入随机介质生成机制,探究介质 A 填充体积分数与系统导通概率的非线性 S 型相变规律。难点为介质中心点、空间朝向各向同性均匀随机生成,边界截断处理与附件数据格式保持一致,有限仿真样本存在统计偏差;建模构造标准化随机生成流程,按体积分数换算介质根数,采用并查集实时判定单次仿真导通状态,引入 Wilson 95% 置信区间量化概率估计不确定性,多组固定填充量批量重复仿真,绘制概率 - 体积分数曲线与 Logit 变换曲线直观呈现渗流相变特征,对比不同填充档位导通概率差异,锁定 90% 可靠性对应的填充量搜索区间,完整复用线段距离、电极接触全部几何判据,无需重构底层计算模块。
问题三分析
本题属于渗流临界参数反求问题,是问题二正仿真的逆优化任务,目标求解导通概率不低于 90% 的介质 A 最小填充体积分数。难点是导通概率随填充量单调递增但相变区间斜率陡峭,微小填充量变化会带来概率大幅波动,有限仿真存在随机噪声干扰;建模基于问题二区间结论在 0.80%~0.90% 临界区精细化加密采样,以 0.01% 为步长批量开展 Monte Carlo 仿真,结合 Wilson 置信区间区分名义点估计解与工程保守解,绘制临界区间局部放大图可视化阈值边界,兼顾离散介质根数整数约束与百分比精度要求,定量分析统计不确定性对临界填充量判定的影响,输出基础理论解与高稳健工程推荐两套结果,几何连通判定逻辑完全继承前两问体系。
问题四分析
本题属于混合介质随机约束离散优化问题,新增球形介质 B,以导通概率≥90% 为约束、材料总成本最小为目标构建双变量优化模型。难点是存在 A-A、A-B、B-B 三类差异化几何导通阈值,两类介质成本、空间渗流能力差异显著,二维填充组合搜索空间庞大、随机仿真计算成本高;建模拓展接触判定模块新增球体与线段、球体间距离判据,采用共同随机数方差缩减策略降低多方案对比的仿真噪声,先粗筛最优 A 填充区间再固定 A 批量扫描 B 添加量,绘制成本 - 可靠性权衡曲线识别最优混合配比,量化细长介质长程导通优势与低价球体局部桥接协同效应,同时分析 Monte Carlo 抽样带来的方案排序不确定性,给出名义最低成本解与高置信稳健备选方案,整套几何连通、随机仿真、统计评估模块复用前三问成熟框架。
模型的建立与求解整体论文缩略图

全部论文请见下方" 只会建模 QQ名片" 点击QQ名片即可
部分程序代码:
python
from __future__ import annotations
import argparse, math, os, re, zipfile
import xml.etree.ElementTree as ET
from pathlib import Path
from collections import deque
import numpy as np
import numba as nb
import matplotlib.pyplot as plt
# ---------------------- 常量 ----------------------
H = 5000.0 # 半边长 nm
SIDE = 2 * H
L_A = 5000.0 # A轴向长度 nm
R_A = 30.0 # A半径 nm
R_B = 200.0 # B半径 nm
DELTA = 1.8 # 导通阈值 nm
V_CUBE = SIDE ** 3
V_A = math.pi * R_A**2 * L_A
V_B = 4.0 / 3.0 * math.pi * R_B**3
C_A = 1.05 # 元/um^3
C_B = 0.05
# ---------------------- Excel标准库读取 ----------------------
def read_xlsx_six_columns(path: str):
ns = {'m':'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
'r':'http://schemas.openxmlformats.org/officeDocument/2006/relationships'}
zf = zipfile.ZipFile(path)
wb = ET.fromstring(zf.read('xl/workbook.xml'))
rels = ET.fromstring(zf.read('xl/_rels/workbook.xml.rels'))
relmap = {r.attrib['Id']:r.attrib['Target'] for r in rels}
shared = []
if 'xl/sharedStrings.xml' in zf.namelist():
sst = ET.fromstring(zf.read('xl/sharedStrings.xml'))
for si in sst.findall('m:si', ns):
shared.append(''.join(t.text or '' for t in si.iter('{%s}t' % ns['m'])))
out = {}
for s in wb.find('m:sheets', ns):
name = s.attrib['name']
rid = s.attrib['{%s}id' % ns['r']]
root = ET.fromstring(zf.read('xl/' + relmap[rid]))
rows = []
for row in root.findall('.//m:sheetData/m:row', ns):
vals = {}
for c in row.findall('m:c', ns):
ref = c.attrib['r']
col = re.match(r'([A-Z]+)', ref).group(1)
v = c.find('m:v', ns)
val = None if v is None else v.text
if c.attrib.get('t') == 's' and val is not None:
val = shared[int(val)]
elif val is not None:
try: val = float(val)
except Exception: pass
vals[col] = val
rows.append(vals)
data = []
for r in rows[2:]:
if all(r.get(c) is not None for c in 'ABCDEF'):
data.append([float(r[c]) for c in 'ABCDEF'])
out[name] = np.asarray(data, dtype=float)
return out
# ---------------------- 几何距离 ----------------------
def seg_seg_dist(p1, q1, p2, q2):
u=q1-p1; v=q2-p2; w=p1-p2
a=float(np.dot(u,u)); b=float(np.dot(u,v)); c=float(np.dot(v,v))
d=float(np.dot(u,w)); e=float(np.dot(v,w)); D=a*c-b*b
small=1e-12; sN=sD=D; tN=tD=D
if D < small:
sN=0.0; sD=1.0; tN=e; tD=c
else:
sN=b*e-c*d; tN=a*e-b*d
if sN < 0: sN=0.0; tN=e; tD=c
elif sN > sD: sN=sD; tN=e+b; tD=c
if tN < 0:
tN=0.0
if -d < 0: sN=0.0
elif -d > a: sN=sD
else: sN=-d; sD=a
elif tN > tD:
tN=tD
if -d+b < 0: sN=0.0
elif -d+b > a: sN=sD
else: sN=-d+b; sD=a
sc=0.0 if abs(sN)<small else sN/sD
tc=0.0 if abs(tN)<small else tN/tD
return float(np.linalg.norm(w+sc*u-tc*v))
def analyze_group(arr):
n=len(arr); adj=[[] for _ in range(n)]; edges=[]
for i in range(n):
for j in range(i+1,n):
da=seg_seg_dist(arr[i,:3],arr[i,3:],arr[j,:3],arr[j,3:])
if da <= 2*R_A + DELTA:
adj[i].append(j); adj[j].append(i)
edges.append((i,j,da,max(0.0,da-2*R_A)))
left=[]; right=[]
for i,row in enumerate(arr):
u=row[3:]-row[:3]; ln=np.linalg.norm(u); ux=u[0]/ln if ln else 0.0
radial_x=R_A*math.sqrt(max(0.0,1.0-ux*ux))
if min(row[0],row[3]) - radial_x <= -H + DELTA: left.append(i)
if max(row[0],row[3]) + radial_x >= H - DELTA: right.append(i)
q=deque(left); vis=set(left); parent={}; target=None
while q:
x=q.popleft()
if x in right: target=x; break
for y in adj[x]:
if y not in vis:
vis.add(y); parent[y]=x; q.append(y)
path=[]
if target is not None:
cur=target; path=[cur]
while cur not in left:
cur=parent[cur]; path.append(cur)
path.reverse()
comp=[-1]*n; comps=[]
for s in range(n):
if comp[s]>=0: continue
st=[s]; comp[s]=len(comps); mem=[]
while st:
x=st.pop(); mem.append(x)
for y in adj[x]:
if comp[y]<0: comp[y]=comp[s]; st.append(y)
comps.append(mem)
lens=np.linalg.norm(arr[:,3:]-arr[:,:3],axis=1)
return {
'n':n,'conductive':bool(path),'path':[i+1 for i in path],
'edges':edges,'left_n':len(left),'right_n':len(right),
'components':len(comps),'largest_component':max(map(len,comps)) if comps else 0,
'len_min':float(lens.min()),'len_mean':float(lens.mean()),'len_median':float(np.median(lens)),
'len_max':float(lens.max())
}
# ---------------------- Numba Monte Carlo ----------------------
@nb.njit(cache=True)
def seed_nb(seed): np.random.seed(seed)
@nb.njit(cache=True)
def clip_segment_to_box(p,q,H):
d=q-p; t0=0.0; t1=1.0
for k in range(3):
if abs(d[k])<1e-12:
if p[k] < -H or p[k] > H: return p.copy(),p.copy(),False
else:
a=(-H-p[k])/d[k]; b=(H-p[k])/d[k]
if a>b: a,b=b,a
if a>t0: t0=a
if b<t1: t1=b
if t0>t1: return p.copy(),p.copy(),False
return p+t0*d, p+t1*d, True
@nb.njit(cache=True)
def point_seg_d2(pt,a,b):
ab=b-a; den=np.dot(ab,ab)
if den < 1e-20: return np.dot(pt-a,pt-a)
t=np.dot(pt-a,ab)/den
if t<0: t=0.0
elif t>1: t=1.0
v=a+t*ab-pt
return np.dot(v,v)
@nb.njit(cache=True)
def seg_seg_d2(p1,q1,p2,q2):
u=q1-p1; v=q2-p2; w=p1-p2
a=np.dot(u,u); b=np.dot(u,v); c=np.dot(v,v); d=np.dot(u,w); e=np.dot(v,w)
D=a*c-b*b; small=1e-12; sN=D; sD=D; tN=D; tD=D
if D<small:
sN=0.0; sD=1.0; tN=e; tD=c
else:
sN=b*e-c*d; tN=a*e-b*d
if sN<0: sN=0.0; tN=e; tD=c
elif sN>sD: sN=sD; tN=e+b; tD=c
if tN<0:
tN=0.0
if -d<0: sN=0.0
elif -d>a: sN=sD
else: sN=-d; sD=a
elif tN>tD:
tN=tD
if -d+b<0: sN=0.0
elif -d+b>a: sN=sD
else: sN=-d+b; sD=a
sc=0.0 if abs(sN)<small else sN/sD; tc=0.0 if abs(tN)<small else tN/tD
dp=w+sc*u-tc*v
return np.dot(dp,dp)
@nb.njit(cache=True)
def uf_find(parent,x):
while parent[x]!=x:
parent[x]=parent[parent[x]]; x=parent[x]
return x
@nb.njit(cache=True)
def uf_union(parent,rank,a,b):
ra=uf_find(parent,a); rb=uf_find(parent,b)
if ra==rb: return
if rank[ra]<rank[rb]: parent[ra]=rb
elif rank[ra]>rank[rb]: parent[rb]=ra
else: parent[rb]=ra; rank[ra]+=1
@nb.njit(cache=True)
def generate_A_segments(N,H,L):
segs=np.empty((N,6)); half=L/2.0
for i in range(N):
c=np.array((np.random.uniform(-H,H),np.random.uniform(-H,H),np.random.uniform(-H,H)))
z=np.random.uniform(-1.0,1.0); th=np.random.uniform(0.0,2.0*math.pi)
s=math.sqrt(max(0.0,1.0-z*z)); u=np.array((s*math.cos(th),s*math.sin(th),z))
p=c-half*u; q=c+half*u; pc,qc,_=clip_segment_to_box(p,q,H)
segs[i,:3]=pc; segs[i,3:]=qc
return segs
@nb.njit(cache=True)
def conductive_A_trial(N,H,L,r,delta):
A=generate_A_segments(N,H,L); parent=np.arange(N+2); rank=np.zeros(N+2,np.int8)
left=N; right=N+1; th=(2*r+delta)**2
for i in range(N):
if min(A[i,0],A[i,3])<=-H+(r+delta): uf_union(parent,rank,i,left)
if max(A[i,0],A[i,3])>= H-(r+delta): uf_union(parent,rank,i,right)
for i in range(N):
for j in range(i+1,N):
if seg_seg_d2(A[i,:3],A[i,3:],A[j,:3],A[j,3:])<=th:
uf_union(parent,rank,i,j)
return 1 if uf_find(parent,left)==uf_find(parent,right) else 0
@nb.njit(cache=True)
def generate_B_centers(N,H):
B=np.empty((N,3))
for i in range(N):
B[i,0]=np.random.uniform(-H,H); B[i,1]=np.random.uniform(-H,H); B[i,2]=np.random.uniform(-H,H)
return B
@nb.njit(cache=True)
def fixedA_B_sweep_once(NA, NBmax, marks, H,L,rA,rB,delta):
A=generate_A_segments(NA,H,L); B=generate_B_centers(NBmax,H)
N=NA+NBmax; parent=np.arange(N+2); rank=np.zeros(N+2,np.int8); left=N; right=N+1
thAA=(2*rA+delta)**2
for i in range(NA):
if min(A[i,0],A[i,3])<=-H+(rA+delta): uf_union(parent,rank,i,left)
if max(A[i,0],A[i,3])>= H-(rA+delta): uf_union(parent,rank,i,right)
for i in range(NA):
for j in range(i+1,NA):
if seg_seg_d2(A[i,:3],A[i,3:],A[j,:3],A[j,3:])<=thAA: uf_union(parent,rank,i,j)
out=np.zeros(len(marks),np.int8); mi=0
while mi<len(marks) and marks[mi]==0:
out[mi]=1 if uf_find(parent,left)==uf_find(parent,right) else 0; mi+=1
thAB=(rA+rB+delta)**2; thBB=(2*rB+delta)**2
for k in range(NBmax):
idx=NA+k
if B[k,0]<=-H+(rB+delta): uf_union(parent,rank,idx,left)
if B[k,0]>= H-(rB+delta): uf_union(parent,rank,idx,right)
for i in range(NA):
if point_seg_d2(B[k],A[i,:3],A[i,3:])<=thAB: uf_union(parent,rank,idx,i)
for j in range(k):
v=B[k]-B[j]
if np.dot(v,v)<=thBB: uf_union(parent,rank,idx,NA+j)
added=k+1
while mi<len(marks) and marks[mi]==added:
out[mi]=1 if uf_find(parent,left)==uf_find(parent,right) else 0; mi+=1
return out
def wilson(k,n,z=1.96):
p=k/n; den=1+z*z/n; cen=(p+z*z/(2*n))/den
half=z*math.sqrt(p*(1-p)/n+z*z/(4*n*n))/den
return cen-half,cen+half
def estimate_A(phi,trials,seed):
seed_nb(seed); N=int(round(phi*V_CUBE/V_A)); k=0
for _ in range(trials): k += conductive_A_trial(N,H,L_A,R_A,DELTA)
return N,k/trials,wilson(k,trials),k
def sweep_B(phiA, phiB_list, trials, seed):
seed_nb(seed); NA=int(round(phiA*V_CUBE/V_A))
marks=np.array([int(round(x*V_CUBE/V_B)) for x in phiB_list],dtype=np.int64)
counts=np.zeros(len(marks),dtype=np.int64); NBmax=int(marks.max())
for _ in range(trials): counts += fixedA_B_sweep_once(NA,NBmax,marks,H,L_A,R_A,R_B,DELTA)
return NA,marks,counts/trials,counts
# ---------------------- 作图 ----------------------
def setup_cn_font():
plt.rcParams['font.sans-serif']=['Noto Sans CJK SC','SimHei','DejaVu Sans']
plt.rcParams['axes.unicode_minus']=False
def plot_group_3d(arr, ana, title, out):
fig=plt.figure(figsize=(8,6)); ax=fig.add_subplot(111,projection='3d')
pathset=set(i-1 for i in ana['path'])
for i,row in enumerate(arr):
lw=2.8 if i in pathset else 0.8; alpha=1.0 if i in pathset else 0.35
ax.plot([row[0],row[3]],[row[1],row[4]],[row[2],row[5]],lw=lw,alpha=alpha)
ax.set(xlabel='X/nm',ylabel='Y/nm',zlabel='Z/nm',title=title)
ax.set_xlim(-H,H); ax.set_ylim(-H,H); ax.set_zlim(-H,H)
fig.tight_layout(); fig.savefig(out,dpi=220); plt.close(fig)
def make_summary_plots(groups, analyses, q2_rows, q3_rows, outdir):
setup_cn_font(); outdir=Path(outdir); outdir.mkdir(parents=True,exist_ok=True)
# 图1 数据规模
names=list(groups); ns=[len(groups[n]) for n in names]
fig,ax=plt.subplots(figsize=(7,4.5)); ax.bar(names,ns); ax.set_ylabel('介质A数量'); ax.set_title('附件三组微构体的数据规模')
for i,v in enumerate(ns): ax.text(i,v,f'{v}',ha='center',va='bottom')
fig.tight_layout(); fig.savefig(outdir/'fig01_数据规模.png',dpi=220); plt.close(fig)
# 图2 长度箱线图
lens=[np.linalg.norm(groups[n][:,3:]-groups[n][:,:3],axis=1) for n in names]
fig,ax=plt.subplots(figsize=(7,4.5)); ax.boxplot(lens,tick_labels=names); ax.set_ylabel('有效轴段长度/nm'); ax.set_title('边界处理后介质A有效轴段长度分布')
fig.tight_layout(); fig.savefig(outdir/'fig02_长度箱线图.png',dpi=220); plt.close(fig)
# 3D图
for idx,n in enumerate(names,3): plot_group_3d(groups[n],analyses[n],f'{n} 三维轴段与最短导通路径',outdir/f'fig{idx:02d}_{n}_三维.png')
# 图6 图结构指标
fig,ax=plt.subplots(figsize=(8,4.8)); x=np.arange(len(names)); w=.25
ax.bar(x-w,[len(analyses[n]['edges']) for n in names],w,label='导通边数')
ax.bar(x,[analyses[n]['largest_component'] for n in names],w,label='最大连通分量')
ax.bar(x+w,[analyses[n]['components'] for n in names],w,label='连通分量数')
ax.set_xticks(x,names); ax.legend(); ax.set_title('三组接触图的拓扑统计')
fig.tight_layout(); fig.savefig(outdir/'fig06_图结构指标.png',dpi=220); plt.close(fig)
# Q2
ph=np.array([r[0]*100 for r in q2_rows]); pp=np.array([r[2]*100 for r in q2_rows]); lo=np.array([r[3][0]*100 for r in q2_rows]); hi=np.array([r[3][1]*100 for r in q2_rows])
fig,ax=plt.subplots(figsize=(7.5,4.8)); ax.errorbar(ph,pp,yerr=[pp-lo,hi-pp],marker='o',capsize=4); ax.set(xlabel='A体积分数/%',ylabel='导通概率/%',title='问题2:体积分数-导通概率及95% Wilson区间'); ax.grid(alpha=.25)
fig.tight_layout(); fig.savefig(outdir/'fig07_Q2概率曲线.png',dpi=220); plt.close(fig)
# Q3
ph3=np.array([r[0]*100 for r in q3_rows]); p3=np.array([r[2]*100 for r in q3_rows])
fig,ax=plt.subplots(figsize=(7.5,4.8)); ax.plot(ph3,p3,'o-'); ax.axhline(90,ls='--'); ax.axvline(.89,ls='--'); ax.set(xlabel='A体积分数/%',ylabel='导通概率/%',title='问题3:90%概率约束下的临界填充量'); ax.grid(alpha=.25)
fig.tight_layout(); fig.savefig(outdir/'fig08_Q3临界搜索.png',dpi=220); plt.close(fig)
# ---------------------- 主程序 ----------------------
def main():
ap=argparse.ArgumentParser(); ap.add_argument('--xlsx',default='附件(1).xlsx'); ap.add_argument('--mode',choices=['quick','full'],default='quick'); ap.add_argument('--outdir',default='model_output')
args=ap.parse_args(); out=Path(args.outdir); out.mkdir(parents=True,exist_ok=True)
groups=read_xlsx_six_columns(args.xlsx); analyses={k:analyze_group(v) for k,v in groups.items()}
print('\n[问题1]')
for k,a in analyses.items(): print(k,'导通' if a['conductive'] else '不导通','最短导通路径=',a['path'],'导通边数=',len(a['edges']))
tq=30 if args.mode=='quick' else 800
q2=[]
print('\n[问题2]')
for i,phi in enumerate([.005,.006,.007,.01]):
r=estimate_A(phi,tq,20260810+i); q2.append((phi,*r)); print(f'{phi*100:.2f}%: N={r[0]}, p={r[1]:.4f}, CI={r[2]}')
tg=40 if args.mode=='quick' else 700
q3=[]
print('\n[问题3]')
for i,phi in enumerate([.0080,.0084,.0086,.0088,.0089,.0090]):
r=estimate_A(phi,tg,20260900+i); q3.append((phi,*r)); print(f'{phi*100:.2f}%: p={r[1]:.4f}')
feasible=[r for r in q3 if r[2]>=.90]
if feasible: print('离散搜索最低满足值约为',min(feasible,key=lambda x:x[0])[0]*100,'%')
tb=40 if args.mode=='quick' else 800
print('\n[问题4]')
phiBs=[0,.001,.002,.003,.004,.005,.006]
NA,marks,ps,ks=sweep_B(.0087,phiBs,tb,20261111)
for pb,nb,p in zip(phiBs,marks,ps):
cost=NA*(V_A/1e9)*C_A+nb*(V_B/1e9)*C_B
print(f'A≈0.87%, B={pb*100:.2f}%: NA={NA}, NB={nb}, p={p:.4f}, cost={cost:.4f}元')
feas=[(.0087,pb,NA,nb,p,NA*(V_A/1e9)*C_A+nb*(V_B/1e9)*C_B) for pb,nb,p in zip(phiBs,marks,ps) if p>=.90]
if args.mode=='full':
# 在问题3临界点附近做二维粗-细联合搜索。B扫描采用共同随机数嵌套前缀,
# 对每个固定A,P随NB在同一批随机样本下保持单调,有利于稳定比较相邻方案。
for ia,pa in enumerate([.0085,.0086,.0088,.0089]):
bgrid=[0,.001,.002,.003,.004,.005,.006,.008,.010]
NAi,marksi,psi,ksi=sweep_B(pa,bgrid,400,20261200+ia)
for pb,nb,p in zip(bgrid,marksi,psi):
cost=NAi*(V_A/1e9)*C_A+nb*(V_B/1e9)*C_B
if p>=.90: feas.append((pa,pb,NAi,nb,p,cost))
if feas:
best=min(feas,key=lambda x:x[-1])
print('二维粗-细网格名义最低成本可行点:phiA,phiB,NA,NB,p,cost=',best)
elif feas:
best=min(feas,key=lambda x:x[-1]); print('quick模式局部最低成本可行点:',best)
make_summary_plots(groups,analyses,q2,q3,out)
print('\n图表输出目录:',out.resolve())
if __name__=='__main__': main()