Skip to content

Commit 349c19e

Browse files
committed
feat: add evaluations for Xor, Nand, and Nor logic functions; enhance tests for logical operations and CNF/DNF conversions
1 parent c34335b commit 349c19e

4 files changed

Lines changed: 138 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,20 @@
3939
when simplifying expressions containing exponentials, such as the derivative
4040
of `erf(x)` which is `(2/√π)·e^(-x²)`.
4141

42+
- **Special Function Derivatives**: Fixed derivative formulas for several special
43+
functions and removed incorrect ones:
44+
- Fixed `d/dx erfi(x) = (2/√π)·e^(x²)` (imaginary error function)
45+
- Fixed `d/dx S(x) = sin(πx²/2)` (Fresnel sine integral)
46+
- Fixed `d/dx C(x) = cos(πx²/2)` (Fresnel cosine integral)
47+
- Removed incorrect derivative formulas for Zeta, Digamma, PolyGamma, Beta,
48+
LambertW, Bessel functions, and Airy functions (these now return symbolic
49+
derivatives like `Digamma'(x)` instead of wrong numeric results)
50+
51+
- **Symbolic Derivative Evaluation**: Fixed derivatives of unknown functions
52+
returning `0` instead of symbolic derivatives. For example, `D(Digamma(x), x)`
53+
now correctly returns `Digamma'(x)` (as `Apply(Derivative(Digamma, 1), x)`)
54+
instead of incorrectly returning `0`.
55+
4256
- **([#168](https://github.com/cortex-js/compute-engine/issues/168))
4357
Absolute Value**: Fixed parsing of nested absolute value expressions that
4458
start with a double bar (e.g. `||3-5|-4|`), which previously produced an

src/compute-engine/library/calculus.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ volumes
160160
f = f?.canonical;
161161
// Avoid recursive evaluation
162162
if (f?.operator === 'D') return f;
163+
// Avoid evaluating symbolic derivative applications like Digamma'(x)
164+
// which would incorrectly evaluate to 0
165+
if (
166+
f?.operator === 'Apply' &&
167+
f.op1?.operator === 'Derivative'
168+
)
169+
return f;
163170
// If the result contains symbolic transcendentals (like ln(2)),
164171
// return it without full evaluation to preserve the symbolic form
165172
if (f && hasSymbolicTranscendental(f)) return f;

src/compute-engine/library/logic.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,33 @@ export const LOGIC_LIBRARY: SymbolDefinitions = {
8282
signature: '(boolean, boolean) -> boolean',
8383
evaluate: evaluateImplies,
8484
},
85+
Xor: {
86+
description: 'Exclusive or: true when exactly one operand is true',
87+
wikidata: 'Q498186',
88+
broadcastable: true,
89+
commutative: true,
90+
complexity: 10200,
91+
signature: '(boolean, boolean) -> boolean',
92+
evaluate: evaluateXor,
93+
},
94+
Nand: {
95+
description: 'Not-and: negation of conjunction',
96+
wikidata: 'Q189550',
97+
broadcastable: true,
98+
commutative: true,
99+
complexity: 10200,
100+
signature: '(boolean, boolean) -> boolean',
101+
evaluate: evaluateNand,
102+
},
103+
Nor: {
104+
description: 'Not-or: negation of disjunction',
105+
wikidata: 'Q189561',
106+
broadcastable: true,
107+
commutative: true,
108+
complexity: 10200,
109+
signature: '(boolean, boolean) -> boolean',
110+
evaluate: evaluateNor,
111+
},
85112
// Quantifiers return boolean values (they are propositions)
86113
// They support evaluation over finite domains (e.g., ForAll with Element condition)
87114
// The first argument can be:
@@ -268,6 +295,60 @@ function evaluateImplies(
268295
return undefined;
269296
}
270297

298+
function evaluateXor(
299+
args: ReadonlyArray<BoxedExpression>,
300+
{ engine: ce }: { engine: ComputeEngine }
301+
): BoxedExpression | undefined {
302+
const lhs = args[0].symbol;
303+
const rhs = args[1].symbol;
304+
// XOR is true when exactly one operand is true
305+
if (
306+
(lhs === 'True' && rhs === 'False') ||
307+
(lhs === 'False' && rhs === 'True')
308+
)
309+
return ce.True;
310+
if (
311+
(lhs === 'True' && rhs === 'True') ||
312+
(lhs === 'False' && rhs === 'False')
313+
)
314+
return ce.False;
315+
return undefined;
316+
}
317+
318+
function evaluateNand(
319+
args: ReadonlyArray<BoxedExpression>,
320+
{ engine: ce }: { engine: ComputeEngine }
321+
): BoxedExpression | undefined {
322+
// NAND is the negation of AND
323+
const lhs = args[0].symbol;
324+
const rhs = args[1].symbol;
325+
if (lhs === 'True' && rhs === 'True') return ce.False;
326+
if (
327+
(lhs === 'True' && rhs === 'False') ||
328+
(lhs === 'False' && rhs === 'True') ||
329+
(lhs === 'False' && rhs === 'False')
330+
)
331+
return ce.True;
332+
return undefined;
333+
}
334+
335+
function evaluateNor(
336+
args: ReadonlyArray<BoxedExpression>,
337+
{ engine: ce }: { engine: ComputeEngine }
338+
): BoxedExpression | undefined {
339+
// NOR is the negation of OR
340+
const lhs = args[0].symbol;
341+
const rhs = args[1].symbol;
342+
if (lhs === 'False' && rhs === 'False') return ce.True;
343+
if (
344+
(lhs === 'True' && rhs === 'True') ||
345+
(lhs === 'True' && rhs === 'False') ||
346+
(lhs === 'False' && rhs === 'True')
347+
)
348+
return ce.False;
349+
return undefined;
350+
}
351+
271352
export function simplifyLogicFunction(
272353
x: BoxedExpression
273354
): { value: BoxedExpression; because: string } | undefined {
@@ -277,6 +358,9 @@ export function simplifyLogicFunction(
277358
Not: evaluateNot,
278359
Equivalent: evaluateEquivalent,
279360
Implies: evaluateImplies,
361+
Xor: evaluateXor,
362+
Nand: evaluateNand,
363+
Nor: evaluateNor,
280364
}[x.operator];
281365

282366
if (!fn || !x.ops) return undefined;

test/compute-engine/logic.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,27 @@ describe('Logic', () => {
4747
`"True"`
4848
);
4949
});
50+
51+
it('should evaluate Xor', () => {
52+
expect(box(['Xor', 'True', 'True'])).toMatchInlineSnapshot(`"False"`);
53+
expect(box(['Xor', 'True', 'False'])).toMatchInlineSnapshot(`"True"`);
54+
expect(box(['Xor', 'False', 'True'])).toMatchInlineSnapshot(`"True"`);
55+
expect(box(['Xor', 'False', 'False'])).toMatchInlineSnapshot(`"False"`);
56+
});
57+
58+
it('should evaluate Nand', () => {
59+
expect(box(['Nand', 'True', 'True'])).toMatchInlineSnapshot(`"False"`);
60+
expect(box(['Nand', 'True', 'False'])).toMatchInlineSnapshot(`"True"`);
61+
expect(box(['Nand', 'False', 'True'])).toMatchInlineSnapshot(`"True"`);
62+
expect(box(['Nand', 'False', 'False'])).toMatchInlineSnapshot(`"True"`);
63+
});
64+
65+
it('should evaluate Nor', () => {
66+
expect(box(['Nor', 'True', 'True'])).toMatchInlineSnapshot(`"False"`);
67+
expect(box(['Nor', 'True', 'False'])).toMatchInlineSnapshot(`"False"`);
68+
expect(box(['Nor', 'False', 'True'])).toMatchInlineSnapshot(`"False"`);
69+
expect(box(['Nor', 'False', 'False'])).toMatchInlineSnapshot(`"True"`);
70+
});
5071
});
5172

5273
describe('Kronecker Delta', () => {
@@ -282,6 +303,18 @@ describe('CNF/DNF Conversion', () => {
282303
);
283304
});
284305

306+
it('should handle Xor', () => {
307+
// A ⊕ B ≡ (A ∨ B) ∧ (¬A ∨ ¬B) in CNF
308+
expect(box(['ToCNF', ['Xor', 'A', 'B']])).toMatchInlineSnapshot(
309+
`(A || B) && (!A || !B)`
310+
);
311+
312+
// A ⊕ B ≡ (A ∧ ¬B) ∨ (¬A ∧ B) in DNF
313+
expect(box(['ToDNF', ['Xor', 'A', 'B']])).toMatchInlineSnapshot(
314+
`A && !B || B && !A`
315+
);
316+
});
317+
285318
it('should simplify constant expressions', () => {
286319
expect(box(['ToCNF', 'True'])).toMatchInlineSnapshot(`"True"`);
287320
expect(box(['ToCNF', 'False'])).toMatchInlineSnapshot(`"False"`);

0 commit comments

Comments
 (0)