text:
var _ = require('lodash').runInContext();

// the module: lodash extended with math mixins
var lomath = _.mixin({
  AUTHOR: "kengz",
  VERSION: "0.2.9",

  //////////////////////////////
  // Function builder backend //
  //////////////////////////////

  // We employ clearer terminologies to distinguish the "depth" or the "dimension" of the objects. In general, we call generic array of depth-N a "N-tensor" or "rank-N tensor". A scalar is "0-tensor"; a simple array/vector is "1-tensor", matrix (array of arrays) is "2-tensor", and so on.
  // A generic function that operates over tensor is built from an atomic function fn taking two scalar arguments.
  // Applying a function into depths of tensor is done via distribution, and evaluating a multi-argument function is done via associativity.
  // sample operation to demonstrate function composition
  op: function(x, y) {
    return x + '*' + y;
  },
  // distribute a unary function over every scalar in tensor Y;
  distributeSingle: function(fn, Y) {
    if (!(Y instanceof Array)) return fn(Y);
    var len = Y.length,
      res = Array(len);
    while (len--) res[len] = Y[len] instanceof Array ?
      lomath.distributeSingle(fn, Y[len]) : fn(Y[len])
    return res;
  },
  // Distribute fn with left tensor X over right scalar y.
  distributeLeft: function(fn, X, y) {
    var len = X.length,
      res = Array(len);
    while (len--) res[len] = X[len] instanceof Array ?
      lomath.distributeLeft(fn, X[len], y) : fn(X[len], y)
    return res;
  },
  // Distribute fn with left scalar x over right tensor Y.
  distributeRight: function(fn, x, Y) {
    var len = Y.length,
      res = Array(len);
    while (len--) res[len] = Y[len] instanceof Array ?
      lomath.distributeRight(fn, x, Y[len]) : fn(x, Y[len])
    return res;
  },
  // Distribute fn between non-scalar tensors X, Y: pair them up term-wise and calling distribute recursively.
  // If at any depth X and Y have different lengths, recycle if the mod of lengths is 0.
  distributeBoth: function(fn, X, Y) {
    var Xlen = X.length,
      Ylen = Y.length;
    if (Xlen % Ylen == 0 || Ylen % Xlen == 0) {
      var res;
      if (Xlen > Ylen) {
        res = Array(Xlen);
        while (Xlen--) res[Xlen] = lomath.distribute(fn, X[Xlen], Y[Xlen % Ylen]);
      } else {
        res = Array(Ylen);
        while (Ylen--) res[Ylen] = lomath.distribute(fn, X[Ylen % Xlen], Y[Ylen]);
      }
      return res;
    } else throw "Cannot distribute arrays of different dimensions.";
  },
  // Generic Distribute: Distribute fn between left tensor X and right tensor Y, while preserving the argument-ordering (vital for non-commutative functions).
  // lomath pairs up the tensors term-wise while descending down the depths recursively, until finding a scalar to distributeLeft/Right.
  // Method is at its fastest, and assuming the data depth isn't too deep (otherwise JS will have troubles with it)
  distribute: function(fn, X, Y) {
    if (X instanceof Array)
      return Y instanceof Array ?
        lomath.distributeBoth(fn, X, Y) : lomath.distributeLeft(fn, X, Y);
    else
      return Y instanceof Array ?
        lomath.distributeRight(fn, X, Y) : fn(X, Y);
  },
  // Generic associate: take the arguments object or array and apply atomic fn (non-tensor) from left to right
  asso: function(fn, argObj) {
    var len = argObj.length,
      i = 0;
    // optimize arg form based on length or argObj
    var args = len < 3 ? argObj : _.toArray(argObj),
      res = fn(args[i++], args[i++]);
    while (i < len) res = fn(res, args[i++]);
    return res;
  },
  // Associate with distributivity: Similar to asso but is for tensor functions; apply atomic fn distributively from left to right.
  // Usage: for applying fn on tensors element-wise if they have matching dimensions.
  assodist: function(fn, argObj) {
    var len = argObj.length,
      i = 0;
    // optimize arg form based on length or argObj
    var args = len < 3 ? argObj : _.toArray(argObj),
      res = lomath.distribute(fn, args[i++], args[i++]);
    while (i < len) res = lomath.distribute(fn, res, args[i++]);
    return res;
  },

  // Future:
  // Future:
  // Future:
  // cross and wedge, need index summation too, matrix mult.

  /////////////////////
  // Basic functions //
  /////////////////////
  // Concat all arguments into single vector by _.flattenDeep
  c: function() {
    return _.flattenDeep(_.toArray(arguments));
  },
  // atomic sum: takes in a tensor (any rank) and sum all values
  a_sum: function(T) {
    // actual function call; recurse if need to
    var total = 0,
      len = T.length;
    while (len--) total += (T[len] instanceof Array ?
      lomath.a_sum(T[len], 0) : T[len])
    return total;
  },
  // sum all values in all arguments
  sum: function() {
    var res = 0;
    var len = arguments.length;
    while (len--) res += (arguments[len] instanceof Array ?
      lomath.a_sum(arguments[len]) : arguments[len])
    return res;
  },
  fsum: function(T, fn) {
    var sum = 0;
    for (var i = 0; i < T.length; i++)
      sum += fn(T, i);
    return sum;
  },
  // atomic prod, analogue to a_sum. Multiply all values in a tensor
  a_prod: function(T) {
    // actual function call; recurse if need to
    var total = 1,
      len = T.length;
    while (len--) total *= (T[len] instanceof Array ?
      lomath.a_prod(T[len], 1) : T[len])
    return total;
  },
  // product of all values in all arguments
  prod: function() {
    var res = 1,
      len = arguments.length;
    while (len--) res *= (arguments[len] instanceof Array ?
      lomath.a_prod(arguments[len]) : arguments[len])
    return res;
  },
  // atomic add: add two scalars x, y.
  a_add: function(x, y) {
    return x + y;
  },
  // add all tensor arguments element-wise/distributively and associatively
  add: function() {
    // sample call pattern: pass whole args
    return lomath.assodist(lomath.a_add, arguments);
  },
  // atomic subtract
  a_subtract: function(x, y) {
    return x - y;
  },
  // subtract all tensor arguments element-wise/distributively and associatively
  subtract: function() {
    return lomath.assodist(lomath.a_subtract, arguments);
  },
  // atomic multiply
  a_multiply: function(x, y) {
    return x * y;
  },
  // multiply all tensor arguments element-wise/distributively and associatively
  // Note: lomath is generic; is different from matrix multiplication
  multiply: function() {
    return lomath.assodist(lomath.a_multiply, arguments);
  },
  // atomic divide
  a_divide: function(x, y) {
    return x / y;
  },
  // divide all tensor arguments element-wise/distributively and associatively
  divide: function() {
    return lomath.assodist(lomath.a_divide, arguments);
  },
  // atomic log. Use base e by default
  a_log: function(x, base) {
    return base == undefined ? Math.log(x) : Math.log(x) / Math.log(base);
  },
  // take the log of tensor T to the n element-wise
  log: function(T, base) {
    return lomath.distribute(lomath.a_log, T, base);
  },
  // atomic square
  a_square: function(x) {
    return x * x;
  },
  square: function(T) {
    return lomath.distributeSingle(lomath.a_square, T);
  },
  // atomic root
  a_root: function(x, base) {
    var n = base == undefined ? 2 : base;
    return n % 2 ?
      // if odd power
      Math.sign(x) * Math.pow(Math.abs(x), 1 / n) :
      Math.pow(x, 1 / n);
  },
  // take the n-th root of tensor T element-wise
  root: function(T, n) {
    return lomath.distribute(lomath.a_root, T, n);
  },
  // atomic logistic
  a_logistic: function(z) {
    return 1 / (1 + Math.exp(-z))
  },
  logistic: function(T) {
    return lomath.distributeSingle(lomath.a_logistic, T);
  },

  ////////////////////
  // Basic checkers //
  ////////////////////
  // check if x is in range set by left, right
  inRange: function(left, right, x) {
    return left - 1 < x && x < right + 1;
  },
  // check if x is an integer
  isInteger: function(x) {
    return x == Math.floor(x);
  },
  // check if x is a double
  isDouble: function(x) {
    return x != Math.floor(x);
  },
  // check if x is positive
  isPositive: function(x) {
    return x > 0;
  },
  // check if x less than or eq to 0
  nonPositive: function(x) {
    return !(x > 0);
  },
  // check if x is negative
  isNegative: function(x) {
    return x < 0;
  },
  // check if x greater than or eq to 0
  nonNegative: function(x) {
    return !(x < 0);
  },
  isZero: function(x) {
    return x == 0;
  },
  nonZero: function(x) {
    return x != 0;
  },
  parity: function(x) {
    return x % 2 ? -1 : 1;
  },
  // check if all tensor entries are of the same sign, with the specified sign function
  sameSig: function(T, sigFn) {
    return Boolean(lomath.prod(lomath.distributeSingle(sigFn, T)));
  },
  // check if all tensor entries are of the same sign, with the specified sign function
  deepEqual: function(T, S) {
    if (T.length != S.length) return false;
    var Left = T,
      Right = S;
    if (!lomath.isFlat(T)) {
      Left = _.flattenDeep(T);
      Right = _.flattenDeep(S);
    };
    var Llen = Left.length,
      Rlen = Right.length;
    while (Llen--) {
      if (Left[Llen] != Right[Llen]) return false;
    }
    return true;
  },

  //////////////////////////////////////////
  // Unary functions from JS Math object, //
  //////////////////////////////////////////
  // wrapped to function with generic tensor
  abs: function(T) {
    return lomath.distributeSingle(Math.abs, T);
  },
  acos: function(T) {
    return lomath.distributeSingle(Math.acos, T);
  },
  acosh: function(T) {
    return lomath.distributeSingle(Math.acosh, T);
  },
  asin: function(T) {
    return lomath.distributeSingle(Math.asin, T);
  },
  asinh: function(T) {
    return lomath.distributeSingle(Math.asinh, T);
  },
  atan: function(T) {
    return lomath.distributeSingle(Math.atan, T);
  },
  atanh: function(T) {
    return lomath.distributeSingle(Math.atanh, T);
  },
  ceil: function(T) {
    return lomath.distributeSingle(Math.ceil, T);
  },
  cos: function(T) {
    return lomath.distributeSingle(Math.cos, T);
  },
  cosh: function(T) {
    return lomath.distributeSingle(Math.cosh, T);
  },
  exp: function(T) {
    return lomath.distributeSingle(Math.exp, T);
  },
  floor: function(T) {
    return lomath.distributeSingle(Math.floor, T);
  },
  log10: function(T) {
    return lomath.distributeSingle(Math.log10, T);
  },
  log1p: function(T) {
    return lomath.distributeSingle(Math.log1p, T);
  },
  log2: function(T) {
    return lomath.distributeSingle(Math.log2, T);
  },
  round: function(T) {
    return lomath.distributeSingle(Math.round, T);
  },
  pow: function(T, n) {
    return lomath.distribute(Math.pow, T, n);
  },
  sign: function(T) {
    return lomath.distributeSingle(Math.sign, T);
  },
  sin: function(T) {
    return lomath.distributeSingle(Math.sin, T);
  },
  sinh: function(T) {
    return lomath.distributeSingle(Math.sinh, T);
  },
  sqrt: function(T) {
    return lomath.distributeSingle(Math.sqrt, T);
  },
  tan: function(T) {
    return lomath.distributeSingle(Math.tan, T);
  },
  tanh: function(T) {
    return lomath.distributeSingle(Math.tanh, T);
  },
  trunc: function(T) {
    return lomath.distributeSingle(Math.trunc, T);
  },

  /////////////////////
  // Regex functions //
  /////////////////////
  // return a function that matches regex,
  // e.g. matchRegex(/red/)('red Apple') returns true
  reMatch: function(regex) {
    return function(str) {
      if (str != undefined)
        return str.search(regex) != -1;
    }
  },
  // negation of reMatch
  reNotMatch: function(regex) {
    return function(str) {
      if (str != undefined)
        return str.search(regex) == -1;
    }
  },
  // return the string matched by regex
  reGet: function(regex) {
    return function(str) {
      if (str != undefined) {
        var matched = str.match(regex);
        return matched == null ? null : matched[0];
      }
    }
  },
  // wrap a regex into string for regex set operation
  reWrap: function(reg) {
    return '(?:' + String(reg).replace(/\//g, '') + ')'
  },
  // return a single regex as the "AND" of all arg regex's
  reAnd: function() {
    return new RegExp(_.map(_.toArray(arguments), lomath.reWrap).join(''));
  },
  // return a function that matches all(AND) of the regexs
  reAndMatch: function() {
    return lomath.reMatch(lomath.reAnd.apply(null, arguments));
  },
  // return a single regex as the "OR" of all arg regex's
  reOr: function() {
    return new RegExp(_.map(_.toArray(arguments), lomath.reWrap).join('|'));
  },
  // return a function that matches at least one(OR) of the regexs
  reOrMatch: function() {
    return lomath.reMatch(lomath.reOr.apply(null, arguments));
  },
  reString: function(regexp) {
    return regexp.toString().replace(/^\/|\/.*$/g, '')
  },

  ////////////////////
  // Array creation //
  ////////////////////

  // union, intersection, difference, xor
  // seq from R: like _.range, but starts with 1 by default
  seq: function(start, stop, step) {
    if (stop == null) {
      stop = start || 1;
      start = 1;
    }
    step = step || 1;
    var length = Math.max(Math.ceil((stop - start) / step), 0) + 1;
    var range = Array(length);
    for (var idx = 0; idx < length; idx++, start += step) {
      range[idx] = start;
    }
    return range;
  },
  // return an array of length N initialized to val (default to 0)
  numeric: function(N, val) {
    return val == undefined ? _.fill(Array(N), 0) : _.fill(Array(N), val);
  },

  ///////////////////////
  // Tensor properties //
  ///////////////////////

  // Note that a tensor has homogenous depth, that is, there cannot tensors of different ranks in the same vector, e.g. [1, [2,3], 4] is prohibited.
  // return the depth (rank) of tensor, assuming homogeneity
  depth: function(T) {
    var t = T,
      d = 0;
    while (t.length) {
      d++;
      t = t[0];
    }
    return d;
  },
  // return the size of a tensor (total number of scalar entries)
  // return 0 for scalar
  volume: function(T) {
    return _.flattenDeep(T).length;
  },
     [[1,1,1,1],[2,2,2,2],[3,3,3,3]],
     [[4,4,4,4],[5,5,5,5],[6,6,6,6]]
   ])
  // Get the dimension of a (non-scalar) tensor by _.flattenDeep, assume rectangular
  dim: function(T) {
    var dim = [],
      ptr = T;
    while (ptr.length) {
      dim.push(ptr.length);
      ptr = ptr[0];
    }
    return dim;
  },
  // check if a tensor is rank-1
  isFlat: function(T) {
    var flat = true,
      len = T.length;
    while (len--) {
      flat *= !(T[len] instanceof Array);
      if (!flat) break;
    }
    return Boolean(flat);
  },
  // get the maximum length of the deepest array in (non-scalar) tensor T.
  maxDeepestLength: function(T) {
    if (!(T instanceof Array)) return 0;
    var stack = [],
      sizes = [];
    stack.push(T);
    while (stack.length) {
      var curr = stack.pop(),
        len = curr.length;
      if (lomath.isFlat(curr))
        sizes.push(len);
      else
        while (len--)
          stack.push(curr[len]);
    }
    return _.max(sizes);
  },

  ///////////////////////////
  // Tensor transformation //
  ///////////////////////////

  // lodash methods
  // _.chunk
  // _.flatten, _.flattenDeep
  // swap at index i, j
  // Mutates the array
  swap: function(arr, i, j) {
    arr[i] = arr.splice(j, 1, arr[i])[0];
    return arr;
  },
  // return a copy of reversed arr from index i to j inclusive
  reverse: function(arr, i, j) {
    var vec = arr.slice(0);
    var k = i == undefined ? 0 : i;
    var l = j == undefined ? arr.length - 1 : j;
    var mid = Math.ceil((k + l) / 2);
    while (k < mid)
      lomath.swap(vec, k++, l--);
    return vec;
  },
  // return a copy: extend an array till toLen, filled with val defaulted to 0.
  // Mutates the array
  extend: function(arr, toLen, val) {
    var lendiff = toLen - arr.length,
      rePal = (val == undefined ? 0 : val);
    if (lendiff < 0)
      throw new Error("Array longer than the length to extend to")
    while (lendiff--)
      arr.push(rePal);
    return arr;
  },
  // applying _.indexOf in batch; returns -1 for field if not found
  batchIndexOf: function(arr, fieldArr) {
    return _.map(fieldArr, function(t) {
      return _.indexOf(arr, t)
    });
  },
  // return valid indices from indArr, i.e. in range 0 to maxLen
  validInds: function(indArr, maxLen) {
    return _.filter(indArr, lomath.inRange.bind(null, 0, maxLen));
  },
  // return a copy with sub rows from matrix M
  rbind: function(M, indArr) {
    indArr = lomath.validInds(indArr, M.length);
    if (indArr.length == 0) return [];
    return _.map(indArr, function(i) {
      return _.cloneDeep(M[i]);
    });
  },
  // return a copy with sub rows from matrix M
  cbind: function(M, indArr) {
    indArr = lomath.validInds(indArr, M[0].length)
    if (indArr.length == 0) return [];
    return _.map(M, function(row) {
      return _.map(indArr, function(i) {
        return row[i];
      });
    });
  },
  // Assuming matrix has header, rbind by header fields instead of indices
  cbindByField: function(M, fieldArr) {
    // assuming header is first row of matrix
    var header = M[0],
      fieldInds = lomath.batchIndexOf(header, fieldArr);
    return lomath.cbind(M, fieldInds);
  },
     [1, 2, 3],
     [4]
   ])
     [1, 2, 3],
     [4]
   ], 'a')
  // make a tensor rectangular by filling with val, defaulted to 0.
  // mutates the tensor
  rectangularize: function(T, val) {
    var toLen = lomath.maxDeepestLength(T),
      stack = [];
    stack.push(T);
    while (stack.length) {
      var curr = stack.pop();
      if (lomath.isFlat(curr))
        lomath.extend(curr, toLen, val);
      else
        _.each(curr, function(c) {
          stack.push(c);
        })
    }
    return T;
  },
  // use chunk from inside to outside:
  reshape: function(arr, dimArr) {
    var tensor = arr;
    var len = dimArr.length;
    while (--len)
      tensor = _.chunk(tensor, dimArr[len]);
    return tensor;
  },
  flattenJSON: function(obj, delimiter) {
    var delim = delimiter || '.'
    var nobj = {}

    _.each(obj, function(val, key) {
      if (_.isPlainObject(val) && !_.isEmpty(val)) {
        var strip = lomath.flattenJSON(val, delim)
        _.each(strip, function(v, k) {
          nobj[key + delim + k] = v
        })
      } else if (_.isArray(val) && !_.isEmpty(val)) {
        _.each(val, function(v, index) {
          nobj[key + delim + index] = v
          if (_.isObject(v)) {
            nobj = lomath.flattenJSON(nobj, delim)
          };
        })
      } else {
        nobj[key] = val
      }
    })
    return nobj
  },
  unflattenJSON: function(obj, delimiter) {
    return lomath.unobjectifyArray(lomath.unflattenJSON_objectifyArray(obj, delimiter))
  },
  unflattenJSON_objectifyArray: function(obj, delimiter) {
    var delim = delimiter || '.';
    // cache deep keys for deletion later
    var deepKeys = [];

    // iterate over each key-val pair breath first
    _.each(obj, function(val, key) {
      // if can be delimiter-splitted
      if (_.includes(key, delim)) {
        var ind = key.lastIndexOf(delim)
          // new key
        var v0 = key.slice(0, ind);
        // the tail as new key now
        var v1 = key.slice(ind + delim.length);
        // guard, if sibling didn't already create it
        if (!obj[v0]) {
          obj[v0] = {}
        };
        obj[v0][v1] = val;
        // push for deletion
        deepKeys.push(key)
      }
    });
    // delete the old deepkeys
    _.map(deepKeys, function(dkey) {
      delete obj[dkey]
    });
    // check if should go down further if still has delim
    var goDown = false;
    _.each(_.keys(obj), function(k) {
      if (k.lastIndexOf(delim) != -1) {
        goDown = true;
        return false
      };
    })
    if (goDown) {
      return lomath.unflattenJSON_objectifyArray(obj, delim)
    } else {
      return obj
    }
  },
  unobjectifyArray: function(obj) {
    var index = 0;
    var arr = [];
    var isIndex = true;
    if (_.isEmpty(obj)) {
      isIndex = false;
    };
    // iterate through and see if all keys indeed form a numeric sequence
    // sort keys. Sort since iteration order is not guaranteed.
    var sortedKeys = _.keys(obj).sort()
    _.each(sortedKeys, function(k) {
      // add the value while keys still behaving like numeric
      if (parseInt(k) == index++) {
        arr.push(obj[k])
      } else {
        isIndex = false
        return false
      }
    })
    var res = isIndex ? arr : obj
      // recurse down the val if it's not primitive type
    _.each(res, function(v, k) {
      if (_.isObject(v)) {
        res[k] = lomath.unobjectifyArray(v)
      }
    })
    return res
  },

  //////////////
  // Matrices //
  //////////////
  // transpose a matrix
  transpose: function(M) {
    return _.zip.apply(null, M);
  },
  // trace a matrix
  trace: function(M) {
    var len = M.length,
      sum = 0;
    while (len--)
      sum += M[len][len]
    return sum;
  },
  // multiply two matrices
  matMultiply: function(A, B) {
    var T = lomath.transpose(B);
    return _.map(A, function(a) {
      return _.map(T, lomath.dot.bind(null, a))
    })
  },
  coSubMatrix: function(M, r, c) {
    r = r || 0;
    c = c || 0;
    var coSubMat = [];
    for (var i = 0; i < M.length; i++) {
      if (i != r) {
        var row = M[i],
          coRow = [];
        for (var j = 0; j < M.length; j++) {
          if (j != c) coRow.push(row[j]);
        }
        coSubMat.push(coRow);
      }
    }
    return coSubMat;
  },
  coMatrix: function(M) {
    var coMat = [];
    for (var i = 0; i < M.length; i++) {
      var coRow = [];
      for (var j = 0; j < M.length; j++) {
        var subMat = lomath.coSubMatrix(M, i, j);
        coRow.push(lomath.parity(i + j) * lomath.det(subMat));
      }
      coMat.push(coRow);
    }
    return coMat;
  },
  adj: function(M) {
    var adjMat = [];
    for (var i = 0; i < M.length; i++) {
      var adjRow = [];
      for (var j = 0; j < M.length; j++) {
        var subMat = lomath.coSubMatrix(M, j, i);
        adjRow.push(lomath.parity(i + j) * lomath.det(subMat));
      }
      adjMat.push(adjRow);
    }
    return adjMat;
  },
  detSum: function(M, i) {
    var sign = lomath.parity(i),
      cofactor = M[0][i],
      coSubMat = lomath.coSubMatrix(M, 0, i);
    return sign * cofactor * lomath.det(coSubMat);
  },
  det: function(M) {
    var n = M.length;
    var det = 0;
    if (!n) {
      det = M
    } else if (n == 1) {
      det = M[0][0] || M[0]
    } else if (n == 2) {
      det = M[0][0] * M[1][1] - M[1][0] * M[0][1]
    } else {
      var head = M[0];
      det += lomath.fsum(M, lomath.detSum)
    }
    return det;
  },
  inv: function(M) {
    var det = lomath.det(M);
    if (det == 0) return null;
    else
      return lomath.multiply(1 / det, lomath.adj(M))
  },

  ///////////////////////////////
  // Subsets and combinatorics //
  ///////////////////////////////
  // generate n-nary number of length
  genAry: function(length, n) {
    var range = _.map(_.range(n), String);
    var tmp = range,
      it = length;
    while (--it) {
      tmp = _.flattenDeep(_.map(range, function(x) {
        return lomath.distributeRight(lomath.a_add, x, tmp)
      }));
    }
    return tmp;
  },
  // convert array of strings to array of array of numbers
  toNumArr: function(sarr) {
    return _.map(sarr, function(str) {
      return _.map(str.split(''), function(x) {
        return parseInt(x);
      })
    })
  },
  // generate all permutation subset indices of n items
  pSubset: function(n) {
    var range = _.map(_.range(n), String),
      res = [],
      count = n;
    res.push(range); //init
    if (count == 0) return res;
    while (--count) {
      // the last batch to expand on
      var last = _.last(res);
      var batch = [];
      _.each(last, function(k) {
        for (var i = 0; i < n; i++)
          if (!_.includes(k.split(''), String(i)))
            batch.push(k + i);
      })
      res.push(batch);
    }
    return res;
  },
  // generate all subset indices of n items
  subset: function(n) {
    var range = _.map(_.range(n), String),
      res = [],
      count = n;
    res.push(range); //init
    if (count == 0) return res;
    while (--count) {
      // the last batch to expand on
      var last = _.last(res);
      var batch = [];
      _.each(last, function(k) {
        for (var i = Number(_.last(k)) + 1; i < n; i++)
          batch.push(k + i);
      })
      res.push(batch);
    }
    return res;
  },
  // generate the indices of n-perm-r
  permList: function(n, r) {
    return lomath.toNumArr(lomath.pSubset(n)[r - 1]);
  },
  // generate the indices of n-choose-r
  combList: function(n, r) {
    return lomath.toNumArr(lomath.subset(n)[r - 1]);
  },
  // generate all permutations of n items
  permute: function(n) {
    var range = _.range(n),
      res = [],
      diffs, k = 0;
    while (k != -1) {
      res.push(range.slice(0));
      diffs = lomath.stairs(range),
        k = _.findLastIndex(diffs, lomath.isPositive);
      var l = _.findLastIndex(range, function(t) {
        return t > range[k];
      });
      lomath.swap(range, k, l);
      range = lomath.reverse(range, k + 1, null);
    }
    return res;
  },
  // return factorial(n)
  // alias: fact
  factorial: function(n) {
    if (n == 0) return 1;
    if (n < 0) throw "Negative factorial not defined"
    var count = n,
      res = n;
    while (--count)
      res *= count;
    return res;
  },
  // return n-permute-r
  // alias: perm
  permutation: function(n, r) {
    if (r == 0) return 1;
    if (n < 0 || r < 0) throw "Negative permutation not defined"
    var count = r,
      term = n;
    res = n;
    while (--count)
      res *= --term;
    return res;
  },
  // return n-choose-r
  // alias: comb
  combination: function(n, r) {
    var l = (r > n / 2) ? n - r : r;
    if (n < 0 || l < 0) throw "Negative combination not defined"
    return lomath.permutation(n, l) / lomath.factorial(l);
  },

  /////////////////////
  // Handy vectorial //
  /////////////////////
  // return the dot product of two vectors
  // recyle if lengths mismatch
  dot: function(X, Y) {
    return _.sum(lomath.multiply(X, Y));
  },
  // return the sum of n-powers of a tensor, default to n = 2
  powSum: function(T, n) {
    var L = n == undefined ? 2 : n;
    return _.sum(lomath.pow(T, L));
  },
  // return the L-n norm of a vector, default to L-2
  norm: function(v, n) {
    var L = n == undefined ? 2 : n;
    return lomath.a_root(lomath.powSum(v, L), L);
  },
  // normalize a vector(tensor) by L-n norm, default to n=2
  normalize: function(v, n) {
    return lomath.divide(v, lomath.norm(v, n));
  },
  // rescale a vector to unit length
  rescale: function(v) {
    return lomath.normalize(v, 1);
  },

  //////////////////
  // handy Matrix //
  //////////////////

  // Matrix ops
  // Matrix ops
  // Matrix ops

  /////////////////
  // Handy trend //
  /////////////////
  // return the stairs: adjacent difference in a vector
  stairs: function(v) {
    var dlen = v.length - 1,
      st = Array(dlen);
    while (dlen--)
      st[dlen] = v[dlen + 1] - v[dlen];
    return st;
  },
  // check the trend of vector v using sign-function
  stairsTrend: function(v, sigFn) {
    return lomath.sameSig(lomath.stairs(v), sigFn);
  },
  // check if vector v is increasing
  increasing: function(v) {
    return lomath.stairsTrend(v, lomath.isPositive);
  },
  // check is vector v is non-decreasing
  nonDecreasing: function(v) {
    return lomath.stairsTrend(v, lomath.nonNegative);
  },
  // check is vector v is decreasing
  decreasing: function(v) {
    return lomath.stairsTrend(v, lomath.isNegative);
  },
  // check is vector v is non-increasing
  nonIncreasing: function(v) {
    return lomath.stairsTrend(v, lomath.nonPositive);
  },

  ///////////////////////
  // Handy statistical //
  ///////////////////////
  // return the average of a vector
  mean: function(v) {
    return _.sum(v) / v.length;
  },
  // return the expectation value E(fn(x)), given probability and value vectors, and an optional atomic fn, defaulted to identity E(x).
  // Note: fn must be atomic
  // alias E
  expVal: function(X, P, fn) {
    var val, prob, func;
    // if only X is specified
    if (P == undefined && fn == undefined) {
      var hist = lomath.histogram(X);
      val = hist.value,
        prob = hist.prob;
    }
    // if X, P specified (maybe fn too)
    else if (typeof P === 'object') {
      val = X;
      prob = P;
      func = fn;
    }
    // if X, fn specified
    else if (typeof P === 'function') {
      var hist = lomath.histogram(X);
      val = hist.value,
        prob = hist.prob;
      func = P;
    }
    if (func != undefined)
      return lomath.dot(lomath.distributeSingle(func, val), prob);
    return lomath.dot(val, prob);
  },
  // return the variance, given probability and value vectors
  // alias Var
  variance: function(X, P, fn) {
    // if only X is specified
    if (P == undefined && fn == undefined) {
      return lomath.expVal(X, lomath.a_square) - lomath.a_square(lomath.expVal(X))
    }
    // if X, P specified (maybe fn too)
    else if (typeof P === 'object') {
      return fn == undefined ?
        lomath.expVal(X, P, lomath.a_square) - lomath.a_square(lomath.expVal(X, P)) :
        lomath.expVal(X, P, _.flow(fn, lomath.a_square)) - lomath.a_square(lomath.expVal(X, P, fn));
    }
    // if X, fn specified
    else if (typeof P === 'function') {
      return lomath.expVal(X, _.flow(P, lomath.a_square)) - lomath.a_square(lomath.expVal(X, P));
    }
  },
  // return the variance, given probability and value vectors
  stdev: function(X, P, fn) {
    return Math.sqrt(lomath.variance(X, P, fn));
  },
  histogram: function(data, fn, pair) {
    if (fn == true) // called with data, pair
      return _.toPairs(_.countBy(data));
    var bin = _.countBy(data, fn);
    if (pair == true) // called with data, fn, pair
      return _.toPairs(bin);
    var freq = _.values(bin);
    return { //called with data, [fn]
      value: _.keys(bin),
      freq: freq,
      prob: lomath.rescale(freq)
    }
  },
  // Calculate the rate of return r in % of an exp growth, given final value m_f, initial value m_i, and time interval t
  expGRate: function(m_f, m_i, t) {
    return 100 * (Math.exp(Math.log(m_f / m_i) / t) - 1);
  },
  // Calculate the trailing exp rate of return in the last t years given a vector v
  trailExpGRate: function(v, t) {
    var len = v.length;
    return lomath.expGRate(v[len - 1], v[len - 1 - t], t);
  },

  //////////////////////////////////////////
  // Plotting modules: normal and dynamic //
  //////////////////////////////////////////
       [{
           name: "linear",
           data: v
       }, {
           name: "square",
           data: vv
       }],
       "Title 1"
       )
       [{
           name: "log",
           data: _.log(v)
       }],
       "Title 2"
       )
  // hc: require(__dirname+'/chart/plot.js').hc
  hc: function() {
    var p = require(__dirname + '/chart/plot.js').p;
    return new p();
  },
       [{
           name: "linear",
           data: [1, 2, 3, 4, 5, 6]
       }, {
           name: "square",
           data: [1, 4, 9, 16, 25, 36]
       }],
       "Title 1"
       )
       [{
           name: "square",
           data: [[3, 9], [4, 16], [5, 25], [6, 36]]
       }],
       "Title 2"
       )
  plot: function(seriesArr, title, yLabel, xLabel) {
    console.log("Please call this method by _.hc.plot");
    return 0;
  },
   chart: {
        type: 'column'
    },
    title: {
        text: 'Monthly Average Rainfall'
    },
    subtitle: {
        text: 'Source: WorldClimate.com'
    },
    xAxis: {
        categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
        crosshair: true
    },
    yAxis: {
        min: 0,
        title: {
            text: 'Rainfall (mm)'
        }
    },
    plotOptions: {
        column: {
            pointPadding: 0.2,
            borderWidth: 0
        }
    },
    series: [{
        name: 'Tokyo',
        data: [49.9, 71.5, 106.4, 129.2, 144.0, 176.0]

    }, {
        name: 'New York',
        data: [83.6, 78.8, 98.5, 93.4, 106.0, 84.5]

    }, {
        name: 'London',
        data: [48.9, 38.8, 39.3, 41.4, 47.0, 48.3]

    }, {
        name: 'Berlin',
        data: [42.4, 33.2, 34.5, 39.7, 52.6, 75.5]

    }]
       })
  advPlot: function(options) {
    console.log("Please call this method by _.hc.advPlot");
    return 0;
  },
  render: function(autosave) {
    console.log("Please call this method by _.hc.advPlot");
    return 0;
  },
  // The time variables for tick tock
  t_start: 0,
  t_end: 0,
  tick: function() {
    lomath.t_start = _.now();
    return lomath.t_start;
  },
  tock: function() {
    lomath.t_end = _.now();
    var diff = lomath.t_end - lomath.t_start;
    console.log('Elapsed ms:', diff);
    return diff;
  },
  p: function() {
    console.log.apply(null, arguments);
  }
})

