| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| /** | |
| * Check if value is a promise type | |
| * | |
| * @param value The input value | |
| * @returns Whether value is promise | |
| */ | |
| export function isPromise(value: any): boolean { | |
| return value !== undefined && ( | |
| typeof value == "object" || typeof value == "function" | |
| ) && typeof value.then == "function"; | |
| } | |
| /** | |
| * Convert string to Uint8array. | |
| * @param str The string. | |
| * @returns The corresponding Uint8Array. | |
| */ | |
| export function StringToUint8Array(str: string): Uint8Array { | |
| const arr: Uint8Array = new TextEncoder().encode(str); | |
| const resArr = new Uint8Array(arr.length + 1); | |
| for (let i = 0; i < arr.length; ++i) { | |
| resArr[i] = arr[i]; | |
| } | |
| resArr[arr.length] = 0; | |
| return resArr; | |
| } | |
| /** | |
| * Convert Uint8array to string. | |
| * @param array The array. | |
| * @returns The corresponding string. | |
| */ | |
| export function Uint8ArrayToString(arr: Uint8Array): string { | |
| const ret = []; | |
| for (const ch of arr) { | |
| ret.push(String.fromCharCode(ch)); | |
| } | |
| return ret.join(""); | |
| } | |
| /** | |
| * Internal assert helper | |
| * @param condition The condition to fail. | |
| * @param msg The message. | |
| */ | |
| export function assert(condition: boolean, msg?: string): asserts condition { | |
| if (!condition) { | |
| throw new Error("AssertError:" + (msg || "")); | |
| } | |
| } | |
| /** | |
| * Get the path to the wasm library in nodejs. | |
| * @return The wasm path. | |
| */ | |
| export function wasmPath(): string { | |
| return __dirname + "/wasm"; | |
| } | |
| /** | |
| * Linear congruential generator for random number generating that can be seeded. | |
| * | |
| * Follows the implementation of `include/tvm/support/random_engine.h`, which follows the | |
| * specification in https://en.cppreference.com/w/cpp/numeric/random/linear_congruential_engine. | |
| * | |
| * Note `Number.MAX_SAFE_INTEGER = 2^53 - 1`, and our intermediates are strictly less than 2^48. | |
| */ | |
| export type RNGState = number; | |
| export class LinearCongruentialGenerator { | |
| readonly modulus: number; | |
| readonly multiplier: number; | |
| readonly increment: number; | |
| // Always within the range (0, modulus) non-inclusive; if 0, will forever generate 0. | |
| private rand_state: number; | |
| /** | |
| * Set modulus, multiplier, and increment. Initialize `rand_state` according to `Date.now()`. | |
| */ | |
| constructor() { | |
| this.modulus = 2147483647; // 2^31 - 1 | |
| this.multiplier = 48271; // between 2^15 and 2^16 | |
| this.increment = 0; | |
| this.setSeed(Date.now()); | |
| } | |
| /** | |
| * Sets `rand_state` after normalized with `modulus` to ensure that it is within range. | |
| * @param seed Any integer. Used to set `rand_state` after normalized with `modulus`. | |
| * | |
| * Postcondition: pass `checkRandState()`, i.e. rand_state > 0 and is an integer. | |
| */ | |
| setSeed(seed: number) { | |
| if (!Number.isInteger(seed)) { | |
| throw new Error("Seed should be an integer."); | |
| } | |
| this.rand_state = seed % this.modulus; | |
| if (this.rand_state == 0) { | |
| this.rand_state = 1; | |
| } | |
| this.checkRandState(); | |
| } | |
| /** | |
| * Get the current generator state for deterministic restoration. | |
| */ | |
| getState(): RNGState { | |
| return this.rand_state; | |
| } | |
| /** | |
| * Restore a state returned by `getState()`. | |
| */ | |
| setState(state: RNGState): void { | |
| if (!Number.isInteger(state)) { | |
| throw new Error("RNG state should be an integer."); | |
| } | |
| if (state <= 0 || state >= this.modulus) { | |
| throw new Error( | |
| `RNG state should be an integer in (0, ${this.modulus}).`, | |
| ); | |
| } | |
| this.rand_state = state; | |
| } | |
| /** | |
| * Generate the next integer in the range (0, this.modulus) non-inclusive, updating `rand_state`. | |
| * | |
| * Postcondition: pass `checkRandState()`, i.e. rand_state > 0 and is an integer. | |
| */ | |
| nextInt(): number { | |
| // `intermediate` is always < 2^48, hence less than `Number.MAX_SAFE_INTEGER` due to the | |
| // invariants as commented in the constructor. | |
| const intermediate = this.multiplier * this.rand_state + this.increment; | |
| this.rand_state = intermediate % this.modulus; | |
| this.checkRandState(); | |
| return this.rand_state; | |
| } | |
| /** | |
| * Generates random float between (0, 1) non-inclusive, updating `rand_state`. | |
| * | |
| * Postcondition: pass `checkRandState()`, i.e. rand_state > 0 and is an integer. | |
| */ | |
| randomFloat(): number { | |
| return this.nextInt() / this.modulus; | |
| } | |
| private checkRandState(): void { | |
| if (this.rand_state <= 0) { | |
| throw new Error("Random state is unexpectedly not strictly positive."); | |
| } | |
| if (!Number.isInteger(this.rand_state)) { | |
| throw new Error("Random state is unexpectedly not an integer."); | |
| } | |
| } | |
| } |