docs: function decision2DStumpTrain(data, labels, ix, options) {
      
      options = options || {};
      var numtries = options.numTries || 10;
      
      // choose a dimension at random and pick a best split
      var N = ix.length;
      
      var ri1= 0;
      var ri2= 1;
      if(data[0].length > 2) {
        // more than 2D data. Pick 2 random dimensions
        ri1= randi(0, data[0].length);
        ri2= randi(0, data[0].length);
        while(ri2 == ri1) ri2= randi(0, data[0].length); // must be distinct!
      }
      
      // evaluate class entropy of incoming data
      var H= entropy(labels, ix);
      var bestGain=0; 
      var bestw1, bestw2, bestthr;
      var dots= new Array(ix.length);
      for(var i=0;i<numtries;i++) {
          
          // pick random line parameters
          var alpha= randf(0, 2*Math.PI);
          var w1= Math.cos(alpha);
          var w2= Math.sin(alpha);
          
          // project data on this line and get the dot products
          for(var j=0;j<ix.length;j++) {
            dots[j]= w1*data[ix[j]][ri1] + w2*data[ix[j]][ri2];
          }
          
          // we are in a tricky situation because data dot product distribution
          // can be skewed. So we don't want to select just randomly between
          // min and max. But we also don't want to sort as that is too expensive
          // let's pick two random points and make the threshold be somewhere between them.
          // for skewed datasets, the selected points will with relatively high likelihood
          // be in the high-desnity regions, so the thresholds will make sense
          var ix1= ix[randi(0, N)];
          var ix2= ix[randi(0, N)];
          while(ix2==ix1) ix2= ix[randi(0, N)]; // enforce distinctness of ix2
          var a= Math.random();
          var dotthr= dots[ix1]*a + dots[ix2]*(1-a);
          
          // measure information gain we'd get from split with thr
          var l1=1, r1=1, lm1=1, rm1=1; //counts for Left and label 1, right and label 1, left and minus 1, right and minus 1
          for(var j=0;j<ix.length;j++) {
              if(dots[j] < dotthr) {
                if(labels[ix[j]]==1) l1++;
                else lm1++;
              } else {
                if(labels[ix[j]]==1) r1++;
                else rm1++;
              }
          }
          var t= l1+lm1; 
          l1=l1/t;
          lm1=lm1/t;
          t= r1+rm1;
          r1=r1/t;
          rm1= rm1/t;
          
          var LH= -l1*Math.log(l1) -lm1*Math.log(lm1); // left and right entropy
          var RH= -r1*Math.log(r1) -rm1*Math.log(rm1);
          
          var informationGain= H - LH - RH;
          //console.log("Considering split %f, entropy %f -> %f, %f. Gain %f", thr, H, LH, RH, informationGain);
          if(informationGain > bestGain || i === 0) {
              bestGain= informationGain;
              bestw1= w1;
              bestw2= w2;
              bestthr= dotthr;
          }
      }
      
      model= {};
      model.w1= bestw1;
      model.w2= bestw2;
      model.dotthr= bestthr;
      return model;
  }