Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Statistical functions added (4) #5

Merged
merged 7 commits into from
Jul 24, 2023
Merged

Conversation

riya-patil
Copy link
Contributor

Description

The following functions have been added into stats.js:
Linear Moving Average
Differencing
Heterskedaticity
Multiply Matrix
Transpose Matrix
Matrix Inverse
Regression
Multi-Regression

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • Chrome browser testing: using Inspect element, I used the console to test all the functions I'm implementing by making an instance of HydroLang and calling the function for a result.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • Any dependent changes have been merged and published in downstream modules

@riya-patil
Copy link
Contributor Author

Description

The following functions have been added into hydro.js:

  • Penman-Montieth model (evapotranspiration)
  • Hargreaves model
  • Thornthwaite model
  • Blaney-Criddle
  • Priestly-Taylor method
  • Green-Ampt (infiltration)
  • Horton model
  • Philip model

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • Chrome browser testing: using Inspect element, I used the console to test all the functions I'm implementing by making an instance of HydroLang and calling the function for a result.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • Any dependent changes have been merged and published in downstream modules

@riya-patil
Copy link
Contributor Author

Description

The following functions have been added into hydro.js:
Muskingum Cunge
Lag and Route
Time Area Method
Kinematic Wave Routing
Darceys Law Unconfined Aquifer
Confined Aquifer
Dynamic Systems
Dissolved Oxygen Demand
pH calculation

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • Chrome browser testing: using Inspect element, I used the console to test all the functions I'm implementing by making an instance of HydroLang and calling the function for a result.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • Any dependent changes have been merged and published in downstream modules

Copy link
Collaborator

