Skip to content

Commit

Permalink
fix: enforce nonnegative integer input for factorial function
Browse files Browse the repository at this point in the history
Tests have also been added for the factorial function. Doesn't currently work if used in a data table.
  • Loading branch information
mgreminger committed Sep 26, 2024
1 parent 4cad95e commit c4208a0
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 2 deletions.
13 changes: 12 additions & 1 deletion public/dimensional_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
floor,
ceiling,
sign,
sqrt
sqrt,
factorial
)

class ExprWithAssumptions(Expr):
Expand Down Expand Up @@ -1120,6 +1121,15 @@ def IndexMatrix(expression: Expr, i: Expr, j: Expr) -> Expr:

return cast(Expr, cast(Matrix, expression)[i, j])

def custom_factorial(expression: ExprWithAssumptions) -> Expr:
if expression.is_number:
if not (expression.is_real and expression.is_finite and expression.is_integer and cast(int, expression) >= 0):
raise Exception("The factorial function can only be evaluated a nonnegative integer")
return factorial(expression)

# case of symbolic input, doesn't currently work with data tables
return factorial(expression)

def custom_norm(expression: Matrix):
return expression.norm()

Expand Down Expand Up @@ -1461,6 +1471,7 @@ def get_next_id(self):
cast(Function, Function('_Derivative')) : {"dim_func": custom_derivative_dims, "sympy_func": custom_derivative},
cast(Function, Function('_Integral')) : {"dim_func": custom_integral_dims, "sympy_func": custom_integral},
cast(Function, Function('_range')) : {"dim_func": custom_range, "sympy_func": custom_range},
cast(Function, Function('_factorial')) : {"dim_func": factorial, "sympy_func": custom_factorial},
}

global_placeholder_set = set(global_placeholder_map.keys())
Expand Down
2 changes: 1 addition & 1 deletion src/parser/LatexToSympy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1799,7 +1799,7 @@ export class LatexToSympy extends LatexParserVisitor<string | Statement | UnitBl
}

visitFactorial = (ctx: FactorialContext) => {
return `factorial(${this.visit(ctx.expr())})`;
return `_factorial(${this.visit(ctx.expr())})`;
}

visitUnitExponent = (ctx: UnitExponentContext) => {
Expand Down
85 changes: 85 additions & 0 deletions tests/test_basic.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1709,3 +1709,88 @@ test.skip('Test zero placeholder', async () => {

await expect(page.locator('#cell-9 >> text=Dimension Error')).toBeVisible();
});

test('Test factorial function', async () => {
await page.setLatex(0, String.raw`4!=`);

await page.click('#add-math-cell');
await page.setLatex(1, String.raw`5-4!=`);

await page.click('#add-math-cell');
await page.setLatex(2, String.raw`\frac{10!}{9!}=`);

await page.click('#add-math-cell');
await page.setLatex(3, String.raw`2!\cdot3!=`);

await page.click('#add-math-cell');
await page.setLatex(4, String.raw`\left(9-6\right)!=`);

await page.click('#add-math-cell');
await page.setLatex(5, String.raw`0!=`);

await page.click('#add-math-cell');
await page.setLatex(6, String.raw`a!=`);

await page.waitForSelector('.status-footer', { state: 'detached' });

let content = await page.textContent('#result-value-0');
expect(parseLatexFloat(content)).toBeCloseTo(24, precision);
content = await page.textContent('#result-units-0');
expect(content).toBe('');

content = await page.textContent('#result-value-1');
expect(parseLatexFloat(content)).toBeCloseTo(-19, precision);
content = await page.textContent('#result-units-1');
expect(content).toBe('');

content = await page.textContent('#result-value-2');
expect(parseLatexFloat(content)).toBeCloseTo(10, precision);
content = await page.textContent('#result-units-2');
expect(content).toBe('');

content = await page.textContent('#result-value-3');
expect(parseLatexFloat(content)).toBeCloseTo(12, precision);
content = await page.textContent('#result-units-3');
expect(content).toBe('');

content = await page.textContent('#result-value-4');
expect(parseLatexFloat(content)).toBeCloseTo(6, precision);
content = await page.textContent('#result-units-4');
expect(content).toBe('');

content = await page.textContent('#result-value-5');
expect(parseLatexFloat(content)).toBeCloseTo(1, precision);
content = await page.textContent('#result-units-5');
expect(content).toBe('');

content = await page.textContent('#result-value-6');
expect(content).toBe('a!');
content = await page.textContent('#result-units-6');
expect(content).toBe('');

await page.click('#add-math-cell');
await page.setLatex(7, String.raw`a=4`);

await page.waitForSelector('.status-footer', { state: 'detached' });

content = await page.textContent('#result-value-6');
expect(parseLatexFloat(content)).toBeCloseTo(24, precision);
content = await page.textContent('#result-units-6');
expect(content).toBe('');
});

test('Test factorial error check for non integer input', async () => {
await page.setLatex(0, String.raw`1.1!=`);

await page.waitForSelector('text=Updating...', {state: 'detached'});

await expect(page.locator('text=The factorial function can only be evaluated a nonnegative integer')).toBeVisible();
});

test('Test factorial error check for negative input', async () => {
await page.setLatex(0, String.raw`-1!=`);

await page.waitForSelector('text=Updating...', {state: 'detached'});

await expect(page.locator('text=The factorial function can only be evaluated a nonnegative integer')).toBeVisible();
});

0 comments on commit c4208a0

Please sign in to comment.