Skip to content

Commit 7b7c479

Browse files
committed
feat: add standard LaTeX operators \ker, \dim, \deg, \hom with implicit arguments and update serialization
1 parent 463f79c commit 7b7c479

4 files changed

Lines changed: 151 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,13 @@ ce.simplificationRules.push({
315315
arguments serialize as `\det A` instead of `\det\left(A\right)`. Matrix
316316
arguments still serialize as `\begin{vmatrix}...\end{vmatrix}`.
317317

318+
- **Added standard LaTeX operators `\ker`, `\dim`, `\deg`, `\hom`**: These
319+
commands are now in the MathJSON LaTeX dictionary as function entries with
320+
implicit arguments, so forms like `\ker V`, `\dim V`, `\deg p`, and
321+
`\hom(V, W)` parse correctly and serialize back to the corresponding standard
322+
operator notation. The corresponding function symbols (`Kernel`, `Dimension`,
323+
`Degree`, `Hom`) are also registered in the linear algebra library.
324+
318325
### LaTeX Parsing
319326

320327
- **`arguments: 'implicit'` option for function dictionary entries**: Function

src/compute-engine/latex-syntax/dictionary/definitions-linear-algebra.ts

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -232,19 +232,58 @@ export const DEFINITIONS_LINEAR_ALGEBRA: LatexDictionary = [
232232
kind: 'function',
233233
latexTrigger: '\\tr',
234234
arguments: 'implicit',
235-
serialize: (serializer: Serializer, expr: Expression): string => {
236-
const arg = operand(expr, 1);
237-
const argLatex = serializer.serialize(arg);
238-
// Use \tr A for simple args, \tr\left(...\right) for complex ones
239-
if (typeof arg === 'string' || typeof arg === 'number')
240-
return `\\tr ${argLatex}`;
241-
return `\\tr\\left(${argLatex}\\right)`;
242-
},
235+
serialize: (serializer: Serializer, expr: Expression): string =>
236+
serializeImplicitOperator(serializer, expr, '\\tr'),
243237
},
244238

245239
// Also support plain text: tr(A)
246240
{ symbolTrigger: 'tr', kind: 'function', parse: 'Trace', arguments: 'implicit' },
247241

242+
{
243+
name: 'Kernel',
244+
kind: 'function',
245+
latexTrigger: '\\ker',
246+
arguments: 'implicit',
247+
serialize: (serializer: Serializer, expr: Expression): string =>
248+
serializeImplicitOperator(serializer, expr, '\\ker'),
249+
},
250+
{ symbolTrigger: 'ker', kind: 'function', parse: 'Kernel', arguments: 'implicit' },
251+
252+
{
253+
name: 'Dimension',
254+
kind: 'function',
255+
latexTrigger: '\\dim',
256+
arguments: 'implicit',
257+
serialize: (serializer: Serializer, expr: Expression): string =>
258+
serializeImplicitOperator(serializer, expr, '\\dim'),
259+
},
260+
{
261+
symbolTrigger: 'dim',
262+
kind: 'function',
263+
parse: 'Dimension',
264+
arguments: 'implicit',
265+
},
266+
267+
{
268+
name: 'Degree',
269+
kind: 'function',
270+
latexTrigger: '\\deg',
271+
arguments: 'implicit',
272+
serialize: (serializer: Serializer, expr: Expression): string =>
273+
serializeImplicitOperator(serializer, expr, '\\deg'),
274+
},
275+
{ symbolTrigger: 'deg', kind: 'function', parse: 'Degree', arguments: 'implicit' },
276+
277+
{
278+
name: 'Hom',
279+
kind: 'function',
280+
latexTrigger: '\\hom',
281+
arguments: 'implicit',
282+
serialize: (serializer: Serializer, expr: Expression): string =>
283+
serializeImplicitOperator(serializer, expr, '\\hom'),
284+
},
285+
{ symbolTrigger: 'hom', kind: 'function', parse: 'Hom', arguments: 'implicit' },
286+
248287
{
249288
name: 'Determinant',
250289
kind: 'function',
@@ -262,11 +301,7 @@ export const DEFINITIONS_LINEAR_ALGEBRA: LatexDictionary = [
262301
stringValue(operand(arg, 2))
263302
);
264303
}
265-
const argLatex = serializer.serialize(arg);
266-
// Use \det A for simple args, \det\left(...\right) for complex ones
267-
if (typeof arg === 'string' || typeof arg === 'number')
268-
return `\\det ${argLatex}`;
269-
return `\\det\\left(${argLatex}\\right)`;
304+
return serializeImplicitOperator(serializer, expr, '\\det');
270305
},
271306
},
272307

