bmi-implementation/hydroLangBMI.js

//Required imports for the library usage.
import BMI from "./bmi.js";
import { Hydro } from "./globalHydro.js";
import BMIConfig from "./bmiConfig.js";

//Calling the HydroLang instance from previously defined namespace
const hydro = Hydro.ins(),
  config = new BMIConfig();

/**
 * BMI implementation of the functions of HydroLang.js
 * Creates and updates forcings and data following the models from configuration files
 * and makes calls from other modules.
 * If no link is added into the configuration, then an error will be raised.
 * All instances of HydroLang will be accessible through the BMI configuration
 * file.
 * @class
 * @name HydroLangBMI
 * @extends BMI
 * @param {Object} configfile - The configuration file object.
 * @param {string} configfile.modelName - The name of the model.
 * @param {string} configfile.componentName - The name of the component.
 * @param {string} configfile.moduleName - The name of the module.
 * @param {string} configfile.modelCode - The code of the model.
 * @param {string} configfile.functionName - The name of the function.
 * @param {Array} configfile.args - The arguments for the function.
 * @param {Array} configfile.params - The parameters for the function.
 * @param {Object} configfile.duration - The duration configuration.
 * @param {number} [configfile.duration.defaultStep] - The default step duration in hours.
 * @param {string} [configfile.duration.timeUnit] - The time unit for the default step (possible values: "s", "min", "h", "d").
 * @param {number} [configfile.duration.startTime] - The start time for the simulation in Unix timestamp.
 * @param {number} [configfile.duration.endTime] - The end time for the simulation in Unix timestamp.
 * @param {Array} configfile.inputVars - The input variables for the function.
 * @param {Array} configfile.outputVars - The output variables for the function.
 * @param {number} [configfile.now] - The current time in the simulation.
 * @param {Object} configfile.units - The units configuration.
 * @param {Object} configfile.units.input - The input units.
 * @param {Object} configfile.units.output - The output units.
 */

class HydroLangBMI extends BMI {
  constructor(configfile = undefined) {
    super();
    if (!configfile || configfile == undefined) {
      throw Error(
        "BMI_FAILURE: Cannot initialize library. Please provide configuration file!"
      );
    } else {
      this.initialize(configfile);
    }
  }

  /**
   * @method initialize
   * @memberof HydroLangBMI
   * @param {String} configfile - configuration file that contains minimum the following:
   * model name, component name, module name, model code, function name, arguments, parameters.
   * @returns {void} sets the variables from above to the this object.
   */

  initialize(configfile = undefined) {
    if (configfile) {
      fetch(configfile)
        .then((res) => res.json())
        .then((data) => {
          console.log(data);
          this._modelName = data["modelName"];
          this._componentName = data["componentName"];
          this._moduleName = data["moduleName"];
          this._modelCode = data["modelCode"];
          this._functionName = data["functionName"];
          this._args = data["args"];
          this._params = data["params"];
          this.results = [];

          //Defining the default step to be 1 unit in case there is nothing passed as a parameter
          //Time stamp parameter still dealt with in unixtime to avoid further confusion down the line
          !data["duration"]["defaultStep"]
            ? (this._defaultStep = 1 * 3600)
            : (this._defaultStep = data["duration"]["defaultStep"] * 3600);

          //Defining the default units for the time

          //Possibilities in time types: s, min, h, d
          !data["duration"]["timeUnit"]
            ? (this._timeUnit = "h")
            : (this._timeUnit = data["duration"]["timeUnit"]);

          //Time parameters
          //Assuming the required data to be used for simulations are the same as the API calls. If not, a time parameter must be specified.
          !data["duration"]["startTime"]
            ? (() => {
                var stageDate = new Date(this._args["startDate"]),
                stgUnix = stageDate.getTime() / 1000;
                this._timeUnit === "hr" ? stgUnix / 1000 : stgUnix;
                return (this._startTime = stgUnix);
              })()
            : (this._startTime = data["duration"]["startTime"]);
          !data["duration"]["endTime"]
            ? (() => {
                var stageDate = new Date(this._args["endDate"]),
                stgUnix = stageDate.getTime() / 1000;
                this._timeUnit === "hr" ? stgUnix / 1000 : stgUnix;
                return (this._endTime = stgUnix);
              })()
            : (this._endTime = data["duration"]["endTime"]);

          this._inputVars = data["inputVars"];
          this._inputVars.forEach((str) => {
            eval(`this._${str}`);
          });
          this._outputVars = data["outputVars"];
          this._outputVars.forEach((str) => {
            eval(`this._${str}`);
          });
          this._now = 0;

          this._inputUnits = data["units"]["input"];
          this._outputUnits = data["units"]["output"];

          this._params.calls
            ? console.log("Multiple Calls Required.")
            : this.handleConfig();
        });
    }
  }

