// Random set of numbers with a fixed sum. Much of this code is a PHP port of // the original Matlab code, as noted. // // Author: Jeremy Epstein // Date: 24 Sep 2008 // License: GNU General Public License v2 // http://www.gnu.org/licenses/gpl-2.0.txt using System; using System.Collections.Generic; using System.Text; namespace RandomNess { /// /// Random number utility class. /// class RandomNess { public Random random; public RandomNess() { random = new Random(); } /// /// Generates a random number between min and max. /// public int RandomNumber(int min, int max) { return random.Next(min, max); } /// /// Generates a set of random numbers, such that all numbers are within /// a given range, and such that the sum of all numbers is equal to /// a user-defined value. /// /// This is a C# port of the randfixedsum function, written in Matlab /// by Roger Stafford, and with minimal modifications from the original /// code. Randfixedsum can be found here: /// http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=9700&objectType=file /// /// Number of elements in the set. /// Total value that the sum of all the elements must /// equal. /// Minimum value of a given element. /// Maximum value of a given element. public List RandFixedSum(double size, double sum, double min, double max) { // Check the arguments if (size < 1) { throw new System.ArgumentException("RandomNumber.RandFixedSum was passed arguments that violate the rule: size < 1"); } if (sum < size * min) { throw new System.ArgumentException("RandomNumber.RandFixedSum was passed arguments that violate the rule: sum < size * min"); } if (sum > size * max) { throw new System.ArgumentException("RandomNumber.RandFixedSum was passed arguments that violate the rule: sum > size * max"); } if (min >= max) { throw new System.ArgumentException("RandomNumber.RandFixedSum was passed arguments that violate the rule: min >= max"); } // Rescale to a unit cube: 0 <= x(i) <= 1 sum = (sum - size * min) / (max - min); // Construct the transition probability table, t. // t(i,j) will be utilized only in the region where j <= i + 1. // Must have 0 <= k <= size - 1 double k = Math.Max(Math.Min(Math.Floor(sum), (size - 1f)), 0f); // Must have k <= sum <= k + 1 sum = Math.Max(Math.Min(sum, k + 1f), k); // s1 & s2 will never be negative List s1 = new List(), s2 = new List(); foreach (double s1Element in new Range(k, k - size + 1f, -1f)) { s1.Add(sum - s1Element); } foreach (double s2Element in new Range(k + size, k + 1f, -1f)) { s2.Add(s2Element - sum); } // Scale for full 'double' range List> w = new List>(); for (int i = 0; i < size; i++) { w.Add(ListFiller.GetList(size + 1, 0f)); } w[0][1] = double.MaxValue; List> t = new List>(); for (int i = 0; i < size - 1; i++) { t.Add(ListFiller.GetList(size, 0f)); } // The smallest positive matlab 'double' no. double tiny = Math.Pow(2f, -1074f); for (int i = 1; i < size; i++) { List tmp1 = new List(i + 1), tmp2 = new List(i + 1), tmp3 = new List(i + 1), tmp4 = new List(i + 1); int sizeIndex = (int)size - (i + 1); for (int j = 0; j <= i; j++) { tmp1.Add(w[i - 1][j + 1] * (s1[j] / ((double)i + 1f))); tmp2.Add(w[i - 1][j] * (s2[sizeIndex] / ((double)i + 1f))); sizeIndex++; } for (int j = 0; j <= i; j++) { w[i][j + 1] = tmp1[j] + tmp2[j]; } sizeIndex = (int)size - (i + 1); for (int j = 0; j <= i; j++) { // In case tmp1 & tmp2 are both 0, tmp3.Add(w[i][j + 1] + tiny); // then t is 0 on left & 1 on right tmp4.Add(s2[sizeIndex] > s1[j] ? 1f : 0f); t[i - 1][j] = (tmp2[j] / tmp3[j]) * tmp4[j] + (1f - tmp1[j] / tmp3[j]) * (tmp4[j] == 0f ? 1f : 0f); sizeIndex++; } } // Derive the polytope volume v from the appropriate // element in the bottom row of w. double v = Math.Pow(size, 3f / 2f) * (w[(int)size - 1][(int)k + 1] / Double.MaxValue) * Math.Pow(max - min, size - 1); // Now compute the matrix x. List x = ListFiller.GetList(size, 0f); List rt = new List((int)size - 1); List rs = new List((int)size - 1); for (int i = 0; i < size - 1; i++) { // For random selection of simplex type rt.Add(random.NextDouble()); // For random location within a simplex rs.Add(random.NextDouble()); } // Start with sum zero & product 1 double sm = 0f, pr = 1f; int jj = 0; // Work backwards in the t table for (int i = (int)size - 1; i > 0; i--) { double e, sx; // Use rt to choose a transition e = rt[jj] <= t[i - 1][(int)k] ? 1f : 0f; // Use rs to compute next simplex coord. sx = Math.Pow(rs[jj], 1f / (double)i); // Update sum sm += (1f - sx) * pr * sum / (double)(i + 1); // Update product pr *= sx; // Calculate x using simplex coords. x[jj] = sm + pr * e; // Transition adjustment sum -= e; k -= e; jj++; } // Compute the last x x[(int)size - 1] = sm + pr * sum; // Randomly permute the order in the columns of x and rescale. List p = new List((int)size); for (int i = 0; i < size; i++) { p.Add(i); } // Use p to carry out a matrix 'randperm' try { p.Sort(delegate(int p1, int p2) { return ((p1 == p2) ? 0 : RandomNumber(-1, 1)); }); } catch (ArgumentException e) { // Sort sometimes throws an IComparable exception. // This doesn't seem to affect the result of the algorithm, // so catch and ignore the error. } List ret = new List((int)size); for (int i = 0; i < size; i++) { // Permute & rescale x ret.Add((double)(max - min) * (double)x[p[i]] + (double)min); } return ret; } /// /// Converts the floating-point numbers returned by the randfixedsum /// algorithm into rounded integers. Adds or subtracts any disrepancy /// from the total to random numbers in the set, as needed. /// /// Number of elements in the set. /// Total value that the sum of all the elements must /// equal. /// Minimum value of a given element. /// Maximum value of a given element. public List RandFixedSum(int size, int sum, int min, int max) { List listDbl = RandFixedSum((double)size, (double)sum, (double)min, (double)max); List listInt = new List(size); int sumOfRounded = 0; foreach (double randDbl in listDbl) { int randInt = (int)Math.Round(randDbl); listInt.Add(randInt); sumOfRounded += randInt; } // Randomly permute the order in the columns of listInt and rescale. List p = new List((int)size); for (int i = 0; i < size; i++) { p.Add(i); } // Use p to carry out a matrix 'randperm' try { p.Sort(delegate(int p1, int p2) { return ((p1 == p2) ? 0 : RandomNumber(-1, 1)); }); } catch (ArgumentException e) { // Sort sometimes throws an IComparable exception. // This doesn't seem to affect the result of the algorithm, // so catch and ignore the error. } // The total of the rounded integers is lower than the total // that we want. if (sumOfRounded < sum) { // Start by searching for an element with value 'min', and // increase if unsuccessful. int seekAmt = min; while (seekAmt < max && sumOfRounded < sum) { // Try and raise the rounded total to equal the required // total, by adding onto a single element in the set. int addAmt = sum - sumOfRounded; bool isAdded = false; // No single element in the set can exceed max. In cases // where adding (sum - sumOfRounded) would violate this, // add as much as is permitted, and leave the rest for // another element. if (seekAmt + addAmt > max) { addAmt = max - seekAmt; } for (int i = 0; i < size; i++) { if (listInt[p[i]] == seekAmt) { listInt[p[i]] += addAmt; sumOfRounded += addAmt; isAdded = true; break; } } if (!isAdded) { seekAmt++; } } } // Or the total of the rounded integers is higher than the total // that we want. else if (sumOfRounded > sum) { // Start by searching for an element with value 'max', and // decrease if unsuccessful. int seekAmt = max; while (seekAmt > min && sumOfRounded > sum) { // Try and lower the rounded total to equal the required // total, by subtracting from a single element in the set. int subtractAmt = sumOfRounded - sum; bool isSubtracted = false; // No single element in the set can be lower than min. // In cases where subtracting (sumOfRounded - sum) // would violate this, subtract as much as is permitted, // and leave the rest for another element. if (seekAmt - subtractAmt < min) { subtractAmt = seekAmt - min; } for (int i = 0; i < size; i++) { if (listInt[p[i]] == seekAmt) { listInt[p[i]] -= subtractAmt; sumOfRounded -= subtractAmt; isSubtracted = true; break; } } if (!isSubtracted) { seekAmt--; } } } return listInt; } } /// /// Creates a List of doubles, with "size" copies of "value". /// Similar to PHP's array_fill() function. /// class ListFiller { public static List GetList(double size, double value) { List filledList = new List((int)size); for (int i = 0; i < size; i++) { filledList.Add(value); } return filledList; } } /// /// Utility class for an iterable sequence of values. /// class Range : System.Collections.IEnumerable { private readonly double start; private double stop; private double step; public Range(double start, double stop, double step) { this.start = start; this.stop = stop; this.step = step; } public void Do(Action f) { foreach (double i in this) { f(i); } } public System.Collections.IEnumerator GetEnumerator() { double max = Math.Max(start, stop); double min = Math.Min(start, stop); if (step == default(double)) { step = (start == min) ? 1 : -1; } double current = start; while (current >= min && current <= max) { yield return current; current += step; } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } }