import {addToSetInMap, removeFromSetInMap, removeInPlace} from 'gems/iterables'; import {ParametricManager} from './parametric'; let SUB_SYSTEM_ORDER = 0; class SubSystem { constructor() { this.alg = 1; this.error = 0; this.reduce = false; this.constraints = []; this.dependencies = []; this.nativeParams = new Set(); this._internaOrder = SUB_SYSTEM_ORDER++; } mergeWith(other) { other.constraints.forEach(c => this.constraints.push(c)); other.dependencies.forEach(d => { if (this.dependencies.indexOf(d) === -1) { this.dependencies.push(d); } }); other.nativeParams.forEach(p => this.nativeParams.add(p)); } prepare() { } } class Index { constructor() { this.constraints = []; this.paramToConstraintsIndex = new Map(); this.paramToConstraintsGraph = new Map(); this.generatorConstraints = []; this.generatedParams = new Map(); } _reset() { this.constraints = []; this.paramToConstraintsIndex.clear(); this.paramToConstraintsGraph.clear(); this.generatorConstraints = []; this.generatedParams.clear(); } _pushConstraint(constr) { this.constraints.push(constr); visitParams(constr, true, p => addToSetInMap(this.paramToConstraintsGraph, p, constr)); visitParams(constr, false, p => addToSetInMap(this.paramToConstraintsIndex, p, constr)); if (constr.GENERATOR) { this.generatorConstraints.push(constr); constr.visitGeneratedParams(p => this.generatedParams.set(p, constr)); } } _popConstraint(constr) { removeInPlace(this.constraints, constr); visitParams(constr, true, p => removeFromSetInMap(this.paramToConstraintsGraph, p, constr)); visitParams(constr, false, p => removeFromSetInMap(this.paramToConstraintsIndex, p, constr)); if (constr.GENERATOR) { removeInPlace(this.generatorConstraints, constr); constr.visitGeneratedParams(p => this.generatedParams.delete(p)); } } } export class System extends Index{ constructor() { super(); this.subSystems = []; this.constraintToSubSystem = new Map(); this.paramToSubSystem = new Map(); } _reset() { super._reset(); this.subSystems = []; this.constraintToSubSystem.clear(); this.paramToSubSystem.clear(); COUNTER = 0; } _collectDependenciesForSubSystemFromConstraint(subSystem, constr) { visitParams(constr, false, p => { const generator = this.generatedParams.get(p); if (generator) { const generatorSS = this.constraintToSubSystem.get(generator); if (generatorSS) { if (subSystem.dependencies.indexOf(generatorSS) === -1) { subSystem.dependencies.push(generatorSS); } } } }); } _rebuildDependencies() { this.subSystems.forEach(ss => { if (ss.dependencies.length !== 0) { ss.dependencies = []; } }); this.subSystems.forEach(subSystem => { subSystem.constraints.forEach(constr => { this._collectDependenciesForSubSystemFromConstraint(subSystem, constr); }); }); } _groupBySubsystems() { if (this.subSystems.length !== 0) { this.subSystems = []; } this.constraintToSubSystem.clear(); const visited = new Set(); this.constraints.forEach(constr => { if (visited.has(constr)) { return; } const subSystem = this.createSubSystem(); const stack = [constr]; while (stack.length) { const workingConstr = stack.pop(); if (visited.has(workingConstr)) { continue; } this._assignConstraint(workingConstr, subSystem); visited.add(workingConstr); visitParams(workingConstr, true, p => { const constrs = this.paramToConstraintsGraph.get(p); if (constrs) { constrs.forEach(constrToAdvance => { if (constrToAdvance !== workingConstr) { stack.push(constrToAdvance); } }) } }); } }); } _rebuild() { this._groupBySubsystems(); this._rebuildDependencies(); } _assignConstraint(constr, subSystem) { subSystem.constraints.push(constr); this.constraintToSubSystem.set(constr, subSystem); } add(constr) { constr.id = "C_" + (COUNTER ++) ; //fixme const affectedSubsystems = new Set(); const freeParams = []; visitParams(constr, false, p => { const subSystem = this.paramToSubSystem.get(p); if (subSystem) { affectedSubsystems.add(subSystem); } else { if (!isAuxParam(p) && !this.generatedParams.has(p)) { freeParams.push(p); } } }); affectedSubsystems.forEach(ss => { ss.dependencies.forEach(d => affectedSubsystems.delete(d)); }); let toMerge = Array.from(affectedSubsystems).sort((a, b) => a._internaOrder - b._internaOrder); let master; if (toMerge.length === 0 ) { // console.error("system has circular dependencies"); master = this.createSubSystem(); } else { [master, ...toMerge] = toMerge; } toMerge.forEach(s => { master.mergeWith(s); s.nativeParams.forEach(p => this.paramToSubSystem.set(p, master)); removeInPlace(this.subSystems, s); }); freeParams.forEach(p => { master.nativeParams.add(p); this.paramToSubSystem.set(p, master) }); master.constraints.push(constr); this.constraintToSubSystem.set(constr, master); if (constr.GENERATOR) { const dependant = this.createSubSystem(); dependant.dependencies.push(master); constr.visitGeneratedParams(p => { this.generatedParams.set(p, constr) this.paramToSubSystem.set(p, dependant); }); } this._pushConstraint(constr); } remove(constr) { removeInPlace(this.constraints, constr); this.setConstraints(this.constraints); } setConstraints(constraints) { this._reset(); constraints.forEach(c => this.add(c)); } subSystemsByParam(param, callback) { const constraints = this.paramToConstraintsIndex.get(param); if (constraints) { constraints.forEach(c => callback(this.constraintToSubSystem.get(c))); } } traverse(callback, onCircular) { const visited = new Set(); const loop = new Set(); function doVisit(subSystem) { if (loop.has(subSystem)) { onCircular(subSystem); return; } loop.add(subSystem); subSystem.dependencies.forEach(dep => { if (!visited.has(dep)) { doVisit(dep); } }); callback(subSystem); visited.add(subSystem); loop.delete(subSystem) } this.subSystems.forEach(doVisit); } createSubSystem() { const subSystem = new SubSystem(); this.subSystems.push(subSystem); return subSystem; } } function visitParams(constraint, skipAux, callback) { if (skipAux) { const delegate = callback; callback = p => { if (!isAuxParam(p)) { delegate(p) } }; } if (constraint.visitParams) { constraint.visitParams(callback); } else { constraint.getSolveData(FAKE_RESOLVER).forEach(([, sParams]) => sParams.forEach(callback)); } } function isAuxParam(param) { return ParametricManager.isAux(param.obj, GOT_NOTHING); } const GOT_NOTHING = { has: () => false }; const FAKE_RESOLVER = () => 0; let COUNTER = 0;