docs: function (D, perplexity, tol) {
  		var Nf = Math.sqrt(D.length); // this better be an integer
  		var N = Math.floor(Nf);
  		assert(N === Nf, "D should have square number of elements.");
  		var Htarget = Math.log(perplexity); // target entropy of distribution
  		var P = zeros(N * N); // temporary probability matrix
  
  		var prow = zeros(N); // a temporary storage compartment
  		for (var i = 0; i < N; i++) {
  			var betamin = -Infinity;
  			var betamax = Infinity;
  			var beta = 1; // initial value of precision
  			var done = false;
  			var maxtries = 50;
  
  			// perform binary search to find a suitable precision beta
  			// so that the entropy of the distribution is appropriate
  			var num = 0;
  			while (!done) {
  				//debugger;
  
  				// compute entropy and kernel row with beta precision
  				var psum = 0.0;
  				for (var j = 0; j < N; j++) {
  					var pj = Math.exp(-D[i * N + j] * beta);
  					if (i === j) {
  						pj = 0;
  					} // we dont care about diagonals
  					prow[j] = pj;
  					psum += pj;
  				}
  				// normalize p and compute entropy
  				var Hhere = 0.0;
  				for (var j = 0; j < N; j++) {
  					var pj = prow[j] / psum;
  					prow[j] = pj;
  					if (pj > 1e-7) Hhere -= pj * Math.log(pj);
  				}
  
  				// adjust beta based on result
  				if (Hhere > Htarget) {
  					// entropy was too high (distribution too diffuse)
  					// so we need to increase the precision for more peaky distribution
  					betamin = beta; // move up the bounds
  					if (betamax === Infinity) {
  						beta = beta * 2;
  					}
  					else {
  						beta = (beta + betamax) / 2;
  					}
  
  				} else {
  					// converse case. make distrubtion less peaky
  					betamax = beta;
  					if (betamin === -Infinity) {
  						beta = beta / 2;
  					}
  					else {
  						beta = (beta + betamin) / 2;
  					}
  				}
  
  				// stopping conditions: too many tries or got a good precision
  				num++;
  				if (Math.abs(Hhere - Htarget) < tol) {
  					done = true;
  				}
  				if (num >= maxtries) {
  					done = true;
  				}
  			}
  
  			// console.log('data point ' + i + ' gets precision ' + beta + ' after ' + num + ' binary search steps.');
  			// copy over the final prow to P at row i
  			for (var j = 0; j < N; j++) {
  				P[i * N + j] = prow[j];
  			}
  
  		} // end loop over examples i
  
  		// symmetrize P and normalize it to sum to 1 over all ij
  		var Pout = zeros(N * N);
  		var N2 = N * 2;
  		for (var i = 0; i < N; i++) {
  			for (var j = 0; j < N; j++) {
  				Pout[i * N + j] = Math.max((P[i * N + j] + P[j * N + i]) / N2, 1e-100);
  			}
  		}
  
  		return Pout;
  	}