buildSnnGraph.js

import * as utils from "./utils.js";
import * as gc from "./gc.js";
import { FindNearestNeighborsResults, findNearestNeighbors } from "./findNearestNeighbors.js";

/**
 * Wrapper around the SNN graph object on the Wasm heap, produced by {@linkcode buildSnnGraph}.
 * @hideconstructor
 */
export class BuildSnnGraphResults {
    #id;
    #graph;

    constructor(id, raw) {
        this.#id = id;
        this.#graph = raw;
        return;
    }

    /**
     * @return Frees the memory allocated on the Wasm heap for this object.
     * This invalidates this object and all references to it.
     */
    free() {
        if (this.#graph !== null) {
            gc.release(this.#id);
            this.#graph = null;
        }
        return;
    }

    // Not documented, internal use only.
    get graph() {
        return this.#graph;
    }
}

/**
 * Build a shared nearest graph where each cell is a node.
 * Edges are formed between cells that share one or more nearest neighbors, weighted by the number or rank of those shared neighbors.
 *
 * @param {BuildNeighborSearchIndexResults|FindNearestNeighborsResults} x A pre-built neighbor search index from {@linkcode buildNeighborSearchIndex}.
 *
 * Alternatively, a pre-computed set of neighbor search results from {linkcode findNearestNeighbors}.
 * The number of neighbors should be equal to `neighbors`, otherwise a warning is raised.
 * @param {object} [options={}] - Optional parameters.
 * @param {number} [options.scheme="rank"] - Weighting scheme for the edges between cells.
 * This can be based on the top ranks of the shared neighbors (`"rank"`),
 * the number of shared neighbors (`"number"`) 
 * or the Jaccard index of the neighbor sets between cells (`"jaccard"`).
 * @param {number} [options.neighbors=10] - Number of nearest neighbors to use to construct the graph.
 * Ignored if `x` is a {@linkplain FindNearestNeighborsResults} object.
 * @param {?number} [options.numberOfThreads=null] - Number of threads to use.
 * If `null`, defaults to {@linkcode maximumThreads}.
 *
 * @return {BuildSnnGraphResults} Object containing the graph.
 */
export function buildSnnGraph(x, options = {}) {
    const { scheme = "rank", neighbors = 10, numberOfThreads = null, ...others } = options;
    utils.checkOtherOptions(others);
    var output;
    var my_neighbors;
    let nthreads = utils.chooseNumberOfThreads(numberOfThreads);

    utils.matchOptions("scheme", scheme, [ "rank", "number", "jaccard" ]);

    try {
        let ref;
        if (x instanceof FindNearestNeighborsResults) {
            if (neighbors != x.numberOfNeighbors()) {
                console.warn("number of neighbors in 'x' does not match 'neighbors'");
            }
            ref = x;
        } else {
            my_neighbors = findNearestNeighbors(x, neighbors, { numberOfThreads: nthreads }); 
            ref = my_neighbors ; // separate assignment is necessary for only 'my_neighbors' but not 'x' to be freed.
        }

        output = gc.call(
            module => module.build_snn_graph(ref.results, scheme, nthreads),
            BuildSnnGraphResults
        );

    } catch(e) {
        utils.free(output);
        throw e;

    } finally {
        utils.free(my_neighbors);
    }

    return output;
}