F: Implementing M-Graph pt2
This commit is contained in:
parent
abca112b28
commit
c60b4398ed
|
@ -6,8 +6,11 @@
|
|||
* Represents single node of a {@link Graph}, as implemented by storing outgoing and incoming connections.
|
||||
*/
|
||||
export class GraphNode {
|
||||
/** Unique identifier of the node. */
|
||||
id: number;
|
||||
/** List of outgoing nodes. */
|
||||
outputs: number[];
|
||||
/** List of incoming nodes. */
|
||||
inputs: number[];
|
||||
|
||||
constructor(id: number) {
|
||||
|
@ -48,6 +51,7 @@ export class GraphNode {
|
|||
* This class is optimized for TermGraph use case and not supposed to be used as generic graph implementation.
|
||||
*/
|
||||
export class Graph {
|
||||
/** Map of nodes. */
|
||||
nodes = new Map<number, GraphNode>();
|
||||
|
||||
constructor(arr?: number[][]) {
|
||||
|
|
72
rsconcept/frontend/src/models/TMGraph.test.ts
Normal file
72
rsconcept/frontend/src/models/TMGraph.test.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { TMGraph } from './TMGraph';
|
||||
|
||||
const typificationData = [
|
||||
['', ''],
|
||||
['X1', 'X1'],
|
||||
['Z', 'Z'],
|
||||
['R1', 'R1'],
|
||||
['C1', 'C1'],
|
||||
['C1×X1', 'C1 X1 C1×X1'],
|
||||
['X1×X1', 'X1 X1×X1'],
|
||||
['X1×X1×X1', 'X1 X1×X1×X1'],
|
||||
['ℬ(X1)', 'X1 ℬ(X1)'],
|
||||
['ℬℬ(X1)', 'X1 ℬ(X1) ℬℬ(X1)'],
|
||||
['ℬℬ(X1×X2)', 'X1 X2 X1×X2 ℬ(X1×X2) ℬℬ(X1×X2)'],
|
||||
['ℬ((X1×X1)×X2)', 'X1 X1×X1 X2 (X1×X1)×X2 ℬ((X1×X1)×X2)'],
|
||||
['ℬ(ℬ(X1)×ℬ(X1))', 'X1 ℬ(X1) ℬ(X1)×ℬ(X1) ℬ(ℬ(X1)×ℬ(X1))'],
|
||||
[
|
||||
'ℬ(ℬ((X1×ℬ(X1))×X1)×X2)',
|
||||
'X1 ℬ(X1) X1×ℬ(X1) (X1×ℬ(X1))×X1 ℬ((X1×ℬ(X1))×X1) X2 ℬ((X1×ℬ(X1))×X1)×X2 ℬ(ℬ((X1×ℬ(X1))×X1)×X2)'
|
||||
]
|
||||
];
|
||||
describe('Testing parsing typifications', () => {
|
||||
it.each(typificationData)('Typification parsing %p', (input: string, expected: string) => {
|
||||
const graph = new TMGraph();
|
||||
graph.addConstituenta('X1', input, []);
|
||||
const nodeText = graph.nodes.map(node => node.text).join(' ');
|
||||
expect(nodeText).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing constituents parsing', () => {
|
||||
test('simple expression no arguments', () => {
|
||||
const graph = new TMGraph();
|
||||
graph.addConstituenta('X1', 'ℬ(X1)', []);
|
||||
|
||||
expect(graph.nodes.length).toBe(2);
|
||||
expect(graph.nodes.at(-1)?.annotations).toStrictEqual(['X1']);
|
||||
});
|
||||
|
||||
test('no expression with single argument', () => {
|
||||
const graph = new TMGraph();
|
||||
graph.addConstituenta('X1', '', [{ alias: 'a', typification: 'X1' }]);
|
||||
const nodeText = graph.nodes.map(node => node.text).join(' ');
|
||||
|
||||
expect(nodeText).toBe('X1 ℬ(X1)');
|
||||
expect(graph.nodes.at(-1)?.annotations).toStrictEqual(['X1']);
|
||||
});
|
||||
|
||||
test('no expression with multiple arguments', () => {
|
||||
const graph = new TMGraph();
|
||||
graph.addConstituenta('X1', '', [
|
||||
{ alias: 'a', typification: 'X1' },
|
||||
{ alias: 'b', typification: 'R1×X1' }
|
||||
]);
|
||||
const nodeText = graph.nodes.map(node => node.text).join(' ');
|
||||
|
||||
expect(nodeText).toBe('X1 R1 R1×X1 X1×(R1×X1) ℬ(X1×(R1×X1))');
|
||||
expect(graph.nodes.at(-1)?.annotations).toStrictEqual(['X1']);
|
||||
});
|
||||
|
||||
test('expression with multiple arguments', () => {
|
||||
const graph = new TMGraph();
|
||||
graph.addConstituenta('X1', 'ℬ(X2×Z)', [
|
||||
{ alias: 'a', typification: 'X1' },
|
||||
{ alias: 'b', typification: 'R1×X1' }
|
||||
]);
|
||||
const nodeText = graph.nodes.map(node => node.text).join(' ');
|
||||
|
||||
expect(nodeText).toBe('X1 R1 R1×X1 X1×(R1×X1) X2 Z X2×Z ℬ(X2×Z) (X1×(R1×X1))×ℬ(X2×Z) ℬ((X1×(R1×X1))×ℬ(X2×Z))');
|
||||
expect(graph.nodes.at(-1)?.annotations).toStrictEqual(['X1']);
|
||||
});
|
||||
});
|
|
@ -2,6 +2,212 @@
|
|||
* Module: Multi-graph for typifications.
|
||||
*/
|
||||
|
||||
export class TMGraphNode {}
|
||||
import { IArgumentInfo } from './rslang';
|
||||
|
||||
export class TMGraph {}
|
||||
/**
|
||||
* Represents a single node of a {@link TMGraph}.
|
||||
*/
|
||||
export interface TMGraphNode {
|
||||
id: number;
|
||||
rank: number;
|
||||
text: string;
|
||||
parents: number[];
|
||||
annotations: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a typification multi-graph.
|
||||
*/
|
||||
export class TMGraph {
|
||||
/** List of nodes. */
|
||||
nodes: TMGraphNode[] = [];
|
||||
/** Map of nodes by ID. */
|
||||
nodeById = new Map<number, TMGraphNode>();
|
||||
/** Map of nodes by alias. */
|
||||
nodeByAlias = new Map<string, TMGraphNode>();
|
||||
|
||||
/**
|
||||
* Adds a constituent to the graph.
|
||||
*
|
||||
* @param alias - The alias of the constituent.
|
||||
* @param result - typification of the formal definition.
|
||||
* @param args - arguments for term or predicate function.
|
||||
*/
|
||||
addConstituenta(alias: string, result: string, args: IArgumentInfo[]): void {
|
||||
const argsNode = this.processArguments(args);
|
||||
const resultNode = this.processResult(result);
|
||||
const combinedNode = this.combineResults(resultNode, argsNode);
|
||||
if (!combinedNode) {
|
||||
return;
|
||||
}
|
||||
this.addAliasAnnotation(combinedNode.id, alias);
|
||||
}
|
||||
|
||||
addBaseNode(baseAlias: string): TMGraphNode {
|
||||
const existingNode = this.nodes.find(node => node.text === baseAlias);
|
||||
if (existingNode) {
|
||||
return existingNode;
|
||||
}
|
||||
|
||||
const node: TMGraphNode = {
|
||||
id: this.nodes.length,
|
||||
text: baseAlias,
|
||||
rank: 0,
|
||||
parents: [],
|
||||
annotations: []
|
||||
};
|
||||
this.nodes.push(node);
|
||||
this.nodeById.set(node.id, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
addBooleanNode(parent: number): TMGraphNode {
|
||||
const existingNode = this.nodes.find(node => node.parents.length === 1 && node.parents[0] === parent);
|
||||
if (existingNode) {
|
||||
return existingNode;
|
||||
}
|
||||
|
||||
const parentNode = this.nodeById.get(parent);
|
||||
if (!parentNode) {
|
||||
throw new Error(`Parent node ${parent} not found`);
|
||||
}
|
||||
|
||||
const text = parentNode.parents.length === 1 ? `ℬ${parentNode.text}` : `ℬ(${parentNode.text})`;
|
||||
const node: TMGraphNode = {
|
||||
id: this.nodes.length,
|
||||
rank: parentNode.rank,
|
||||
text: text,
|
||||
parents: [parent],
|
||||
annotations: []
|
||||
};
|
||||
this.nodes.push(node);
|
||||
this.nodeById.set(node.id, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
addCartesianNode(parents: number[]): TMGraphNode {
|
||||
const existingNode = this.nodes.find(
|
||||
node => node.parents.length === parents.length && node.parents.every((p, i) => p === parents[i])
|
||||
);
|
||||
if (existingNode) {
|
||||
return existingNode;
|
||||
}
|
||||
|
||||
const parentNodes = parents.map(parent => this.nodeById.get(parent));
|
||||
if (parentNodes.some(parent => !parent) || parents.length < 2) {
|
||||
throw new Error(`Parent nodes ${parents.join(', ')} not found`);
|
||||
}
|
||||
|
||||
const text = parentNodes.map(node => (node!.parents.length > 1 ? `(${node!.text})` : node!.text)).join('×');
|
||||
const node: TMGraphNode = {
|
||||
id: this.nodes.length,
|
||||
text: text,
|
||||
rank: Math.max(...parentNodes.map(parent => parent!.rank)) + 1,
|
||||
parents: parents,
|
||||
annotations: []
|
||||
};
|
||||
this.nodes.push(node);
|
||||
this.nodeById.set(node.id, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
addAliasAnnotation(node: number, alias: string): void {
|
||||
const nodeToAnnotate = this.nodeById.get(node);
|
||||
if (!nodeToAnnotate) {
|
||||
throw new Error(`Node ${node} not found`);
|
||||
}
|
||||
nodeToAnnotate.annotations.push(alias);
|
||||
this.nodeByAlias.set(alias, nodeToAnnotate);
|
||||
}
|
||||
|
||||
private processArguments(args: IArgumentInfo[]): TMGraphNode | undefined {
|
||||
if (args.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const argsNodes = args.map(argument => this.parseToNode(argument.typification));
|
||||
if (args.length === 1) {
|
||||
return argsNodes[0];
|
||||
}
|
||||
return this.addCartesianNode(argsNodes.map(node => node.id));
|
||||
}
|
||||
|
||||
private processResult(result: string): TMGraphNode | undefined {
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
return this.parseToNode(result);
|
||||
}
|
||||
|
||||
private combineResults(result: TMGraphNode | undefined, args: TMGraphNode | undefined): TMGraphNode | undefined {
|
||||
if (!result && !args) {
|
||||
return undefined;
|
||||
}
|
||||
if (!result) {
|
||||
return this.addBooleanNode(args!.id);
|
||||
}
|
||||
if (!args) {
|
||||
return result;
|
||||
}
|
||||
const argsAndResult = this.addCartesianNode([args.id, result.id]);
|
||||
return this.addBooleanNode(argsAndResult.id);
|
||||
}
|
||||
|
||||
private parseToNode(typification: string): TMGraphNode {
|
||||
const tokens = this.tokenize(typification);
|
||||
return this.parseTokens(tokens);
|
||||
}
|
||||
|
||||
private tokenize(expression: string): string[] {
|
||||
const tokens = [];
|
||||
let currentToken = '';
|
||||
for (const char of expression) {
|
||||
if (['(', ')', '×', 'ℬ'].includes(char)) {
|
||||
if (currentToken) {
|
||||
tokens.push(currentToken);
|
||||
currentToken = '';
|
||||
}
|
||||
tokens.push(char);
|
||||
} else {
|
||||
currentToken += char;
|
||||
}
|
||||
}
|
||||
if (currentToken) {
|
||||
tokens.push(currentToken);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private parseTokens(tokens: string[], isBoolean: boolean = false): TMGraphNode {
|
||||
const stack: TMGraphNode[] = [];
|
||||
let isCartesian = false;
|
||||
while (tokens.length > 0) {
|
||||
const token = tokens.shift();
|
||||
if (!token) {
|
||||
throw new Error('Unexpected end of expression');
|
||||
}
|
||||
|
||||
if (isBoolean && token === '(') {
|
||||
return this.parseTokens(tokens);
|
||||
}
|
||||
|
||||
if (token === ')') {
|
||||
break;
|
||||
} else if (token === 'ℬ') {
|
||||
const innerNode = this.parseTokens(tokens, true);
|
||||
stack.push(this.addBooleanNode(innerNode.id));
|
||||
} else if (token === '×') {
|
||||
isCartesian = true;
|
||||
} else if (token === '(') {
|
||||
stack.push(this.parseTokens(tokens));
|
||||
} else {
|
||||
stack.push(this.addBaseNode(token));
|
||||
}
|
||||
}
|
||||
|
||||
if (isCartesian) {
|
||||
return this.addCartesianNode(stack.map(node => node.id));
|
||||
} else {
|
||||
return stack.pop()!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ function HelpSubstitutions() {
|
|||
учетом других отождествлений
|
||||
</li>
|
||||
<li>логические выражения могут замещать только другие логические выражения</li>
|
||||
<li>при отождествлении параметризованных конституент количество и типизации операндов должно совпадать</li>
|
||||
<li>при отождествлении параметризованных конституент количество и типизации аргументов должно совпадать</li>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue
Block a user