Skip to content

Commit

Permalink
Datafit v0.3.0 (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicfv authored Mar 9, 2024
1 parent 19ee575 commit ac18227
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 29 deletions.
6 changes: 6 additions & 0 deletions datafit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.3.0

- Update variable and type names to be more descriptive
- Minor tsdoc updates
- Start working on main readme

## 0.2.0

- Rename `CurveFit.ts` to `lib.ts`
Expand Down
26 changes: 24 additions & 2 deletions datafit/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
Simple curve-fitting algorithm: Curve fitting with single-variable or multivariate problems, using a genetic algorithm.
Simple curve-fitting algorithm using a genetic-style algorithm for single-variable or multivariate problems.

![NPM Version](https://img.shields.io/npm/v/datafit)
![NPM Downloads](https://img.shields.io/npm/dt/datafit)
![NPM Downloads](https://img.shields.io/npm/dt/datafit)

## Installation

`datafit` can be installed from the official [npm package repository](https://www.npmjs.com/package/datafit). It is highly recommended to install the latest version, which is installed by default with the following command.

```shell
npm i datafit
```

## Getting Started

`datafit` exports only 1 function, [`fit`](https://npm.nicfv.com/datafit/functions/fit-1.html) which is used for curve fitting. All other exports are purely for information and defining types used within this package.

## Example

### Single Variable

In this example written in JavaScript, we will fit a 2nd degree polynomial to a set of (x,y) points.

```js
// TODO
```
2 changes: 1 addition & 1 deletion datafit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "datafit",
"version": "0.2.0",
"version": "0.3.0",
"description": "Simple curve-fitting algorithm",
"main": "dist/index.js",
"types": "types/index.d.ts",
Expand Down
34 changes: 17 additions & 17 deletions datafit/src/lib.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
import { SMath } from 'smath';
import { Config, Dataset, Fit, Params, X, fx } from './types';
import { Config, Dataset, Fit, Params, VariableType, fx } from './types';

/**
* Minimize the sum of squared errors to fit a set of data
* points to a curve with a set of unknown parameters.
* @param f The model function for curve fitting.
* @param data The entire dataset, as an array of points.
* @param a_initial The initial guess for function parameters,
* @param params_initial The initial guess for function parameters,
* which defaults to an array filled with zeroes.
* @param config Configuration options for curve fitting.
* @returns The set of parameters and error for the best fit.
* @example
* ```ts
* const bestFit: Fit = fit(f, dataset),
* a: Params = bestFit.a,
* params: Params = bestFit.params,
* err: number = bestFit.err;
* ```
*/
export function fit<T = X>(f: fx<T>, data: Dataset<T>, a_initial: Params = [], config: Config = { generations: 100, population: 100, survivors: 10, initialDeviation: 10, finalDeviation: 1 }): Fit {
export function fit<T = VariableType>(f: fx<T>, data: Dataset<T>, params_initial: Params = [], config: Config = { generations: 100, population: 100, survivors: 10, initialDeviation: 10, finalDeviation: 1 }): Fit {
const N_params: number = f.length - 1;
if (a_initial.length === 0) {
a_initial.length = N_params;
a_initial.fill(0);
if (params_initial.length === 0) {
params_initial.length = N_params;
params_initial.fill(0);
}
if (a_initial.length !== N_params) {
if (params_initial.length !== N_params) {
throw new Error('The initial guess should contain ' + N_params + ' parameters.');
}
const census: Array<Fit> = [];
for (let generation = 0; generation < config.generations; generation++) {
for (let i = 0; i < config.population; i++) {
// Mutate a random parent from the prior generation of survivors
const a: Params = mutate(
census[randInt(0, config.survivors)]?.a ?? a_initial,
const params: Params = mutate(
census[randInt(0, config.survivors)]?.params ?? params_initial,
SMath.translate(generation, 0, config.generations, config.initialDeviation, config.finalDeviation)
);
census.push({ a: a, err: err(f, a, data) });
census.push({ params: params, err: err(f, params, data) });
}
// Sort by increasing error and only keep the survivors
census.sort((x, y) => x.err - y.err);
Expand All @@ -45,23 +45,23 @@ export function fit<T = X>(f: fx<T>, data: Dataset<T>, a_initial: Params = [], c
/**
* Calculate the sum of squared errors for a set of function parameters.
* @param f The model function for curve fitting.
* @param a The array of parameters to check.
* @param params The array of parameters to check.
* @param data The entire dataset, as an array of points.
* @returns The sum of squared errors.
*/
function err<T = X>(f: fx<T>, a: Params, data: Dataset<T>): number {
function err<T = VariableType>(f: fx<T>, params: Params, data: Dataset<T>): number {
let sum: number = 0;
data.forEach(point => sum += (point.y - f(point.x, ...a)) ** 2);
data.forEach(point => sum += (point.y - f(point.x, ...params)) ** 2);
return sum;
}
/**
* Randomly mutate the set of function parameters by some maximum deviation.
* @param a The set of function parameters to mutate.
* @param params The set of function parameters to mutate.
* @param deviation The maximum amount to deviate in any direction.
* @returns A mutated set of parameters.
*/
function mutate(a: Params, deviation: number): Params {
return a.map(c => c += SMath.expand(Math.random(), -deviation, deviation));
function mutate(params: Params, deviation: number): Params {
return params.map(c => c += SMath.expand(Math.random(), -deviation, deviation));
}
/**
* Generate a random integer between `min, max`
Expand Down
18 changes: 9 additions & 9 deletions datafit/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ export type SingleVariable = number;
*/
export type MultiVariable = Array<number>;
/**
* Defines whether this is a single- or multi-variable problem.
* Declares whether this is a single- or multi-variable problem.
*/
export type X = SingleVariable | MultiVariable;
export type VariableType = SingleVariable | MultiVariable;
/**
* Type of function constant parameters to fit the curve with.
*/
export type Params = Array<number>;
/**
* Represents a mathematical function y = f(x) with unknown constants `a`
* Represents a mathematical function y = f(x) with unknown parameters.
* @example
* Single variable function in Typescript, 2nd degree polynomial:
* ```ts
Expand All @@ -32,18 +32,18 @@ export type Params = Array<number>;
* **Note:** `SingleVariable` can be replaced with `number` and
* `MultiVariable` can be replaced with `Array<number>` or `number[]`.
*/
export type fx<T = X> = (x: T, ...a: Params) => number;
export type fx<T = VariableType> = (x: T, ...params: Params) => number;
/**
* Stores a data point. For multivariable points, the `x`
* coordinate contains an array of all the free variables.
*/
export interface Datum<T = X> {
export interface Datum<T = VariableType> {
/**
* Input: X variable(s)
* **Input:** X variable(s)
*/
readonly x: T;
/**
* Output: Y variable
* **Output:** Y variable
*/
readonly y: number;
}
Expand Down Expand Up @@ -73,15 +73,15 @@ export interface Datum<T = X> {
* ];
* ```
*/
export type Dataset<T = X> = Array<Datum<T>>;
export type Dataset<T = VariableType> = Array<Datum<T>>;
/**
* Includes information about a best-fit for a curve.
*/
export interface Fit {
/**
* Contains the set of best-fit parameters for the function `f(x)`
*/
readonly a: Params;
readonly params: Params;
/**
* This is the residual sum of squared errors.
*/
Expand Down

0 comments on commit ac18227

Please sign in to comment.