// Export lomath as _
module.exports = lomath;


text:
/*jshint multistr:true */

this.recline = this.recline || {};
this.recline.View = this.recline.View || {};

(function($, my) {
  "use strict";

my.QueryEditor = Backbone.View.extend({
  className: 'recline-query-editor',
  template: ' \
    <form action="" method="GET" class="form-inline" role="form"> \

 \

 \

 \
             \

 \
          Search \
          <input class="form-control search-query" type="text" id="q" name="q" value="{{q}}" placeholder="Search data ..."> \

 \

 \
      <button type="submit" class="btn btn-default">Go »</button> \
    </form> \
  ',

  events: {
    'submit form': 'onFormSubmit'
  },

  initialize: function() {
    _.bindAll(this, 'render');
    this.listenTo(this.model, 'change', this.render);
    this.render();
  },
  onFormSubmit: function(e) {
    e.preventDefault();
    var query = this.$el.find('.search-query').val();
    this.model.set({q: query});
  },
  render: function() {
    var tmplData = this.model.toJSON();
    var templated = Mustache.render(this.template, tmplData);
    this.$el.html(templated);
  }
});

})(jQuery, recline.View);

redpajama_set_name: RedPajamaGithub


text:
package org.uma.jmetal.algorithm.multiobjective.spea2;

