Add concept derivation relation to backend

This commit is contained in:
IRBorisov 2024-04-05 15:53:05 +03:00
parent 6957d3d2fa
commit f03fd337ba
13 changed files with 126 additions and 5 deletions

View File

@ -44,6 +44,8 @@ export { BiDownvote as IconMoveDown } from 'react-icons/bi';
export { LuRotate3D as IconRotate3D } from 'react-icons/lu';
export { MdOutlineFitScreen as IconFitImage } from 'react-icons/md';
export { LuSparkles as IconClustering } from 'react-icons/lu';
export { LuSparkle as IconClusteringOff } from 'react-icons/lu';
interface IconSVGProps {
viewBox: string;

View File

@ -13,6 +13,18 @@ function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaPro
return (
<div className={clsx('dense', className)} {...restProps}>
<h2>Конституента {data.alias}</h2>
{data.derived_alias ? (
<p>
<b>Основана на: </b>
{data.derived_alias}
</p>
) : null}
{data.derived_children.length > 0 ? (
<p>
<b>Порождает: </b>
{data.derived_children.join(', ')}
</p>
) : null}
<p>
<b>Типизация: </b>
{labelCstTypification(data)}

View File

@ -55,6 +55,12 @@ function DlgGraphParams({ hideWindow, initial, onConfirm }: DlgGraphParamsProps)
value={params.noTransitive}
setValue={value => updateParams({ noTransitive: value })}
/>
<Checkbox
label='Свернуть производные'
title='Отображать производные понятия вместе с основным'
value={params.foldDerived}
setValue={value => updateParams({ foldDerived: value })}
/>
</div>
<div className='flex flex-col gap-1'>
<h1 className='mb-2'>Типы конституент</h1>

View File

@ -69,6 +69,10 @@ export class Graph {
return result;
}
at(target: number): GraphNode | undefined {
return this.nodes.get(target);
}
addNode(target: number): GraphNode {
let node = this.nodes.get(target);
if (!node) {

View File

@ -96,6 +96,7 @@ export interface GraphFilterParams {
noTransitive: boolean;
noTemplates: boolean;
noText: boolean;
foldDerived: boolean;
allowBase: boolean;
allowStruct: boolean;

View File

@ -96,6 +96,9 @@ export interface IConstituenta extends IConstituentaMeta {
cst_class: CstClass;
status: ExpressionStatus;
is_template: boolean;
derived_from: ConstituentaID;
derived_alias?: string;
derived_children: string[];
parse: {
status: ParsingStatus;
valueClass: ValueClass;

View File

@ -18,7 +18,7 @@ import {
IRSFormStats
} from './rsform';
import { ParsingStatus, ValueClass } from './rslang';
import { extractGlobals } from './rslangAPI';
import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from './rslangAPI';
/**
* Loads data into an {@link IRSForm} based on {@link IRSFormData}.
@ -32,7 +32,11 @@ export function loadRSFormData(input: IRSFormData): IRSForm {
result.graph = new Graph();
result.stats = calculateStats(result.items);
const derivationLookup: Map<ConstituentaID, ConstituentaID> = new Map();
result.items.forEach(cst => {
derivationLookup.set(cst.id, cst.id);
cst.derived_from = cst.id;
cst.derived_children = [];
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass);
cst.is_template = inferTemplate(cst.definition_formal);
cst.cst_class = inferClass(cst.cst_type, cst.is_template);
@ -45,6 +49,34 @@ export function loadRSFormData(input: IRSFormData): IRSForm {
}
});
});
// Calculate derivation of constituents based on formal definition analysis
result.graph.topologicalOrder().forEach(id => {
const cst = result.items.find(item => item.id === id)!;
const resolvedInput: Set<ConstituentaID> = new Set();
let definition = '';
if (!isFunctional(cst.cst_type)) {
const node = result.graph.at(id)!;
node.inputs.forEach(id => resolvedInput.add(derivationLookup.get(id)!));
definition = cst.definition_formal;
} else {
const expression = splitTemplateDefinition(cst.definition_formal);
definition = expression.body;
const dependencies = extractGlobals(definition);
dependencies.forEach(alias => {
const targetCst = result.items.find(item => item.alias === alias);
if (targetCst) {
resolvedInput.add(derivationLookup.get(targetCst.id)!);
}
});
}
if (resolvedInput.size === 1 && isSimpleExpression(definition)) {
const parent = result.items.find(item => item.id === resolvedInput.values().next().value)!;
cst.derived_from = parent.id;
cst.derived_alias = parent.alias;
parent.derived_children.push(cst.alias);
derivationLookup.set(cst.id, parent.id);
}
});
return result;
}
@ -174,6 +206,8 @@ export function inferClass(type: CstType, isTemplate: boolean): CstClass {
export function createMockConstituenta(id: ConstituentaID, alias: string, comment: string): IConstituenta {
return {
id: id,
derived_from: id,
derived_children: [],
order: -1,
schema: -1,
alias: alias,

View File

@ -1,4 +1,4 @@
import { extractGlobals, splitTemplateDefinition } from './rslangAPI';
import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from './rslangAPI';
const globalsData = [
['', ''],
@ -13,6 +13,23 @@ describe('Testing extract globals', () => {
});
});
const simpleExpressionData = [
['', 'true'],
['Pr1(S1)', 'true'],
['pr1(S1)', 'true'],
['red(S1)', 'true'],
['red(Pr1(F1[α,σ]))', 'true'],
['D{(α,β)∈D6×D6 | α≠β & α∩β≠∅}', 'false'],
['I{(β,α) | α:∈D2; σ:=F5[α]; β:∈σ}', 'false'],
['∀σ∈S1 (F1[σ]×F1[σ])∩D11=∅', 'false']
];
describe('Testing simple expression', () => {
it.each(simpleExpressionData)('isSimpleExpression %p', (input: string, expected: string) => {
const result = isSimpleExpression(input);
expect(String(result)).toBe(expected);
});
});
const splitData = [
['', '||'],
['[α∈ℬ(R1)] α⊆red(σ)', 'α∈ℬ(R1)||α⊆red(σ)'],

View File

@ -7,14 +7,24 @@ import { applyPattern } from '@/utils/utils';
import { CstType } from './rsform';
import { IArgumentValue, IRSErrorDescription, RSErrorClass, RSErrorType } from './rslang';
// cspell:disable
const LOCALS_REGEXP = /[_a-zα-ω][a-zα-ω]*\d*/g;
const GLOBALS_REGEXP = /[XCSADFPT]\d+/g;
const COMPLEX_SYMBOLS_REGEXP = /[∀∃×ℬ;|:]/g;
// cspell:enable
/**
* Extracts global variable names from a given expression.
*/
export function extractGlobals(expression: string): Set<string> {
// cspell:disable-next-line
return new Set(expression.match(/[XCSADFPT]\d+/g) ?? []);
return new Set(expression.match(GLOBALS_REGEXP) ?? []);
}
/**
* Check if expression is simple derivation.
*/
export function isSimpleExpression(text: string): boolean {
return !COMPLEX_SYMBOLS_REGEXP.test(text);
}
/**

View File

@ -36,6 +36,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
noTemplates: false,
noTransitive: true,
noText: false,
foldDerived: false,
allowBase: true,
allowStruct: true,
@ -217,11 +218,18 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
is3D={is3D}
orbit={orbit}
noText={filterParams.noText}
foldDerived={filterParams.foldDerived}
showParamsDialog={() => setShowParamsDialog(true)}
onCreate={handleCreateCst}
onDelete={handleDeleteCst}
onResetViewpoint={() => setToggleResetView(prev => !prev)}
toggleOrbit={() => setOrbit(prev => !prev)}
toggleFoldDerived={() =>
setFilterParams(prev => ({
...prev,
foldDerived: !prev.foldDerived
}))
}
toggleNoText={() =>
setFilterParams(prev => ({
...prev,

View File

@ -1,6 +1,8 @@
'use client';
import {
IconClustering,
IconClusteringOff,
IconDestroy,
IconFilter,
IconFitImage,
@ -22,12 +24,14 @@ interface GraphToolbarProps {
orbit: boolean;
noText: boolean;
foldDerived: boolean;
showParamsDialog: () => void;
onCreate: () => void;
onDelete: () => void;
onResetViewpoint: () => void;
toggleFoldDerived: () => void;
toggleNoText: () => void;
toggleOrbit: () => void;
}
@ -35,7 +39,9 @@ interface GraphToolbarProps {
function GraphToolbar({
is3D,
noText,
foldDerived,
toggleNoText,
toggleFoldDerived,
orbit,
toggleOrbit,
showParamsDialog,
@ -67,6 +73,17 @@ function GraphToolbar({
}
onClick={toggleNoText}
/>
<MiniButton
title={!foldDerived ? 'Скрыть производные' : 'Отображать производные'}
icon={
!foldDerived ? (
<IconClustering size='1.25rem' className='icon-green' />
) : (
<IconClusteringOff size='1.25rem' className='icon-primary' />
)
}
onClick={toggleFoldDerived}
/>
<MiniButton
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
title='Граф целиком'

View File

@ -46,6 +46,13 @@ function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams)
}
});
}
if (params.foldDerived) {
schema.items.forEach(cst => {
if (cst.derived_alias) {
graph.foldNode(cst.id);
}
});
}
setFiltered(graph);
}, [schema, params, allowedTypes]);

View File

@ -92,7 +92,7 @@ export const storage = {
librarySearchStrategy: 'library.search.strategy',
libraryPagination: 'library.pagination',
rsgraphFilter: 'rsgraph.filter',
rsgraphFilter: 'rsgraph.filter_options',
rsgraphLayout: 'rsgraph.layout',
rsgraphColoringScheme: 'rsgraph.coloring_scheme',