Skip to content

Commit

Permalink
add exponential distribution (#513)
Browse files Browse the repository at this point in the history
* add exponantial distribution

* address comments
  • Loading branch information
mck authored Nov 12, 2024
1 parent 3dd0c82 commit a986fde
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/wild-knives-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tokens-studio/graph-engine": minor
---

Add exponantial distribution node to spread a number over a length of items, with adjusting the decay.
70 changes: 70 additions & 0 deletions packages/graph-engine/src/nodes/series/exponentialDecay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { INodeDefinition, ToInput, ToOutput } from '../../index.js';
import { Node } from '../../programmatic/node.js';
import { NumberSchema } from '../../schemas/index.js';
import { arrayOf } from '../../schemas/utils.js';
import { setToPrecision } from '../../utils/precision.js';

export default class NodeDefinition extends Node {
static title = 'Exponential Decay';
static type = 'studio.tokens.series.exponentialDecay';
static description =
'Generates a sequence using exponential decay formula: P*e^(-kx)';

declare inputs: ToInput<{
initialValue: number;
length: number;
decayRate: number;
precision: number;
}>;

declare outputs: ToOutput<{
values: number[];
}>;

constructor(props: INodeDefinition) {
super(props);
this.addInput('initialValue', {
type: {
...NumberSchema,
default: 100,
description: 'Initial value (P)'
}
});
this.addInput('length', {
type: {
...NumberSchema,
default: 5,
minimum: 1,
description: 'Number of values to generate'
}
});
this.addInput('decayRate', {
type: {
...NumberSchema,
default: 0.5,
minimum: 0,
description: 'Decay rate constant (k)'
}
});
this.addInput('precision', {
type: {
...NumberSchema,
default: 2,
minimum: 0
}
});
this.addOutput('values', {
type: arrayOf(NumberSchema)
});
}

execute(): void | Promise<void> {
const { initialValue, length, decayRate, precision } = this.getAllInputs();

const values = Array.from({ length }, (_, i) =>
setToPrecision(initialValue * Math.exp(-decayRate * i), precision)
);

this.outputs.values.set(values);
}
}
2 changes: 2 additions & 0 deletions packages/graph-engine/src/nodes/series/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import alternating from './alternating.js';
import arithmetic from './arithmetic.js';
import exponentialDecay from './exponentialDecay.js';
import fibonacci from './fibonacci.js';
import geometric from './geometric.js';
import harmonic from './harmonic.js';
Expand All @@ -9,6 +10,7 @@ import power from './power.js';
export const nodes = [
alternating,
arithmetic,
exponentialDecay,
fibonacci,
harmonic,
geometric,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Graph } from '../../../../src/graph/graph.js';
import { describe, expect, test } from 'vitest';
import Node from '../../../../src/nodes/series/exponentialDecay.js';

describe('series/exponentialDecay', () => {
test('generates sequence with default parameters', async () => {
const graph = new Graph();
const node = new Node({ graph });

await node.execute();

const values = node.outputs.values.value;
expect(values).to.have.lengthOf(5);
expect(values[0]).to.equal(100); // Initial value
expect(values[1]).to.be.lessThan(values[0]); // Decay occurs
});

test('respects custom length parameter', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.length.setValue(3);
await node.execute();

const values = node.outputs.values.value;
expect(values).to.have.lengthOf(3);
});

test('follows exponential decay formula', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.initialValue.setValue(100);
node.inputs.decayRate.setValue(0.5);
node.inputs.length.setValue(3);

await node.execute();

const values = node.outputs.values.value;
expect(values[0]).to.equal(100); // P
expect(values[1]).to.be.approximately(100 * Math.exp(-0.5), 0.01); // P*e^(-k*1)
expect(values[2]).to.be.approximately(100 * Math.exp(-1), 0.01); // P*e^(-k*2)
});

test('higher decay rate increases decay speed', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.decayRate.setValue(1.0);
await node.execute();

const values = node.outputs.values.value;
const ratio = values[0] / values[1];
expect(ratio).to.be.approximately(Math.E, 0.01); // e^1 for k=1
});

test('respects precision setting', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.precision.setValue(3);
await node.execute();

const values = node.outputs.values.value;
values.forEach(value => {
expect(value.toString()).to.match(/^\d*\.?\d{0,3}$/);
});
});
});

0 comments on commit a986fde

Please sign in to comment.