import org.uma.jmetal.algorithm.AlgorithmBuilder;
import org.uma.jmetal.operator.crossover.CrossoverOperator;
import org.uma.jmetal.operator.mutation.MutationOperator;
import org.uma.jmetal.operator.selection.SelectionOperator;
import org.uma.jmetal.operator.selection.impl.BinaryTournamentSelection;
import org.uma.jmetal.problem.Problem;
import org.uma.jmetal.solution.Solution;
import org.uma.jmetal.util.JMetalException;
import org.uma.jmetal.util.evaluator.SolutionListEvaluator;
import org.uma.jmetal.util.evaluator.impl.SequentialSolutionListEvaluator;

import java.util.List;

/**
 * @author Juan J. Durillo
 */
public class SPEA2Builder<S extends Solution<?>> implements AlgorithmBuilder<SPEA2<S>> {
  /**
   * SPEA2Builder class
   */
  protected final Problem<S> problem;
  protected int maxIterations;
  protected int populationSize;
  protected CrossoverOperator<S> crossoverOperator;
  protected MutationOperator<S> mutationOperator;
  protected SelectionOperator<List<S>, S> selectionOperator;
  protected SolutionListEvaluator<S> evaluator;
  protected int k ;

  /**
   * SPEA2Builder constructor
   */
  public SPEA2Builder(Problem<S> problem, CrossoverOperator<S> crossoverOperator,
      MutationOperator<S> mutationOperator) {
    this.problem = problem;
    maxIterations = 250;
    populationSize = 100;
    this.crossoverOperator = crossoverOperator ;
    this.mutationOperator = mutationOperator ;
    selectionOperator = new BinaryTournamentSelection<S>();
    evaluator = new SequentialSolutionListEvaluator<S>();
    k = 1 ;
  }

