diff --git a/README.md b/README.md index 6c8c373..e0ea3ec 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,98 @@ # roll-calc -Roll (spiral) calculator functions. + +Roll calculator functions. + +This module was created for calculating dimesions of materials in rolls for inventory and winding machines. + +## Install + +```sh +npm install roll-calc +``` + +## Usage + +```js +import { rollSolve } from 'roll-calc'; + +const materialHeight = 0.06; +const innerDiameter = 18; +const outerDiameter = 60; +const length = rollSolve(materialHeight, innerDiameter, outerDiameter); + +console.log(length); +//=> 42882.74547004675 +``` + +## API + +### `function rollLength(h: number, d0: number, d1: number): number` + +Calculates length of a roll (2D spiral). + +Equations from https://www.giangrandi.ch/soft/spiral/spiral.shtml + +```js +import { rollLength } from 'roll-calc'; + +const materialHeight = 0.1; +const innerDiameter = 2; +const outerDiameter = 3; +const length = rollLength(materialHeight, innerDiameter, outerDiameter); + +console.log(length); +//=> 39.27313461871492 +``` + +### `function rollOuterDiameter(h: number, d0: number, l: number, maxIter?: number): number` + +Calcuates the outer diameter of a roll (2D spiral). + +```js +import { rollOuterDiameter } from 'roll-calc'; + +const materialHeight = 0.1; +const innerDiameter = 2; +const length = 39.273_134_62; +const outerDiameter = rollOuterDiameter(materialHeight, innerDiameter, length); + +console.log(outerDiameter); +//=> 3.0000000000272684 +``` + +### `function rollInnerDiameter(h: number, d1: number, l: number): number` + +Calculates the inner diameter of a roll (2D spiral). + +```js +import { rollInnerDiameter } from 'roll-calc'; + +const materialHeight = 0.1; +const outerDiameter = 3; +const length = 39.273_134_62; +const innerDiameter = rollInnerDiameter(materialHeight, innerDiameter, length); + +console.log(innerDiameter); +//=> 1.999999999959101 +``` + +### `rollMaterialHeight(d0: number, d1: number, l: number): number` + +Calculates the nominal material height or thickness in a roll (2D Spiral). + +Equations from: +> Thickness of Material on a Roll Winding: Machines, Mechanics and Measurements +By James K. Good, David R. Roisum +page 124 + +```js +import { rollMaterialHeight } from 'roll-calc'; + +const innerDiameter = 2; +const outerDiameter = 3; +const length = 39.273_134_62; +const materialHeight = rollMaterialHeight(innerDiameter, outerDiameter, length); + +console.log(materialHeight); +//=> 0.09999178458720241 +``` diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..124f76b --- /dev/null +++ b/index.d.ts @@ -0,0 +1,124 @@ +/** +Calculates lenght of a roll (2D spiral). + +Equations from https://www.giangrandi.ch/soft/spiral/spiral.shtml + +@example +``` +import { rollLength } from 'roll-calc'; + +const materialHeight = 0.1; +const innerDiameter = 2; +const outerDiameter = 3; +const length = rollLength(materialHeight, innerDiameter, outerDiameter); + +console.log(length); +//=> 39.27313461871492 +``` + +@param h - Material height (thickness); 0 < h < Infinity +@param d0 - Inner diameter; h < d0 > d1 +@param d1 - Outer diameter; d0 < d1 < Infinity +@returns calculated roll length +*/ +export function rollLength(h: number, d0: number, d1: number): number | undefined; + +/** +Calcuates the outer diameter of a roll (2D spiral). + +@example +``` +import { rollOuterDiameter } from 'roll-calc'; + +const materialHeight = 0.1; +const innerDiameter = 2; +const length = 39.273_134_62; +const outerDiameter = rollOuterDiameter(materialHeight, innerDiameter, length); + +console.log(outerDiameter); +//=> 3.0000000000272684 +``` + +@param h - Material height (thickness); 0 < h < Infinity +@param d0 - Inner diameter; h < d0 > Infinity +@param l - Roll Length; pi * d0 <= l < Infinity +@param maxIter - Maximum number of interation for convergence of the Newton's method; default 10 +@returns calculated outer diameter +*/ +export function rollOuterDiameter(h: number, d0: number, l: number, maxIter?: number): number | undefined; + +/** +Calculates the inner diameter of a roll (2D spiral). + +@example +``` +import { rollInnerDiameter } from 'roll-calc'; + +const materialHeight = 0.1; +const outerDiameter = 3; +const length = 39.273_134_62; +const innerDiameter = rollInnerDiameter(materialHeight, innerDiameter, length); + +console.log(innerDiameter); +//=> 1.999999999959101 +``` + +@param h - Material height (thickness); 0 < h < Infinity +@param d1 - Outer diameter; h < d1 < Infinity +@param l - Roll Length; pi * d1 <= l < Infinity +@returns calculated inner diameter +*/ +export function rollInnerDiameter(h: number, d1: number, l: number): number | undefined; + +/** +Calculates the nominal material height or thickness in a roll (2D Spiral). + +Equations from: +Thickness of Material on a Roll Winding: Machines, Mechanics and Measurements +By James K. Good, David R. Roisum +page 124 + +@example +``` +import { rollMaterialHeight } from 'roll-calc'; + +const innerDiameter = 2; +const outerDiameter = 3; +const length = 39.273_134_62; +const materialHeight = rollMaterialHeight(innerDiameter, outerDiameter, length); + +console.log(materialHeight); +//=> 0.09999178458720241 +``` + +@param d0 - Inner diameter; 0 < d0 > d1 +@param d1 - Outer diameter; d0 < d1 < Infinity +@param l - Roll Length; pi * d1 <= l < Infinity +@returns calculated material height (thickness) +*/ +export function rollMaterialHeight(d0: number, d1: number, l: number): number | undefined; + +/** +Solves one missing dimension of a roll (2D spiral). + +Pass in at least three arguments to solve for the fourth. + +@example +``` +import { rollSolve } from 'roll-calc'; + +const materialHeight = 0.06; +const innerDiameter = 18; +const outerDiameter = 60; +const length = rollSolve(materialHeight, innerDiameter, outerDiameter, undefined); + +console.log(length); +//=> 42882.74547004675 +``` +@param h - Material height; 0 < h > Infinity +@param d0 - Inner diameter; 0 < d0 > Infinity +@param d1 - Outer diameter; 0 < d1 < Infinity +@param l - Roll Length; 0 < l < Infinity +@returns calculated material height (thickness) +*/ +export function rollSolve(h?: number, d0?: number, d1?: number, l?: number): number | undefined; diff --git a/index.js b/index.js new file mode 100644 index 0000000..63d04b3 --- /dev/null +++ b/index.js @@ -0,0 +1,138 @@ +const {sqrt, PI} = Math; +const notFinite = n => !Number.isFinite(n); + +function getExactLength(phi0, phi1, h) { + let length; + length = (phi1 / 2) * sqrt((phi1 * phi1) + 1); + length += (1 / 2) * Math.log(phi1 + sqrt((phi1 * phi1) + 1)); + length -= (phi0 / 2) * sqrt((phi0 * phi0) + 1); + length -= (1 / 2) * Math.log(phi0 + sqrt((phi0 * phi0) + 1)); + length *= h / (2 * PI); + return length; +} + +function getExactDeltaLengthDeltaPhi(phi, h) { + let delta; + const phi2 = phi * phi; + delta = ((2 * phi2) + 1) / (2 * sqrt(phi2 + 1)); + delta += (phi + sqrt(phi2 + 1)) / ((2 * phi * sqrt(phi2 + 1)) + (2 * phi2) + 2); + delta *= h / (2 * PI); + return delta; +} + +export function rollLength(h, d0, d1) { + if (notFinite(h) + || notFinite(d0) + || notFinite(d1) + || h <= 0 + || d0 <= 0 + || d1 <= 0 + || h > d0 + || d0 >= d1 + ) { + return; + } + + const phi0 = PI * d0 / h; + const phi1 = PI * d1 / h; + return getExactLength(phi0, phi1, h); +} + +export function rollOuterDiameter(h, d0, l, maxIter = 10) { + if (notFinite(h) + || notFinite(d0) + || notFinite(l) + || h <= 0 + || d0 <= 0 + || h > d0 + || l < PI * d0 + ) { + return; + } + + /* To find "phi1" from L(phi1)=(h/2pi)(...) we have to numerically solve + * (h/2pi)(...)-L=0, so we have f(phi1)=(h/2pi)(...)-L=0 + * The approximate formula is used to find a starting point, from which we + * use Newton's method to find a more accurate numerical solution as follows: + * + * phi_n+1 = phi_n - (f(phi_n)/f'(phi_n)) + * + * Whith this method, there is no need to invert the function, it's only + * necessary to find it's derivative. It converges quite quickly and only a + * few iterations are required for the precision of the floating point + * variable used. + */ + const n = (h - d0 + sqrt((((d0 - h) * (d0 - h)) + (4 * h * l)) / PI)) / (2 * h); + let d1 = (2 * n * h) + d0; + const phi0 = PI * d0 / h; + let phi1 = PI * d1 / h; + let deltaPhi; + // This is the starting approximation. + for (let i = 0; i <= maxIter; i++) { + deltaPhi = (getExactLength(phi0, phi1, h) - l) / getExactDeltaLengthDeltaPhi(phi1, h); + phi1 -= deltaPhi; + // Stop looping if solution already found. + if (deltaPhi === 0) { + break; + } + } + + // Now we have phi1 and we find d1. + d1 = phi1 * h / PI; + // Note we could find a more accurate n if needed with: + // n = (d1 - d0) / (2 * h); + return d1; +} + +export function rollInnerDiameter(h, d1, l) { + if (notFinite(h) + || notFinite(d1) + || notFinite(l) + || h <= 0 + || d1 <= h + || l < PI * d1 + ) { + return; + } + + const minOuterDia = rollOuterDiameter(h, h, l); + const length = rollLength(h, minOuterDia, d1); + return rollOuterDiameter(h, h, length); +} + +export function rollMaterialHeight(d0, d1, l) { + if (notFinite(d0) + || notFinite(d1) + || notFinite(l) + || d0 <= 0 + || d1 <= d0 + || l < PI * d1 + ) { + return; + } + + return (PI / (4 * l)) * ((d1 ** 2) - (d0 ** 2)); +} + +export function rollSolve(h, d0, d1, l) { + if (h && d0 && d1 && l) { + // Nothing to solve. + return; + } + + if (h && d0 && d1 && !l) { + return rollLength(h, d0, d1); + } + + if (h && d0 && l && !d1) { + return rollOuterDiameter(h, d0, l); + } + + if (h && d1 && l && !d0) { + return rollInnerDiameter(h, d1, l); + } + + if (d0 && d1 && l && !h) { + return rollMaterialHeight(d0, d1, l); + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4633ed9 --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name": "roll-calc", + "version": "1.0.0", + "description": "Roll (spiral) calculator functions.", + "homepage": "https://github.com/coryasilva/roll-calc#readme", + "bugs": { + "url": "https://github.com/coryasilva/roll-calc/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/coryasilva/roll-calc.git" + }, + "author": { + "name": "Cory Silva", + "url": "https://corysilva.com" + }, + "keywords": [ + "roll", + "2D", + "spiral", + "calculator", + "length", + "diameter", + "winding", + "inventory" + ], + "engines": { + "node": ">=18" + }, + "type": "module", + "sideEffects": false, + "main": "index.js", + "exports": { + "types": "./index.d.ts", + "default": "./index.js" + }, + "scripts": { + "test": "xo && ava && tsc index.d.ts" + }, + "devDependencies": { + "ava": "^6.0.1", + "typescript": "^5.3.3", + "xo": "^0.56.0" + } +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..50e9403 --- /dev/null +++ b/test.js @@ -0,0 +1,94 @@ +import test from 'ava'; +import { + rollLength, + rollOuterDiameter, + rollInnerDiameter, + rollMaterialHeight, + rollSolve, +} from './index.js'; + +const round = (n, precision = 0) => Math.round(n * (10 ** precision)) / (10 ** precision); + +const rolls = [ + { + h: 0.1, + d0: 2, + d1: 3, + l: 39.273_134_62, + }, + { + h: 0.06, + d0: 18, + d1: 60, + l: 42_882.745_470_05, + }, + { + h: 0.008, + d0: 4, + d1: 48.5, + l: 229_360.808_993_2, + }, +]; + +test('roll length', t => { + for (const roll of rolls) { + const length = rollLength(roll.h, roll.d0, roll.d1); + t.is(round(length, 8), roll.l); + } + + // Guard + t.is(rollLength(0, 99, 99), undefined); + t.is(rollLength(2, 1, 99), undefined); + t.is(rollLength(1, 2, 1), undefined); + t.is(rollLength(1, 2, 2), undefined); +}); + +test('roll outer diameter', t => { + for (const roll of rolls) { + const dia = rollOuterDiameter(roll.h, roll.d0, roll.l); + t.is(round(dia, 8), roll.d1); + } + + // Guard + t.is(rollOuterDiameter(0, 1, 99), undefined); + t.is(rollOuterDiameter(2, 1, 99), undefined); + t.is(rollOuterDiameter(2, 2, 1), undefined); + t.is(rollOuterDiameter(1, 2, (2 * Math.PI) - 0.001), undefined); +}); + +test('roll inner diameter', t => { + for (const roll of rolls) { + const dia = rollInnerDiameter(roll.h, roll.d1, roll.l); + t.is(round(dia, 8), roll.d0); + } + + // Guard + t.is(rollInnerDiameter(0, 1, 99), undefined); + t.is(rollInnerDiameter(2, 1, 5), undefined); + t.is(rollInnerDiameter(2, 2, 1), undefined); + t.is(rollInnerDiameter(2, 4, 99), undefined); + t.is(rollInnerDiameter(1, 8, (8 * Math.PI) - 0.001), undefined); +}); + +test('roll material height', t => { + for (const roll of rolls) { + const h = rollMaterialHeight(roll.d0, roll.d1, roll.l); + t.is(round(h, 4), roll.h); + } + + // Guard + t.is(rollMaterialHeight(2, 1, 99), undefined); + t.is(rollMaterialHeight(1, 1, Math.PI), undefined); + t.is(rollMaterialHeight(1, 2, (2 * Math.PI) - 0.001), undefined); +}); + +test('roll solve', t => { + const [roll] = rolls; + t.is(round(rollSolve(roll.h, roll.d0, roll.d1, undefined), 8), roll.l); + t.is(round(rollSolve(roll.h, roll.d0, undefined, roll.l), 8), roll.d1); + t.is(round(rollSolve(roll.h, undefined, roll.d1, roll.l), 8), roll.d0); + t.is(round(rollSolve(undefined, roll.d0, roll.d1, roll.l), 4), roll.h); + + // Guard + t.is(rollSolve(1, 2, 3, 4), undefined); +});