package squidpony.squidmath; import squidpony.StringKit; import java.io.Serializable; /** * A quasi-random number generator that goes through one of many sub-random sequences found by J.G. van der Corput. * More specifically, this offers both the normal van der Corput sequence, which only changes the state by incrementing * it (this works better in the normal usage as part of a 2D or 3D point generator) and a kind of scrambled van der * Corput sequence, where the state changes unpredictably (this works better when using this to generate 1D sequences, * or when the base may be larger than 7 or non-prime). In both cases, the state is internally stored in a 64-bit long * that is incremented once per generated number, but when scramble is true, the state is altered with something similar * to a Gray code before being used; more on this later if you want to read about it. The important things to know about * this class are: size of state affects speed (prefer smaller seeds, but quality is sometimes a bit poor at first if * you start at 0); the base (when given) should be prime and moderately small (or very small if scramble is false, any * of 2, 3, 5, or 7 should be safe); this doesn't generate very random numbers when scramble is false (which can be good * for making points that should not overlap), but it will seem much more random when scramble is true; this is a * StatefulRandomness with an additional method for generating quasi-random doubles, {@link #nextDouble()}; and there * are several static methods offered for convenient generation of points on the related Halton sequence (as well as * faster generation of doubles in the base-2 van der Corput sequence, and a special method that switches which base * it uses depending on the index to seem even less clearly-patterned). * <br> * This generator allows a base (also called a radix) that changes the sequence significantly; a base should be prime, * and this performs a little better in terms of time used with larger primes, though quality is also improved by * preferring primes that aren't very large relative to the quantity of numbers expected to be generated. Unfortunately, * performance is not especially similar to conventional PRNGs; smaller (positive) state values are processed more * quickly than larger ones, or most negative states. At least one 64-bit integer modulus and one 64-bit integer * division are required for any number to be produced, and the amount of both of these relatively-non-cheap operations * increases linearly with the number of digits (in the specified base, which is usually not 10) needed to represent * the current state. Since performance is not optimal, and the results are rather strange anyway relative to PRNGs, * this should not be used as a direct substitute for a typical RandomnessSource (however, it is similar to, but simpler * than, {@link squidpony.squidmath.SobolQRNG}). So what's it good for? * <br> * A VanDerCorputSequence can be a nice building block for more complicated quasi-randomness, especially for points in * 2D or 3D, when scramble is false. There's a simple way that should almost always "just work" as the static method * {@link #halton(int, int, int)} here. If it doesn't meet your needs, there's a little more complexity involved. Using * a VanDerCorputQRNG with base 3 for the x-axis and another VanDerCorputQRNG with base 5 for the y-axis, requesting a * double from each to make points between (0.0, 0.0) and (1.0, 1.0), has an interesting trait that can be desirable for * many kinds of positioning in 2D: once a point has been generated, an identical point will never be generated until * floating-point precision runs out, but more than that, nearby points will almost never be generated for many * generations, and most points will stay at a comfortable distance from each other if the bases are different and both * prime (as well as, more technically, if they share no common denominators). This is also known as a Halton sequence, * which is a group of sequences of points instead of simply numbers. The choices of 3 and 5 are just examples; any two * different primes will technically work for 2D, but patterns can become noticeable with primes larger than about 7, * with 11 and 13 sometimes acceptable. Three VanDerCorputQRNG sequences could be used for a Halton sequence of 3D * points, using three different prime bases, four for 4D, etc. SobolQRNG can be used for the same purpose, but the * points it generates are typically more closely-aligned to a specific pattern, the pattern is symmetrical between all * four quadrants of the square between (0.0, 0.0) and (1.0, 1.0) in 2D, and it probably extends into higher dimensions. * Using one of the possible Halton sequences gives some more flexibility in the kinds of random-like points produced. * <br> * Because just using the state in a simple incremental fashion puts some difficult requirements on the choice of base * and seed, we can use a technique like Gray codes to scramble the state. The sequence of these Gray-like codes for the * integers from 0 to 16 is {@code 0, 9, 21, 16, 50, 51, 23, 10, 4, 28, 49, 39, 246, 198, 179, 97, 393}, and this can be * generated efficiently with {@code (i * i) ^ ((i * 137) >>> 4)}. No duplicate results were found in any numbers of 20 * bits or less, and trying to find duplicates in 24 bits exhausted the testing computer's memory, without finding a * duplicate. You should be fine. * <br> * Expected output for {@link #nextDouble()} called 33 times on an instance made with {@code new VanDerCorputQRNG(11, 83L, true)}: * 0.5194317328051362, 0.8590943241581859, 0.5931288846390274, 0.5045420394781778, 0.7892220476743392, * 0.6645037907246772, 0.7809575848644218, 0.0725360289597705, 0.6322655556314459, 0.6998838877125879, * 0.5578853903421898, 0.5969537599890717, 0.6323338569769824, 0.873505908066389, 0.09514377433235435, * 0.6623864490130456, 0.4376750221979373, 0.6946929854518133, 0.4250392732736835, 0.5294720305990028, * 0.4790656375930606, 0.2626869749334062, 0.056075404685472306, 0.9758213236800765, 0.676729731575712, * 0.899118912642579, 0.1876237961887849, 0.652755959292398, 0.039683081756710606, 0.10504746943514787, * 0.2598183184208729, 0.4078273341984837, 0.3034628782187009 * <br> * Expected output for {@link #nextDouble()} called 33 times on an instance made with {@code new VanDerCorputQRNG(19, 83L, true)}: * 0.8944452544102639, 0.7842327790609341, 0.4352023081468067, 0.0696971324652205, 0.5957213342439054, * 0.8864342661581787, 0.0167739658228528, 0.8956192785506557, 0.26943470353972115, 0.3525371966145134, * 0.09754375733765087, 0.8924118139056637, 0.3585147443619984, 0.9126771587081131, 0.28926266679967155, * 0.8542138258607592, 0.8789987799356972, 0.7905019145034184, 0.1624220194749887, 0.9349836173755572, * 0.48623015477167914, 0.6716799287912155, 0.8240344994283346, 0.6557883994137552, 0.4561966221867542, * 0.07946532024769608, 0.15134168706501638, 0.1382202407900492, 0.7890439760284221, 0.7142517322611092, * 0.3069037223471275, 0.7030256060036372, 0.01678163918324752 * <br> * Note on Gray-like code implementation: This is not a typical Gray code. Normally, the operation looks like * {@code i ^ (i >>> 1)}, which gives negative results for negative values of i (not wanted here), and also clusters * two very similar numbers together for every pair of sequential numbers. An earlier version scrambled with a basic * Gray code, but the current style, {@code (i * i) ^ ((i * 137) >>> 4)}, produces much more "wild and crazy" results, * but never negative ones. The period of this, if it is seen as a RandomnessSource, is probably about 2^56, at least * for reasonably small starting states (under 1000 or so?). This is ideal for a scramble. * <br> * Created by Tommy Ettinger on 11/18/2016. */ public class VanDerCorputQRNG implements StatefulRandomness, RandomnessSource, Serializable { private static final long serialVersionUID = 5; public long state; public final int base; public final boolean scramble; /** * Constructs a new van der Corput sequence generator with base 13, starting point 83, and scrambling enabled. */ public VanDerCorputQRNG() { base = 7; state = 37; scramble = true; } /** * Constructs a new van der Corput sequence generator with the given starting point in the sequence as a seed. * Usually seed should be at least 20 with this constructor, but not drastically larger; 2000 is probably too much. * This will use a base 13 van der Corput sequence and have scrambling enabled. * @param seed the seed as a long that will be used as the starting point in the sequence; ideally positive but low */ public VanDerCorputQRNG(long seed) { base = 7; state = seed; scramble = true; } /** * Constructs a new van der Corput sequence generator with the given base (a.k.a. radix; if given a base less than * 2, this will use base 2 instead) and starting point in the sequence as a seed. Good choices for base are between * 10 and 60 or so, and should usually be prime. Good choices for seed are larger than base but not by very much, * and should generally be positive at construction time. * @param base the base or radix used for this VanDerCorputQRNG; for most uses this should be prime but small-ish * @param seed the seed as a long that will be used as the starting point in the sequence; ideally positive but low * @param scramble if true, will produce more-random values that are better for 1D; if false, better for 2D or 3D */ public VanDerCorputQRNG(int base, long seed, boolean scramble) { this.base = base < 2 ? 2 : base; state = seed; this.scramble = scramble; } /** * Gets the next quasi-random long as a fraction of {@link Long#MAX_VALUE}; this can never produce a negative value. * It is extremely unlikely to produce two identical values unless the state is very high or is negative; state * increases by exactly 1 each time this, {@link #next(int)}, or {@link #nextDouble()} is called and can potentially * wrap around to negative values after many generations. * @return a quasi-random non-negative long; may return 0 rarely, probably can't return {@link Long#MAX_VALUE} */ @Override public long nextLong() { // when scrambling the sequence, intentionally uses a non-standard Gray-like code long s = (scramble) ? (++state & 0x7fffffffffffffffL) : (++state * state) ^ (state * 137 >>> 4), num = s % base, den = base; while (den <= s) { num *= base; num += (s % (den * base)) / den; den *= base; } return (Long.MAX_VALUE / den) * num; } @Override public int next(int bits) { return (int)(nextLong()) >>> (32 - bits); } /** * Gets the next quasi-random double from between 0.0 and 1.0 (normally both exclusive; only if state is negative or * has wrapped around to a negative value can 0.0 ever be produced). It should be nearly impossible for this to * return the same number twice unless floating-point precision has been exhausted or a very large amount of numbers * have already been generated. Certain unusual bases may make this more likely, and similar numbers may be returned * more frequently if scramble is true. * @return a quasi-random double that will always be less than 1.0 and will be no lower than 0.0 */ public double nextDouble() { // when scrambling the sequence, intentionally uses a non-standard Gray-like code long s = (scramble) ? (++state & 0x7fffffffffffffffL) : (++state * state) ^ (state * 137 >>> 4), num = s % base, den = base; while (den <= s) { num *= base; num += (s % (den * base)) / den; den *= base; } return num / (double)den; } @Override public VanDerCorputQRNG copy() { return new VanDerCorputQRNG(base, state, scramble); } @Override public long getState() { return state; } @Override public void setState(long state) { this.state = state; } @Override public String toString() { return "VanDerCorputQRNG with base " + base + ", scrambling " + (scramble ? "on" : "off") + ", and state 0x" + StringKit.hex(state); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; VanDerCorputQRNG that = (VanDerCorputQRNG) o; return state == that.state && base == that.base && scramble == that.scramble; } @Override public int hashCode() { int result = (int) (state ^ (state >>> 32)); result = 31 * result + base; result = 31 * result + (scramble ? 1 : 0); return result; } /** * Convenience method that gets a quasi-random Coord between integer (0,0) inclusive and (width,height) exclusive. * This is roughly equivalent to creating two VanDerCorputQRNG generators, one with * {@code new VanDerCorputQRNG(2, index, false)} and the other with {@code new VanDerCorputQRNG(3, index, false)}, * then getting an x-coordinate from the first with {@code (int)(nextDouble() * width)} and similarly for y with the * other generator. The advantage here is you don't actually create any objects using this static method, other than * a (almost always shared) reference to the returned Coord. You might find an advantage in using values for index * that start higher than 20 or so, but you can pass sequential values for index and generally get Coords that won't * be near each other; this is not true for all parameters to Halton sequences, but it is true for this one. * @param width the maximum exclusive bound for the x-positions of Coord values this can return * @param height the maximum exclusive bound for the y-positions of Coord values this can return * @param index an int that, if unique, positive, and not too large, will usually result in unique Coord values * @return a Coord that usually will have a comfortable distance from Coords produced with close index values */ public static Coord halton(int width, int height, int index) { int s = (index+1 & 0x7fffffff), numY = s % 3, denY = 3; while (denY <= s) { numY *= 3; numY += (s % (denY * 3)) / denY; denY *= 3; } return Coord.get((int)(width * determine2(s)), numY * height / denY); } /** * Convenience method that gets a quasi-random Coord3D between integer (0,0,0) inclusive and (width,height,depth) * exclusive. This is roughly equivalent to creating three VanDerCorputQRNG generators, one with * {@code new VanDerCorputQRNG(2, index, false)} another with {@code new VanDerCorputQRNG(3, index, false)}, * and another with {@code new VanDerCorputQRNG(5, index, false)}, then getting an x-coordinate from the first with * {@code (int)(nextDouble() * width)} and similarly for y and z with the other generators. The advantage here is * you don't actually create any objects using this static method, other than a returned Coord3D. You might find an * advantage in using values for index that start higher than 20 or so, but you can pass sequential values for index * and generally get Coord3Ds that won't be near each other; this is not true for all parameters to Halton * sequences, but it is true for this one. * @param width the maximum exclusive bound for the x-positions of Coord3D values this can return * @param height the maximum exclusive bound for the y-positions of Coord3D values this can return * @param depth the maximum exclusive bound for the z-positions of Coord3D values this can return * @param index an int that, if unique, positive, and not too large, will usually result in unique Coord3D values * @return a Coord3D that usually will have a comfortable distance from Coord3Ds produced with close index values */ public static Coord3D halton(int width, int height, int depth, int index) { int s = (index+1 & 0x7fffffff), numY = s % 3, denY = 3, numZ = s % 5, denZ = 5; while (denY <= s) { numY *= 3; numY += (s % (denY * 3)) / denY; denY *= 3; } while (denY <= s) { numZ *= 5; numZ += (s % (denZ * 5)) / denZ; denZ *= 5; } return Coord3D.get((int)(width * determine2(s)), numY * height / denY, numZ * depth / denZ); } /** * Convenience method to get a double from the van der Corput sequence with the given {@code base} at the requested * {@code index} without needing to construct a VanDerCorputQRNG. You should use a prime number for base; 2, 3, 5, * and 7 should be among the first choices. This does not perform any scrambling on index other than incrementing it * and ensuring it is positive (by discarding the sign bit; for all positive index values other than 0x7FFFFFFF, * this has no effect). * <br> * Delegates to {@link #determine2(int)} when base is 2, which should offer some speed improvement. * @param base a (typically very small) prime number to use as the base/radix of the van der Corput sequence * @param index the position in the sequence of the requested base * @return a quasi-random double between 0.0 (inclusive) and 1.0 (exclusive). */ public static double determine(int base, int index) { if(base == 2) return determine2(index); int s = (index+1 & 0x7fffffff), num = s % base, den = base; while (den <= s) { num *= base; num += (s % (den * base)) / den; den *= base; } return num / (double)den; } /** * Convenience method to get a double from the van der Corput sequence with the base 2 at the requested * {@code index} without needing to construct a VanDerCorputQRNG. This does not perform any scrambling on index * other than incrementing it and ensuring it is positive (by discarding the sign bit; for all positive index values * other than 0x7FFFFFFF, this has no effect). * <br> * Because binary manipulation of numbers is easier and more efficient, this method should be somewhat faster than * the alternatives, like {@link #determine(int, int)} with base 2. * @param index the position in the sequence of the requested base * @return a quasi-random double between 0.0 (inclusive) and 1.0 (exclusive). */ public static double determine2(int index) { int s = (index+1 & 0x7fffffff), leading = Integer.numberOfLeadingZeros(s); return (Integer.reverse(s) >>> leading) / (double)(1 << (32 - leading)); } private static final int[] lowPrimes = {2, 3, 2, 3, 5, 2, 3, 2}; /** * Chooses one sequence from the van der Corput sequences with bases 2, 3, and 5, where 5 is used 1/8 of the time, * 3 is used 3/8 of the time, and 2 is used 1/2 of the time, and returns a double from the chosen sequence at the * specified {@code index}. The exact setup used for the choice this makes is potentially fragile, but in certain * circumstances this does better than {@link SobolQRNG} at avoiding extremely close values (the kind that overlap * on actual maps). Speed is not a concern here; this should be very much fast enough for the expected usage in * map generation (it's used in {@link GreasedRegion#quasiRandomSeparated(double)}. * @param index the index to use from one of the sequences * @return a double from 0.0 (inclusive, but extremely rare) to 1.0 (exclusive); values will tend to spread apart */ public static double determineMixed(int index) { return determine(lowPrimes[index & 7], index); } /** * Given any int (0 is allowed), this gets a somewhat-sub-random float from 0.0 (inclusive) to 1.0 (exclusive) * using the same implementation as {@link NumberTools#randomFloat(int)} but with index alterations. Only "weak" * because it lacks the stronger certainty of subsequent numbers being separated that the Van der Corput sequence * has. Not actually sub-random, but manages to be distributed remarkably evenly, likely due to the statistical * strength of the algorithm it uses (the same as {@link PintRNG}, derived from PCG-Random). Because PintRNG is * pseudo-random and uses very different starting states on subsequent calls to its {@link PintRNG#next(int)} * method, this method needs to perform some manipulation of index to keep a call that passes 2 for index different * enough from a call that passes 3 (this should result in 0.4257662296295166 and 0.7359509468078613). You may want * to perform a similar manipulation if you find yourself reseeding a PRNG with numerically-close seeds; this can be * done for a value called index with {@code ((index ^ 0xD0E89D2D) >>> 19 | (index ^ 0xD0E89D2D) << 13)}. * * <br> * Not all int values for index will produce unique results, since this produces a float and there are less distinct * floats between 0.0 and 1.0 than there are all ints (1/512 as many floats in that range as ints, specifically). * It should take a while calling this method before you hit an actual collision. * @param index any int * @return a float from 0.0 (inclusive) to 1.0 (exclusive) that should not be closely correlated to index */ public static float weakDetermine(int index) { return NumberTools.randomFloat((index ^= 0xD0E89D2D) >>> 19 | index << 13); //return NumberTools.longBitsToDouble(0x3ff0000000000000L | ((index<<1|1) * 0x9E3779B97F4A7C15L * ~index // - ((index ^ ~(index * 11L)) * 0x632BE59BD9B4E019L)) >>> 12) - 1.0; //return NumberTools.setExponent( // (NumberTools.setExponent((index<<1|1) * 0.618033988749895, 0x3ff)) // * (0x232BE5 * (~index)), 0x3ff) - 1.0; } /** * Like {@link #weakDetermine(int)}, but returns a float between -1.0f and 1.0f, exclusive on both. Uses * {@link NumberTools#randomSignedFloat(int)} internally but alters the index parameter so calls with nearby values * for index are less likely to have nearby results. * @param index any int * @return a sub-random float between -1.0f and 1.0f (both exclusive, unlike some other methods) */ public static float weakSignedDetermine(int index) { return NumberTools.randomSignedFloat((index ^= 0xD0E89D2D) >>> 19 | index << 13); } }