  /**
   * Update the model until a required time,
   * Depending on the module and function required.
   * @method update
   * @memberof HydroLangBMI
   */

  update() {
    this.update_until();
  }

  /**
   * Updates a model and function call depending on the requirements
   * of the function and module called.
   * @method update_until
   * @memberof HydroLangBMI
   * @param {Number} time - default time to be updated depending on the model
   * @returns {void} updates the current variable to the required timestep.
   */

  update_until(time = this._now + this._defaultStep) {
    var finalUpate = Math.min(this._endDate, time);
    this._now = finalUpate;
    return this.now;
  }

  /**
   * Finalizes the requirements of a specific function.
   * @method finalize
   * @memberof HydroLangBMI
   * @returns {void} alert about model simulation being finalized.
   */

  finalize() {
    for (const prop of Object.getOwnPropertyNames(this)) {
      delete this[prop];
    }
    return alert("Model has finished");
  }

  /**
   * Gives back a model name defined by the configuration file
   * @method get_component_name
   * @memberof HydroLangBMI
   * @param {void}
   * @returns {String} model and modelcode as a full name
   */

  get_component_name() {
    return `${this._modelName} - ${this._modelCode}`;
  }

  /**
   * Lets other models see the available variables included within the model
   * @method get_input_item_count
   * @memberof HydroLangBMI
   * @param {void}
   * @return {Object} array with names as strings
   */

  get_input_item_count() {
    return Object.keys(this._inputVars).length;
  }

  /**
   * Lets other models see the number of available variables as outputs from this model
   * @method get_output_item_count
   * @memberof hydroLangBMI
   * @returns {Number} Number of output variables
   */

  get_output_item_count() {
    return this._outputVars.length;
  }

  /**
   * Returns the names of the variables used as inputs for this model
   * @method get_input_var_names
   * @memberof hydroLangBMI
   * @returns {Object[]} Array with strings of input variable names
   */

  get_input_var_names() {
    return this._inputVars;
  }

  /**
   * Returns the names of the variables used as outputs for this model
   * @method get_ouput_var_names
   * @memberof hydroLangBMI
   * @returns {Object[]} Array with strings of input variable names
   */

  get_output_var_names() {
    return this._outputVars;
  }

  /**
   * Returns the id for the grid used in a model for a specific variable.
   * HydroLang does not used a grid system. If implemented, uses 1 single location at '0' index.
   * @method get_var_grid
   * @memberof hydroLangBMI
   * @returns {Number} Grid identifier.
   */

  get_var_grid() {
    return 0;
  }

  /**
   * Returns the data type of the given variable.
   * @method get_var_type
   * @memberof HydroLangBMI
   * @param {String} name - input or output variable name, a CSDMS Standard Name
   * @returns {String} Variable nme depending on the type of variable.
   */

  get_var_type(varName) {
    varName = 0;
    return typeof varName;
  }

  /**
   * Returns the units of the given variable.
   * @method get_var_units
   * @memberof hydroLangBMI
   * @param {String} var_name
   * @returns {String} units of the variable in question.
   */

  get_var_units(var_name) {
    var unitsIn = this.value_index(var_name, this._inputVars),
      unitsOut = this.value_index(var_name, this._outputUnits);
    if (unitsIn != "object") {
      return this._inputUnits[unitsIn];
    }
    if (unitsOut != "object") {
      return this._outputUnits[unitsOut];
    }
  }

  /**
   * Returns the size, in bytes, of a single element of a variable.
   * @method get_var_itemsize
   * @memberof hydroLangBMI
   * @param {String} var_name - Input name of the variable
   * @returns {Number} Size of a single item in a variable
   */

  get_var_itemsize(var_name) {
    return this.dataTypes[typeof var_name](var_name);
  }

  /**
   * Provides the total amount of memory used for a variable.
   * @method get_var_nbytes
   * @memberof hydroLangBMI
   * @param {String} var_name - Input or ouput variable
   * @returns {Number} Total size of the variable
   */

  get_var_nbytes(var_name) {
    return this.dataTypes[typeof var_name](var_name);
  }

  /**
   * Returns the location of a variable in space or time (whether input or output)
   * @method get_var_location
   * @memberof hydroLangBMI
   * @param {String} var_name - Input or output variable
   * @returns {String} Location of the variable in the predfined structure of the model (node, edge, face)
   */

