用vue实现波谱拟合

时间:2021-1-8 作者:admin

主页面-功能介绍

小白初入职场第一篇总结,废话比较多,求轻喷~

       波谱拟合用来对某种材料或物质的谱图进行识别和分析,每种物质可以有多种成分,每种成分用component1、component2…表示,用Add another component和Remove component来控制每种成分的增加和删除,每种成分由多种原子核构成,即nuclei,用Add nucleus和Remove nucleus来控制每种成分内原子核数量,每新增一个原子核,波谱就会分裂一次,谱峰数量由(1->2->4->8…)依次分裂。另外可以通过更改默认参数,改变波谱形态,成分参数中:Relative amount表示每种成分占绘图分量的百分比,百分比之和不超过100,giso用来计算分裂的中心位置,LineWidth用来控设置谱峰到谱谷的宽度,%Lorentzian表示谱峰形态,一共两种形态,高斯和洛伦兹,两者之和为100;原子核参数:No of equivalent nuclei用来改变原子核个数,如果一种成分内包含很多个一模一样的参数时,就可以通过改变这个参数实现,Nuclear spin用来改变原子核种类,Hyperfine用来设置分裂后两峰之间的宽度。

再来一张图:

每种成分数量和参数、每种成分内每种原子核数量和参数设置好后,对数据进行处理,由三种结果,卷积、积分、二重积分,那就来看看数据的处理逻辑吧~

      从数据流角度,主要进行三步处理:数据->数据裂变->光谱计算->绘图,左边是算法实现所需的参数、右边是对数据及每个步骤的描述。

代码实现

遇到一个坑,一开始写demo的时候用的vue+Ant design of vue,在select等其他组件的使用上都是正常的,但是在input number中就很变态了,给input number绑定的change事件,用户在输入两位以上数据的时候,change事件会触发两次!!!想避免这个问题,于是用blur事件,问题又来了,因为这个页面中组件的生成和删除需要动态渲染,并且根据前面的介绍很容易知道组件的渲染是有两层结构的,那么在用户进行点击或输入操作的时候,就需要传递一个参数(用来定位是哪个component以及每个component下面对应的某一个nucle等等),能力有限( ╯□╰ )目前我没有找到解决办法,于是转elementUI框架。

     组件的动态渲染用了一个比较巧妙的办法,一开始我打算用render来写,后来从部门大神那里学到通过遍历列表进行渲染,脑子之间还是有差距的。。。

<div v-for="(Con, i) in componentList" :key="Con[i]"><strong>Component {{i+1}}.</div>

     同理原子核的动态渲染也是这么实现的:

<div v-for="(li, j) in nucleusList[i]" :key="li[j]">{{j+1}}. No of equivalent nuclei:</div>

     然后每次增加和删除只需要操作数组列表的长度即可~

     各参数的绑定:component中参数均使用一维数组,chenge事件需传递一维数组的下标,component内的nucleui均使用二维数组,change事件需传递二维数组的下标。

以上介绍参数定义,接下来是数据处理:

首先进行数据裂变:

// 首先计算光谱信号裂变数据stickspectrum (w) {  const stick = new Array(2)  stick[0] = new Array()// 光谱强度  stick[1] = new Array()// 光谱位置  stick[0][0] = 1  stick[1][0] = this.h * this.frequency / (this.r[w].g * this.mu) // 中心领域  for (var j = 0; j < this.r[w].equiv.length; j++) {    for (var i = stick[0].length - 1; i >= 0; i--) {      stick[0][i] /= Math.pow((2 * this.r[w].spin[j] + 1), this.r[w].equiv[j])      stick[1][i] -= this.r[w].equiv[j] * this.r[w].spin[j] * this.r[w].hfc[j]      for (var k = 0; k < 2 * this.r[w].equiv[j] * this.r[w].spin[j]; k++) {        stick[1].splice(i + k + 1, 0, stick[1][i] + this.r[w].hfc[j] * (k + 1))        stick[0].splice(i + k + 1, 0, 0)      }      for (var k = 0; k < this.r[w].equiv[j]; k++) {        for (var m = i + 2 * this.r[w].spin[j] * k; m >= i; m--) {          for (var ii = 0; ii < 2 * this.r[w].spin[j]; ii++) {            stick[0][m + ii + 1] += stick[0][m]          }        }      }    }  }  return stick}

// 再根据裂变后的数据计算光谱spectrum (stick) {  let xmin = Infinity; let xmax = 0  for (var k = 0; k < this.r.length; k++) {    xmin = Math.min(Math.min.apply(Math, stick[k][1]) - 10 * this.r[k].width, xmin)// Math.min.apply获取数组中的最小值    xmax = Math.max(Math.max.apply(Math, stick[k][1]) + 10 * this.r[k].width, xmax)  }  const tmp = xmax - xmin  xmax += tmp * 0.05  xmin -= tmp * 0.05  const step = (xmax - xmin) / (this.No_integers - 1)  for (let i = 0; i < this.No_integers; i++) {    this.XY[0][i][0] = xmin + step * i // 横坐标数据    this.XY[0][i][1] = 0 // 纵坐标数据    this.XYint[0][i][0] = this.XY[0][i][0]    this.XYint[0][i][1] = 0    this.XYdoubleint[0][i][0] = this.XY[0][i][0]    this.XYdoubleint[0][i][1] = 0  }  for (let k = 0; k < this.r.length; k++) { // 将所有分量相加    const sticks = new Array(this.No_integers) // 存放绘图数据的数组    for (var i = 0; i < stick[k][0].length; i++) { // 遍历光谱强度和位置的数据长度      var j = Math.round((stick[k][1][i] - xmin) / step) // 四舍五入计算 谱线峰值所在的位置(按照总点数为2048计算)      sticks[j] = sticks[j] ? sticks[j] + stick[k][0][i] : stick[k][0][i] // 谱峰位置的光谱强度,把光谱强度数据放在sticks中    }    const tmp = new Array(this.No_integers)// 第一种光谱绘图位置数据    let ind = 0    for (var i = 0; i < this.No_integers; i++) {      if (sticks[i]) { // 建立峰值索引——sticks[i]===1即峰值所在。        tmp[ind] = i        ind++      }    }    const tmpint = new Array(this.No_integers) // 用来保存每个分量的积分    const tmpdoubleint = new Array(this.No_integers) // 用来保存每个分量的二重积分    for (var i = 0; i < this.No_integers; i++) tmpint[i] = 0    tmpdoubleint[0] = 0    const rwid = Number(this.r[k].width) // 提高了性能    const rwid2 = Math.pow(rwid, 2) // rwid的2次幂    const lortmp = Number(this.r[k].percent) * Number(this.r[k].lor) / 100 * Math.sqrt(3) / Math.PI // 洛伦兹线乘积;业绩助推器    const gaustmp = Number(this.r[k].percent) * (100 - Number(this.r[k].lor)) / 100 * Math.sqrt(2 / Math.PI) // 高斯线乘法器;性能助推器    for (let i = 0; i < this.No_integers; i++) { // 对于每一个点,将每条线的强度加在一起——这需要用卷积方法代替!      for (let j = 0; j < ind; j++) { // 计算每个组件的每个峰值的贡献        const delta = this.XY[0][i][0] - this.XY[0][tmp[j]][0]        const delta2 = Math.pow(delta, 2)        if ((rwid > step && Math.abs(-0.5 * rwid - delta) < 0.5 * step) || (rwid < step && -0.5 * rwid - delta > 0 && -0.5 * rwid - delta < step)) {          this.XY[0][i][1] += sticks[tmp[j]] * (lortmp * 0.5 / rwid2 + gaustmp * 2 / Math.sqrt(Math.E) / rwid2) // Lor和Gaus线都增加一半最大值 add half-max for both Lor and Gaus lines        } else if ((rwid > step && Math.abs(0.5 * rwid - delta) < 0.5 * step) || (rwid < step && delta - 0.5 * rwid > 0 && delta - 0.5 * rwid < step)) {          // 宽度大于增量,且最小值(例如,sticks位置+ 0.5*线宽)在距离现场位置半步以内;或宽度小于字段增量,则最小值在字段位置下面的步骤内          this.XY[0][i][1] -= sticks[tmp[j]] * (lortmp * 0.5 / rwid2 + gaustmp * 2 / Math.sqrt(Math.E) / rwid2) // Lor和Gaus线都减去一半最大值 subtract half-max for both Lor and Gaus lines        } else {          this.XY[0][i][1] += sticks[tmp[j]] * (gaustmp * (-4) / rwid / rwid2 * delta * Math.exp(-2 * delta2 / rwid2) + lortmp * (-delta) * rwid / Math.pow((delta2 + 3 / 4 * rwid2), 2)) // 其他情况下的正常计算,高斯+洛伦兹        }        this.dataarray = [this.XY, this.XYint, this.XYdoubleint]        tmpint[i] += sticks[tmp[j]] * (gaustmp * Math.exp(-2 * delta2 / rwid2) / rwid + lortmp / 2 / rwid / (0.75 + delta2 / rwid2)) // 高斯+洛伦兹积分-明确计算以避免积分误差      }    }    for (let j = 1; j < this.No_integers; j++) {      tmpdoubleint[j] = tmpdoubleint[j - 1] + step * (tmpint[j] + tmpint[j - 1]) / 2    } // 二重积分    const mm = tmpdoubleint[this.No_integers - 1] / Number(this.r[k].percent) // 有多少积分高于理论(只发生在非常尖锐的线)    for (let j = 1; j < this.No_integers; j++) {      this.XYdoubleint[0][j][1] += mm > 1 ? tmpdoubleint[j] / mm : tmpdoubleint[j] // 第三种频谱数据  如果二重积分高于理论,将其标准化      this.XYint[0][j][1] += tmpint[j] // 第二种频谱数据    }  }},

计算完成的光谱,返回三种数据XY,XYint,XYdouble,然后就是绘图~

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。