JEXL Rule Engine |
Top Previous Next |
Rules are
•Arithmetic/logical expressions or small scripts that evaluate to a value
•Can be used in solution or project files to enhance SimComponents and control the simulation flow
•They can be added via the rule=““ attribute to several elements
•Examples:
${Phenology.DevStage} == 2 (returns true or false)
${weather.VP}*10 (returns a numeric value)
How and where to use rules/expressions in your solution/project.
Purpose
•Triggering the output
<output frequence=“COMPLEX“ rule=“${Pheno.DVS}==2.0“ …
•Triggering the updating of resources (e.g. for crop rotation)
<resource id=“crop“ frequence=“COMPLEX“
rule=“${CURRENT.DOY}==${vSowingDoy}“…
•Modify output values (unit conversion, aggregation of layered values)
•Management rules (determine Sowing/Harvesting, Irrigation etc.)
•Simple SimComponents – e.g. to combine different modules that use different variable types (layered vs. non-layered, different units, etc.)
Syntax
SimVariables have to be enclosed in ${..}, e.g. ${vSowingDOY}, ${crop.LUE}, ${Lintul.sWSO}
Operators:
•+,-,*,/
•and (&&), or (||), not (!)
•lt (<), le (<=), gt (>), ge (>=), eq (==), ne (!=)
Expressions can be grouped as usual by round parantheses () – eg. (a+b)*c
Local variables
var x = 3;
Scripts
Rules can contain more than one expressions, even more complex scripts
The result of the last expression will be returned to Simplace
rule = “
var x = ${vConversionFactor};
var y = ${Module.State};
x * y
“
Conditions and Loops:
•Conditions
if(condition) {doSomething} else {doElse}
•Looping over an array
var sum = 0;
for(var wc in ${Slim.WCperLayer})
{
sum = sum + wc
}
•While-Loop
var i = 0;
while (i lt 10)
{
doSomething;
i = i + 1;
}
•Blocks of code are grouped by curly braces { }
Where to use
In Solutions/Projects
•The rules normally go to the rule=““ attribute (or resetrule=““) of the elements capable to handle rules like <resource>, <output>, <out>, <selector>, <action>, <mgm>, <var>
•The rule attribute can handle both: SimVariables or expressions
<out rule=“Lintul.WSO“ … gives the value directly
<out rule=“${Lintul.WSO}“ … same as above – but slower
<out rule=“${Lintul.WSO}*10“… gives WSO multiplied by 10
<out rule=“Lintul.WSO * 10“ does not work!
•Simplace seeks inside the rule attribute text for ${…} to decide whether it‘s a real rule or simply a SimVariable
In Management
•Rules can appear in two places in management
A boolean rule, that determines when the action (calculation) should be performed
•Rules for each management variables, that determines how the values should be calculated.
<action rule="${Phenology.sDVS} ge 2">
<mgm id="DoHarvest" datatype="BOOLEAN"
default="false"
rule="true“ />
<mgm id=“HarvestIndex” datatype=“DOUBLE”
default =“0.0”
rule=“${Lintul.Yield}/${Lintul.AboveGroundBM}” />
</action>
For outputs
Few examples from HillflowTest.sol.xml:
1. To sum up all the values from a double array(Hillflow1D.sTHETA)
<out id="VolumetricWatercontentSumAll" datatype="DOUBLE" rule="var res=0;for (var item : ${Hillflow1D.sTHETA}){res = res + item;}; return res;"/>
2. To take an element from an array use the following syntax: Hillflow1D.sTHETA[index], where 'index' starts from 0, in order to sum up first five elements from the array use the following example:
<out id="VolumetricWatercontentSum1_5" datatype="DOUBLE" rule="${Hillwflow1D.sTHETA[0]}+${Hillflow1D.sTHETA[1]}+${Hillflow1D.sTHETA[2]}+${Hillflow1D.sTHETA[3]}+${Hillflow1D.sTHETA[4]}"/>
Output Frequency
Frequency of outputs can be
•DAILY
•YEARLY (on 31.12.)
•BOOLEAN by using
frequency=“BOOLEAN“
rule=“Component.SomeBooleanValue“
•COMPLEX by using a rule that evaluates to a boolean value (i.e. true or false)
frequency=“COMPLEX“
rule=“${CURRENT.DOY} == ${vSowDOY}+20“
Same apply if you want to determine when resources should be updated.
Resetting Output
•Non-Daily outputs can aggregate the values from the previous output time up to the actual output time.
•Aggregation method is supplied via the mode attribute of each output variable (SUM, AVG, FIRST, LAST)
•Outputs have an attribute resetrule that works similar to rule to determine when the outputs should be reset:
<output frequency=“COMPLEX“
rule=“${P.IsMaturity}“
resetrule=“${CURRENT.DOY}==${vSowDOY}“ …>
<out id=“EvapDuringVegetation“ mode=“SUM“
rule=“ET.Evaporation“>
Transform Output Variables
•In the <out> element one can use rules attribute to modify the value of a variable or compute new values (aggregation etc)
<out id=“EvapTran“
rule=“${ET.Evap}+${ET.Transp}“ />
•One can set the value explicitly to null, thus avoiding that the value is taken into account when averaging.
•Modifying individual output variables with rules can be done independently whether the ouput is daily, yearly or triggerd by a complex rule.
If the number of elements which you want to take, lets say, is more than 20, then you can use the javascript syntax as in the first example and loop through the array.
Array Function in Rules
There are many fancy array functions one can use in rules:
•array:clone(arr)- make a copy of an array, which you can modify without changing the original
•array:Double(n,v) - Create an array with n elements by repeating the value v (similar functions array:Integer, array:Char)
•array:sum(arr), array:sum(arr,start,end) – sum of an array or a part of an array from start to end (inclusive), (similar functions array:avg, array:min, array:max)
•array:calc(arr1,op,arr2), array:calc(arr,op,scalar) performs an operation (‘+‘,‘-‘,‘*‘,‘/‘,‘^‘) elementwise – no need for loops in rules anymore to do these calculations
Expressions
Expressions involve arithmetical, logical and comparison operators.
Evaluation of Rules in SIMPLACE Framework with the SchmitzM Operation Tree Parser
The Operation Tree Parser evaluates mathematical rules of different type. Logical or numerical operations can be evaluated. Before evaluating the given rules the parameters given in the algorithm are replaced. If a parameter was not correctly found the rule cannot be evaluated. Make sure that either a value or a default is given for the specific parameter.
Variables enclosed in ${variable.name}
First parameters are replaced hierarchically, then calculation takes place.
${some.parameter.name} * 15 / ${some.other.param${param.index.param}}
Constant numerical values:
•Numbers in double type
•rand or random as alias for random numbers between 0 and 1
•"Not a number" (NaN): "NaN"
•Boolean TRUE (1)
•Boolean FALSE (0)
•Current time ms (System.currentTimeMillis()): "CURR_MILLIS"
•One day in ms (LangUtil?.DAY_MILLIS): "DAY_MILLIS"
•One Week in ms (LangUtil?.WEEK_MILLIS): "WEEK_MILLIS"
Constant String values
•Character arrays are started and ended by " or '.
2-partner arithmetic operation
•Summation: "+"
•Subtraction: "-"
•Multiplication: "*"
•Division: "/"
•Exponent: ""
15 + 5.6 (20.6)
The 2-partner Boolean operator
•Boolean AND: "&"
•Boolean OR: "|"
•Equals: "=" or "=="
•Unequal: "!=" or "<>"
•Smaller than: "<"
•Greater than ">"
•Smaller equals: "<="
•Greater equals: ">="
The 2-partner character operation
•String-concatenation: "+"
•Regular expression check: "regex(expression,regular expr)"
•First value of a String-Splits via regular expression: "split(expression,regular expr)"
•N-th value of the string split via regular expression: "split(expression,regular expr,n)"
•String-Replace: "replAll(expression,pattern expr,repl expr)"
The singular operation
•Absolute Value: "abs(.)"
•Square root: "sqr(.)" or "sqrt(.)"
•Round: "rnd(.)" or "round(.)"
•Cut: "int(.)" or "trunc(.)"
•Test Not A Number (NaN): "isNaN(.)"
•Sinus: "sin(.)"
•Co sinus: "cos(.)"
•Tangent: "tan(.)"
•Arcos-Sinus: "arcsin(.)" or "asin(.)"
•Arcos - Co sinus: "arccos(.)" or "acos(.)"
•Arcos - Tangent: "arctan(.)" or "atan(.)"
•Exponential: "exp(.)"
•Logarithmic base e: "ln(.)"
•Logarithmic base 10: "log(.)"
The singular Boolean operation
•Boolean NOT: "!(.)"
The singular String operation
•Boolean NOT: "!(.)"
•Convert Number to String: "str(.)"
•Convert String to Number: "val(.)"
•Convert to upper case: "toupper(.)"
•Convert to lower case: "tolower(.)"
•Convert date-String to Milli-Seconds: str2millis(string expr, format string expr)
•Convert date to Milli-Seconds: date2millis(year, month, day)
•Convert Milli-Seconds to date-String: millis2Str(millis, format string expr)
•Convert date to date-String: date2Str(year, month, day, format string expr)
The 3-partner operation ITE(.,.,.)
•The ITE-operation: "If .. Then .. Else ..".
ITE(${some.parameter.name} > 5,0,1)
First parameter is replaced, then checked first partner. Result (0) if parameter was > 5, (1) if not
To combine Boolean and arithmetic operations the operands have to be coded as (1) or (0), not as TRUE or FALSE where op > 0 is TRUE and 0 is FALSE.
Rule-Engine
net.simplace.simulation.io.resources.transformer.DefaultRuleTransformer
Where in XML you have rule="the rule" Jexl is used.
All other places OperationTreeParser with Replacement Factory is used!
tNewParameter = tNewParameter.replace("_MODEL_DOY_", "CURRENT.DOY");
tNewParameter = tNewParameter.replace("_MODEL_DAY_", "CURRENT.DAY");
tNewParameter = tNewParameter.replace("_MODEL_MONTH_", "CURRENT.MONTH");
tNewParameter = tNewParameter.replace("_MODEL_YEAR_", "CURRENT.YEAR");
tNewParameter = tNewParameter.replace("=", "==");
tNewParameter = tNewParameter.replace("&", "&&");
tNewParameter = tNewParameter.replace("|", "||");
No use of !EXP:…! For rules any more.
Rule-Engine: Examples
•Boolean expressions
${CURRENT.DOY}==${vIDPL} && ${Phenology.sDVS}>=2.1
•Calculate simple result
(${weather.AirTemperatureMax}+${weather.AirTemperatureMin})/2
•Add explicitly values from arrays
${Hillflow1D.sTHETA[0]}+${Hillflow1D.sTHETA[1]}
•Sum all values from an array
var res=0;for (var item : ${Hillflow1D.sTHETA}){res = res + item;}; return res;
An example from HillflowTest.sol.xml:
<out id="VolumetricWatercontentSumAll" datatype="DOUBLE" rule="var res=0;for (var item : ${Hillflow1D.sTHETA}){res = res + item;}; return res;"/>
<out id="VolumetricWatercontentSum1_5" datatype="DOUBLE" rule="${Hillflow1D.sTHETA[0]}+${Hillflow1D.sTHETA[1]}+${Hillflow1D.sTHETA[2]}+${Hillflow1D.sTHETA[3]}+${Hillflow1D.sTHETA[4]}"/>