  public SPEA2Builder<S> setMaxIterations(int maxIterations) {
    if (maxIterations < 0) {
      throw new JMetalException("maxIterations is negative: " + maxIterations);
    }
    this.maxIterations = maxIterations;

    return this;
  }

  public SPEA2Builder<S> setPopulationSize(int populationSize) {
    if (populationSize < 0) {
      throw new JMetalException("Population size is negative: " + populationSize);
    }

    this.populationSize = populationSize;

    return this;
  }

  public SPEA2Builder<S> setSelectionOperator(SelectionOperator<List<S>, S> selectionOperator) {
    if (selectionOperator == null) {
      throw new JMetalException("selectionOperator is null");
    }
    this.selectionOperator = selectionOperator;

    return this;
  }

  public SPEA2Builder<S> setSolutionListEvaluator(SolutionListEvaluator<S> evaluator) {
    if (evaluator == null) {
      throw new JMetalException("evaluator is null");
    }
    this.evaluator = evaluator;

    return this;
  }

  public SPEA2Builder<S> setK(int k) {
    this.k = k ;

    return this;
  }

  public SPEA2<S> build() {
    SPEA2<S> algorithm = null ;
    algorithm = new SPEA2<S>(problem, maxIterations, populationSize, crossoverOperator,
          mutationOperator, selectionOperator, evaluator, k);

    return algorithm ;
  }