@erazocar erazocar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stats
General

  • Keep consistency in the parameters for main functions. Close the parameters so that in case the user does not pass the parameter it defaults to an empty object. Example: `static linearMovingAverage ({params, args, data} = {})``

Linear Moving Average

  • Parameter descriptor is wrong.
  • In the first for loop, instead of creating a new array each time, keep a running sum that does the same job and is more efficient. Example:
static LinearMovingAverage({ params, args, data } = {}) {
...
  const movingAverage = [];
  let sum = 0;
 
  for (let i = 0; i < windowSize; i++) {
	sum += data[i];
  }
 
  movingAverage.push(sum / windowSize);
 
  for (let i = windowSize; i < data.length; i++) {
	sum += data[i] - data[i - windowSize];
    movingAverage.push(sum / windowSize);
  }
 
  return movingAverage;
}

Differencing

  • Improve the name or description. It is true that the function does differentiation, but it is used to make time series non stationary data into stationary. You can leave the name but improve description.
  • The for loop is inefficient for large time series. Make the following change:
...
const differencedSeries = timeSeries.slice(order).map((value, i) => value - timeSeries[i]);
return differencedSeries

heteroskedasticity

  • Calculating heteroskedasticity involves using other tests alongside the implementation of variance of residuals. Rename this function as residualVariance or something similar. The missing tests can also be implemented (White's test, Bresuch-Pagan, Goldfeld-Quandt). Example:
static residualVariance({ params, args, data } = {}) {
  const residuals = data;
 
  if (!Array.isArray(residuals)) {
	throw new Error('Invalid data. Expecting an array of residuals.');
  }
 
  if (residuals.length < 2) {
	throw new Error('Insufficient data. Expecting an array of at least 2 residuals.');
  }
//...
}

Hydro
General

  • Similarly to stats, use the destructuring parameter with default to an empty object. This makes each function available for usage within the HydroLang software. Example:
const functionName = ({params, args, data} = {}){/...}
  • The functions are mostly implemented with real data coming from a sensor. Considering this, using array calculations rather than single values is more helpful.
  • We need references for each of the functions/models you are implementing so that users can go back to the textbook or resource and further read. You can add it directly into the comments as follows:
/** 
*...
*Reference: (link to book or reference)
*/

Penman Monteith
-Rename to ETPenmanMontheith

  • Consider that temperature, netRadiation, windSpeed, and the vapor pressures can be fed as arrays. Change the function so that the evapotranspiration value is returned as a time series array rather than a single value.
  • Make some simple validation to account for the inputs required. Generic example:
  if (!temperature || !temperatureUnit || !netRadiation || !windSpeed || !saturationVaporPressure || !actualVaporPressure) {
    throw new Error('Missing required parameters: temperature, temperatureUnit, netRadiation, windSpeed, saturationVaporPressure, actualVaporPressure.');
  }
  • Remove unnecessary 0 substraction in the denominator variable.
  • Add reference to online resource or book section.

Hargraves Method

  • Rename ETHargreaves
  • Move latitude to params.
  • Move temperature, tempmax, tempmin, date to the data parameter. This should be fed as arrays. The code needs to be modified so it returns a time series.
  • Add some simple validation of the inputs, if the temperatures are not given throw an Error.
  • The variable julianDay is not defined. Consider that the dates are in pair to the temperatures and coming from an API as a time string. A new getJulianDate helper function should be added to change the value from a date string to a julian value. Example:
function getJulianDay(date) {
  let inputDate;
  if (typeof date === 'string') {
    inputDate = new Date(date);
  } else if (date instanceof Date) {
    inputDate = date;
  } else {
    throw new Error('Invalid date format. Expected a string or a Date object.');
  }

  const year = inputDate.getFullYear();
  const month = inputDate.getMonth() + 1;
  const day = inputDate.getDate();

  // Julian Day Calculation
  const a = Math.floor((14 - month) / 12);
  const y = year + 4800 - a;
  const m = month + 12 * a - 3;

  return day + Math.floor((153 * m + 2) / 5) + 365 * y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045;
}
  • Update the documentation and put a reference.

Thornthwaite Method

  • Rename ETThornthwaite
  • temperature and monthdays are data objects. This should be moved into the data argument.
  • Reference for the method on the comments.
  • Data validation on temperature and monthDays.

Blaney-Criddle

  • Rename ETBlaneyCriddle
  • Move temperature and monthDays to data argument.
  • Put reference in comments.
  • Implement validation to the data inputs.

Priestley-Taylor

  • Rename ETPriestelyTaylor
  • Explain the inputs in the comments (netRadiation (units?), latentHeatFlux (units?))
  • Put reference in comments.

Green-Ampt

  • Rename InfGreenAmpt
  • Put reference in comments.
  • Implement validation of data inputs.

Horton

  • Rename InfHorton
  • Put reference in comments.
  • Implement validation of data inputs.

Philip

  • Rename InfPhilip
  • Put reference in comments.
  • Implement validation of data inputs.

Muskingum-Cunge

  • The implementation works good for small watersheds, but might not be memory efficient for large datasets. For a simple implementation, lets add a cache object to memoize and keep track of latter values and use them to propagate further. Example:
const getMemoInflow (K, inflow, i, cache) {
  if (cache[index]) {
    return cache[index];
  }

  const inflowComponent = K * inflow[index];
  cache[index] = inflowComponent;
  return inflowComponent;
}

const getMemoOuflow(K, X, inflow, prevStorage, inflowComponent, index, cache) {
  if (cache[index]) {
    return cache[index];
  }

  const outflowComponent = K * (inflow[index] + X * (inflowComponent - inflow[index]) + X * (prevStorage - inflowComponent));
  cache[index] = outflowComponent;
  return outflowComponent;
}

In the code:

static muskingumCunge({ params, args, data } ={}) {
//...
  // Memoization cache
  const cache = {};

  for (let i = 0; i < inflow.length; i++) {
    const prevStorage = storage;
    const inflowComponent = getMemoInflow(K, inflow, i, cache);
    const outflowComponent = getMemoOutflow(K, X, inflow, prevStorage, inflowComponent, i, cache);
   //...
}
  • Move inflowData to data argument.
  • Explain the X and K parameters from the method.
  • Put reference in comments.

lagRoute Method

  • We can improve the storage output in a single loop rather than iterating through it over again. Example:
static lagAndRouter({params, args, data}){
//...
//Use the routing coefficients rather than the storage for update
for (let j = routingCoefficients.length - 1; j >= 1; j--) {
      storage[j] = storage[j - 1] + routingCoefficients[j - 1] * inflow[i - j];
    }
//...
}
  • Put reference in comments.
  • Explain lagTime and routing coefficients in comments.

Time-Area method

  • Reference in comments.
  • Units for both inflow and area data.

Kinematic Wave Routing

  • Method only considers single channel routing. This should be explicitly mentioned in the comments.
  • Move initialDepth to params or args
  • Explain the parameters used for the calculations attaching the variables defined within (C-travel time coefficient, etc)
  • Put reference in comments.

Darcy's Law

  • We can merge both cases into a single equation, passing a flag into the params to account for the changes.
  • Being aquifers dynamic systems, we can merge the three functions into one so there is no unnecessary repetition of work. Example:
static darcysLaw({params, args, data} = {}) {
 const {aquiferType} = params;
 const {hydraulicConductivity, porosity = 0} = args //add the porosity as an input variable. If porosity is not given (unconfined case), then it is set to 0 to avoid errors.
 const {hydraulicGradients = [], aquiferThickness} = data; //1d arrays for each.

//...

const groundwaterFlows = [];

  for (let i = 0; i < hydraulicGradients.length; i++) {

    let groundwaterFlow;

    if (aquiferType === 'confined') {
      const transmissivity = hydraulicConductivity * aquiferThickness[i];
      groundwaterFlow = transmissivity * hydraulicGradients[i];
    } else if (aquiferType === 'unconfined') {
      groundwaterFlow = hydraulicConductivity * hydraulicGradients[i] * aquiferThickness[i] * porosity;
    }
   groundwaterFlows.push(groundwaterFlow)
  }

return groundwaterFlows;
  • Put reference in comments.
  • Explain the variables and their units in comments.

@riya-patil
Copy link
Contributor Author

Description

The following functions have been added into hydro.js:
proportionalDistribution
stochasticRainfallGeneration
generateSyntheticValue
calibratePH
calculateDOSaturation
compensate_ec
convertTurbidity
calculate_tds
calculate_tss
compensateORP
convertTemperature

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • Chrome browser testing: using Inspect element, I used the console to test all the functions I'm implementing by making an instance of HydroLang and calling the function for a result.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • Any dependent changes have been merged and published in downstream modules

Copy link
Collaborator

@erazocar erazocar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Infiltration Philip

  • There is a mistake with the name of the function.

TimeArea method

Proportional Distribution

  • This function is good, but we can improve it by adding a more comprehensive overview of what would happen if we have a collection of basins that are close together and a rainfall event happens. We can then, by considering the distance between the basins and their areas rather than weights, distribute rainfall on a more likely scenario. We call this inverse distance weighting. If the distance parameter is not given, then it would do a proportional distribution as what you implemented. Example:
static inverseDistanceWeighting({ totalRainfall, basinAreas, distances } = {}) {
//Some error handling here
  // If distances are not provided, distribute rainfall evenly across all basins
  if (!distances) {
    distances = new Array(basinAreas.length).fill(1); // Default all distances to 1 (even distribution)
  }
//...
  // Calculate the sum of the inverse distances
  const sumInverseDistances = distances.reduce((acc, distance) => acc + 1 / distance, 0);

  // Calculate the weights based on inverse distances
  const weights = distances.map((distance) => (1 / distance) / sumInverseDistances);

  // Calculate rainfall distribution based on weights and basin areas
  const rainfallDistribution = basinAreas.map((area, index) => weights[index] * totalRainfall);
return rainfallDistribution
}
  • Change the example.

Stochastic Rainfall

  • We will modify this function so that a flag for the type of distribution that you apply should be given by the user, default to normal. This flag should be put in the params.
  • The data for the rainfall should be passed as an array directly as data: [dataArray].
  • mean and stdDev can be calculated using the functions in stats module.

CalibratePH

  • sensor_reading will probably be given as an array. Move it to the data without passing it as a variable. Example: data: [sensorData].
  • You can pass the slope and intercept as parameters directly into the params object.
  • Modify the function so that for each of the values passed in the data variable, the calibration happens.

DO Saturation

  • temperature and sensor_reading will probably be given as arrays. Consider them as such as pass them as such in to the data argument. Modify the function accordingly.

Compensate_EC

  • temperature and sensor_reading will probably be given as arrays. Consider them as such as pass them as such in to the data argument. Modify the function accordingly.

Calculate TDS

  • temperature and sensor_reading will probably be given as arrays. Consider them as such as pass them as such in to the data argument. Modify the function accordingly.

Compensate ORP

  • temperature and sensor_reading will probably be given as arrays. Consider them as such as pass them as such in to the data argument. Modify the function accordingly.

Generate Synthetic Values

  • This function should change the random value based on the type of distribution passed to the Stochasticgenerator function. Call the available distributions from stats to be used in here.
  • There is already a normal distribution CDF available in stats.

Copy link
Collaborator

@erazocar erazocar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor mistakes that will be corrected during doc generation.

@erazocar erazocar merged commit 9e0efde into uihilab:master Jul 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants