From f8dbf6f7f0fc6d01e9784b5a32a6f7fbbf199d83 Mon Sep 17 00:00:00 2001 From: Sacha Froment Date: Sun, 23 Feb 2025 13:46:55 +0100 Subject: [PATCH] chore: add recursive mul blueprints Signed-off-by: Sacha Froment --- packages/blueprints/src/RecMul/index.ts | 44 ++++++++ packages/blueprints/src/index.ts | 1 + packages/blueprints/tests/RecMul.test.ts | 130 ++++++++++++++++++++++ packages/object/tests/actiontypes.test.ts | 53 +++++++++ packages/object/tests/hashgraph.bench.ts | 51 ++++++++- 5 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 packages/blueprints/src/RecMul/index.ts create mode 100644 packages/blueprints/tests/RecMul.test.ts diff --git a/packages/blueprints/src/RecMul/index.ts b/packages/blueprints/src/RecMul/index.ts new file mode 100644 index 00000000..0cd8f895 --- /dev/null +++ b/packages/blueprints/src/RecMul/index.ts @@ -0,0 +1,44 @@ +import { + ActionType, + type DRP, + type ResolveConflictsType, + SemanticsType, + type Vertex, +} from "@ts-drp/object"; + +export class RecMulDRP implements DRP { + semanticsType = SemanticsType.pair; + + private _value: number; + + constructor(initialValue?: number) { + if (typeof initialValue === "number") { + this._value = initialValue; + } else { + this._value = 1; + } + } + + recursive_mul(value: number): void { + if (typeof value !== "number") { + return; + } + if (value === 0) { + return; + } + this._value = this._multiply(value, this._value); + } + + private _multiply(value: number, base: number): number { + if (value === 1) return base; // Base case + return base + this._multiply(value - 1, base); + } + + query_value(): number { + return this._value; + } + + resolveConflicts(_: Vertex[]): ResolveConflictsType { + return { action: ActionType.Nop }; + } +} diff --git a/packages/blueprints/src/index.ts b/packages/blueprints/src/index.ts index 60b1ae51..eb576e43 100644 --- a/packages/blueprints/src/index.ts +++ b/packages/blueprints/src/index.ts @@ -1,3 +1,4 @@ export * from "./AddMul/index.js"; +export * from "./RecMul/index.js"; export * from "./Set/index.js"; export * from "./Map/index.js"; diff --git a/packages/blueprints/tests/RecMul.test.ts b/packages/blueprints/tests/RecMul.test.ts new file mode 100644 index 00000000..82b28ff2 --- /dev/null +++ b/packages/blueprints/tests/RecMul.test.ts @@ -0,0 +1,130 @@ +import { ActionType, Vertex } from "@ts-drp/object"; +import { beforeEach, describe, expect, test } from "vitest"; + +import { RecMulDRP } from "../src/RecMul/index.js"; + +describe("RecMulDRP tests", () => { + let drp: RecMulDRP; + + beforeEach(() => { + drp = new RecMulDRP(); + }); + + test("Test: recursive_mul (Basic)", () => { + drp.recursive_mul(3); + let val = drp.query_value(); + expect(val).toEqual(3); // 1 * 3 + + drp.recursive_mul(4); + val = drp.query_value(); + expect(val).toEqual(12); // 3 * 4 + + drp.recursive_mul(0); + expect(drp.query_value()).toEqual(12); // Should not change for 0 + }); + + test("Test: recursive_mul (Type Safety)", () => { + drp.recursive_mul(2); + expect(drp.query_value()).toEqual(2); // 1 * 2 + + // @ts-expect-error Testing invalid input + drp.recursive_mul(""); + expect(drp.query_value()).toEqual(2); + + // @ts-expect-error Testing invalid input + drp.recursive_mul(true); + expect(drp.query_value()).toEqual(2); + + // @ts-expect-error Testing invalid input + drp.recursive_mul({}); + expect(drp.query_value()).toEqual(2); + }); + + test("Test: initialValue (Basic)", () => { + drp = new RecMulDRP(10); + expect(drp.query_value()).toEqual(10); + + drp = new RecMulDRP(-10); + expect(drp.query_value()).toEqual(-10); + + drp = new RecMulDRP(0); + expect(drp.query_value()).toEqual(0); + + drp = new RecMulDRP(); + expect(drp.query_value()).toEqual(1); // Default value is 1 for multiplication + }); + + test("Test: initialValue (Type Safety)", () => { + // @ts-expect-error Testing invalid input + drp = new RecMulDRP("10"); + expect(drp.query_value()).toEqual(1); + + // @ts-expect-error Testing invalid input + drp = new RecMulDRP(true); + expect(drp.query_value()).toEqual(1); + + // @ts-expect-error Testing invalid input + drp = new RecMulDRP({}); + expect(drp.query_value()).toEqual(1); + + // @ts-expect-error Testing invalid input + drp = new RecMulDRP([]); + expect(drp.query_value()).toEqual(1); + }); + + test("Test: resolveConflicts (Basic)", () => { + const vertex1: Vertex = { + hash: "1", + peerId: "1", + operation: { + drpType: "DRP", + opType: "recursive_mul", + value: [3], + }, + dependencies: [], + timestamp: 0, + signature: new Uint8Array(), + }; + const vertex2: Vertex = { + hash: "2", + peerId: "2", + operation: { + drpType: "DRP", + opType: "recursive_mul", + value: [2], + }, + dependencies: [], + timestamp: 0, + signature: new Uint8Array(), + }; + + let action = drp.resolveConflicts([]); + expect(action).toEqual({ action: ActionType.Nop }); + + action = drp.resolveConflicts([vertex1]); + expect(action).toEqual({ action: ActionType.Nop }); + + action = drp.resolveConflicts([vertex1, vertex2]); + expect(action).toEqual({ action: ActionType.Nop }); + }); + + test("Test: resolveConflicts (Type Safety)", () => { + const vertex1: Vertex = { + hash: "1", + peerId: "1", + operation: { + drpType: "DRP", + opType: "recursive_mul", + value: [2], + }, + dependencies: [], + timestamp: 0, + signature: new Uint8Array(), + }; + + const vertex2 = {}; + + let action = drp.resolveConflicts([vertex1, vertex2]); + expect(action).toEqual({ action: ActionType.Nop }); + }); +}); diff --git a/packages/object/tests/actiontypes.test.ts b/packages/object/tests/actiontypes.test.ts index 939023b3..f4ee5e32 100644 --- a/packages/object/tests/actiontypes.test.ts +++ b/packages/object/tests/actiontypes.test.ts @@ -1,4 +1,5 @@ import { AddMulDRP } from "@ts-drp/blueprints/src/AddMul/index.js"; +import { RecMulDRP } from "@ts-drp/blueprints/src/RecMul/index.js"; import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import { DRPObject, ObjectACL } from "../src/index.js"; @@ -118,3 +119,55 @@ describe("Test: ActionTypes (Drops)", () => { test("Test: Drop", () => {}); }); + +describe("Test: ActionTypes (RecMul)", () => { + let drp: DRPObject; + let drp2: DRPObject; + let recMul: RecMulDRP; + let recMul2: RecMulDRP; + + beforeEach(() => { + drp = new DRPObject({ peerId: "peer1", drp: new RecMulDRP(), acl }); + drp2 = new DRPObject({ peerId: "peer2", drp: new RecMulDRP(), acl }); + recMul = drp.drp as RecMulDRP; + recMul2 = drp2.drp as RecMulDRP; + + vi.useFakeTimers(); + vi.setSystemTime(new Date(Date.UTC(1998, 11, 19))); + }); + + test("Test: Basic Operations", () => { + recMul.recursive_mul(3); + recMul2.recursive_mul(2); + console.log(recMul.query_value()); + drp.merge(drp2.vertices); + drp2.merge(drp.vertices); + expect(recMul.query_value()).toBe(6); // 1*3*2 + expect(recMul2.query_value()).toBe(6); + + recMul.recursive_mul(2); + recMul2.recursive_mul(1); + drp.merge(drp2.vertices); + drp2.merge(drp.vertices); + expect(recMul.query_value()).toBe(12); // 6 * 2 * 1 + expect(recMul2.query_value()).toBe(12); + }); + + test("Test: Multiple Operations", () => { + recMul.recursive_mul(3); + recMul.recursive_mul(3); + recMul2.recursive_mul(3); + drp.merge(drp2.vertices); + drp2.merge(drp.vertices); + expect(recMul.query_value()).toBe(27); // 1*3*3*3 + expect(recMul2.query_value()).toBe(27); + + recMul.recursive_mul(2); + recMul.recursive_mul(1); + recMul2.recursive_mul(2); + drp.merge(drp2.vertices); + drp2.merge(drp.vertices); + expect(recMul.query_value()).toBe(108); // 27 * 2 * 1 * 2 + expect(recMul2.query_value()).toBe(108); + }); +}); diff --git a/packages/object/tests/hashgraph.bench.ts b/packages/object/tests/hashgraph.bench.ts index 15021297..1bbb38cd 100644 --- a/packages/object/tests/hashgraph.bench.ts +++ b/packages/object/tests/hashgraph.bench.ts @@ -1,5 +1,4 @@ -import { MapDRP } from "@ts-drp/blueprints/src/index.js"; -import { SetDRP } from "@ts-drp/blueprints/src/index.js"; +import { MapDRP, RecMulDRP, SetDRP } from "@ts-drp/blueprints/src/index.js"; import Benchmark from "benchmark"; import { DRP, DRPObject, ObjectACL } from "../src/index.js"; @@ -50,6 +49,40 @@ function benchmarkForAddWinSet( } }); } + +function benchmarkForAddMulRecCall( + name: string, + numDRPs: number, + verticesPerDRP: number, + mergeFn: boolean +) { + return suite.add(name, () => { + const objects: DRPObject[] = []; + for (let i = 0; i < numDRPs; i++) { + const obj: DRPObject = new DRPObject({ + peerId: `peer${i + 1}`, + acl, + drp: new RecMulDRP(), + }); + const drp = obj.drp as RecMulDRP; + for (let j = 0; j < verticesPerDRP; j++) { + drp.recursive_mul(j); + } + objects.push(obj); + } + + if (mergeFn) { + for (let i = 0; i < objects.length; i++) { + for (let j = 0; j < objects.length; j++) { + if (i !== j) { + objects[i].merge(objects[j].hashGraph.getAllVertices()); + } + } + } + } + }); +} + const suite = new Benchmark.Suite(); benchmarkForAddWinSet( @@ -66,6 +99,20 @@ benchmarkForAddWinSet( true ); +benchmarkForAddMulRecCall( + "Create a HashGraph with 1000 operations for rec mul", + 1, + NUMBER_OF_OPERATIONS, + false +); + +benchmarkForAddMulRecCall( + `Create 2 DRP Objects ${NUMBER_OF_OPERATIONS} vertices each and Merge for rec mul`, + 2, + NUMBER_OF_OPERATIONS, + true +); + suite.add("Create a HashGraph with 1000 operations for set wins map", () => { const object: DRPObject = new DRPObject({ peerId: "peer1",