  get_var_location(var_name) {
    throw new Error("Please implement as required in your case study.");
  }

  /**
   * Start time of the model.
   * @method get_start_time
   * @memberof hydroLangBMI
   * @returns {Number} start time of the simulation as specified by the simulation file
   */
  get_start_time() {
    return this._startTime;
  }

  /**
   * End time of the model.
   * @method get_end_time
   * @memberof HydroLangBMI
   * @returns {Number} end time specified.
   */

  get_end_time() {
    return this._endTime;
  }

  /**
   * Time units of the model
   * @method get_time_units
   * @memberof HydroLangBMI
   * @returns {String} unit of time. If not specified in config file, the unit used throughout HydroLang is hour
   */

  get_time_units() {
    return this._timeUnit;
  }

  /**
   * Returns the current state of the model. For HydroLangBMI is the default time step.
   * @method get_time_step
   * @memberof HydroLangBMI
   * @returns {Number} default time step
   */

  get_time_step() {
    return this._defaultStep;
  }

  /**
   * Current time of the model
   * @method get_current_time
   * @memberof HydroLangBMI
   * @returns {Number} - The current model time.
   */

  get_current_time() {
    return this._now;
  }

  /**
   * Copies the values from a variable into an array.
   * @method get_value
   * @memberof hydroLangBMI
   * @param {String} var_name - name of input or output variable
   * @param {Object[]} array - Array object that will hold a copy of the values of the var
   * @returns {Object[]} - Array with copied values
   */

  get_value(var_name, array) {
    array = [...var_name];
    return array;
  }

  /**
   * Get a reference to the values of a given variable. Tailored towards the
   * final implementation of the
   * @method get_value_ptr
   * @memberof HydroLangBMI
   * @param {String} var_name - name of input or output variable
   * @param {Object[]} array - array object holding result
   * @returns {Error} no implementation for HydroLangBMI/JavaScript.
   */

  get_value_ptr(var_name, array) {
    throw new Error("No implementation for HydroLangBMI in JavaScript.");
  }

  /**
   * @method get_value_at_indices
   * @memberof HydroLangBMI
   * @param {String} var_name - name of input variable
   * @param {Object[]} dest - array destiny for the results
   * @param {Object[]} inds - array containing the indices
   * @returns {Object[]} destiny array with objects
   */

  get_value_at_indices(var_name, dest, inds) {
    throw new Error("Please implement as required in your case study.");
  }

  /**
   * Sets a value for an exisiting variable from an array.
   * @method set_value
   * @memberof HydroLangBMI
   * @param {String} var_name - name of the input variable
   * @param {Object[]} array - array with values to be copied
   */

  set_value(var_name, array) {
    var_name = [...array];
  }

  /**
   * Sets values at particular indices
   * @method set_value_at_indices
   * @memberof HydroLangBMI
   * @param {String} var_name - name of input variable
   * @param {Object[]} inds - array containing the indices
   * @param {Object[]} dest - array contaiing the results
   */

  set_value_at_indices(var_name, inds, dest) {
    throw new Error("Please implement as required in your case study.");
  }

  /**
   * Get the grid type as a string.
   * @method get_grid_type
   * @memberof HydroLangBMI
   * @param {Number} grid - A grid identifier
   * @returns {String} Type of grid as a string.
   */

  get_grid_type(grid) {
    throw new Error("Please implement as required in your case study.");
  }

  /**
   * Get the dimensions of a computational grid.
   * @method get_grid_size
   * @memberof HydroLangBMI
   * @param {Number} grid- grid identifier
   * @returns {Number} Grid rank
   */

