From b6707a256b60973a9daf6bacc7e7b58bbc0ce5a6 Mon Sep 17 00:00:00 2001 From: mama Date: Tue, 12 Dec 2017 20:10:22 +0100 Subject: [PATCH] =?UTF-8?q?Impl=C3=A9mentation=20alternative=20du=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Qui résoud le problème de l'inversion qui vide le cache lors du calcul de multiples objectifs --- package.json | 2 ++ source/components/Results.js | 1 - source/components/ResultsGrid.js | 6 ++-- source/engine/evaluation.js | 16 +++++------ source/engine/mecanisms.js | 48 ++++++++++++++++++------------- source/engine/traverse.js | 49 ++++++++++++++------------------ test/mecanisms.test.js | 1 - 7 files changed, 63 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index 1b1be654c..a7253ae68 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "classnames": "^2.2.5", "dedent-js": "^1.0.1", "deep-assign": "^2.0.0", + "flow": "^0.2.3", + "global": "^4.3.2", "ignore-loader": "^0.1.2", "install": "^0.10.1", "js-yaml": "^3.9.1", diff --git a/source/components/Results.js b/source/components/Results.js index 6ec372b45..0e6451da4 100644 --- a/source/components/Results.js +++ b/source/components/Results.js @@ -6,7 +6,6 @@ import { connect } from "react-redux" import { withRouter } from "react-router" import "./Results.css" -import { clearDict } from "Engine/traverse" import { encodeRuleName } from "Engine/rules" import RuleValueVignette from "./rule/RuleValueVignette" import ProgressTip from "Components/ProgressTip" diff --git a/source/components/ResultsGrid.js b/source/components/ResultsGrid.js index b7922766d..01eb9bb45 100644 --- a/source/components/ResultsGrid.js +++ b/source/components/ResultsGrid.js @@ -39,8 +39,8 @@ export let subCell = (row, name, payer) => { } export let byBranch = analysis => { - let sal = analysis.dict["contrat salarié . cotisations salariales"] - let pat = analysis.dict["contrat salarié . cotisations patronales"] + let sal = analysis.cache["contrat salarié . cotisations salariales"] + let pat = analysis.cache["contrat salarié . cotisations patronales"] let l1 = sal ? sal.explanation.formule.explanation.explanation : [], l2 = pat ? pat.explanation.formule.explanation.explanation : [], @@ -71,7 +71,7 @@ export default class ResultsGrid extends Component { let extract = x => typeof x == 'string' ? +x : ((x && x.nodeValue) || 0), fromEval = name => R.find(R.propEq("dottedName", name), analysis.targets), - fromDict = name => analysis.dict[name], + fromDict = name => analysis.cache[name], get = name => extract(situationGate(name) || fromEval(name) || fromDict(name)) let results = byBranch(analysis), diff --git a/source/engine/evaluation.js b/source/engine/evaluation.js index 7fc203b3d..ee1b26964 100644 --- a/source/engine/evaluation.js +++ b/source/engine/evaluation.js @@ -8,8 +8,8 @@ export let makeJsx = node => export let collectNodeMissing = (node) => node.collectMissing ? node.collectMissing(node) : [] -export let evaluateNode = (situationGate, parsedRules, node) => - node.evaluate ? node.evaluate(situationGate, parsedRules, node) : node +export let evaluateNode = (cache, situationGate, parsedRules, node) => + node.evaluate ? node.evaluate(cache, situationGate, parsedRules, node) : node export let rewriteNode = (node, nodeValue, explanation, collectMissing) => ({ @@ -19,8 +19,8 @@ export let rewriteNode = (node, nodeValue, explanation, collectMissing) => explanation }) -export let evaluateArray = (reducer, start) => (situationGate, parsedRules, node) => { - let evaluateOne = child => evaluateNode(situationGate, parsedRules, child), +export let evaluateArray = (reducer, start) => (cache, situationGate, parsedRules, node) => { + let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child), explanation = R.map(evaluateOne, node.explanation), values = R.pluck("nodeValue",explanation), nodeValue = R.any(R.equals(null),values) ? null : R.reduce(reducer, start, values) @@ -29,8 +29,8 @@ export let evaluateArray = (reducer, start) => (situationGate, parsedRules, node return rewriteNode(node,nodeValue,explanation,collectMissing) } -export let evaluateArrayWithFilter = (filter, reducer, start) => (situationGate, parsedRules, node) => { - let evaluateOne = child => evaluateNode(situationGate, parsedRules, child), +export let evaluateArrayWithFilter = (filter, reducer, start) => (cache, situationGate, parsedRules, node) => { + let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child), explanation = R.map(evaluateOne, R.filter(filter(situationGate),node.explanation)), values = R.pluck("nodeValue",explanation), nodeValue = R.any(R.equals(null),values) ? null : R.reduce(reducer, start, values) @@ -48,8 +48,8 @@ export let parseObject = (recurse, objectShape, value) => { return R.evolve(transforms,objectShape) } -export let evaluateObject = (objectShape, effect) => (situationGate, parsedRules, node) => { - let evaluateOne = child => evaluateNode(situationGate, parsedRules, child), +export let evaluateObject = (objectShape, effect) => (cache, situationGate, parsedRules, node) => { + let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child), collectMissing = node => R.chain(collectNodeMissing,R.values(node.explanation)) let transforms = R.map(k => [k,evaluateOne], R.keys(objectShape)), diff --git a/source/engine/mecanisms.js b/source/engine/mecanisms.js index 3c3b85e73..4feac591a 100644 --- a/source/engine/mecanisms.js +++ b/source/engine/mecanisms.js @@ -101,11 +101,11 @@ let devariate = (recurse, k, v) => { condition: recurse(c.si) })) - let evaluate = (situationGate, parsedRules, node) => { + let evaluate = (cache, situationGate, parsedRules, node) => { let evaluateOne = child => { - let condition = evaluateNode(situationGate, parsedRules, child.condition) + let condition = evaluateNode(cache, situationGate, parsedRules, child.condition) return { - ...evaluateNode(situationGate, parsedRules, child), + ...evaluateNode(cache, situationGate, parsedRules, child), condition } } @@ -184,8 +184,8 @@ export let mecanismOneOf = (recurse, k, v) => { /> ) - let evaluate = (situationGate, parsedRules, node) => { - let evaluateOne = child => evaluateNode(situationGate, parsedRules, child), + let evaluate = (cache, situationGate, parsedRules, node) => { + let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child), explanation = R.map(evaluateOne, node.explanation), values = R.pluck('nodeValue', explanation), nodeValue = R.any(R.equals(true), values) @@ -230,8 +230,8 @@ export let mecanismAllOf = (recurse, k, v) => { ) - let evaluate = (situationGate, parsedRules, node) => { - let evaluateOne = child => evaluateNode(situationGate, parsedRules, child), + let evaluate = (cache, situationGate, parsedRules, node) => { + let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child), explanation = R.map(evaluateOne, node.explanation), values = R.pluck('nodeValue', explanation), nodeValue = R.any(R.equals(false), values) @@ -272,7 +272,7 @@ export let mecanismNumericalSwitch = (recurse, k, v) => { let conditionNode = recurse(condition), // can be a 'comparison', a 'variable', TODO a 'negation' consequenceNode = mecanismNumericalSwitch(recurse, condition, consequence) - let evaluate = (situationGate, parsedRules, node) => { + let evaluate = (cache, situationGate, parsedRules, node) => { let collectMissing = node => { let missingOnTheLeft = collectNodeMissing(node.explanation.condition), investigate = node.explanation.condition.nodeValue !== false, @@ -284,8 +284,8 @@ export let mecanismNumericalSwitch = (recurse, k, v) => { let explanation = R.evolve( { - condition: R.curry(evaluateNode)(situationGate, parsedRules), - consequence: R.curry(evaluateNode)(situationGate, parsedRules) + condition: R.curry(evaluateNode)(cache, situationGate, parsedRules), + consequence: R.curry(evaluateNode)(cache, situationGate, parsedRules) }, node.explanation ) @@ -317,8 +317,8 @@ export let mecanismNumericalSwitch = (recurse, k, v) => { } } - let evaluateTerms = (situationGate, parsedRules, node) => { - let evaluateOne = child => evaluateNode(situationGate, parsedRules, child), + let evaluateTerms = (cache, situationGate, parsedRules, node) => { + let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child), explanation = R.map(evaluateOne, node.explanation), nonFalsyTerms = R.filter(node => node.condValue !== false, explanation), getFirst = prop => R.pipe(R.head, R.prop(prop))(nonFalsyTerms), @@ -401,12 +401,15 @@ let doInversion = (situationGate, parsedRules, v, dottedName) => { nodeValue: null } let { fixedObjectiveValue, fixedObjectiveRule } = inversion - let fx = x => - clearDict() && evaluateNode( + let inversionCache = {} + let fx = x => { + inversionCache = {} + return evaluateNode(inversionCache, // with an empty cache n => dottedName === n ? x : situationGate(n), parsedRules, fixedObjectiveRule ).nodeValue + } // si fx renvoie null pour une valeur numérique standard, disons 1000, on peut // considérer que l'inversion est impossible du fait de variables manquantes @@ -415,7 +418,7 @@ let doInversion = (situationGate, parsedRules, v, dottedName) => { return { nodeValue: null, inversionMissingVariables: collectNodeMissing( - evaluateNode( + evaluateNode({}, n => dottedName === n ? 1000 : situationGate(n), parsedRules, @@ -436,14 +439,15 @@ let doInversion = (situationGate, parsedRules, v, dottedName) => { return { nodeValue, - inversionMissingVariables: [] + inversionMissingVariables: [], + inversionCache } } export let mecanismInversion = dottedName => (recurse, k, v) => { - let evaluate = (situationGate, parsedRules, node) => { + let evaluate = (cache, situationGate, parsedRules, node) => { let inversion = // avoid the inversion loop ! situationGate(dottedName) == undefined && @@ -452,7 +456,11 @@ export let mecanismInversion = dottedName => (recurse, k, v) => { collectMissing = () => inversion.inversionMissingVariables, nodeValue = inversion.nodeValue - return rewriteNode(node, nodeValue, null, collectMissing) + let evaluatedNode = rewriteNode(node, nodeValue, null, collectMissing) + // rewrite the simulation cache with the definitive inversion values + + R.toPairs(inversion.inversionCache).map(([k,v])=>cache[k] = v) + return evaluatedNode } return { @@ -829,9 +837,9 @@ export let mecanismSelection = (recurse, k, v) => { let dataTargetName = v['renvoie'] let explanation = recurse(v['cherche']) - let evaluate = (situationGate, parsedRules, node) => { + let evaluate = (cache, situationGate, parsedRules, node) => { let collectMissing = node => collectNodeMissing(node.explanation), - explanation = evaluateNode(situationGate, parsedRules, node.explanation), + explanation = evaluateNode(cache, situationGate, parsedRules, node.explanation), dataSource = findRuleByName(parsedRules, dataSourceName), data = dataSource ? dataSource['data'] : null, dataKey = explanation.nodeValue, diff --git a/source/engine/traverse.js b/source/engine/traverse.js index 658339fb7..770b6e875 100644 --- a/source/engine/traverse.js +++ b/source/engine/traverse.js @@ -78,9 +78,9 @@ par exemple ainsi : https://github.com/Engelberg/instaparse#transforming-the-tre // TODO - this is becoming overly specific let fillFilteredVariableNode = (rules, rule) => (filter, parseResult) => { - let evaluateFiltered = originalEval => (situation, parsedRules, node) => { + let evaluateFiltered = originalEval => (cache, situation, parsedRules, node) => { let newSituation = name => (name == 'sys.filter' ? filter : situation(name)) - return originalEval(newSituation, parsedRules, node) + return originalEval(cache, newSituation, parsedRules, node) } let node = fillVariableNode(rules, rule, filter)(parseResult), // Decorate node with who's paying @@ -93,19 +93,13 @@ let fillFilteredVariableNode = (rules, rule) => (filter, parseResult) => { } } -// TODO: dirty, dirty -// ne pas laisser trop longtemps cette "optimisation" qui tue l'aspect fonctionnel de l'algo -var dict - -export let clearDict = () => dict = {} - let fillVariableNode = (rules, rule, filter) => parseResult => { - let evaluate = (situation, parsedRules, node) => { + let evaluate = (cache, situation, parsedRules, node) => { let dottedName = node.dottedName, // On va vérifier dans le cache courant, dict, si la variable n'a pas été déjà évaluée // En effet, l'évaluation dans le cas d'une variable qui a une formule, est coûteuse ! cacheName = dottedName + (filter ? '.' + filter : ''), - cached = dict[cacheName], + cached = cache[cacheName], // make parsedRules a dict object, that also serves as a cache of evaluation ? variable = cached ? cached @@ -113,7 +107,7 @@ let fillVariableNode = (rules, rule, filter) => parseResult => { variableIsCalculable = variable.formule != null, parsedRule = variableIsCalculable && - (cached ? cached : evaluateNode(situation, parsedRules, variable)), + (cached ? cached : evaluateNode(cache, situation, parsedRules, variable)), // evaluateVariable renvoit la valeur déduite de la situation courante renseignée par l'utilisateur situationValue = evaluateVariable(situation, dottedName, variable), nodeValue = @@ -137,11 +131,11 @@ let fillVariableNode = (rules, rule, filter) => parseResult => { } else { - dict[cacheName] = { + cache[cacheName] = { ...rewriteNode(node, nodeValue, explanation, collectMissing), missingVariables } - return dict[cacheName] + return cache[cacheName] } } @@ -165,8 +159,8 @@ let fillVariableNode = (rules, rule, filter) => parseResult => { } let buildNegatedVariable = variable => { - let evaluate = (situation, parsedRules, node) => { - let explanation = evaluateNode(situation, parsedRules, node.explanation), + let evaluate = (cache, situation, parsedRules, node) => { + let explanation = evaluateNode(cache, situation, parsedRules, node.explanation), nodeValue = explanation.nodeValue == null ? null : !explanation.nodeValue let collectMissing = node => collectNodeMissing(node.explanation) return rewriteNode(node, nodeValue, explanation, collectMissing) @@ -250,7 +244,7 @@ let treat = (rules, rule) => rawNode => { parseResult.category == 'calcExpression' || parseResult.category == 'comparison' ) { - let evaluate = (situation, parsedRules, node) => { + let evaluate = (cache, situation, parsedRules, node) => { let operatorFunctionName = { '*': 'multiply', '/': 'divide', @@ -264,7 +258,7 @@ let treat = (rules, rule) => rawNode => { '!=': 'equals' }[node.operator], explanation = R.map( - R.curry(evaluateNode)(situation, parsedRules), + R.curry(evaluateNode)(cache, situation, parsedRules), node.explanation ), value1 = explanation[0].nodeValue, @@ -400,7 +394,7 @@ let treat = (rules, rule) => rawNode => { [R.T, treatOther] ]) - let defaultEvaluate = (situationGate, parsedRules, node) => node + let defaultEvaluate = (cache, situationGate, parsedRules, node) => node let parsedNode = onNodeType(rawNode) return parsedNode.evaluate @@ -422,9 +416,9 @@ export let treatRuleRoot = (rules, rule) => { Ces mécanismes où variables sont descendues à leur tour grâce à `treat()`. Lors de ce traitement, des fonctions 'evaluate', `collectMissingVariables` et `jsx` sont attachés aux objets de l'AST */ - let evaluate = (situationGate, parsedRules, r) => { + let evaluate = (cache, situationGate, parsedRules, r) => { - let evolveRule = R.curry(evaluateNode)(situationGate, parsedRules), + let evolveRule = R.curry(evaluateNode)(cache, situationGate, parsedRules), evaluated = R.evolve( { formule: evolveRule, @@ -483,9 +477,9 @@ export let treatRuleRoot = (rules, rule) => { 'non applicable si': evolveCond('non applicable si', rule, rules), 'applicable si': evolveCond('applicable si', rule, rules), formule: value => { - let evaluate = (situationGate, parsedRules, node) => { + let evaluate = (cache, situationGate, parsedRules, node) => { let collectMissing = node => collectNodeMissing(node.explanation) - let explanation = evaluateNode( + let explanation = evaluateNode(cache, situationGate, parsedRules, node.explanation @@ -520,9 +514,9 @@ export let treatRuleRoot = (rules, rule) => { } let evolveCond = (name, rule, rules) => value => { - let evaluate = (situationGate, parsedRules, node) => { + let evaluate = (cache, situationGate, parsedRules, node) => { let collectMissing = node => collectNodeMissing(node.explanation) - let explanation = evaluateNode( + let explanation = evaluateNode(cache, situationGate, parsedRules, node.explanation @@ -578,16 +572,17 @@ export let parseAll = flatRules => { } export let analyseMany = (parsedRules, targetNames) => situationGate => { - clearDict() // TODO: we should really make use of namespaces at this level, in particular // setRule in Rule.js needs to get smarter and pass dottedName + let cache = {} + let parsedTargets = targetNames.map(t => findRuleByName(parsedRules, t)), targets = R.chain(pt => getTargets(pt, parsedRules), parsedTargets).map(t => - evaluateNode(situationGate, parsedRules, t) + evaluateNode(cache, situationGate, parsedRules, t) ) // Don't use 'dict' for anything else than ResultsGrid - return {targets, dict} + return {targets, cache} } export let analyse = (parsedRules, target) => { diff --git a/test/mecanisms.test.js b/test/mecanisms.test.js index 8b1acd730..b1d2f78e0 100644 --- a/test/mecanisms.test.js +++ b/test/mecanisms.test.js @@ -28,7 +28,6 @@ describe('Mécanismes', () => missing = collectMissingVariables(analysis.targets), target = analysis.targets[0] - // console.log('JSON.stringify(analysis', JSON.stringify(analysis)) if (isFloat(valeur)) { expect(target.nodeValue).to.be.closeTo(valeur,0.001) }