Browse Source

逼近算法

chenshilong 4 years ago
parent
commit
1e1272dd0e
1 changed files with 32 additions and 17 deletions
  1. 32 17
      web/building_saas/main/js/models/calc_program.js

+ 32 - 17
web/building_saas/main/js/models/calc_program.js

@@ -2601,20 +2601,24 @@
    // 反向调价逼近
    reverseTenderApproach(callback){
      let me = this;
-     let G_DIGIT = 0.01;   // 系数调整步距(0.1最终结果误差大。0.001目标金额与逼前金额差距大时无法有效逼近)
-     let times = 100;      // 逼近轮数
 
-     let quickModel = false;  // 快速模式
-     let qmTimes = 50;        // 快速模式下的极限次数
-     let qmValue = 10;        // 快速模式下允许的误差值上限
-
-     let isTest = true;    // 测试
+     let G_DIGIT = 0.01;      // 系数调整步距(0.1最终结果误差大。0.001目标金额与逼前金额差距大时无法有效逼近)
+     let times = 200;         // 逼近计算的极限次数。正常情况下“单位系数金额”列表中的结点耗尽即退出,这里指定轮数是最后保险阀,防止无限死循环。
+     let calcModel = 1;       // 计算模式:1 精度优先(差值不接近0不停,直到结点用完或极限次数用完。时间长精度高)2 速度优先(达到指定差值范围即熔断逼近。时间短差值大)
+     let diffProp = 0.0001;   // 速度优先模式下的:差值比例
+     let isTest = true;       // 测试
 
      // 取树结点的调价系数。
      function getCoe(node){
        return (calcTools.isVP_or_GLJR(node)) ? node.data.rationQuantityCoe : node.data.quantityCoe.labour;
      }
 
+     // 按指定的比例获取可接受的差值:如目标金额的万分之一。
+     function getPropV(node){
+       let v = parseFloat((node.data.targetTotalFee * diffProp).toFixed(0));   // node.data.feesIndex.common.totalFee
+       return Math.max(v, 1);
+     }
+
      // 取根结点的:调后金额跟目标金额差值,看还有多少误差需要处理。
      function  getRootDiff() {
        let root = tender_obj.tenderTree.roots[0];
@@ -2633,7 +2637,7 @@
            if (coe!= 0) {
              let diff = Math.abs(node.data.feesIndex.common.tenderTotalFee - node.data.feesIndex.common.totalFee);
              node.data.tender_diffValuePerCoe  = (diff * G_DIGIT / coe).toDecimal(decimalObj.process);
-             node.data.tender_rowNo = i + 1;
+             node.data.tender_rowNo = i + 1;       // node在UI上显示的行号
              arr.push(node);
            }
          }
@@ -2668,8 +2672,8 @@
        return {idx: index, node: arr[index]}
      }
 
-     // 单轮逼近。arr 参考取值列表。
-     // 返回{type, node, nodeIdx}。type:1正常,2无结点,3结点过调。node:结点。nodeIdx:结点在列表中的索引位置。
+     // 逼近(单轮)。arr 参考取值列表。返回{type, node, nodeIdx}。
+     // type:1正常,2无结点,3结点过调。node:结点。nodeIdx:结点在列表中的索引位置。node.data.tender_rowNo 在UI上的行号。
      function approach(arr){
        let v = getRootDiff();
        let obj = getCloseNode(arr, Math.abs(v));    // {idx, node}  极端:{0, undefind}
@@ -2716,20 +2720,31 @@
        if (isTest) console.log(`       [索引${obj.nodeIdx} 行${obj.node.data.tender_rowNo}]过调,已回退`);
      }
 
+     let root = tender_obj.tenderTree.roots[0];
      let vArr = getNodeDiffs();
+     let propV = getPropV(root);
      let d1 = getRootDiff();
-     let root = tender_obj.tenderTree.roots[0];
 
-     if (isTest) console.log(`调前${root.data.feesIndex.common.totalFee}|目标${root.data.targetTotalFee}|反向调价后不逼近的调后金额${root.data.feesIndex.common.tenderTotalFee}|差值${d1}`);
+     if (isTest){
+       let tq = root.data.feesIndex.common.totalFee;
+       let mb = root.data.targetTotalFee;
+       let bbj = root.data.feesIndex.common.tenderTotalFee;
+       let ms = (calcModel == 2) ? `速度优先(0~${propV})` : "精度优先";
+       console.log(`调前${tq}|目标${mb}|调后无逼近${bbj}|差值${d1}|${ms}`);
+     }
 
      // 多轮逼进
      for (let i = 1; i <= times; i++) {
-       if (Math.abs(d1) < 1) break;              // 误差在1以内,结果与目标金额完全一致,完美。
-       if (quickModel){                          // 快速模式:在指定的计算轮数内,能达到指定的差值范围内,也很理想。这个条件用于减少计算轮数,提高效率。
-         if ((Math.abs(d1) < qmValue) && (times > qmTimes)) break;
+       // 与目标金额差值在1以内,整数部分已相同,结果很棒。很多时候能达到完美的差值0,但不能用0判断,因为金额取小数时有问题。
+       if (Math.abs(d1) < 1) break;
+
+       // 速度优先模式下,结果到达差值范围内,即熔断逼近。
+       if (calcModel == 2){
+         if (Math.abs(d1) < propV) break;
        };
+
        let obj = approach(vArr);
-       if (obj.type == 2) break;        // 列表已清空,无结点可调。
+       if (obj.type == 2) break;        // 结点耗完,列表已清空,无结点可调。
        if (obj.type == 3) {             // 结点过调,该结点任务已完成,不再参与调节,从列表中清除
          vArr.splice(obj.nodeIdx, 1);
          continue;
@@ -2807,7 +2822,7 @@
        this.reverseTenderInitDatas();
        this.prepareForDistribute(tender_obj.tenderTree.roots[0]);
        this.distributeTargetTotalFee(tender_obj.tenderTree.roots[0]);
-       this.calcAllNodes(calcAllType.catAll, tender);    // 先来个反算,得到基本的调后值(此值误差大,需要逼近)
+       this.calcAllNodes(calcAllType.catAll, tender);    // 先来个反算(计算coe),得到基本的调后值(此值误差大,需要逼近)
        this.reverseTenderApproach(callback);   // 逼近上述调后值
      }
      else{