  get_grid_rank(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Get the dimensions of a computational grid.
   * @method get_grid_size
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @returns {Number} Grid size
   */

  get_grid_size(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Get dimensions of the computational grid
   * @method get_grid_shape
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @param {Object[]} shape - A Number Array of n-dim shape into which to place the shape of the grid.
   */

  get_grid_shape(grid, shape) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Get the spacing between grid nodes.
   * @method get_grid_spacing
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @returns {Object[]} spacing of computational grid
   */

  get_grid_spacing(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Get the origin of a grid.
   * @method get_grid_origin
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @returns {Object[]} coordinates of the origin of a specific grid
   */

  get_grid_origin(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Get the locations of a grid’s nodes in dimension x.
   * @method get_grid_x
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @returns {Object[]} array containing n-rows holding x-coordinates
   */

  get_grid_x(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Get the locations of a grid’s nodes in dimension y.
   * @method get_grid_y
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @returns {Object[]} array containing n-rows holding y-coordinates
   */

  get_grid_y(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Get the locations of a grid’s nodes in dimension z.
   * @method get_grid_z
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @returns {Object[]} array containing n-rows holding z-coordinates
   */

  get_grid_z(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Get the number of nodes in the grid.
   * @method get_grid_node_count
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @returns {Number} total number of grid nodes
   */

  get_grid_node_count(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Get the number of edges in the grid.
   * @method get_grid_edge_count
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @returns {Number} total number of grid edges
   */

  get_grid_edge_count(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Get the number of faces in the grid.
   * @method get_grid_face_count
   * @memberof HydroLangBMI
   * @param {Number} grid - A grid identifier
   * @returns {Number} The total number of grid faces.
   */

  get_grid_face_count(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Get the edge-node connectivity.
   * @method get_grid_edge_nodes
   * @memberof HydroLangBMI
   * @param {Number} grid - A grid identifier
   * @returns {Object[]} number array of (2 x n-nodes) shape to place the edge-node connectivity. For each edge, connectivity is given as node at edge tail, followed by node at edge head.
   */

  get_grid_edge_nodes(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Returns the face-edge connectivity.
   * @method get_grid_face_nodes
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @returns {Object[]} number array to place face-edge connectivity
   */

  get_grid_face_edges(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Returns the face-node connectivity for a grid.
   * @method get_grid_face_nodes
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @returns {Object[]} number array to place face-node connectivity
   */

  get_grid_face_nodes(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**
   * Returns the number of nodes per face of grid.
   * @method get_grid_nodes_per_face
   * @memberof HydroLangBMI
   * @param {Number} grid - grid identifier
   * @returns {Object[]} A number Array of (n-faces)shape to place the number of nodes per face.
   */

  get_grid_nodes_per_face(grid) {
    throw Error("Please implement as required in your case study.");
  }

  /**********************************/
  /*** Helper functions **/
  /**********************************/

  /**
   * Configuration object that creates a result based on the passed configurations to the HydroLang instance requirement.
   * It saves any results on a result variable that is attached into the class after its called. The method runs
   * once the HydroLangBMI class is called.
   * @method handleConfig
   * @memberof HydroLangBMI
   * @returns {void} - creates a result variable attached to the class.
   */

  async handleConfig() {
    !this._componentName === null || this._componentName == undefined
      ? this.results.push(
          await hydro[this._moduleName][this._functionName]({
            args: this._args,
            params: this._params,
            data: this._inputVars,
          })
        )
      : this.results.push(
          await hydro[this._moduleName][this._componentName][
            this._functionName
          ]({ args: this._args, params: this._params, data: this._inputVars })
        );
  }

  /**
   * @method value_index
   * @memberof hydroLangBMI
   * @returns {Number} index location of variable in array
   * Retrieves the index of variables in any specific array
   */

  value_index(value, searchArray) {
    var res;
    searchArray.indexOf(value) != -1 || searchArray.indexOf(value) != undefined
      ? (res = searchArray.indexOf(value))
      : (res = Error("No variable with that naming found in the given array."));
    return res;
  }

  /**
   * @method visualizer
   * @memberof HydroLangBMI
   * @param {String} type - either column or line chart
   * @param {Object[]} data - n-d array containing the data to render
   * @returns {Object} creates a div on screen that renders the graph using HydroLang's visualize module.
   */

  visualizer(chart, data, name) {
    name = name || null;
    hydro["visualize"]["draw"]({
      params: { type: "chart", name: name ? name : this._modelName },
      args: { charttype: chart },
      data: data,
    });
  }

  /**
   * Creates an output file downloaded to local machine with the outputs of the model
   * results.
   * @method generateOutputs
   * @todo Finilize implementation
   * @memberof HydroLangBMI
   * @returns {Object} creates a memory reporter for all the values and actions taken for the model run
   */

  generateOutputs(data) {
    var outputData = {};
    //Data manipulation for downloading and stuff
    config.configGen(outputData);
  }

  /**
   * Returns the data types found within JS and used throughout
   * a specific model run.
   * @method dataTypes
   * @memberof HydroLangBMI
   * @returns {String} type of value passed to the function
   */

  dataTypes = {
    undefined: () => 0,
    boolean: () => 4,
    number: () => 8,
    string: (item) => 2 * item.length,
    object: (item) => {
      let str = JSON.stringify(item);
      const bytes = new TextEncoder().encode(str).length;
      return bytes;
    },
  };

  /**********************************/
  /*** End of Helper functions **/
  /**********************************/
}
typeof window !== "undefined" ? (window.HydroLangBMI = HydroLangBMI) : null;

export default HydroLangBMI;