Skip to content

Commit 36c9d04

Browse files
authored
Merge pull request #1 from spexy11/main
quantity reviewd
2 parents 81d86b3 + b4830d5 commit 36c9d04

2 files changed

Lines changed: 89 additions & 8 deletions

File tree

packages/core/src/expr.test.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect, test } from 'bun:test'
22

3-
import { expr } from './expr'
3+
import { expr, quantity } from './expr'
44

55
type Test<T = any> = {
66
desc: string
@@ -200,6 +200,47 @@ test.each<Test>([
200200
promise: expr('x^2').subs({ x: 'y' }).latex(),
201201
result: 'y^{2}',
202202
}),
203+
define({
204+
desc: 'subs: x^2, x -> y',
205+
promise: expr('x^2').subs({ x: 'y' }).latex(),
206+
result: 'y^{2}',
207+
}),
208+
209+
define({
210+
desc: 'isEqual with error: 1.005kg equals 1kg ± 0.01kg',
211+
promise: quantity('1.005\\mathrm{kg}').isEqual('1\\mathrm{kg}', '0.01\\mathrm{kg}'),
212+
result: true,
213+
}),
214+
define({
215+
desc: 'isEqual with error: 1.02kg does not equal 1kg ± 0.01kg',
216+
promise: quantity('1.02\\mathrm{kg}').isEqual('1\\mathrm{kg}', '0.01\\mathrm{kg}'),
217+
result: false,
218+
}),
219+
define({
220+
desc: 'isEqual with error: 9.85 m/s² equals 9.8 m/s² ± 0.1 m/s²',
221+
promise: quantity('9.85\\mathrm{m/s^2}').isEqual('9.8\\mathrm{m/s^2}', '0.1\\mathrm{m/s^2}'),
222+
result: true,
223+
}),
224+
define({
225+
desc: 'isEqual with error: 1050g equals 1kg ± 100g',
226+
promise: quantity('1050\\mathrm{g}').isEqual('1\\mathrm{kg}', '100\\mathrm{g}'),
227+
result: true,
228+
}),
229+
define({
230+
desc: 'isEqual with error: 1200g does not equal 1kg ± 100g',
231+
promise: quantity('1200\\mathrm{g}').isEqual('1\\mathrm{kg}', '100\\mathrm{g}'),
232+
result: false,
233+
}),
234+
define({
235+
desc: 'isEqual with error: 101400 Pa equals 101325 Pa ± 100 Pa',
236+
promise: quantity('101400\\mathrm{Pa}').isEqual('101325\\mathrm{Pa}', '100\\mathrm{Pa}'),
237+
result: true,
238+
}),
239+
define({
240+
desc: 'isEqual with error: 101600 Pa does not equal 101325 Pa ± 100 Pa',
241+
promise: quantity('101600\\mathrm{Pa}').isEqual('101325\\mathrm{Pa}', '100\\mathrm{Pa}'),
242+
result: false,
243+
}),
203244
])('$desc', async ({ promise, result }) => {
204245
expect(await promise).toEqual(result)
205246
})

packages/core/src/expr.ts

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1+
import { type ExpressionInput, ComputeEngine, N } from '@cortex-js/compute-engine'
12
import * as v from 'valibot'
2-
3-
import { type MathJsonExpression, ComputeEngine } from '@cortex-js/compute-engine'
43
import symapi from './symapi'
54

65
const ce = new ComputeEngine()
@@ -17,7 +16,7 @@ const integrateParams = v.union([
1716
),
1817
])
1918

20-
const Math: v.GenericSchema<MathJsonExpression> = v.union([
19+
const Math: v.GenericSchema<ExpressionInput> = v.union([
2120
v.pipe(
2221
v.string(),
2322
v.check((expr) => ce.parse(expr).isValid, "Ceci n'est pas une expression mathématique valide"),
@@ -57,21 +56,23 @@ function _expr(input: Math) {
5756
degree: () => symapi.expr.degree({ expr: json }),
5857
diff: (x = 'x') => expr(['Derivative', json, x]),
5958
expand: () => expr(['Expand', json]),
59+
evaluate: () => ce.expr(json).evaluate(),
60+
N: () => N(expr(json).evaluate()) as unknown as number,
6061
factor: () => expr(['Factor', json]),
6162
func: () => {
6263
if (!Array.isArray(json)) throw new Error(`Only arrays have the property func`)
6364
return json[0] as string
6465
},
6566
integrate: (...params: v.InferInput<typeof integrateParams>) =>
6667
expr(['Integrate', json, ...v.parse(integrateParams, params)]),
67-
isEqual: (expr: Expression) =>
68-
symapi.expr.equal({ expr1: json, expr2: v.parse(Expression, expr) }),
68+
isEqual: (other: Expression) =>
69+
symapi.expr.equal({ expr1: json, expr2: v.parse(Expression, other) }),
6970
isFactored: () => symapi.expr.isFactored({ expr: json }),
7071
isPartialFractionDecomposition: () =>
7172
symapi.expr.isPartialFractionDecomposition({ expr: json }),
7273
latex: () => symapi.expr.latex({ expr: json }),
73-
matches: (expr: Expression) =>
74-
symapi.expr.match({ expr1: json, expr2: v.parse(Expression, expr) }),
74+
matches: (other: Expression) =>
75+
symapi.expr.match({ expr1: json, expr2: v.parse(Expression, other) }),
7576
roots: (complex = false) => symapi.expr.roots({ expr: json, complex }),
7677
simplify: () => expr(['Simplify', json]),
7778
subs: (substitutions: Record<string, Math>) => expr(ce.expr(json).subs(substitutions).json),
@@ -84,3 +85,42 @@ export function expr<T extends Math | undefined>(
8485
if (input === undefined) return undefined as any
8586
return _expr(input) as any
8687
}
88+
89+
const Quantity = v.union([
90+
Math,
91+
v.pipe(
92+
v.tuple([Math, Math]),
93+
v.transform((quantity) => ce.expr(['Quantity', ...quantity]).evaluate()),
94+
),
95+
])
96+
type Quantity = v.InferInput<typeof Quantity>
97+
98+
export function quantity(qty: Quantity) {
99+
const json = v.parse(Quantity, qty)
100+
101+
function apply(method: string, ...args: ExpressionInput[]) {
102+
return ce.expr([method, ...args]).evaluate()
103+
}
104+
105+
return {
106+
convert: (rawUnit: Quantity) => {
107+
const unit = v.parse(Quantity, rawUnit)
108+
return quantity(apply('UnitConvert', json, unit).json)
109+
},
110+
magnitude: () => expr(apply('QuantityMagnitude', json).json),
111+
subtract: (rawOther: Quantity) => {
112+
const other = v.parse(Quantity, rawOther)
113+
return quantity(apply('Subtract', json, other).json)
114+
},
115+
unit: () => apply('QuantityUnit', json).latex,
116+
json,
117+
118+
async isEqual(rawExpr2: Quantity, rawError?: Quantity) {
119+
const expr2 = v.parse(Quantity, rawExpr2)
120+
const error = rawError ? v.parse(Quantity, rawError) : undefined
121+
if (!error) return this.subtract(expr2).magnitude().isEqual(0)
122+
const diff = this.subtract(expr2).convert(error).magnitude().abs().N()
123+
return diff < quantity(error).magnitude().abs().N()
124+
},
125+
}
126+
}

0 commit comments

Comments
 (0)