From cba741f572c332a0f34bc8db1e9e78c2d788eb29 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 21:38:15 +0000 Subject: [PATCH 1/2] Initial plan From 0a5d448e097f110e96cc6fa2467811873a461342 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 21:40:39 +0000 Subject: [PATCH 2/2] Initialize TypeScript NumPy-style core API --- .gitignore | 2 ++ README.md | 24 +++++++++++++- package-lock.json | 30 ++++++++++++++++++ package.json | 17 ++++++++++ src/index.ts | 77 +++++++++++++++++++++++++++++++++++++++++++++ test/numpst.test.js | 25 +++++++++++++++ tsconfig.json | 46 +++++++++++++++++++++++++++ 7 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 test/numpst.test.js create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b947077 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/README.md b/README.md index 1c3e3c7..c213cde 100644 --- a/README.md +++ b/README.md @@ -1 +1,23 @@ -# numpst \ No newline at end of file +# numpst + +A small TypeScript port of common NumPy-style vector operations. + +## API + +- `array(iterable)` +- `zeros(length)` +- `ones(length)` +- `arange(start, stop?, step?)` +- `add(a, b)` +- `subtract(a, b)` +- `multiply(a, b)` +- `divide(a, b)` +- `sum(values)` +- `mean(values)` + +## Development + +```bash +npm install +npm test +``` diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..172e560 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "numpst", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "numpst", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "typescript": "^6.0.3" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..991951d --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "numpst", + "version": "1.0.0", + "description": "TypeScript port of NumPy", + "main": "dist/index.js", + "scripts": { + "test": "npm run build && node --test test", + "build": "tsc" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "typescript": "^6.0.3" + }, + "types": "dist/index.d.ts" +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..435cb9f --- /dev/null +++ b/src/index.ts @@ -0,0 +1,77 @@ +export type VectorLike = number[]; + +const assertSameLength = (a: VectorLike, b: VectorLike): void => { + if (a.length !== b.length) { + throw new Error(`Shape mismatch: ${a.length} !== ${b.length}`); + } +}; + +export const array = (values: Iterable): number[] => [...values]; + +export const zeros = (length: number): number[] => { + if (!Number.isInteger(length) || length < 0) { + throw new Error("length must be a non-negative integer"); + } + return new Array(length).fill(0); +}; + +export const ones = (length: number): number[] => { + if (!Number.isInteger(length) || length < 0) { + throw new Error("length must be a non-negative integer"); + } + return new Array(length).fill(1); +}; + +export const arange = (start: number, stop?: number, step = 1): number[] => { + if (step === 0) { + throw new Error("step must not be zero"); + } + + const from = stop === undefined ? 0 : start; + const to = stop === undefined ? start : stop; + const out: number[] = []; + + if (step > 0) { + for (let i = from; i < to; i += step) { + out.push(i); + } + } else { + for (let i = from; i > to; i += step) { + out.push(i); + } + } + + return out; +}; + +const elementwise = ( + a: VectorLike, + b: VectorLike, + op: (left: number, right: number) => number, +): number[] => { + assertSameLength(a, b); + return a.map((value, idx) => op(value, b[idx]!)); +}; + +export const add = (a: VectorLike, b: VectorLike): number[] => + elementwise(a, b, (left, right) => left + right); + +export const subtract = (a: VectorLike, b: VectorLike): number[] => + elementwise(a, b, (left, right) => left - right); + +export const multiply = (a: VectorLike, b: VectorLike): number[] => + elementwise(a, b, (left, right) => left * right); + +export const divide = (a: VectorLike, b: VectorLike): number[] => + elementwise(a, b, (left, right) => left / right); + +export const sum = (values: VectorLike): number => + values.reduce((acc, current) => acc + current, 0); + +export const mean = (values: VectorLike): number => { + if (values.length === 0) { + throw new Error("cannot compute mean of empty array"); + } + + return sum(values) / values.length; +}; diff --git a/test/numpst.test.js b/test/numpst.test.js new file mode 100644 index 0000000..10e5912 --- /dev/null +++ b/test/numpst.test.js @@ -0,0 +1,25 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const np = require('../dist/index.js'); + +test('array construction helpers', () => { + assert.deepEqual(np.array([1, 2, 3]), [1, 2, 3]); + assert.deepEqual(np.zeros(3), [0, 0, 0]); + assert.deepEqual(np.ones(2), [1, 1]); + assert.deepEqual(np.arange(4), [0, 1, 2, 3]); + assert.deepEqual(np.arange(1, 6, 2), [1, 3, 5]); +}); + +test('vector arithmetic', () => { + const a = [1, 2, 3]; + const b = [4, 5, 6]; + assert.deepEqual(np.add(a, b), [5, 7, 9]); + assert.deepEqual(np.subtract(b, a), [3, 3, 3]); + assert.deepEqual(np.multiply(a, b), [4, 10, 18]); + assert.deepEqual(np.divide(b, a), [4, 2.5, 2]); +}); + +test('reductions', () => { + assert.equal(np.sum([1, 2, 3, 4]), 10); + assert.equal(np.mean([1, 2, 3, 4]), 2.5); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3ae2719 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,46 @@ +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + "rootDir": "src", + "outDir": "dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "commonjs", + "target": "es2020", + "types": [], + // For nodejs: + // "lib": ["esnext"], + // "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": false, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + + "esModuleInterop": true, + } +}