Corrige le calcul des variables manquantes des variations

pull/197/head
Laurent Bossavit 2018-04-10 17:16:27 +02:00
parent 768e7aee6b
commit 03d1228324
5 changed files with 52 additions and 41 deletions

View File

@ -5,6 +5,7 @@ import {
equals,
reduce,
chain,
length,
fromPairs,
keys,
values,

View File

@ -41,6 +41,7 @@ import { findRuleByDottedName, disambiguateRuleReference } from './rules'
export let collectMissingVariables = targets => {
let missing = flatten(pluck('missingVariables', targets))
console.log("total # missing", length(missing))
return groupBy(identity, missing)
}

View File

@ -1,4 +1,6 @@
import {
flatten,
length,
objOf,
toPairs,
dissoc,
@ -144,7 +146,8 @@ let devariate = (recurse, k, v) => {
}
let explanation = map(evaluateOne, node.explanation),
choice = find(node => node.condition.nodeValue, explanation),
satisfied = filter(node => node.condition.nodeValue, explanation),
choice = head(satisfied),
nodeValue = choice ? choice.nodeValue : null
let leftMissing = choice
@ -152,9 +155,9 @@ let devariate = (recurse, k, v) => {
: uniq(
map(collectNodeMissing, pluck('condition', explanation))
),
rightMissing = choice
? choice.missingVariables
: map(collectNodeMissing, explanation),
rightMissing = !choice
? []
: map(collectNodeMissing, satisfied),
missingVariables = concat(leftMissing, rightMissing || [])
return rewriteNode(node, nodeValue, explanation, missingVariables)
@ -423,7 +426,7 @@ let doInversion = (situationGate, parsedRules, v, dottedName) => {
if (inversion.inversionChoiceNeeded)
return {
inversionMissingVariables: [dottedName],
missingVariables: [dottedName],
nodeValue: null
}
let { fixedObjectiveValue, fixedObjectiveRule } = inversion
@ -435,28 +438,21 @@ let doInversion = (situationGate, parsedRules, v, dottedName) => {
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
// TODO fx peut être null pour certains x, et valide pour d'autres : on peut implémenter ici le court-circuit
if (fx(1000) == null)
return {
nodeValue: null,
inversionMissingVariables:
evaluateNode(
{},
n => (dottedName === n ? 1000 : situationGate(n)),
parsedRules,
fixedObjectiveRule
).missingVariables
}
let attempt = fx(1000)
if (attempt.nodeValue == null) {
return attempt
}
let tolerancePercentage = 0.00001,
// cette fonction détermine la racine d'une fonction sans faire trop d'itérations
nodeValue = uniroot(
x => fx(x) - fixedObjectiveValue,
x => fx(x).nodeValue - fixedObjectiveValue,
0,
1000000000,
tolerancePercentage * fixedObjectiveValue,
@ -465,7 +461,7 @@ let doInversion = (situationGate, parsedRules, v, dottedName) => {
return {
nodeValue,
inversionMissingVariables: [],
missingVariables: [],
inversionCache
}
}
@ -477,12 +473,12 @@ export let mecanismInversion = dottedName => (recurse, k, v) => {
situationGate(dottedName) == undefined &&
doInversion(situationGate, parsedRules, v, dottedName),
nodeValue = inversion.nodeValue,
missingVariables = inversion.inversionMissingVariables
missingVariables = inversion.missingVariables
let evaluatedNode = rewriteNode(node, nodeValue, null, missingVariables)
// rewrite the simulation cache with the definitive inversion values
toPairs(inversion.inversionCache).map(([k, v]) => (cache[k] = v))
// toPairs(inversion.inversionCache).map(([k, v]) => (cache[k] = v))
return evaluatedNode
}

View File

@ -129,38 +129,35 @@ let fillVariableNode = (rules, rule, filter) => parseResult => {
// 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 = cache[cacheName],
// make parsedRules a dict object, that also serves as a cache of evaluation ?
variable = cached
? cached
: findRuleByDottedName(parsedRules, dottedName),
cached = cache[cacheName]
if (cached) {
return cached
}
let variable = findRuleByDottedName(parsedRules, dottedName),
variableIsCalculable = variable.formule != null,
parsedRule =
variableIsCalculable &&
(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),
needsEvaluation = (variableIsCalculable && situationValue == null),
parsedRule = needsEvaluation
? evaluateNode(cache, situation, parsedRules, variable)
: variable,
// evaluateVariable renvoit la valeur déduite de la situation courante renseignée par l'utilisateur
explanation = parsedRule,
nodeValue =
situationValue != null
? situationValue // cette variable a été directement renseignée
: variableIsCalculable
? parsedRule.nodeValue // la valeur du calcul fait foi
: null, // elle restera donc nulle
explanation = parsedRule,
missingVariables = nodeValue != null // notamment si situationValue != null
? []
: variableIsCalculable
? parsedRule.missingVariables
: [dottedName]
if (cached) {
return cached
} else {
cache[cacheName] = rewriteNode(node, nodeValue, explanation, missingVariables)
return cache[cacheName]
}
cache[cacheName] = rewriteNode(node, nodeValue, explanation, missingVariables)
return cache[cacheName]
}
let { fragments } = parseResult,

View File

@ -1,3 +1,4 @@
import { length } from 'ramda'
import { expect } from 'chai'
import { rules as realRules, enrichRule } from '../source/engine/rules'
import { analyse, analyseMany, parseAll } from '../source/engine/traverse'
@ -105,8 +106,23 @@ describe('inversions', () => {
- net
- nom: cadre
- nom: assiette
formule: 67 + brut
formule:
somme:
- 1200
- brut
- taxeOne
- nom: taxeOne
non applicable si: cadre
formule: taxe + taxe
- nom: taxe
formule:
multiplication:
assiette: 1200
variations:
- si: cadre
taux: 80%
- si: cadre
taux: 70%
`,
rules = parseAll(yaml.safeLoad(rawRules).map(enrichRule)),
stateSelector = name => ({ net: 2000 }[name]),