  /* Getters */
  public Problem<S> getProblem() {
    return problem;
  }

  public int getMaxIterations() {
    return maxIterations;
  }

  public int getPopulationSize() {
    return populationSize;
  }

  public CrossoverOperator<S> getCrossoverOperator() {
    return crossoverOperator;
  }

  public MutationOperator<S> getMutationOperator() {
    return mutationOperator;
  }

  public SelectionOperator<List<S>, S> getSelectionOperator() {
    return selectionOperator;
  }

  public SolutionListEvaluator<S> getSolutionListEvaluator() {
    return evaluator;
  }
}


text:
<head>

  <script src="//cdn.optimizely.com/js/3208520529.js"></script>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="KAAZING WebSocket Intercloud Connect" content="">

  <title>KAAZING - KWIC {{ page.title }}</title>

  <!-- Bootstrap Core CSS -->
  <link href="css/bootstrap.min.css" rel="stylesheet">

  <!-- Custom CSS -->
  <link href="css/landing-page.css" rel="stylesheet">

  <!-- Custom Fonts -->
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
  <link href="http://fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">

  <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
  <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
  <!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
        <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->

  <script type="text/javascript">
    setTimeout(function() {
      var a = document.createElement("script");
      var b = document.getElementsByTagName("script")[0];
      a.src = document.location.protocol + "//script.crazyegg.com/pages/scripts/0037/0800.js?" + Math.floor(new Date().getTime() / 3600000);
      a.async = true;
      a.type = "text/javascript";
      b.parentNode.insertBefore(a, b)
    }, 1);
  </script>

</head>


text:
title: Q3 2021 Update
description: An update on our mission for Q3 2021.
date: 2021-10-26T00:00:00.000Z
authors: ['oliviertassinari']
card: true
tags: ['Company update']
---

This update covers our progress over the last three months.
It also covers what we aim to achieve in the months ahead.

## Overview

