-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Basic exports for dimensions and a few units.
- Loading branch information
Showing
11 changed files
with
409 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,12 @@ | ||
# Changelog | ||
|
||
## 0.1.0 | ||
|
||
- Install relevant dependencies | ||
- Add base classes for compounds, dimensions, and units | ||
- Add unit tests for dimensions and units | ||
- Units are associated with base dimensions from a conversion table | ||
|
||
## 0.0.0 | ||
|
||
- Initialize empty package |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { SMath } from 'smath'; | ||
import { NumberDictionary } from './lib'; | ||
|
||
/** | ||
* Represents a compound unit or dimension. | ||
*/ | ||
export abstract class Compound<T extends string, C extends Compound<T, C>> { | ||
private readonly num: NumberDictionary<T> = {}; | ||
private readonly den: NumberDictionary<T> = {}; | ||
/** | ||
* Create a new compound unit or dimension. | ||
* @param exponents Contains an object of exponent values | ||
* @param toLaTeX A function to convert the exponent to its representation in LaTeX | ||
*/ | ||
constructor(private readonly exponents: NumberDictionary<T>, private readonly toLaTeX: (exponent: T) => string) { | ||
for (const t in exponents) { | ||
const exponent: number = this.getExponent(t); | ||
if (exponent > 0) { | ||
this.num[t] = exponent; | ||
} else if (exponent < 0) { | ||
this.den[t] = -exponent; | ||
} | ||
} | ||
} | ||
/** | ||
* Combine two compounds by applying a factor on the second compound. | ||
* @param other Another compount | ||
* @param factor The factor to use on the other compound | ||
* @returns The combination of the two compounds | ||
*/ | ||
protected combine(other: C, factor: number): NumberDictionary<T> { | ||
const exponents_combined: NumberDictionary<T> = {}; | ||
for (const t in this.exponents) { | ||
exponents_combined[t] = this.getExponent(t); | ||
} | ||
for (const t in other.exponents) { | ||
exponents_combined[t] = this.getExponent(t) + factor * other.getExponent(t); | ||
} | ||
return exponents_combined; | ||
} | ||
/** | ||
* Multiply this compound by another after applying an exponent on the second compound. | ||
* @param other Another compound fraction | ||
* @param exponent The exponent to apply on the other compound | ||
* @returns The product of the two compounds | ||
*/ | ||
public abstract mult(other: C, exponent: number): C; | ||
/** | ||
* Determine whether two compounds contain the same units or dimensions. | ||
* @param other Another compound | ||
* @returns A boolean | ||
*/ | ||
public is(other: C): boolean { | ||
const dividend: NumberDictionary<T> = this.combine(other, -1); | ||
for (let t in dividend) { | ||
if (!SMath.approx(dividend[t] ?? 0, 0)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
/** | ||
* Determine the exponent on the specified unit or dimension. | ||
* @param exponent The unit or dimension to retrieve the exponent from | ||
* @returns The exponent | ||
*/ | ||
public getExponent(exponent: T): number { | ||
return this.exponents[exponent] ?? 0; | ||
} | ||
/** | ||
* Generate an array of nonzero exponent units or dimensions. | ||
* @returns An array of nonzero exponent units or dimensions | ||
*/ | ||
public getNonzeroExponents(): Array<T> { | ||
const nonzeroExponents: Array<T> = []; | ||
for (const t in this.exponents) { | ||
if (this.getExponent(t)) { | ||
nonzeroExponents.push(t); | ||
} | ||
} | ||
return nonzeroExponents; | ||
} | ||
/** | ||
* Generate LaTeX code for a number dictionary. | ||
* @param dict Any number dictionary | ||
* @returns Partial LaTeX code | ||
*/ | ||
private prettyPrint(dict: NumberDictionary<T>): string { | ||
let str: string = ''; | ||
for (const t in dict) { | ||
if (str.length) { | ||
str += ' \\cdot '; | ||
} | ||
const exponent: number = dict[t] ?? 0; | ||
if (SMath.approx(exponent, 1)) { | ||
str += this.toLaTeX(t); | ||
} else if (SMath.approx(exponent, 0.5)) { | ||
str += '\\sqrt{' + this.toLaTeX(t) + '}'; | ||
} else { | ||
str += this.toLaTeX(t) + '^{' + exponent.toString() + '}'; | ||
} | ||
} | ||
return str; | ||
} | ||
/** | ||
* Generate valid LaTeX code representing this compound. | ||
* @returns A valid LaTeX equation | ||
*/ | ||
public toString(): string { | ||
let str: string = ''; | ||
const hasNum: boolean = Object.keys(this.num).length > 0, | ||
hasDen: boolean = Object.keys(this.den).length > 0; | ||
if (hasDen) { | ||
str += '\\frac{'; | ||
} | ||
if (hasNum) { | ||
str += this.prettyPrint(this.num); | ||
} else { | ||
str += '1'; | ||
} | ||
if (hasDen) { | ||
str += '}{' + this.prettyPrint(this.den) + '}'; | ||
} | ||
return str; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { Dim, Dimension } from './dimension'; | ||
import { Units } from './unit'; | ||
|
||
/** | ||
* Contains information on how units should be converted. | ||
*/ | ||
export interface Conversion { | ||
/** | ||
* The LaTeX representation of this unit. | ||
*/ | ||
readonly latex: string; | ||
/** | ||
* The base physical dimensions of this unit. | ||
*/ | ||
readonly dim: Dimension; | ||
/** | ||
* The scale of this unit in relation to the base unit of this dimension. | ||
*/ | ||
readonly scale: number; | ||
} | ||
/** | ||
* Represents the full conversion table for **absolute** units only. | ||
*/ | ||
export const ConversionTable: { [index in Units]: Conversion } = { | ||
'centimeters': { | ||
latex: 'cm', | ||
dim: Dim({ length: 1 }), | ||
scale: 1e-2, | ||
}, | ||
'days': { | ||
latex: 'd', | ||
dim: Dim({ time: 1 }), | ||
scale: 60 * 60 * 24, | ||
}, | ||
'feet': { | ||
latex: 'ft', | ||
dim: Dim({ length: 1 }), | ||
scale: 0.3048, | ||
}, | ||
'hours': { | ||
latex: 'h', | ||
dim: Dim({ time: 1 }), | ||
scale: 60 * 60, | ||
}, | ||
'inches': { | ||
latex: 'in', | ||
dim: Dim({ length: 1 }), | ||
scale: 0.3048 / 12, | ||
}, | ||
'kilometers': { | ||
latex: 'km', | ||
dim: Dim({ length: 1 }), | ||
scale: 1e3, | ||
}, | ||
'meters': { | ||
latex: 'm', | ||
dim: Dim({ length: 1 }), | ||
scale: 1, | ||
}, | ||
'micrometers': { | ||
latex: '\\mu m', | ||
dim: Dim({ length: 1 }), | ||
scale: 1e-6, | ||
}, | ||
'microns': { | ||
latex: '\\mu m', | ||
dim: Dim({ length: 1 }), | ||
scale: 1e-6, | ||
}, | ||
'miles': { | ||
latex: 'mi', | ||
dim: Dim({ length: 1 }), | ||
scale: 0.3048 * 5280, | ||
}, | ||
'millimeters': { | ||
latex: 'mm', | ||
dim: Dim({ length: 1 }), | ||
scale: 1e-3, | ||
}, | ||
'milliseconds': { | ||
latex: 'ms', | ||
dim: Dim({ time: 1 }), | ||
scale: 1e-3, | ||
}, | ||
'minutes': { | ||
latex: 'm', | ||
dim: Dim({ time: 1 }), | ||
scale: 60, | ||
}, | ||
'months': { | ||
latex: 'M', | ||
dim: Dim({ time: 1 }), | ||
scale: 60 * 60 * 24 * 365.25 / 12, | ||
}, | ||
'nanometers': { | ||
latex: 'nm', | ||
dim: Dim({ length: 1 }), | ||
scale: 1e-9, | ||
}, | ||
'nanoseconds': { | ||
latex: 'ns', | ||
dim: Dim({ time: 1 }), | ||
scale: 1e-9, | ||
}, | ||
'seconds': { | ||
latex: 's', | ||
dim: Dim({ time: 1 }), | ||
scale: 1, | ||
}, | ||
'weeks': { | ||
latex: 'w', | ||
dim: Dim({ time: 1 }), | ||
scale: 60 * 60 * 24 * 7, | ||
}, | ||
'yards': { | ||
latex: 'yd', | ||
dim: Dim({ length: 1 }), | ||
scale: 0.3048, | ||
}, | ||
'years': { | ||
latex: 'y', | ||
dim: Dim({ time: 1 }), | ||
scale: 60 * 60 * 24 * 365.25, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Compound } from './compound'; | ||
import { Dictionary, NumberDictionary } from './lib'; | ||
|
||
/** | ||
* A list of common names for each of the physical base dimensions. | ||
*/ | ||
export type Dimensions = 'time' | 'length' | 'mass' | 'current' | 'temperature' | 'amount' | 'intensity'; | ||
/** | ||
* Contains all physical base dimensions and their corresponding abbreviations. | ||
*/ | ||
const DimensionTable: Dictionary<Dimensions> = { | ||
'amount': '\\textbf{N}', | ||
'current': '\\textbf{I}', | ||
'intensity': '\\textbf{J}', | ||
'length': '\\textbf{L}', | ||
'mass': '\\textbf{M}', | ||
'temperature': '\\boldsymbol{\\Theta}', | ||
'time': '\\textbf{T}', | ||
}; | ||
/** | ||
* Defines the class for physical base dimensions. | ||
*/ | ||
export class Dimension extends Compound<Dimensions, Dimension> { | ||
constructor(exponents: NumberDictionary<Dimensions>) { | ||
super(exponents, t => DimensionTable[t]); | ||
} | ||
public mult(other: Dimension, exponent: number): Dimension { | ||
return new Dimension(super.combine(other, exponent)); | ||
} | ||
} | ||
/** | ||
* Shorthand for creating a dimension object. | ||
* @param exponents Exponents of each of the physical base dimensions | ||
* @returns A new dimension | ||
*/ | ||
export function Dim(exponents: NumberDictionary<Dimensions>): Dimension { | ||
return new Dimension(exponents); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/** | ||
* Represents a type where every object value is a string. | ||
*/ | ||
export type Dictionary<T extends string> = { [index in T]: string }; | ||
/** | ||
* Represents a type where every object value is a number. | ||
*/ | ||
export type NumberDictionary<T extends string> = { [index in T]?: number }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { SMath } from 'smath'; | ||
import { ConversionTable } from './conversion'; | ||
import { Unit } from './unit'; | ||
|
||
export class Quantity { | ||
private readonly base: number; | ||
constructor(private readonly value: number, private readonly unit: Unit) { | ||
this.base = value; | ||
for (const u of unit.getNonzeroExponents()) { | ||
this.base = SMath.expand(this.base, 0, ConversionTable[u].scale ** unit.getExponent(u)); | ||
} | ||
} | ||
public as(newUnit: Unit): Quantity { | ||
if (!this.unit.dimension.is(newUnit.dimension)) { | ||
throw new Error('\\text{Dimensions do not match! } ' + this.unit.dimension.toString() + ' \\text{ vs. } ' + newUnit.dimension.toString()) | ||
} | ||
let newValue: number = this.base; | ||
for (const u of newUnit.getNonzeroExponents()) { | ||
newValue = SMath.normalize(newValue, 0, ConversionTable[u].scale ** newUnit.getExponent(u)); | ||
} | ||
return new Quantity(newValue, newUnit); | ||
} | ||
public toString(): string { | ||
return this.value.toString() + '\\left[' + this.unit.toString() + '\\right]'; | ||
} | ||
} |
Oops, something went wrong.