/*
* Copyright (C) 2012 Addition, Lda. (addition at addition dot pt)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package org.addition.epanet.hydraulic.structures;
import org.addition.epanet.Constants;
import org.addition.epanet.util.ENException;
import org.addition.epanet.util.Utilities;
import java.util.logging.Logger;
import org.addition.epanet.network.FieldsMap;
import org.addition.epanet.network.PropertiesMap;
import org.addition.epanet.network.structures.Rule;
import org.addition.epanet.network.structures.Rule.*;
import org.addition.epanet.network.FieldsMap.*;
import org.addition.epanet.network.structures.Link.*;
import java.util.ArrayList;
import java.util.List;
public class SimulationRule {
// Temporary action item
public static class ActItem{
public ActItem(SimulationRule rule, Action action) {
this.rule = rule;
this.action = action;
}
SimulationRule rule;
SimulationRule.Action action;
}
// Rules execution result
public static class Result{
public Result(long step, long htime) {
this.step = step;
this.htime = htime;
}
public long step;
public long htime;
}
// Rule premise
public class Premise{
public Premise(String []Tok,Rule.Rulewords lOp,List<SimulationNode> nodes, List<SimulationLink> links) throws ENException {
Rule.Objects loType;
Rule.Varwords lVar;
Object lObj;
Operators lROp;
if (Tok.length != 5 && Tok.length != 6)
throw new ENException(201);
loType = Rule.Objects.parse(Tok[1]);
if (loType == Rule.Objects.r_SYSTEM){
lVar = Rule.Varwords.parse(Tok[2]);
if (lVar != Rule.Varwords.r_DEMAND && lVar != Rule.Varwords.r_TIME && lVar != Rule.Varwords.r_CLOCKTIME)
throw new ENException(201);
lObj = Rule.Objects.r_SYSTEM;
}
else
{
lVar = Rule.Varwords.parse(Tok[3]);
if (lVar == null)
throw new ENException(201);
switch (loType)
{
case r_NODE:
case r_JUNC:
case r_RESERV:
case r_TANK:
loType = Rule.Objects.r_NODE;
break;
case r_LINK:
case r_PIPE:
case r_PUMP:
case r_VALVE:
loType = Rule.Objects.r_LINK;
break;
default:
throw new ENException(201);
}
if (loType == Rule.Objects.r_NODE){
//Node nodeRef = net.getNode(Tok[2]);
SimulationNode nodeRef = null;
for(SimulationNode simNode : nodes)
if(simNode.getNode().getId().equals(Tok[2]))
nodeRef = simNode;
if (nodeRef == null)
throw new ENException(203);
switch (lVar){
case r_DEMAND:
case r_HEAD:
case r_GRADE:
case r_LEVEL:
case r_PRESSURE:
break;
case r_FILLTIME:
case r_DRAINTIME: if (nodeRef instanceof SimulationTank) throw new ENException(201); break;
default:
throw new ENException(201);
}
lObj = nodeRef;
}
else{
//Link linkRef = net.getLink(Tok[2]);
SimulationLink linkRef = null;
for(SimulationLink simLink : links)
if(simLink.getLink().getId().equals(Tok[2]))
linkRef = simLink;
if (linkRef == null)
throw new ENException(204);
switch (lVar){
case r_FLOW:
case r_STATUS:
case r_SETTING:
break;
default:
throw new ENException(201);
}
lObj = linkRef;
}
}
Rule.Operators op;
if (loType == Rule.Objects.r_SYSTEM)
op = Rule.Operators.parse(Tok[3]);
else
op = Rule.Operators.parse(Tok[4]);
if (op == null)
throw new ENException(201);
switch(op)
{
case IS:
lROp = Operators.EQ;
break;
case NOT:
lROp = Operators.NE;
break;
case BELOW:
lROp = Operators.LT;
break;
case ABOVE:
lROp = Operators.GT;
break;
default:
lROp = op;
}
Values lStat = Values.IS_NUMBER;
Double lVal = Constants.MISSING;
if (lVar == Varwords.r_TIME || lVar == Varwords.r_CLOCKTIME)
{
if (Tok.length == 6)
lVal = Utilities.getHour(Tok[4],Tok[5])*3600.;
else
lVal = Utilities.getHour(Tok[4],"")*3600.;
if (lVal < 0.0)
throw new ENException(202);
}
else{
Values k = Values.parse(Tok[Tok.length-1]);
if(k == null || lStat.id <= Values.IS_NUMBER.id){
if (lStat==null || lStat.id <= Values.IS_NUMBER.id){
if ((lVal = Utilities.getDouble(Tok[Tok.length - 1]))==null)
throw new ENException(202);
if (lVar == Varwords.r_FILLTIME || lVar == Varwords.r_DRAINTIME)
lVal = lVal*3600.0;
}
}
else{
lStat = k;
}
}
status = lStat;
value = lVal;
logop = lOp;
relop = lROp;
variable = lVar;
object = lObj;
}
private final Object object;
private final Rulewords logop; // Logical operator
private final Varwords variable; // Pressure, flow, etc
private final Operators relop; // Relational operator
private final Values status; // Variable's status
private final double value; // Variable's value
public Rulewords getLogop(){
return logop;
}
public Object getObject(){
return object;
}
public Varwords getVariable(){
return variable;
}
public Operators getRelop(){
return relop;
}
public Values getStatus(){
return status;
}
public double getValue(){
return value;
}
// Checks if a particular premise is true
private boolean checkPremise(FieldsMap fMap,PropertiesMap pMap,
long Time1, long Htime, double dsystem) throws ENException
{
if (variable == Varwords.r_TIME || variable == Varwords.r_CLOCKTIME)
return(checkTime(pMap,Time1,Htime));
else if (status.id > Values.IS_NUMBER.id)
return(checkStatus());
else
return(checkValue(fMap,dsystem));
}
// Checks if condition on system time holds
private boolean checkTime(PropertiesMap pMap, long Time1, long Htime) throws ENException
{
boolean flag;
long t1,t2,x;
if (variable == Varwords.r_TIME){
t1 = Time1;
t2 = Htime;
}
else if (variable == Varwords.r_CLOCKTIME)
{
t1 = (Time1 + pMap.getTstart()) % Constants.SECperDAY;
t2 = (Htime + pMap.getTstart()) % Constants.SECperDAY;
}
else
return false;
x = (long)(value);
switch (relop)
{
case LT: if (t2 >= x) return(false); break;
case LE: if (t2 > x) return(false); break;
case GT: if (t2 <= x) return(false); break;
case GE: if (t2 < x) return(false); break;
case EQ:
case NE:
flag = false;
if (t2 < t1)
{
if (x >= t1 || x <= t2) flag = true;
}
else
{
if (x >= t1 && x <= t2) flag = true;
}
if (relop == Operators.EQ && !flag) return true;
if (relop == Operators.NE && flag) return true;
break;
}
return true;
}
// Checks if condition on link status holds
private boolean checkStatus()
{
switch (status)
{
case IS_OPEN:
case IS_CLOSED:
case IS_ACTIVE:
Values j;
StatType i=null;
if(object instanceof SimulationLink)
i = ((SimulationLink) object).getSimStatus();
if (i!=null && i.id <= StatType.CLOSED.id)
j = Values.IS_CLOSED;
else if (i == StatType.ACTIVE)
j = Values.IS_ACTIVE;
else
j = Values.IS_OPEN;
if (j == status && relop == Operators.EQ)
return true;
if (j != status && relop == Operators.NE)
return true;
}
return false;
}
// Checks if numerical condition on a variable is true.
private boolean checkValue(FieldsMap fMap,double dsystem) throws ENException
{
double tol = 1.e-3;
double x;
SimulationLink link = null;
SimulationNode node = null;
if(object instanceof SimulationLink)
link = ((SimulationLink)object);
else if(object instanceof SimulationNode)
node = ((SimulationNode)object);
switch (variable)
{
case r_DEMAND:
if (object == Objects.r_SYSTEM)
x = dsystem*fMap.getUnits(Type.DEMAND);
else
x = node.getSimDemand()*fMap.getUnits(Type.DEMAND);
break;
case r_HEAD:
case r_GRADE:
x = node.getSimHead() * fMap.getUnits(Type.HEAD);
break;
case r_PRESSURE:
x = (node.getSimHead() - node.getElevation())*fMap.getUnits(Type.PRESSURE);
break;
case r_LEVEL:
x = (node.getSimHead() - node.getElevation())*fMap.getUnits(Type.HEAD);
break;
case r_FLOW:
x = Math.abs(link.getSimFlow())*fMap.getUnits(Type.FLOW);
break;
case r_SETTING:
if (link.getSimSetting() == Constants.MISSING)
return false;
x = link.getSimSetting();
switch (link.getType())
{
case PRV:
case PSV:
case PBV:
x = x*fMap.getUnits(Type.PRESSURE);
break;
case FCV:
x = x*fMap.getUnits(Type.FLOW);
break;
}
break;
case r_FILLTIME:
{
if(!(object instanceof SimulationTank))
return false;
SimulationTank tank = (SimulationTank)object;
if (tank.isReservoir())
return false;
if (tank.getSimDemand() <= Constants.TINY)
return false;
x = (tank.getVmax() - tank.getSimVolume())/tank.getSimDemand();
break;
}
case r_DRAINTIME:
{
if(!(object instanceof SimulationTank))
return false;
SimulationTank tank = (SimulationTank)object;
if (tank.isReservoir())
return false;
if (tank.getSimDemand() >= -Constants.TINY)
return false;
x = (tank.getVmin() - tank.getSimVolume())/tank.getSimDemand();
break;
}
default:
return false;
}
switch (relop)
{
case EQ:
if (Math.abs(x - value) > tol)
return false;
break;
case NE:
if (Math.abs(x - value) < tol)
return false;
break;
case LT:
if (x > value + tol)
return false;
break;
case LE:
if (x > value - tol)
return false;
break;
case GT:
if (x < value - tol)
return false;
break;
case GE:
if (x < value + tol)
return false;
break;
}
return true;
}
}
public class Action{
public Action(String[] tok, List<SimulationLink> links) throws ENException {
int Ntokens = tok.length;
Values s,k;
Double x;
if (Ntokens != 6)
throw new ENException(201);
//Link linkRef = net.getLink(tok[2]);
SimulationLink linkRef = null;
for(SimulationLink simLink : links)
if(simLink.getLink().getId().equals(tok[2]))
linkRef = simLink;
if (linkRef == null)
throw new ENException(204);
if (linkRef.getType() == LinkType.CV)
throw new ENException(207);
s = null;
x = Constants.MISSING;
k = Values.parse(tok[5]);
if (k!=null && k.id > Values.IS_NUMBER.id)
s = k;
else
{
if ( (x = Utilities.getDouble(tok[5]))==null )
throw new ENException(202);
if (x < 0.0)
throw new ENException(202);
}
if (x != Constants.MISSING && linkRef.getType() == LinkType.GPV)
throw new ENException(202);
if (x != Constants.MISSING && linkRef.getType() == LinkType.PIPE){
if (x == 0.0)
s = Values.IS_CLOSED;
else
s = Values.IS_OPEN;
x = Constants.MISSING;
}
link = linkRef;
status = s;
setting = x;
}
private final SimulationLink link;
private final Values status;
private final double setting;
public SimulationLink getLink() {
return link;
}
public Values getStatus() {
return status;
}
public double getSetting() {
return setting;
}
// Execute action, returns true if the link was alterated.
private boolean execute(FieldsMap fMap,PropertiesMap pMap,Logger log, double tol,long Htime) throws ENException {
boolean flag = false;
StatType s = link.getSimStatus();
double v = link.getSimSetting();
double x = setting;
if (status == Values.IS_OPEN && s.id <= StatType.CLOSED.id){ // Switch link from closed to open
link.setLinkStatus(true);
flag = true;
}
else if (status == Values.IS_CLOSED && s.id > StatType.CLOSED.id){ // Switch link from not closed to closed
link.setLinkStatus(false);
flag = true;
}
else if(x != Constants.MISSING){ // Change link's setting
switch(link.getType())
{
case PRV:
case PSV:
case PBV: x = x/fMap.getUnits(Type.PRESSURE); break;
case FCV: x = x/fMap.getUnits(Type.FLOW); break;
}
if (Math.abs(x-v) > tol)
{
link.setLinkSetting(x);
flag = true;
}
}
if (flag){
if (pMap.getStatflag()!=null) // Report rule action
logRuleExecution(log,Htime);
return true;
}
return false;
}
public void logRuleExecution(Logger log, long Htime){
log.warning(String.format(Utilities.getText("FMT63"),Utilities.getClockTime(Htime), link.getType().parseStr, link.getLink().getId(), label));
}
}
private final String label;
private final double priority;
private final List<Premise> Pchain;
private final List<Action> Tchain;
private final List<Action> Fchain;
public String getLabel() {
return label;
}
public double getPriority() {
return priority;
}
public Premise[] getPchain() {
return Pchain.toArray(new Premise[]{});
}
public List<Action> getTchain() {
return Tchain;
}
public List<Action> getFchain() {
return Fchain;
}
// Simulation Methods
// Evaluate rule premises.
private boolean evalPremises(FieldsMap fMap,PropertiesMap pMap,
long Time1, long Htime, double dsystem) throws ENException
{
boolean result=true;
for(SimulationRule.Premise p : getPchain())
{
if (p.getLogop() == Rulewords.r_OR){
if (!result)
result = p.checkPremise(fMap,pMap,Time1,Htime,dsystem);
}
else{
if (!result)
return false;
result = p.checkPremise(fMap,pMap,Time1,Htime,dsystem);
}
}
return result;
}
// Adds rule's actions to action list
private static void updateActionList(SimulationRule rule, List<ActItem> actionList, boolean branch){
if(branch){ // go through the true action branch
for(Action a : rule.getTchain()){
if(!checkAction(rule,a,actionList)) // add a new action from the "true" chain
actionList.add(new ActItem(rule,a));
}
}
else{
for(Action a : rule.getFchain()){
if(!checkAction(rule,a,actionList)) // add a new action from the "false" chain
actionList.add(new ActItem(rule,a));
}
}
}
// Checks if an action with the same link is already on the Action List
private static boolean checkAction(SimulationRule rule,Action action, List<ActItem> actionList){
for(ActItem item : actionList)
{
if(item.action.link == action.link){ // Action with same link
if(rule.priority > item.rule.priority){ // Replace Actitem action with higher priority rule
item.rule = rule;
item.action = action;
}
return true;
}
}
return false;
}
// Implements actions on action list, returns the number of actions executed.
private static int takeActions(FieldsMap fMap, PropertiesMap pMap, Logger log,List<ActItem> actionList,
long htime) throws ENException
{
double tol = 1.e-3;
int n = 0;
for(ActItem item : actionList){
if(item.action.execute(fMap,pMap,log,tol,htime))
n++;
}
return n;
}
// Checks which rules should fire at current time.
private static int check(FieldsMap fMap,PropertiesMap pMap, List<SimulationRule> rules,Logger log,
long Htime,long dt,double dsystem) throws ENException {
// Start of rule evaluation time interval
long Time1 = Htime - dt + 1;
List<ActItem> actionList = new ArrayList<ActItem>();
for(SimulationRule rule : rules)
updateActionList(rule,actionList,rule.evalPremises(fMap,pMap,Time1,Htime,dsystem));
return takeActions(fMap,pMap,log,actionList,Htime);
}
// updates next time step by checking if any rules will fire before then; also updates tank levels.
public static Result minimumTimeStep(FieldsMap fMap,PropertiesMap pMap,Logger log,
List<SimulationRule> rules,List<SimulationTank> tanks,
long Htime,long tstep,double dsystem) throws ENException
{
long tnow, // Start of time interval for rule evaluation
tmax, // End of time interval for rule evaluation
dt, // Normal time increment for rule evaluation
dt1; // Actual time increment for rule evaluation
// Find interval of time for rule evaluation
tnow = Htime;
tmax = tnow + tstep;
//If no rules, then time increment equals current time step
if (rules.size() == 0) {
dt = tstep;
dt1 = dt;
}
else{
// Otherwise, time increment equals rule evaluation time step and
// first actual increment equals time until next even multiple of
// Rulestep occurs.
dt = pMap.getRulestep();
dt1 = pMap.getRulestep() - (tnow % pMap.getRulestep());
}
// Make sure time increment is no larger than current time step
dt = Math.min(dt, tstep);
dt1 = Math.min(dt1, tstep);
if (dt1 == 0)
dt1 = dt;
// Step through time, updating tank levels, until either
// a rule fires or we reach the end of evaluation period.
//
// Note: we are updating the global simulation time (Htime)
// here because it is used by functions in RULES.C(this class)
// to evaluate rules when checkrules() is called.
// It is restored to its original value after the
// rule evaluation process is completed (see below).
// Also note that dt1 will equal dt after the first
// time increment is taken.
do {
Htime += dt1; // Update simulation clock
SimulationTank.stepWaterLevels(tanks, fMap, dt1); // Find new tank levels
if (check(fMap,pMap,rules,log,Htime,dt1,dsystem) != 0) break; // Stop if rules fire
dt = Math.min(dt, tmax - Htime); // Update time increment
dt1 = dt; // Update actual increment
}
while (dt > 0);
//Compute an updated simulation time step (*tstep)
// and return simulation time to its original value
tstep = Htime - tnow;
Htime = tnow;
return new Result(tstep,Htime);
}
public SimulationRule(Rule _rule, List<SimulationLink> links,List<SimulationNode> nodes) throws ENException{
label = _rule.getLabel();
Pchain = new ArrayList<Premise>();
Tchain = new ArrayList<Action>();
Fchain = new ArrayList<Action>();
Double tempPriority = 0.0;
Rule.Rulewords ruleState = Rule.Rulewords.r_RULE;
for(String _line : _rule.getCode().split("\n")){
String [] tok = _line.split("[ \t]+");
Rule.Rulewords key = Rule.Rulewords.parse(tok[0]);
if(key == null) throw new ENException(201);
switch (key)
{
case r_IF:
if (!ruleState.equals(Rule.Rulewords.r_RULE))
throw new ENException(221);
ruleState = Rule.Rulewords.r_IF;
parsePremise(tok, Rule.Rulewords.r_AND,nodes,links);
break;
case r_AND:
if (ruleState == Rule.Rulewords.r_IF)
parsePremise(tok, Rule.Rulewords.r_AND,nodes,links);
else if (ruleState == Rule.Rulewords.r_THEN || ruleState == Rule.Rulewords.r_ELSE)
parseAction(ruleState,tok,links);
else
throw new ENException(221);
break;
case r_OR:
if (ruleState == Rule.Rulewords.r_IF)
parsePremise(tok, Rule.Rulewords.r_OR,nodes,links);
else
throw new ENException(221);
break;
case r_THEN:
if (ruleState != Rule.Rulewords.r_IF)
throw new ENException (221);
ruleState = Rule.Rulewords.r_THEN;
parseAction(ruleState,tok,links);
break;
case r_ELSE:
if (ruleState != Rule.Rulewords.r_THEN)
throw new ENException(221);
ruleState = Rule.Rulewords.r_ELSE;
parseAction(ruleState,tok,links);
break;
case r_PRIORITY:
{
if (!ruleState.equals(Rule.Rulewords.r_THEN) && !ruleState.equals(Rule.Rulewords.r_ELSE))
throw new ENException(221);
ruleState = Rule.Rulewords.r_PRIORITY;
if ( (tempPriority = Utilities.getDouble(tok[1]))==null)
throw new ENException(202);
break;
}
default:
throw new ENException(201);
}
}
priority = tempPriority;
}
protected void parsePremise(String []Tok,Rule.Rulewords logop,List<SimulationNode> nodes, List<SimulationLink> links) throws ENException {
Premise p = new Premise(Tok,logop,nodes, links);
Pchain.add(p);
}
protected void parseAction(Rulewords state, String[] tok, List<SimulationLink> links) throws ENException {
Action a = new Action(tok,links);
if (state == Rulewords.r_THEN)
Tchain.add(0,a);
else
Fchain.add(0, a);
}
}