From e29cb11b8122db5ff2e12af2ce013846e01e8d6e Mon Sep 17 00:00:00 2001 From: Max Richter Date: Mon, 15 Apr 2024 14:11:54 +0200 Subject: [PATCH] feat: add encodeFloat decodeFloat to typescript --- app/src/lib/helpers/encode.test.ts | 77 +++++++++++++++++++++++++++ app/src/lib/helpers/encode.ts | 34 ++++++++++++ app/src/lib/runtime-executor.ts | 27 +++++++--- app/src/routes/test/+page.svelte | 17 ++++++ nodes/max/plantarium/float/src/lib.rs | 5 +- packages/plantarium/src/lib.rs | 1 + 6 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 app/src/lib/helpers/encode.test.ts create mode 100644 app/src/lib/helpers/encode.ts diff --git a/app/src/lib/helpers/encode.test.ts b/app/src/lib/helpers/encode.test.ts new file mode 100644 index 0000000..6e99436 --- /dev/null +++ b/app/src/lib/helpers/encode.test.ts @@ -0,0 +1,77 @@ +import { test, expect } from "vitest" +import { encodeFloat, decodeFloat } from "./encode" + +test("encode_float", () => { + const input = 1.23; + const encoded = encodeFloat(input) + const output = decodeFloat(encoded[0], encoded[1]) + console.log(input, output) + expect(output).toBeCloseTo(input); +}); + +test("encode 2.0", () => { + const input = 2.0; + const encoded = encodeFloat(input) + expect(encoded).toEqual([0, 128]) +}); + +test("floating point imprecision", () => { + let maxError = 0; + new Array(10_000).fill(null).forEach((_, i) => { + const input = i < 5_000 ? i : Math.random() * 100; + const encoded = encodeFloat(input); + const output = decodeFloat(encoded[0], encoded[1]); + + const error = Math.abs(input - output); + if (error > maxError) { + maxError = error; + } + }); + + expect(maxError).toBeLessThan(0.00001); +}); + +// Test with negative numbers +test("negative numbers", () => { + const inputs = [-1, -0.5, -123.456, -0.0001]; + inputs.forEach(input => { + const encoded = encodeFloat(input); + const output = decodeFloat(encoded[0], encoded[1]); + expect(output).toBeCloseTo(input); + }); +}); + +// Test with very small numbers +test("very small numbers", () => { + const input = 1.2345e-38; + const encoded = encodeFloat(input) + const output = decodeFloat(encoded[0], encoded[1]) + expect(output).toBeCloseTo(input); +}); + +// Test with zero +test("zero", () => { + const input = 0; + const encoded = encodeFloat(input) + const output = decodeFloat(encoded[0], encoded[1]) + expect(output).toBe(0); +}); + +// Test with infinity +test("infinity", () => { + const input = Infinity; + const encoded = encodeFloat(input) + const output = decodeFloat(encoded[0], encoded[1]) + expect(output).toBe(Infinity); +}); + +// Test with large numbers +test("large numbers", () => { + const inputs = [1e+5, 1e+10]; + inputs.forEach(input => { + const encoded = encodeFloat(input); + const output = decodeFloat(encoded[0], encoded[1]); + // Note: Large numbers may lose precision, hence using toBeCloseTo with a tolerance + expect(output).toBeCloseTo(input, 0); + }); +}); diff --git a/app/src/lib/helpers/encode.ts b/app/src/lib/helpers/encode.ts new file mode 100644 index 0000000..49fd6b1 --- /dev/null +++ b/app/src/lib/helpers/encode.ts @@ -0,0 +1,34 @@ + +export function encodeFloat(f: number): [number, number] { + let buffer = new ArrayBuffer(4); // Create a buffer of 4 bytes (32 bits) + let floatView = new Float32Array(buffer); + let intView = new Uint32Array(buffer); + + floatView[0] = f; // Store the float into the buffer + let bits = intView[0]; // Read the bits as integer + + let mantissa = bits & 0x007FFFFF; + let exponent = (bits >> 23) & 0xFF; + let sign = (f < 0.0) ? 1 : 0; + + // Include the sign bit in the mantissa + mantissa = mantissa | (sign << 23); + + return [mantissa, exponent]; +} + +export function decodeFloat(mantissa: number, exponent: number): number { + let signBit = (mantissa >> 23) & 1; + let mantissaBits = mantissa & 0x007FFFFF; + let exponentBits = (exponent & 0xFF) << 23; + + // Reconstruct all bits including sign + let bits = (signBit << 31) | exponentBits | mantissaBits; + + let buffer = new ArrayBuffer(4); + let floatView = new Float32Array(buffer); + let intView = new Uint32Array(buffer); + + intView[0] = bits; // Set the bits as integer + return floatView[0]; // Read the float back from the buffer +} diff --git a/app/src/lib/runtime-executor.ts b/app/src/lib/runtime-executor.ts index 6d99ef2..2689b89 100644 --- a/app/src/lib/runtime-executor.ts +++ b/app/src/lib/runtime-executor.ts @@ -3,6 +3,8 @@ import type { Graph, NodeRegistry, NodeType, RuntimeExecutor } from "@nodes/type export class MemoryRuntimeExecutor implements RuntimeExecutor { + private typeMap: Map = new Map(); + constructor(private registry: NodeRegistry) { } private getNodeTypes(graph: Graph) { @@ -26,7 +28,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { private addMetaData(graph: Graph) { // First, lets check if all nodes have a type - const typeMap = this.getNodeTypes(graph); + this.typeMap = this.getNodeTypes(graph); const outputNode = graph.nodes.find(node => node.type.endsWith("/output")); if (!outputNode) { @@ -63,8 +65,6 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { if (node) { node.tmp = node.tmp || {}; - node.tmp.type = typeMap.get(node.type); - if (node?.tmp?.depth === undefined) { node.tmp.depth = 0; } @@ -110,9 +110,12 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { const results: Record = {}; for (const node of sortedNodes) { - if (node?.tmp && node?.tmp?.type?.execute) { + + const node_type = this.typeMap.get(node.type)!; + + if (node?.tmp && node_type?.execute) { const inputs: Record = {}; - for (const [key, input] of Object.entries(node.tmp.type.inputs || {})) { + for (const [key, input] of Object.entries(node_type.inputs || {})) { if (input.type === "seed") { inputs[key] = Math.floor(Math.random() * 100000000); @@ -136,10 +139,18 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { // execute the node and store the result try { - console.log(`Executing node ${node.tmp.type.id || node.id}`, inputs); - results[node.id] = node.tmp.type.execute(...Object.values(inputs)) as number; + const node_inputs = Object.entries(inputs); + const transformed_inputs = node_inputs.map(([key, value]) => { + const input_type = node_type.inputs[key]; + if (input.type === "float") { + return + } + console.log(key, input_type); + }); + console.log(`Executing node ${node_type.id || node.id}`, node_inputs); + results[node.id] = node_type.execute(...Object.values(inputs)) as number; } catch (e) { - console.error(`Error executing node ${node.tmp.type.id || node.id}`, e); + console.error(`Error executing node ${node_type.id || node.id}`, e); } } diff --git a/app/src/routes/test/+page.svelte b/app/src/routes/test/+page.svelte index e457072..a3a612c 100644 --- a/app/src/routes/test/+page.svelte +++ b/app/src/routes/test/+page.svelte @@ -1,6 +1,23 @@