- [Product](#product)
  - [MUI Core](#mui-core)
  - [MUI X](#mui-x)
  - [Design kits](#design-kits)
- [Company](#company)
- [Our roadmap intent for Q4 2021](#our-roadmap-intent-for-q4-2021)

## Product

Here are the most significant improvements since early July 2021.

### MUI Core

- 🔥 In September we released a new major version: [v5.0.0](https://github.com/mui/material-ui/releases/tag/v5.0.0) (you might have noticed! 😄 ).
  We're excited about what it means for the future of the library, and we hope you are going to enjoy the simplified customization, the extra flexibility, new components, and more.
  It took us over a year to pull off, with an almost exclusive focus on v5 over v4.
  You can read more about it in the [release blog post](/blog/mui-core-v5/).
- ✨ We also introduced a brand new [product page](/core/) this quarter to better present what the Core is about.
- 🔎 We've reworked the search experience in the docs:

  - The component pages now rank before the API pages.
  - Instead of only displaying up to 5 results, you can scroll for more results.
  - An icon illustrates the nature of the match; either a page, a header, or a paragraph.
  - Your most recent searches are saved in local storage.

Before

After

- [Masonry](/components/masonry/). We introduced a new component for use when the `Grid` component leads to wasted space. It's frequently used in dashboards.

- We introduced a new [package of components without styles](https://www.npmjs.com/package/@mui/base), laying the foundations for supporting multiple design systems with headless components.
  While it was tough to balance the time between working on v5 stable and developing the unstyled components, we still managed to introduce the first few:

  - [Autocomplete](/components/autocomplete/#useautocomplete)

  ```jsx
  import { useAutocomplete } from '@mui/base/AutocompleteUnstyled';
  ```

  - [Button](/components/buttons/#unstyled)

  ```jsx
  import { useButton } from '@mui/base/ButtonUnstyled';
  ```

  - [Modal](/components/modal/#unstyled)

  ```jsx
  import ModalUnstyled from '@mui/base/ModalUnstyled';
  ```

  - [Slider](/components/slider/#unstyled)

  ```jsx
  import SliderUnstyled from '@mui/base/SliderUnstyled';
  ```

  - [Switch](/components/switches/#unstyled)

  ```jsx
  import { useSwitch } from '@mui/base/SwitchUnstyled';
  ```

  - [Portal](/components/portal/#unstyled)

  ```jsx
  import Portal from '@mui/base/Portal';
  ```

  - [TrapFocus](/components/trap-focus/#unstyled)

  ```jsx
  import TrapFocus from '@mui/base/Unstable_TrapFocus';
  ```

### MUI X

Last quarter we focused on the data grid components, working towards a stable release.
We also introduced a brand new [product page](/x/) this quarter to better present what MUI X is about.

#### Date Picker

The date picker sits at the border between the core component and advanced components.

- ❌ A few updates but no major ones worth sharing.
  With the release of v5 stable and our focus on the data grid, nobody in the team had the bandwidth to work on it.

#### Data Grid

- 🎉 We released the first stable version: [v4.0.0](https://github.com/mui/mui-x/releases/tag/v4.0.0)!
  We also released the first v5-beta, to support MUI Core v5,
  and will be working almost exclusively on the v5 release line going forward.
- 🔎 We also added a [quick-filter demo](/components/data-grid/filtering/#quick-filter) to illustrate how it can be implemented,
  and will add a [built-in](https://github.com/mui/mui-x/issues/202) solution later on.

- 🐛 We fixed several non-idiomatic React patterns, for instance we were not handling controllable props by the book, and as we do in MUI Core.
- 🚀 We introduced the [row editing](/components/data-grid/editing/#row-editing) feature.

- 🦺 We added a [`disableVirtualization`](/components/data-grid/virtualization/#disable-virtualization) prop to make it easier to test the data grid without a real browser environment, for example in jsdom with Jest.
- 🚛 We have added an `onViewportRowsChange` prop as a first step toward being able to lazy load a large dataset.
- Last but not least, we have resumed the work on [key missing features](https://github.com/mui/mui-x/issues?q=is%3Aopen+label%3A%22linked+in+docs%22+sort%3Areactions-%2B1-desc).
  Print export and tree data are in the pipeline.

### Design kits

We introduced a brand new [product page](/design-kits/) this quarter to better present the design kits.

#### Figma

The kit is now up-to-date with MUI Core v5,
and also includes the MUI X components. And, as always, we have been refining it in small areas for issues raised by our users in the support channel.

#### Adobe XD

No updates.

#### Sketch

No updates.

## Company

### Retreat

We held our first company retreat 🏝 in Lisbon, Portugal 🇵🇹 ,
for members of the team that were not prevented by COVID-19 related travel restrictions, and who felt safe enough to travel.
Less than half the company made it, but it was still fun!
We spent half the time doing a [Hackathon](https://twitter.com/olivtassinari/status/1441773885259583491) and the other half on activities (surfing, biking, winery tour, and sightseeing).

Sightseeing in Lisbon

### Growth between Q2 2021 and Q3 2021

- 📦 From 9.1M to 9.9M downloads per month on npm. We have 22.4% market share of the React ecosystem as a proportion of `react-dom` downloads.
- ⭐ From 69.1k to 72.1k stars. [Leave us yours 🌟](https://github.com/mui/material-ui)!
- 👨👩👧👦 From 2,223 to 2,316 contributors on GitHub. On average, one new contributor joins every day.
- 💰 Grew financial support by 2.99X [year over year](/blog/2020-q3-update/).

### New member

We welcomed one new member to the company this quarter 🏢:

- Benny was our first intern, and is now the first junior developer on the MUI Core team.

We are now 11 passionate builders empowering the next generation of UI creators.

### A new brand

Alongside the release of v5, we launched our [new brand](/blog/material-ui-is-now-mui/)!
We're excited about our new shorter name, **MUI**.
We were already using it in the class names (`.MuiButton-root`), which embodies our next step beyond Material Design.

This is something we have been considering for some time,
and we did a couple of early design studies with an outside agency,
but it was only after we secured [mui.com](https://mui.com/), the GitHub [`@mui`](https://www.npmjs.com/org/mui) org, and the npm [@mui](https://www.npmjs.com/org/mui) scope that we knew the new name would work.

## Our roadmap intent for Q4 2021

We'll do our best, no guarantee!

### MUI Core

- 🚀 We will double down on v5 before starting to solve new large problems, e.g. a revamp of the select.
  We have made bold changes in this version since v4, but until recently, only a small percentage of the community was using v5.
  In the last few weeks, we have seen a strong influx of feedback from the community.
  We need to make the most of this feedback to solve regressions, improve the documentation for the new APIs, adjust the tradeoffs we took in the light of more information, and more.
  [15%](https://npm-stat.com/charts.html?package=%40material-ui%2Fcore&package=%40mui%2Fmaterial&from=2020-10-25&to=2021-10-15) of the community has migrated, so far.
- 🦴 Migrate more components to `@mui/base`. [Michał](https://github.com/michaldudak) has recently added support for the [Button](https://mui.com/components/buttons/#unstyled).
  You can follow our progress in the [umbrella issue](https://github.com/mui/material-ui/issues/27170).
- 🎨 We are [exploring](https://github.com/mui/material-ui/discussions/29024) the introduction of CSS variables.
  Since v5, MUI is no longer actively supporting IE 11. Dropping this browser requirement unlocks new capabilities of the web platform.
- 🌈 Resume work on the second design system.
  Some users (and potential users) dislike Material Design. We'll try to make the second design system one that they love! You can check our [first RFC](https://github.com/mui/material-ui/discussions/29024).
- 🗓 Execute on all of the items in the [public roadmap](https://github.com/mui/material-ui/projects/25).
- ❓ Please upvote [GitHub issues](https://github.com/mui/material-ui/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc) if you want us to focus on a specific problem. The number of 👍 helps us to prioritize.

### MUI X

#### Data Grid

We have planned to:

- ✨ Release the print export feature.
- ✨ Release the tree data feature.
- ✨ Revamp the virtualization to yield better rendering performance and fix bugs.
  It unlocks new features like column pinning and lazy loading.
- 🗓 Execute on all of the items in the [public roadmap](https://github.com/mui/mui-x/projects/1).
- ❓ Please upvote [GitHub issues](https://github.com/mui/mui-x/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc) if you want us to focus on a specific problem. The number of 👍 helps us to prioritize.

#### Date Picker

No plans. We probably won't progress its development this quarter.

However, we are growing the team.
We plan to resume work on it in Q1 2022, starting with the equivalent of one full-time developer.

### Design kits

We will release a v5 for Sketch and Adobe XD, as we have already done for Figma.
While the visual difference between MUI Core v4 and v5 is not major, we still need to sync the design components.

### Company

We have the following objectives:

- 📊 Run a new edition of the Developer Survey. The last one was done [15 months ago](https://mui.com/blog/2020-developer-survey-results/). Now that v5 is released, it's time for the 2021 edition!
- 👪 Hire for two new roles: a Product Manager to focus on low-code and our first Developer Advocate.
- 💫 Create a great onboarding experience for the 6-7 new hires of Q4.


text:
"""Config flow for PrusaLink integration."""
from __future__ import annotations

import asyncio
import logging
from typing import Any

from aiohttp import ClientError
import async_timeout
from awesomeversion import AwesomeVersion, AwesomeVersionException
from pyprusalink import InvalidAuth, PrusaLink
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

STEP_USER_DATA_SCHEMA = vol.Schema(
    {
        vol.Required("host"): str,
        vol.Required("api_key"): str,
    }
)

async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, str]:
    """Validate the user input allows us to connect.

    Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
    """
    api = PrusaLink(async_get_clientsession(hass), data["host"], data["api_key"])

    try:
        async with async_timeout.timeout(5):
            version = await api.get_version()

    except (asyncio.TimeoutError, ClientError) as err:
        _LOGGER.error("Could not connect to PrusaLink: %s", err)
        raise CannotConnect from err

    try:
        if AwesomeVersion(version["api"]) < AwesomeVersion("2.0.0"):
            raise NotSupported
    except AwesomeVersionException as err:
        raise NotSupported from err

    return {"title": version["hostname"]}

class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for PrusaLink."""

    VERSION = 1

    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle the initial step."""
        if user_input is None:
            return self.async_show_form(
                step_id="user", data_schema=STEP_USER_DATA_SCHEMA
            )

        host = user_input["host"].rstrip("/")
        if not host.startswith(("http://", "https://")):
            host = f"http://{host}"

        data = {
            "host": host,
            "api_key": user_input["api_key"],
        }
        errors = {}

        try:
            info = await validate_input(self.hass, data)
        except CannotConnect:
            errors["base"] = "cannot_connect"
        except NotSupported:
            errors["base"] = "not_supported"
        except InvalidAuth:
            errors["base"] = "invalid_auth"
        except Exception:  # pylint: disable=broad-except
            _LOGGER.exception("Unexpected exception")
            errors["base"] = "unknown"
        else:
            return self.async_create_entry(title=info["title"], data=data)

        return self.async_show_form(
            step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
        )

class CannotConnect(HomeAssistantError):
    """Error to indicate we cannot connect."""

class NotSupported(HomeAssistantError):
    """Error to indicate we cannot connect."""


text:
package com.example.app.windowheatcontrol;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.ogema.core.application.Application;
import org.ogema.core.application.ApplicationManager;

import com.example.app.windowheatcontrol.gui.MainPage;

import org.apache.felix.scr.annotations.Reference;
import de.iwes.widgets.api.OgemaGuiService;
import de.iwes.widgets.api.widgets.WidgetApp;
import de.iwes.widgets.api.widgets.WidgetPage;

/**
 * A sample heat control app, that takes into account a battery
 */
@Component(specVersion = "1.2", immediate = true)
@Service(Application.class)
public class WindowHeatControlApp implements Application {

	public static final String urlPath = "/com/example/app/windowheatcontrol";

    private ApplicationManager appMan;
    private WindowHeatControlController controller;
	private WidgetApp widgetApp;

	// service injected by OSGi
	@Reference
	private OgemaGuiService guiService;

    /*
     * This is the entry point to the application.
     */
 	@Override
    public void start(ApplicationManager appManager) {

        // Remember framework references for later.
        appMan = appManager;

        // contains the management logic
        controller = new WindowHeatControlController(appMan);

		//register a web page with dynamically generated HTML
		widgetApp = guiService.createWidgetApp(urlPath, appManager);
		WidgetPage<?> page = widgetApp.createStartPage();
		new MainPage(page, controller);
     }

     /*
     * Callback called when the application is going to be stopped.
     */
    @Override
    public void stop(AppStopReason reason) {
    	if (widgetApp != null)
    		widgetApp.close();
		if (controller != null)
    		controller.close();
		if (appMan != null)
			appMan.getLogger().info("{} stopped", getClass().getName());
        widgetApp = null;
        controller = null;
        appMan = null;
    }
}


text:
<?php

use Tz\TestBundle\Model\Database as Database;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;

class DatabaseTest extends BaseWebTestCase
{
    /**
     * @var Tz\FixtureBundle\Model\Database
     */
    private $database;

    public function setUp()
    {
        $this->createClient();
        $this->database = new Database('mysql', 'localhost', 'mysql', 'root', static::$kernel->getContainer()->getParameter('database_password'));
        parent::setUp();
    }

    public function tearDown()
    {
        $this->database = null;
    }

    /**
     * @test
     */
    public function getPdoConnection()
    {
        $pdo = $this->database->getPdoConnection();
        $this->assertInstanceOf('PDO', $pdo);
    }

    /**
     * @test
     * @expectedException PDOException
     */
    public function failPdoConnection()
    {
        $database = new Database('mysql', 'localhost', 'mysql', 'nouser', '');
        $pdo = $database->getPdoConnection();
        $this->assertNull($pdo);
    }

    /**
     * @test
     */
    public function getDatabaseName()
    {
        $this->assertEquals('mysql', $this->database->getDatabaseName());
    }
}


text:
package draghelper_study.heaven7.com.common_view;

import android.app.Application;
import android.test.ApplicationTestCase;

/**
 * Testing Fundamentals
 */
public class ApplicationTest extends ApplicationTestCase<Application> {
    public ApplicationTest() {
        super(Application.class);
    }
}


text:
/* @flow */
import { AsyncStorage } from 'react-native';
import { applyMiddleware, compose, createStore } from 'redux';
import { persistStore, autoRehydrate } from 'redux-persist';

import rootReducer from './reducers';
import middleware from './middleware';

// AsyncStorage.clear(); // use to reset storage during development

const store = compose(autoRehydrate(), applyMiddleware(...middleware))(createStore)(rootReducer);

export const restore = (onFinished?: () => void) =>
  persistStore(
    store,
    {
      blacklist: ['app', 'presence', 'nav'],
      storage: AsyncStorage,
    },
    onFinished,
  );

export default store;


text:
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from collections import defaultdict

import ray
import ray.cloudpickle as cloudpickle

# This string should be identical to the name of the signal sent upon
# detecting that an actor died.
# This constant is also used in NodeManager::PublishActorStateTransition()
# in node_manager.cc
ACTOR_DIED_STR = "ACTOR_DIED_SIGNAL"

class Signal(object):
    """Base class for Ray signals."""
    pass

class ErrorSignal(Signal):
    """Signal raised if an exception happens in a task or actor method."""

    def __init__(self, error):
        self.error = error

class ActorDiedSignal(Signal):
    """Signal raised if an exception happens in a task or actor method."""

    def __init__(self):
        pass

def _get_task_id(source):
    """Return the task id associated to the generic source of the signal.

    Args:
        source: source of the signal, it can be either an object id returned
            by a task, a task id, or an actor handle.

    Returns:
        - If source is an object id, return id of task which creted object.
        - If source is an actor handle, return id of actor's task creator.
        - If source is a task id, return same task id.
    """
    if type(source) is ray.actor.ActorHandle:
        return source._ray_actor_id
    else:
        if type(source) is ray.TaskID:
            return source
        else:
            return ray._raylet.compute_task_id(source)

def send(signal):
    """Send signal.

    The signal has a unique identifier that is computed from (1) the id
    of the actor or task sending this signal (i.e., the actor or task calling
    this function), and (2) an index that is incremented every time this
    source sends a signal. This index starts from 1.

    Args:
        signal: Signal to be sent.
    """
    if hasattr(ray.worker.global_worker, "actor_creation_task_id"):
        source_key = ray.worker.global_worker.actor_id.hex()
    else:
        # No actors; this function must have been called from a task
        source_key = ray.worker.global_worker.current_task_id.hex()

    encoded_signal = ray.utils.binary_to_hex(cloudpickle.dumps(signal))
    ray.worker.global_worker.redis_client.execute_command(
        "XADD " + source_key + " * signal " + encoded_signal)

def receive(sources, timeout=None):
    """Get all outstanding signals from sources.

    A source can be either (1) an object ID returned by the task (we want
    to receive signals from), or (2) an actor handle.

    When invoked by the same entity E (where E can be an actor, task or
    driver), for each source S in sources, this function returns all signals
    generated by S since the last receive() was invoked by E on S. If this is
    the first call on S, this function returns all past signals generated by S
    so far. Note that different actors, tasks or drivers that call receive()
    on the same source S will get independent copies of the signals generated
    by S.

    Args:
        sources: List of sources from which the caller waits for signals.
            A source is either an object ID returned by a task (in this case
            the object ID is used to identify that task), or an actor handle.
            If the user passes the IDs of multiple objects returned by the
            same task, this function returns a copy of the signals generated
            by that task for each object ID.
        timeout: Maximum time (in seconds) this function waits to get a signal
            from a source in sources. If None, the timeout is infinite.

    Returns:
        A list of pairs (S, sig), where S is a source in the sources argument,
            and sig is a signal generated by S since the last time receive()
            was called on S. Thus, for each S in sources, the return list can
            contain zero or multiple entries.
    """

    # If None, initialize the timeout to a huge value (i.e., over 30,000 years
    # in this case) to "approximate" infinity.
    if timeout is None:
        timeout = 10**12

    if timeout < 0:
        raise ValueError("The 'timeout' argument cannot be less than 0.")

    if not hasattr(ray.worker.global_worker, "signal_counters"):
        ray.worker.global_worker.signal_counters = defaultdict(lambda: b"0")

    signal_counters = ray.worker.global_worker.signal_counters

    # Map the ID of each source task to the source itself.
    task_id_to_sources = defaultdict(lambda: [])
    for s in sources:
        task_id_to_sources[_get_task_id(s).hex()].append(s)

    # Construct the redis query.
    query = "XREAD BLOCK "
    # Multiply by 1000x since timeout is in sec and redis expects ms.
    query += str(1000 * timeout)
    query += " STREAMS "
    query += " ".join([task_id for task_id in task_id_to_sources])
    query += " "
    query += " ".join([
        ray.utils.decode(signal_counters[ray.utils.hex_to_binary(task_id)])
        for task_id in task_id_to_sources
    ])

    answers = ray.worker.global_worker.redis_client.execute_command(query)
    if not answers:
        return []

    results = []
    # Decoding is a little bit involved. Iterate through all the answers:
    for i, answer in enumerate(answers):
        # Make sure the answer corresponds to a source, s, in sources.
        task_id = ray.utils.decode(answer[0])
        task_source_list = task_id_to_sources[task_id]
        # The list of results for source s is stored in answer[1]
        for r in answer[1]:
            for s in task_source_list:
                if r[1][1].decode("ascii") == ACTOR_DIED_STR:
                    results.append((s, ActorDiedSignal()))
                else:
                    # Now it gets tricky: r[0] is the redis internal sequence
                    # id
                    signal_counters[ray.utils.hex_to_binary(task_id)] = r[0]
                    # r[1] contains a list with elements (key, value), in our
                    # case we only have one key "signal" and the value is the
                    # signal.
                    signal = cloudpickle.loads(
                        ray.utils.hex_to_binary(r[1][1]))
                    results.append((s, signal))

    return results

def forget(sources):
    """Ignore all previous signals associated with each source S in sources.

    The index of the next expected signal from S is set to the index of
    the last signal that S sent plus 1. This means that the next receive()
    on S will only get the signals generated after this function was invoked.

    Args:
        sources: list of sources whose past signals are forgotten.
    """
    # Just read all signals sent by all sources so far.
    # This will results in ignoring these signals.
    receive(sources, timeout=0)

def reset():
    """
    Reset the worker state associated with any signals that this worker
    has received so far.

    If the worker calls receive() on a source next, it will get all the
    signals generated by that source starting with index = 1.
    """
    if hasattr(ray.worker.global_worker, "signal_counters"):
        ray.worker.global_worker.signal_counters = defaultdict(lambda: b"0")


text:
package com.krillsson.sysapi.core.monitoring.monitors;

import com.krillsson.sysapi.core.domain.system.SystemLoad;
import com.krillsson.sysapi.core.monitoring.Monitor;

import java.time.Duration;

import static com.krillsson.sysapi.core.metrics.Empty.DRIVE_LOAD;

public class DriveMonitor extends Monitor {
    public DriveMonitor(String id, Duration inertia, double threshold) {
        super(id, inertia, threshold);
    }

    @Override
    protected double value(SystemLoad systemLoad) {
        return (double) systemLoad.getDriveLoads()
                .stream()
                .filter(i -> i.getName().equalsIgnoreCase(id()))
                .findFirst().orElse(DRIVE_LOAD)
                .getValues()
                .getUsableSpace();
    }

    @Override
    protected boolean isOutsideThreshold(double value) {
        return value < threshold();
    }

    @Override
    protected MonitorType type() {
        return MonitorType.DRIVE;
    }

    @Override
    public String toString() {
        return "DriveMonitor{" +
                "threshold=" + threshold() +
                '}';
    }
}


text:
var _ = require('lodash'),
    EntityHelper = require('../entity_helper'),
    TaxRate = require('../../entities/accounting/taxrate').TaxRate,
    util = require('util')

var TaxRates = EntityHelper.extend({
    constructor: function(application, options) {
        EntityHelper.call(this, application, Object.assign({ entityPlural: 'TaxRates' }, options));
    },
    newTaxRate: function(data, options) {
        return new TaxRate(this.application, data, options);
    },
    getTaxRates: function(options) {
        return this.getEntities(this.setUpOptions(options));
    },
    setUpOptions: function(options) {
        var self = this;
        var clonedOptions = _.clone(options || {});
        clonedOptions.entityPath = 'TaxRates';
        clonedOptions.entityConstructor = function(data) { return self.newTaxRate(data) };
        return clonedOptions;
    }
})

module.exports = TaxRates;


text:
package org.apache.ambari.server.security.credential;

import org.apache.ambari.server.AmbariException;

public class InvalidCredentialValueException extends AmbariException {

  public InvalidCredentialValueException(String msg) {
    super(msg);
  }

  public InvalidCredentialValueException(String msg, Throwable throwable) {
    super(msg, throwable);
  }
}


text:
#import "UIImageView+WMFImageFetching.h"

@class MWKImage;
@class WMFImageController;
@class WMFFaceDetectionCache;

NS_ASSUME_NONNULL_BEGIN

@interface UIImageView (WMFImageFetchingInternal)

/**
 *   The cache used to hold any detected faces
 *
 */
+ (WMFFaceDetectionCache*)faceDetectionCache;

/**
 *  The image URL associated with the receiver.
 *
 *  Used to ensure that images set on the receiver aren't associated with a URL for another metadata entity.
 */
@property (nonatomic, strong, nullable, setter = wmf_setImageURL:) NSURL* wmf_imageURL;

/**
 *  The metadata associated with the receiver.
 *
 *  This is preferred over @c wmf_imageURL since it allows for normalized face detection data to be read from and written
 *  to disk.
 *
 *  @see wmf_imageURL
 */
@property (nonatomic, strong, nullable, setter = wmf_setImageMetadata:) MWKImage* wmf_imageMetadata;

/**
 *  The image controller used to fetch image data.
 *
 *  Used to cancel the previous fetch executed by the receiver. Defaults to @c [WMFImageController sharedInstance].
 */
@property (nonatomic, weak, nullable, setter = wmf_setImageController:) WMFImageController* wmf_imageController;

/**
 *  The URL to fetch, depending on the current values of @c wmf_imageMetadata and @c wmf_imageURL.
 *
 *  @return A URL to the image to display in the receiver, or @c nil if none is set.
 */
@property (nonatomic, strong, nullable, readonly) NSURL* wmf_imageURLToFetch;

/**
 *  Fetch the receiver's @c wmf_imageURLToFetch
 *
 *  @param detectFaces Whether or not face detection & centering is desired.
 *
 *  @return A promise which resolves after the image has been successfully set and animated into view.
 */
- (AnyPromise*)wmf_fetchImageDetectFaces:(BOOL)detectFaces;

/**
 *  Cancels any ongoing fetch for the receiver's current image, using its internal @c WMFImageController.
 *
 *  @see wmf_imageURLToFetch
 */
- (void)wmf_cancelImageDownload;

@end

NS_ASSUME_NONNULL_END


text:
/**
 */
package iso20022.impl;

import org.eclipse.emf.ecore.EClass;

import iso20022.Iso20022Package;
import iso20022.RepositoryType;

/**
 *
 * An implementation of the model object 'Repository Type'.
 *
 *
 * @generated
 */
public abstract class RepositoryTypeImpl extends RepositoryConceptImpl implements RepositoryType {
	/**
	 *
	 *
	 * @generated
	 */
	protected RepositoryTypeImpl() {
		super();
	}

	/**
	 *
	 *
	 * @generated
	 */
	@Override
	protected EClass eStaticClass() {
		return Iso20022Package.eINSTANCE.getRepositoryType();
	}

} //RepositoryTypeImpl


text:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>br.danielcastellani</groupId>
    <artifactId>base-jax-rs-api</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>jax-rs-base-api</name>
    <url>https://github.com/dancastellani/base-jax-rs-api</url>

    <properties>
        <jersey.version>1.17.1</jersey.version>
    </properties>

    <dependencies>
        <!--Swagger-->
        <dependency>
            <groupId>com.wordnik</groupId>
            <artifactId>swagger-jersey-jaxrs_2.10</artifactId>
            <version>1.3.0</version>
        </dependency>

        <!--Jersey-->
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-server</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-core</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-servlet</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-json</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <!-- Log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <!--JUnit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>


text:
from unittest import TestCase

from openarticlegauge.plugins.pmid import PMIDPlugin
from openarticlegauge import models
from openarticlegauge import plugin
from openarticlegauge import util

import os
# some random PMIDs obtained by just doing a search for "test" on the pubmed dataset
# and adding random numbers to the end of http://www.ncbi.nlm.nih.gov/pubmed/<number>
# e.g. http://www.ncbi.nlm.nih.gov/pubmed/1

PMIDS = [
    # These are all real PMIDs
    "1",
    "12",
    "123",
    "1234",
    "12345",
    "123456",
    "9254694",
    "23373100",
    "23373089",
    "23373059",
    "23373049",
    "23373046",
    "23373030"
]

CANONICAL = {
    "1" : "pmid:1",
    "12" : "pmid:12",
    "123" : "pmid:123",
    "1234" : "pmid:1234",
    "12345" : "pmid:12345",
    "123456" : "pmid:123456",
    "9254694" : "pmid:9254694",
    "23373100" : "pmid:23373100",
    "23373089" : "pmid:23373089",
    "23373059" : "pmid:23373059",
    "23373049" : "pmid:23373049",
    "23373046" : "pmid:23373046",
    "23373030" : "pmid:23373030"
}

ENTREZ_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources", "entrez_pmid_23175652.xml")
NCBI_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources", "ncbi.23175652.html")
NCBI_NO_ICON_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources", "ncbi.1234567.html")

class MockResponse():
    def __init__(self, status):
        self.status_code = status
        self.text = None
        self.url = None

def get_doi(url):
    resp = MockResponse(200)
    if url == "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=23175652&retmode=xml":
        with open(ENTREZ_FILE) as f:
            resp.text = f.read()
    elif url == "http://dx.doi.org/10.1128/JB.01321-12":
        resp.url = "http://jb.asm.org/content/195/3/502"
    return resp

def get_icon(url):
    resp = MockResponse(200)
    if url == "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=23175652&retmode=xml":
        resp.text = ""
    elif url == "http://www.ncbi.nlm.nih.gov/pubmed/23175652":
        with open(NCBI_FILE) as f:
            resp.text = f.read()
    return resp

def get_linkout(url):
    resp = MockResponse(200)
    if url == "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=1234567&retmode=xml":
        resp.text = ""
    elif url == "http://www.ncbi.nlm.nih.gov/pubmed/1234567":
        with open(NCBI_NO_ICON_FILE) as f:
            resp.text = f.read()
    return resp

def get_doi_fail(url):
    if url == "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=23175652&retmode=xml":
        with open(ENTREZ_FILE) as f:
            resp = MockResponse(200)
            resp.text = f.read()
            return resp
    elif url == "http://dx.doi.org/10.1128/JB.01321-12":
        resp = MockResponse(404)
        return resp

class TestPmid(TestCase):

    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_01_detect_verify_type_real_pmids(self):
        pmid = PMIDPlugin()
        counter = 0
        for d in PMIDS:
            bjid = {'id' : d}
            record = models.MessageObject(bid=bjid)
            pmid.type_detect_verify(record)
            assert bjid.has_key("type")
            assert bjid["type"] == "pmid"
            counter += 1
        assert counter == len(PMIDS)
        assert counter > 0

    def test_02_detect_verify_type_not_pmids(self):
        #Test the various erroneous PMID possibilities, which will include:
        #- less than 1 and more than 8 digits
        #- random strings (i.e. not just digits)
        pmid = PMIDPlugin()

        # some random digits
        bjid = {'id' : 'qp23u4ehjkjewfiuwqr'} # random numbers and digits
        record = models.MessageObject(bid=bjid)
        pmid.type_detect_verify(record)
        assert not bjid.has_key("type")

        bjid = {'id' : 'qp23u4.10238765.jewfiuwqr'} # has an 8 digit substring in it
            # (therefore also has a 1,2..7-digit substring in it)
        record = models.MessageObject(bid=bjid)
        pmid.type_detect_verify(record)
        assert not bjid.has_key("type")

        # less than 1 and more than 8 digits
        bjid = {'id' : ''} # well, less than 1 digit doesn't exist
            # and letters are covered elsewhere...
        record = models.MessageObject(bid=bjid)
        pmid.type_detect_verify(record)
        assert not bjid.has_key("type")

        bjid = {'id' : '123456789'}
        record = models.MessageObject(bid=bjid)
        pmid.type_detect_verify(record)
        assert not bjid.has_key("type")

    def test_03_detect_verify_type_ignores(self):
        pmid = PMIDPlugin()

        bjid = {"id" : "whatever", "type" : "doi"}
        record = models.MessageObject(bid=bjid)
        pmid.type_detect_verify(record)
        assert bjid['type'] == "doi"

        bjid = {"key" : "value"}
        record = models.MessageObject(record={"identifier" : bjid})
        pmid.type_detect_verify(record)
        assert not bjid.has_key("type")

    def test_04_detect_verify_type_error(self):
        # create an invalid pmid and assert it is a pmid
        pmid = PMIDPlugin()
        bjid = {"id" : "a;lkdsjfjdsajadskja", "type" : "pmid"}
        record = models.MessageObject(bid=bjid)
        with self.assertRaises(models.LookupException):
            pmid.type_detect_verify(record)

    def test_05_canonicalise_real(self):
        pmid = PMIDPlugin()
        counter = 0
        for d in CANONICAL.keys():
            bjid = {'id' : d, 'type' : 'pmid'}
            record = models.MessageObject(bid=bjid)
            pmid.canonicalise(record)
            assert bjid.has_key("canonical")
            assert bjid["canonical"] == CANONICAL[d]
            counter += 1
        assert counter == len(CANONICAL.keys())
        assert counter > 0

    def test_06_canonicalise_ignore(self):
        pmid = PMIDPlugin()
        bjid = {"id" : "whatever", "type" : "doi"}
        record = models.MessageObject(bid=bjid)
        pmid.canonicalise(record)
        assert not bjid.has_key("canonical")

    def test_07_canonicalise_error(self):
        pmid = PMIDPlugin()
        # create an invalid pmid and assert it is a pmid
        bjid = {"id" : "a;lkdsjfjdsajadskja", "type" : "pmid"}
        record = models.MessageObject(bid=bjid)
        with self.assertRaises(models.LookupException):
            pmid.canonicalise(record)

        bjid = {"key" : "value"}
        record = models.MessageObject(record={"identifier" : bjid})
        with self.assertRaises(models.LookupException):
            pmid.canonicalise(record)

    def test_08_provider_resolve_not_relevant(self):
        pmid = PMIDPlugin()
        record = models.MessageObject()

        pmid.detect_provider(record)
        assert len(record.record.keys()) == 0

        record.id = "123"
        record.identifier_type = "doi"
        record.canonical = "doi:123"
        pmid.detect_provider(record)
        assert not "provider" in record.record

        record.identifier_type = "pmid"
        record.canonical = None
        pmid.detect_provider(record)
        assert not "provider" in record.record

    def test_09_provider_resolve_doi(self):
        old_get = util.http_get
        util.http_get = get_doi
        pmid = PMIDPlugin()

        record = {"identifier" : {"id" : "23175652", "type" : "pmid", "canonical" : "pmid:23175652"}}
        record = models.MessageObject(record=record)

        pmid.detect_provider(record)

        record = record.record
        assert "provider" in record
        assert "url" in record["provider"]
        assert record['provider']["url"][0] == "http://jb.asm.org/content/195/3/502", record['provider']['url']
        assert record["provider"]["doi"] == "doi:10.1128/JB.01321-12"

        util.http_get = old_get

    def test_10_provider_resolve_from_icon(self):
        old_get = util.http_get
        util.http_get = get_icon
        pmid = PMIDPlugin()

        record = {"identifier" : {"id" : "23175652", "type" : "pmid", "canonical" : "pmid:23175652"}}
        record = models.MessageObject(record=record)

        pmid.detect_provider(record)

        record = record.record
        assert "provider" in record
        assert "url" in record["provider"]
        assert record['provider']["url"][0] == "http://jb.asm.org/cgi/pmidlookup?view=long&pmid=23175652", record['provider']["url"][0]

        util.http_get = old_get

    def test_11_provider_resolve_from_resources(self):
        old_get = util.http_get
        util.http_get = get_linkout
        pmid = PMIDPlugin()

        record = {"identifier" : {"id" : "1234567", "type" : "pmid", "canonical" : "pmid:1234567"}}
        record = models.MessageObject(record=record)

        pmid.detect_provider(record)

        record = record.record
        assert "provider" in record
        assert "url" in record["provider"]
        assert "http://www.nlm.nih.gov/medlineplus/menopause.html" in record["provider"]["url"], record["provider"]["url"]
        assert "http://toxnet.nlm.nih.gov/cgi-bin/sis/search/r?dbs+hsdb:@term+@rn+50-28-2" in record["provider"]["url"], record["provider"]["url"]
        assert len(record['provider']['url']) == 2

        util.http_get = old_get

    def test_12_provider_resolve_fail_doi(self):
        old_get = util.http_get
        util.http_get = get_doi_fail
        pmid = PMIDPlugin()

        record = {"identifier" : {"id" : "23175652", "type" : "pmid", "canonical" : "pmid:23175652"}}
        record = models.MessageObject(record=record)

        with self.assertRaises(plugin.PluginException):
            pmid.detect_provider(record)

        util.http_get = old_get