@@ -314,6 +349,21 @@ function parseColumnFormat(parser: Parser, optional = true): string {
314349
return result;
315350
}
316351

352+
function serializeImplicitOperator(
353+
serializer: Serializer,
354+
expr: Expression,
355+
command: string
356+
): string {
357+
const args = operands(expr);
358+
if (args.length !== 1) return `${command}${serializer.wrapArguments(expr)}`;
359+
const arg = operand(expr, 1);
360+
const argLatex = serializer.serialize(arg);
361+
// Use \foo A for simple args, \foo\left(...\right) for complex ones
362+
if (typeof arg === 'string' || typeof arg === 'number')
363+
return `${command} ${argLatex}`;
364+
return `${command}\\left(${argLatex}\\right)`;
365+
}
366+
317367
function serializeTabular(
318368
serializer: Serializer,
319369
rows: ReadonlyArray<Expression>,

src/compute-engine/library/linear-algebra.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,30 @@ export const LINEAR_ALGEBRA_LIBRARY: SymbolDefinitions[] = [
406406
},
407407
},
408408

409+
Kernel: {
410+
description: 'Kernel (null space) of a linear map',
411+
complexity: 8200,
412+
signature: '(value) -> value',
413+
},
414+
415+
Dimension: {
416+
description: 'Dimension of an object',
417+
complexity: 8200,
418+
signature: '(value) -> value',
419+
},
420+
421+
Degree: {
422+
description: 'Degree of an object',
423+
complexity: 8200,
424+
signature: '(value) -> value',
425+
},
426+
427+
Hom: {
428+
description: 'Hom-set of morphisms between objects',
429+
complexity: 8200,
430+
signature: '(value*) -> value',
431+
},
432+
409433
// Matrix multiplication: A (m×n) × B (n×p) → result (m×p)
410434
// Handles matrix × matrix, matrix × vector, vector × matrix
411435
MatrixMultiply: {

test/compute-engine/latex-syntax/linear-algebra.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,60 @@ describe('MatrixMultiply LaTeX', () => {
245245
);
246246
});
247247
});
248+
249+
describe('Trace LaTeX', () => {
250+
it('should serialize simple symbol argument without parentheses', () => {
251+
const result = ce.box(['Trace', 'A']);
252+
expect(result.latex).toBe('\\tr A');
253+
});
254+
255+
it('should serialize simple numeric argument without parentheses', () => {
256+
const result = ce.box(['Trace', 2]);
257+
expect(result.latex).toBe('\\tr 2');
258+
});
259+
260+
it('should serialize complex argument with parentheses', () => {
261+
const result = ce.box(['Trace', ['Add', 'A', 'B']]);
262+
expect(result.latex).toBe('\\tr\\left(A+B\\right)');
263+
});
264+
});
265+
266+
describe('Standard operator commands', () => {
267+
it('should parse \\ker with implicit argument', () => {
268+
expect(ce.parse('\\ker V').json).toEqual(['Kernel', 'V']);
269+
});
270+
271+
it('should parse \\dim with implicit argument', () => {
272+
expect(ce.parse('\\dim V').json).toEqual(['Dimension', 'V']);
273+
});
274+
275+
it('should parse \\deg with implicit argument', () => {
276+
expect(ce.parse('\\deg p').json).toEqual(['Degree', 'p']);
277+
});
278+
279+
it('should parse \\hom with explicit argument list', () => {
280+
expect(ce.parse('\\hom(V, W)').json).toEqual(['Hom', 'V', 'W']);
281+
});
282+
});
283+
284+
describe('Kernel/Dimension/Degree/Hom LaTeX', () => {
285+
it('should serialize simple kernel argument without parentheses', () => {
286+
const result = ce.box(['Kernel', 'V']);
287+
expect(result.latex).toBe('\\ker V');
288+
});
289+
290+
it('should serialize complex dimension argument with parentheses', () => {
291+
const result = ce.box(['Dimension', ['Add', 'V', 'W']]);
292+
expect(result.latex).toBe('\\dim\\left(V+W\\right)');
293+
});
294+
295+
it('should serialize simple degree argument without parentheses', () => {
296+
const result = ce.box(['Degree', 'p']);
297+
expect(result.latex).toBe('\\deg p');
298+
});
299+
300+
it('should serialize multiple hom arguments without dropping any', () => {
301+
const result = ce.box(['Hom', 'V', 'W']);
302+
expect(result.latex).toMatchInlineSnapshot(`\\hom(V, W)`);
303+
});
304+
});

0 commit comments

Comments
 (0)