ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DDSCAT多核壳结构建模:用Matlab批量生成shape文件

DDSCAT多核壳结构建模:用Matlab批量生成shape文件 平时用DDSCAT做纳米粒子散射计算的朋友应该都有同感程序本身算起来很省心真正费时间的是怎么把脑海里的三维结构变成它认得的shape文件。前四篇我们聊过安装、单球建模、材料参数配置和一些基础报错这一篇直接把难度往上提一个台阶聊多核壳结构。多核壳multi-core-shell就是多个核心颗粒被同一个外壳包裹在等离激元传感、SERS增强基底、纳米催化反应器这类体系的光学响应计算里非常常见DDSCAT里要建这种模型靠手写坐标根本不可能必须用Matlab批量生成偶极子坐标。这篇就把我实际调试过的建模思路和完整代码放出来覆盖多核壳球和多核壳圆柱两种结构顺便把DDSCAT读shape文件的格式衔接讲清楚能帮你省下好几个晚上的调试时间。1. 为什么多核壳结构的模型文件必须自己写脚本生成先想清楚这一篇要解决的问题到底是什么。DDSCAT本身是一个纯计算内核它不对“多核壳”这个概念做任何语义理解只接收一个朴素的偶极子列表——每个偶极子的位置和材料编号。你给它一个形状文件它就按里面的坐标摆阵子、算极化率、迭代求解。所以建模的全部工作本质上就是回答一个问题在离散格点上哪些点属于壳哪些点属于核哪些点是空白。单球或单圆柱很好办一个球形判断条件r R就全部解决了用Excel甚至文本编辑器都能生成。但多核壳结构的难点在于一个点可能同时落在多个几何区域的判断范围内。比如某个点既在壳层内部又靠近某个核心球这时候到底算壳材料还是核材料这类问题必须通过程序判断优先级逐点扫描包围盒里的每一个格点。一旦核的数量上到三四个手工写坐标文件就是灾难。另外多核壳结构在真实实验里并不是一个标准化形状。核的个数、核心位置、核半径、壳厚度、外壳整体形状每个课题组都有自己的参数组合。DDSCAT内置的规则形状生成器里只有实心球、实心圆柱、椭球这些基础选项没有“多个球核嵌在一个壳里”的预设。所以无论你用什么语言写生成器本质都是在做一个参数化的几何建模工具Matlab只是我比较顺手的选择。还有一个容易被忽略的原因科研实验方案经常要扫参数。核半径从2 nm扫到10 nm核间距从5 nm扫到30 nm如果每次改参数都手动改坐标文件不仅慢而且容易出错。写成Matlab脚本之后改几个数字重新跑一遍几分钟就拿到新shape文件这才是能支撑论文里那批参数扫描图的效率。所以这一篇不只是给一组代码更是给一套“如何把结构参数翻译成DDSCAT输入”的方法。2. 动手写代码前先定好的三件事几何参数、材料编号、格点分辨率写代码之前别急着开Matlab先把三个基本问题定下来几何参数怎么定义、材料编号怎么分配、格点分辨率选多大。这三件事看似简单但后面所有代码逻辑都建立在它们之上我调试时返工最多的也正是这几处。2.1 几何参数化半径、核心坐标和位置约束多核壳球的结构可以拆成三层看。最外层是一个完整的球壳外半径记作Rshell壳的内边界半径记作Rcore_outer壳层厚度就是Rshell - Rcore_outer。壳内部有若干个核心球每个核心球由两个参数描述核半径coreR(k)和核心坐标coreC{k}。判断一个格点属于哪里时先计算它到外壳球心的距离再计算它到每个核心球心的距离按优先级赋予材料编号。这里有一个必须提前想清楚的几何约束每个核心球必须完全位于外壳内部。也就是说对任意核心必须满足norm(coreC{k}) coreR(k) Rshell。如果不满足生成的模型会出现“核戳出壳外”的情况这在物理上可能就不是你想要的结构了。代码里最好加上这个检查跑出的模型要能自己报警。多核壳圆柱的参数化类似只是外层从球壳换成圆柱壳。圆柱的几何定义有四个量圆柱外半径Rcyl、壳内半径RcylCore或者直接定义壳厚度shellT Rcyl - RcylCore、圆柱总长度Lcyl以及圆柱轴向方向。我默认轴向为z这样柱坐标判断最直接。至于圆柱壳内的多个核可以继续用球形核也可以定义成小圆柱核取决于你的物理模型。为了代码统一我推荐核一律用球心坐标加半径来描述因为球的判断在任意参考系下都是一行代码圆柱核还要额外处理轴向变换。2.2 材料编号icomp不是你想填几就填几DDSCAT的shape文件里每个偶极子后面带的那个整数是材料组份标记通常写成icomp它不直接存折射率或介电常数只存一个“第几号材料”的索引。真正对应什么材料是在ddscat.par里通过介电常数输入区定义的。比如你希望核是金、壳是二氧化硅那么在par文件里第二种材料写金的介电常数第三种材料写二氧化硅的介电常数shape文件里相应位置就填2和3。因此写Matlab脚本时最好在文件头部用两个变量shellMat和coreMat单独管理材料编号而不是把魔法数字直接散落在判断逻辑里。我习惯用2表示壳、3表示核因为1通常留给真空或环境介质这样和DDSCAT里“第一种组分默认是背景”的习惯对齐。如果你在别的例子里见过0也代表某种材料那属于不同版本或自定义映射不用纠结只要保证shape文件里的编号和par文件里的介电常数顺序严格对应就不会出问题。2.3 格点分辨率dpl选多大直接决定计算量和精度DDSCAT把目标离散成一个个偶极子偶极子之间的间距通常记作d程序里也叫dpl。分辨率怎么选不能太任性。常规准则是每个波长内至少要有10个偶极子更稳妥是15到20个。这里要考虑介质内的波长也就是lambda / n其中n是材料折射率。比如入射波长600 nm壳材料折射率1.5那么介质内波长是400 nm偶极子间距最好取400/15≈26 nm以内。从形状建模的角度分辨率还决定了一个核能不能被“画出来”。如果核半径只有2个格点那么核的形状会明显离散化散射结果里可能出现伪影。一般来说核半径至少要覆盖6到8个格点形状才算完整。下表给一个粗略的选参参考入射波长介质折射率建议偶极子间距d核半径最小格点数400 nm1.020 nm3600 nm1.520 nm4800 nm1.3330 nm41064 nm1.050 nm5需要特别提醒的是多核壳结构的包围盒往往比单球大很多因为核要分散排布。如果你按照这个间距去算总格点数可能会发现偶极子数量轻松超过几十万甚至上百万。DDSCAT计算内存和时间会随之急剧上升。比较好的做法是先按你要计算的波长反推一个合理的d再估计包围盒需要的格点数如果数量级太大要么扩大d但要保证分辨率要么缩小模型尺寸要么换更高性能的机器。3. Matlab实现多核壳球核心判断逻辑与完整脚本其实整个建模代码的核心就是“逐点判断”。理解了这个逻辑你完全可以自己改成任意结构。3.1 思路拆解从包围盒到核壳归属先在包围盒内生成一个三维整数格点网格。比如Nx、Ny、Nz分别代表三个方向的格点个数那么所有格点的整数坐标就可以用ndgrid生成。DDSCAT读shape文件时关注的是整数坐标的相对位置所以这里坐标直接用整数即可。为了把粒子放在包围盒中心我习惯把所有坐标平移到以盒子中心为原点。接着逐点判断判断区域条件材料编号核内到任一核心距离 ≤ 核半径coreMat壳层在壳内且不在任何核内shellMat外部真空其余格点不输出注意核的判断要优先于壳的判断。也就是说即使某个点既落在壳层区域又落进核内它也要被赋予核材料。这就是常见的“核优先”规则写代码时先标壳再用核的标记覆盖壳的标记逻辑最清晰。3.2 完整代码多核壳球生成脚本下面这段代码直接在Matlab里运行即可输出文件是shape_sphere_mcs.dat。文件名无所谓关键是内容和后续DDSCAT设置保持一致。% multi_core_shell_sphere.m % 生成 DDSCAT 可读取的多核壳球 shape 数据 % 说明输出文件每行为 [ix iy iz icomp] clear; clc; %% 1. 参数定义 Nx 51; Ny 51; Nz 51; % 包围盒格点数奇数方便居中 Rshell 20.0; % 外壳外半径格点单位 RcoreOuter 14.0; % 壳层内边界半径壳厚 6 nuc 3; % 核的数量 coreR [4.0, 4.0, 4.0]; % 每个核的半径 coreC {[-8, 0, 0], [8, 0, 0], [0, 0, 8]}; % 每个核的球心坐标 shellMat 2; % 壳材料编号 coreMat 3; % 核材料编号 %% 2. 生成包围盒内所有整数格点 [X, Y, Z] ndgrid(0:Nx-1, 0:Ny-1, 0:Nz-1); X X(:); Y Y(:); Z Z(:); % 平移到盒子中心方便几何判断 xc (Nx-1)/2; yc (Ny-1)/2; zc (Nz-1)/2; xp X - xc; yp Y - yc; zp Z - zc; %% 3. 核必须在外壳内的预检查 for k 1:nuc dist norm(coreC{k}); if dist coreR(k) Rshell warning(第 %d 个核超出了外壳范围请检查参数, k); end end %% 4. 球壳归属判断 r sqrt(xp.^2 yp.^2 zp.^2); % 先用0初始化0代表“不输出” icomp zeros(size(r)); % 标定壳层区域 inShell (r Rshell) (r RcoreOuter); icomp(inShell) shellMat; % 逐个核判断核内点覆盖为核材料 for k 1:nuc c coreC{k}; rk sqrt((xp - c(1)).^2 (yp - c(2)).^2 (zp - c(3)).^2); inCore (rk coreR(k)); icomp(inCore) coreMat; end %% 5. 提取有效偶极子并输出 idx find(icomp 0); N length(idx); % 输出坐标恢复成从1开始的正整数方便和DDSCAT的网格约定对齐 out [X(idx)1, Y(idx)1, Z(idx)1, icomp(idx)]; fid fopen(shape_sphere_mcs.dat, w); % 第一行写一个注释行第二行写偶极子总数 fprintf(fid, multi-core-shell sphere, Nx%d Ny%d Nz%d\n, Nx, Ny, Nz); fprintf(fid, %d\n, N); for i 1:N fprintf(fid, %d %d %d %d\n, out(i,1), out(i,2), out(i,3), out(i,4)); end fclose(fid); fprintf(完成共生成 %d 个偶极子\n, N);说一下几个关键点第一RcoreOuter的存在是为了让壳层有厚度。如果你的模型是“多个核直接包在一层薄壳里”那么壳层的内边界其实就是核的外边界再往内一点这个值可以按物理需要调整甚至可以把壳内边界设成比核的最大外延小让核嵌入壳中。不过为了代码清晰我建议壳内边界和核的位置解耦通过几何检查保证核不超出外壳。第二坐标从0:Nx-1生成输出时1变成1:Nx这和DDSCAT很多示例里坐标从1开始的习惯一致。实际DDSCAT对坐标的正负没有硬性要求关键是相对距离但统一成正整数可以少踩一些“坐标从0还是1开始”的坑。第三inShell (r Rshell) (r RcoreOuter)里用的是严格大于RcoreOuter。边界上的点判给哪一侧影响不大只要不重复就行。3.3 三核壳球的运行结果验证用上面参数跑一遍程序会输出类似这样的统计信息完成共生成 65412 个偶极子这时候别急着拿去算先在Matlab里用scatter3可视化检查一下结构是否合理figure; hold on; % 壳层偶极子 shellIdx out(:,4) shellMat; scatter3(out(shellIdx,1), out(shellIdx,2), out(shellIdx,3), 1, b, .); % 核偶极子 coreIdx out(:,4) coreMat; scatter3(out(coreIdx,1), out(coreIdx,2), out(coreIdx,3), 3, r, filled); axis equal;从图上应该能看到一个蓝色球壳里嵌着三个红色核心球。如果红点跑到蓝色区域外面说明核位置或者半径参数设置出了问题改参数重新生成。这一步可视化检查非常重要花费两分钟能避免后续在DDSCAT算完才发现模型错了。4. 圆柱变体从球坐标系切换到柱坐标系的注意点多核壳圆柱写起来和球形差不多核心区别就是把“到球心的距离”换成“到圆柱轴线的距离”。我默认圆柱轴向为z那么柱坐标半径就是rho sqrt(xp.^2 yp.^2)轴向范围用abs(zp) Lcyl/2控制。4.1 圆柱壳判断条件与核分布方式外壳是圆柱壳时判断条件有两个一是径向距离要在RcylCore rho Rcyl二是轴向z要在[-Lcyl/2, Lcyl/2]内。落在圆柱内部空腔里的点继续去判断它是否属于某个核心球。核怎么分布这里有两种常见做法。第一种是核仍然用球体多个球核沿轴向排布在圆柱内适合模拟“柱状容器里装了多个催化剂颗粒”的结构。第二种是核本身也用圆柱体多个同轴或错位的圆柱核嵌在外壳里适合模拟多层柱状波导。我的代码里默认是第一种因为球的判断逻辑通用、参数直观如果你需要圆柱核把核判断部分改成柱坐标条件即可。4.2 完整代码多核壳圆柱生成脚本% multi_core_shell_cylinder.m % 生成 DDSCAT 可读取的多核壳圆柱 shape 数据 % 默认圆柱轴向为 z 轴 clear; clc; %% 1. 参数定义 Nx 61; Ny 61; Nz 101; % 包围盒格点数 Rcyl 15.0; % 圆柱外半径 RcylInner 10.0; % 圆柱壳内半径壳厚 5 Lcyl 60.0; % 圆柱总长度轴向 nuc 2; % 核的数量球核 coreR [4.0, 4.0]; coreC {[0, 0, -15], [0, 0, 15]}; shellMat 2; coreMat 3; %% 2. 生成包围盒格点 [X, Y, Z] ndgrid(0:Nx-1, 0:Ny-1, 0:Nz-1); X X(:); Y Y(:); Z Z(:); xc (Nx-1)/2; yc (Ny-1)/2; zc (Nz-1)/2; xp X - xc; yp Y - yc; zp Z - zc; %% 3. 核位置预检查同样要求核不超出圆柱外壳 for k 1:nuc c coreC{k}; axialDist abs(c(3)); radialPos sqrt(c(1)^2 c(2)^2); if radialPos coreR(k) Rcyl || axialDist coreR(k) Lcyl/2 warning(第 %d 个核超出了圆柱外壳范围请检查参数, k); end end %% 4. 圆柱壳归属判断 rho sqrt(xp.^2 yp.^2); % 初始化 icomp zeros(size(rho)); % 圆柱壳区域径向在壳层内轴向在圆柱范围内 inShellCyl (rho Rcyl) (rho RcylInner) (abs(zp) Lcyl/2); icomp(inShellCyl) shellMat; % 核区域球核 for k 1:nuc c coreC{k}; rk sqrt((xp - c(1)).^2 (yp - c(2)).^2 (zp - c(3)).^2); icomp(rk coreR(k)) coreMat; end %% 5. 输出 idx find(icomp 0); N length(idx); out [X(idx)1, Y(idx)1, Z(idx)1, icomp(idx)]; fid fopen(shape_cyl_mcs.dat, w); fprintf(fid, multi-core-shell cylinder\n); fprintf(fid, %d\n, N); for i 1:N fprintf(fid, %d %d %d %d\n, out(i,1), out(i,2), out(i,3), out(i,4)); end fclose(fid); fprintf(完成共生成 %d 个偶极子\n, N);这版代码的包围盒长度取得比较长因为圆柱轴向跨度大偶极子数量可能会显著增多。我建议先跑一遍看N的数量级如果太大优先缩小Nz方向的范围把包围盒贴合模型尺寸避免把大量空白格点也纳入统计。当然DDSCAT计算时空白点本身不占内存但生成阶段遍历所有格点的耗时还是会随包围盒体积增加。4.3 圆柱壳判断中容易忽略的轴向边界圆柱壳和球壳最大的不同在于球壳只有一个径向自由度而圆柱壳有两个独立方向径向和轴向。判断时不能只写rho Rcyl必须同时加上轴向范围限制否则你会得到一个无限长的圆柱壳。反过来轴向范围也不能单独判断否则会把圆柱上下两个圆面之外的点也算进去。两个条件缺一不可。另外圆柱上下两个端面默认是平的。如果你需要半球封头或者圆顶可以在轴向边界处再嵌一个半球壳判断类似球壳代码里的r Rshell相当于在两端各接半个球壳。这个变体在模拟柱状纳米反应器时很常用可以自己扩展。5. shape.dat格式衔接把Matlab数组变成DDSCAT认得的文件模型生成只是第一步接下来要确保DDSCAT能正确读取。很多新手在这里卡住报错后以为是程序问题其实只是文件格式和设置没对齐。5.1 DDSCAT读取外部shape文件的基本格式DDSCAT支持从外部文件读入目标形状通常文件名是shape.dat但具体名称可以在运行参数里指定。文件结构可以按下面这种兼容性好的写法组织multi-core-shell sphere generated by Matlab - 第一行注释可任意写 65412 - 第二行偶极子总数N 1 1 1 3 - 之后每行ix iy iz icomp 1 1 2 3 ......第一行是注释行这一做法在不同版本里略有差异有的版本会忽略有的版本要求必须有。我的建议是保留注释行如果你的DDSCAT版本读取时报错“读文件错误”或“N异常”优先删掉第一行再试一次。每行的四个整数依次是ix偶极子在x方向的格点序号iyy方向格点序号izz方向格点序号icomp材料组份编号只要这四个整数正确DDSCAT就能在目标空间中构造出完整的偶极子阵列。需要注意的是偶极子间距d和初始坐标原点的物理位置并不在这个文件里设置它们是在ddscat.par里配置的。Matlab这边只需要关心整数格点坐标的相对关系。5.2 ddscat.par里如何把shape文件和材料对应起来在ddscat.par里通常有一个指定目标形状的字段你需要把它设置成“从外部文件读取”模式并告诉程序shape文件路径。接下来是介电常数输入区这一块要按你shape文件里的材料编号顺序逐一填写。举一个最简单的例子如果你的shape文件里有2和3两种材料编号2代表壳、3代表核那么介电常数部分就要保证第二种材料是壳材料的介电常数第三种材料是核材料的介电常数。第一种材料通常是背景介质比如空气或水。如果顺序填反了最直观的结果就是散射谱峰位置完全对不上甚至出现“核和壳互换”的结构这种错误在可视化阶段很难发现因为几何形状一样只是材料反了。所以我的习惯是在Matlab脚本头部就把shellMat和coreMat写清楚然后生成完shape文件后立刻在ddscat.par里按同样编号填写材料。两边用同一套编号体系不要想着“反正壳是主要材料就默认填2”一定要回头核对。5.3 有效半径aeff和偶极子间距d的换算DDSCAT计算截面时经常要用到目标有效半径aeff物理上定义为一个等体积球的半径。对于离散偶极子模型可以用下面这个关系换算[ a_{\rm eff} \left( \frac{3 N}{4 \pi} \right)^{1/3} \cdot d ]其中N是shape文件里的偶极子总数d是偶极子间距。也就是说当你在Matlab里生成了N个偶极子又在ddscat.par里设置了daeff其实就已经确定了。反过来如果你想按某个固定aeff建模可以反推需要的N值。我在实际使用中一般分两步走先根据物理尺寸确定d再运行Matlab脚本得到N最后在par文件里填d。举个例子入射波长800 nm壳材料折射率1.5我取d30 nm生成的核壳球N65412那么aeff约等于[ a_{\rm eff} \left( \frac{3 \times 65412}{4\pi} \right)^{1/3} \times 30 \text{ nm} \approx 207 \text{ nm} ]这样算出来的aeff可以直接用于归一化散射截面。如果你在文档或公式里看到aeff相关的参数指的就是这个。6. 导入前的仿真级自检可视化、数量统计和那些我踩过的坑模型文件生成、格式看起来也对是不是就可以直接提交计算了我建议再花几分钟做几个自检步骤。这些都是我被实际报错逼出来的习惯。6.1 可视化检查与偶极子数量合理性第一个自检是可视化。用Matlab的scatter3把shape文件里的点全部画出来分别用不同颜色标记核和壳然后旋转视角看几个方向。重点检查三件事核是否完全在壳内部有没有“穿模”外壳形状是否完整有没有因为包围盒太小导致边缘被截断核的数量和相对位置是否符合预期第二个自检是偶极子数量。如果你的N值比同尺寸单球模型的N大出很多倍多半是包围盒开太大了。比如一个半径20格点的球体积大约是33510个格点如果生成的N有十几万那可能是把大量空白区域也算进去了。偶极子数量直接影响运行内存和时间尽量让包围盒贴合模型表面不要留太多空白边距。6.2 常见错误对照表我在调试多核壳结构时整理了一张问题对照表基本覆盖了最容易踩的坑现象可能原因解决办法生成的核位置偏移明显核心坐标和包围盒中心没有对齐检查coreC里坐标是否以粒子中心为参考核戳出外壳表面核半径或核心距离设置不合理增加预检查打印警告shape文件读入DDSCAT后报错文件头格式不兼容当前版本尝试删掉第一行注释计算完成后散射谱明显不对材料编号和par文件介电常数顺序不一致核对icomp和par文件里材料顺序偶极子数量比预期多得多包围盒体积过大缩小Nx/Ny/Nz范围圆柱壳上下端面缺失轴向判断条件写错或Lcyl设置过小检查abs(zp) Lcyl/2和Lcyl值这里面最坑的是“材料顺序反了”。因为几何图形看起来完全正常你甚至会在可视化阶段觉得模型没问题最后散射结果却离谱。所以我建议生成完shape文件后直接打开文件看前几行如果壳材料编号是2、核材料编号是3那么par文件里第二种材料就必须是壳材料、第三种材料必须是核材料。这个习惯养成后至少能避开一半的无效计算。6.3 两个实用扩展多层同心壳和随机核位置如果你研究的不是“多核单壳”而是“同心多层壳”比如二氧化硅包金核再包一层二氧化硅那代码只需要小改一下。把单个外壳判断改成循环遍历多个壳层半径每一层赋予不同的材料编号即可。本质上还是一个径向距离判断只不过从if-else变成了for循环shellRadii [15, 10, 6]; % 从外到内各层边界 shellMatList [4, 2, 4]; % 每层材料编号 for j 1:length(shellRadii)-1 r sqrt(xp.^2 yp.^2 zp.^2); layerMask (r shellRadii(j)) (r shellRadii(j1)); icomp(layerMask) shellMatList(j); end如果你需要模拟随机分布的多核结构比如核位置在一定范围内随机抖动可以在Matlab里用rand或randn生成核心坐标然后加一个“最小核间距”约束避免核之间重叠或太近导致离散后无法分辨。这个变体在生物医学光热计算里很常用因为实验上核的位置往往不完全规则。最后再分享一个小技巧跑DDSCAT之前可以在Matlab里统计一下每个材料编号的偶极子数量估算一下核体积占比。核壳结构的光学响应强烈依赖核与壳的体积比这个比值如果跟实验TEM估计的对不上说明几何建模可能出现了系统性偏差。回头检查参数比全部算完再返工要划算得多。
返回列表