dimred_DR.js

import { Matrix } from "../matrix/index.js";
import { Randomizer } from "../util/index.js";

/**
 * @class
 * @alias DR
 * @borrows DR#parameter as DR#para
 * @borrows DR#parameter as DR#p
 */
export class DR {
    /**
     * Takes the default parameters and seals them, remembers the type of input {@link X}, and initializes the random number generator.
     * @constructor
     * @memberof module:dimensionality_reduction
     * @alias DR
     * @param {Matrix|number[][]} X - the high-dimensional data.
     * @param {object} parameters - Object containing parameterization of the DR method.
     * @param {number} [parameters.d = 2] - the dimensionality of the projection.
     * @param {function} [parameters.metric = euclidean] - the metric which defines the distance between two points.
     * @param {number} [parameters.seed = 1212] - the seed value for the random number generator.
     * @returns {DR}
     */
    constructor(X, default_parameters, parameters) {
        this._parameters = Object.assign(Object.seal(default_parameters), parameters);
        if (Array.isArray(X)) {
            this._type = "array";
            this.X = Matrix.from(X);
        } else if (X instanceof Matrix) {
            this._type = "matrix";
            this.X = X;
        } else {
            throw new Error("No valid type for X!");
        }
        [this._N, this._D] = this.X.shape;
        this._randomizer = new Randomizer(this._parameters.seed);
        this._is_initialized = false;
        return this;
    }

    /**
     * Set and get parameters
     * @param {string} [name = null] - Name of the parameter. If not given then returns all parameters as an Object.
     * @param {any} [value = null] - Value of the parameter to set. If <code>name</code> is set and <code>value</code> is not given, returns the value of the respective parameter.
     * @returns {DR|any|object}
     * On setting a parameter, this function returns the DR object.
     * If <code>name</code> is set and <code>value == null</code> then return actual parameter value.
     * If <code>name</code> is not given, then returns all parameters as an Object.
     *
     * @example
     * '''
     * const DR = new druid.TSNE(X, {d: 3}); // creates a new DR object, with parameter for <code>d</code> = 3.
     * DR.parameter("d"); // returns 3,
     * DR.parameter("d", 2); // sets parameter <code>d</code> to 2 and returns <code>DR</code>.
     * '''
     */
    parameter(name = null, value = null) {
        if (name === null) {
            return Object.assign({}, this._parameters);
        }
        if (!this._parameters.hasOwnProperty(name)) {
            throw new Error(`${name} is not a valid parameter!`);
        }
        if (value !== null) {
            this._parameters[name] = value;
            this._is_initialized = false;
            return this;
        } else {
            return this._parameters[name];
        }
    }

    para(name = null, value = null) {
        return this.parameter(name, value);
    }

    p(name = null, value = null) {
        return this.parameter(name, value);
    }

    /**
     * Computes the projection.
     * @returns {Matrix} the projection.
     */
    transform() {
        this.check_init();
        return this.projection;
    }

    /**
     * Computes the projection.
     * @yields {Matrix|number[][]} the intermediate steps of the projection.
     */
    *generator() {
        yield this.transform();
    }

    /**
     * If the respective DR method has an <code>init</code> function, call it before <code>transform</code>.
     * @returns {DR}
     */
    check_init() {
        if (!this._is_initialized && typeof this.init === "function") {
            this.init();
            this._is_initialized = true;
        }
        return this;
    }

    /**
     * @returns {Matrix|number[][]} the projection in the type of input <code>X</code>.
     */
    get projection() {
        if (this.hasOwnProperty("Y")) {
            this.check_init();
            return this._type === "matrix" ? this.Y : this.Y.to2dArray;
        } else {
            throw new Error("The dataset is not transformed yet!");
        }
    }

    /**
     * Computes the projection.
     * @param  {...unknown} args - Arguments the transform method of the respective DR method takes.
     * @returns {Promise<Matrix|number[][]>} the dimensionality reduced dataset.
     */
    async transform_async(...args) {
        return this.transform(...args);
    }

    /**
     * Computes the projection.
     * @static
     * @param  {...unknown} args - Takes the same arguments of the constructor of the respective DR method.
     * @returns {Matrix|number[][]} the dimensionality reduced dataset.
     */
    static transform(...args) {
        let dr = new this(...args);
        return dr.transform();
    }

    /**
     * Computes the projection.
     * @static
     * @param  {...unknown} args - Takes the same arguments of the constructor of the respective DR method.
     * @returns {Promise} a promise yielding the dimensionality reduced dataset.
     */
    static async transform_async(...args) {
        return this.transform(...args);
    }

    /**
     * Computes the projection.
     * @static
     * @param  {...unknown} args - Takes the same arguments of the constructor of the respective DR method.
     * @returns {Generator} a generator yielding the intermediate steps of the dimensionality reduction method.
     */
    static *generator(...args) {
        const dr = new this(...args);
        const generator = dr.generator();
        for (const result of generator) {
            yield result;
        }
    }
}