/*
* 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.hydraulic.SparseMatrix;
import org.addition.epanet.hydraulic.models.PipeHeadModel;
import org.addition.epanet.network.FieldsMap;
import org.addition.epanet.network.FieldsMap.Type;
import org.addition.epanet.network.PropertiesMap;
import org.addition.epanet.network.structures.Curve;
import org.addition.epanet.network.structures.Link;
import org.addition.epanet.network.structures.Link.LinkType;
import org.addition.epanet.network.structures.Link.StatType;
import org.addition.epanet.network.structures.Pump;
import org.addition.epanet.network.structures.Valve;
import org.addition.epanet.util.ENException;
import org.addition.epanet.util.Utilities;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
public class SimulationLink {
protected SimulationNode first = null;
protected SimulationNode second = null;
protected final Link link;
protected final int index;
protected StatType status; // Epanet 'S[k]', link current status
protected double flow; // Epanet 'Q[k]', link flow value
protected double invHeadLoss; // Epanet 'P[k]', Inverse headloss derivatives
protected double flowCorrection; // Epanet 'Y[k]', Flow correction factors
protected double setting; // Epanet 'K[k]', Link setting
protected StatType oldStatus;
public static SimulationLink createIndexedLink(Map<String, SimulationNode> byId, Link ref, int idx) {
SimulationLink ret = null;
if (ref instanceof Valve)
ret = new SimulationValve(byId.values(), ref, idx);
else if (ref instanceof Pump)
ret = new SimulationPump(byId.values(), ref, idx);
else
ret = new SimulationLink(byId, ref, idx);
return ret;
}
public static SimulationLink createIndexedLink(List<SimulationNode> indexedNodes, Link ref, int idx) {
SimulationLink ret = null;
if (ref instanceof Valve)
ret = new SimulationValve(indexedNodes, ref, idx);
else if (ref instanceof Pump)
ret = new SimulationPump(indexedNodes, ref, idx);
else
ret = new SimulationLink(indexedNodes, ref, idx);
return ret;
}
public SimulationLink(Map<String, SimulationNode> byId, Link ref, int idx) {
link = ref;
first = byId.get(link.getFirst().getId());
second = byId.get(link.getSecond().getId());
this.index = idx;
// Init
setting = link.getRoughness();
status = link.getStat();
}
public SimulationLink(Collection<SimulationNode> indexedNodes, Link ref, int idx) {
link = ref;
for (SimulationNode indexedNode : indexedNodes) {
if (indexedNode.getId().equals(link.getFirst().getId()))
first = indexedNode;
else if (indexedNode.getId().equals(link.getSecond().getId()))
second = indexedNode;
if (first != null && second != null) break;
}
this.index = idx;
// Init
setting = link.getRoughness();
status = link.getStat();
}
// Indexed link methods
public SimulationNode getFirst() {
return first;
}
public SimulationNode getSecond() {
return second;
}
public Link getLink() {
return link;
}
public int getIndex() {
return index;
}
// Network link Getters
public double[] getC0() {
return link.getC0();
}
//public double[] getParam() {
// return node.getParam();
//}
public double getDiameter() {
return link.getDiameter();
}
//public double getLenght() {
// return node.getLenght();
//}
public double getRoughness() {
return link.getRoughness();
}
public double getKm() {
return link.getKm();
}
//public double getKb() {
// return node.getKb();
//}
//
//public double getKw() {
// return node.getKw();
//}
public double getFlowResistance() {
return link.getFlowResistance();
}
public LinkType getType() {
return link.getType();
}
//public StatType getStat() {
// return node.getStat();
//}
//
//public boolean getRptFlag() {
// return node.isRptFlag();
//}
// Simulation getters & setters
public StatType getSimStatus() {
return status;
}
public void setSimStatus(StatType type) {
status = type;
}
public double getSimFlow() {
return flow;
}
public void setSimFlow(double flow) {
this.flow = flow;
}
public double getSimSetting() {
return setting;
}
public void setSimSetting(double value) {
setting = value;
}
public double getSimInvHeadLoss() {
return invHeadLoss;
}
public void setSimInvHeadLoss(double value) {
invHeadLoss = value;
}
public double getSimFlowCorrection() {
return flowCorrection;
}
public void setSimFlowCorrection(double value) {
flowCorrection = value;
}
public StatType getSimOldStatus() {
return oldStatus;
}
public void setSimOldStatus(StatType oldStatus) {
this.oldStatus = oldStatus;
}
// Simulation Methods
// Sets link status to OPEN(true) or CLOSED(false)
void setLinkStatus(boolean value) {
if (value) {
if (this instanceof SimulationPump)
setting = 1.0;
else if (getType() != LinkType.GPV)
setting = Constants.MISSING;
status = StatType.OPEN;
} else {
if (this instanceof SimulationPump)
setting = 0.0;
else if (getType() != LinkType.GPV)
setting = Constants.MISSING;
status = StatType.CLOSED;
}
}
// Sets pump speed or valve setting, adjusting link status and flow when necessary
public void setLinkSetting(double value) {
if (this instanceof SimulationPump) {
setting = value;
if (value > 0 && status.id <= StatType.CLOSED.id)
status = StatType.OPEN;
if (value == 0 && status.id > StatType.CLOSED.id)
status = StatType.CLOSED;
} else if (getType() == LinkType.FCV) {
setting = value;
status = StatType.ACTIVE;
} else {
if (setting == Constants.MISSING && status.id <= StatType.CLOSED.id)
status = StatType.OPEN;
setting = value;
}
}
// Sets initial flow in link to QZERO if link is closed, to design flow for a pump,
// or to flow at velocity of 1 fps for other links.
public void initLinkFlow() {
if (getSimStatus() == StatType.CLOSED)
flow = Constants.QZERO;
else if (this instanceof SimulationPump)
flow = getRoughness() * ((SimulationPump) this).getQ0();
else
flow = Constants.PI * Math.pow(getDiameter(), 2) / 4.0;
}
public void initLinkFlow(StatType type, double Kc) {
if (type == StatType.CLOSED)
flow = Constants.QZERO;
else if (this instanceof SimulationPump)
flow = Kc * ((SimulationPump) this).getQ0();
else
flow = Constants.PI * Math.pow(getDiameter(), 2) / 4.0;
}
// public static long T1 = 0, T2 = 0, T3 = 0; //TODO:REMOVE THIS
// Compute P, Y and matrix coeffs
private void computeMatrixCoeff(final FieldsMap fMap,
final PropertiesMap pMap,
final PipeHeadModel hlModel,
final Curve[] curves,
final SparseMatrix smat, final LSVariables ls) throws ENException {
switch (getType()) {
// Pipes
case CV:
case PIPE:
computePipeCoeff(pMap, hlModel);
break;
// Pumps
case PUMP:
((SimulationPump) this).computePumpCoeff(fMap, pMap);
break;
// Valves
case PBV:
case TCV:
case GPV:
case FCV:
case PRV:
case PSV:
// If valve status fixed then treat as pipe
// otherwise ignore the valve for now.
if (!((SimulationValve) this).computeValveCoeff(fMap, pMap, curves))
return;
break;
default:
return;
}
int n1 = first.getIndex();
int n2 = second.getIndex();
ls.addNodalInFlow(n1, -flow);
ls.addNodalInFlow(n2, +flow);
ls.addAij(smat.getNdx(getIndex()), -invHeadLoss);
if (!(first instanceof SimulationTank)) {
ls.addAii(smat.getRow(n1), +invHeadLoss);
ls.addRHSCoeff(smat.getRow(n1), +flowCorrection);
} else
ls.addRHSCoeff(smat.getRow(n2), +(invHeadLoss * first.getSimHead()));
if (!(second instanceof SimulationTank)) {
ls.addAii(smat.getRow(n2), +invHeadLoss);
ls.addRHSCoeff(smat.getRow(n2), -flowCorrection);
} else
ls.addRHSCoeff(smat.getRow(n1), +(invHeadLoss * second.getSimHead()));
}
// Computes P & Y coefficients for pipe k
private void computePipeCoeff(PropertiesMap pMap, PipeHeadModel hlModel) throws ENException {
// For closed pipe use headloss formula: h = CBIG*q
if (status.id <= StatType.CLOSED.id) {
invHeadLoss = 1.0 / Constants.CBIG;
flowCorrection = flow;
return;
}
PipeHeadModel.LinkCoeffs coeffs = hlModel.compute(pMap, this);
invHeadLoss = coeffs.getInvHeadLoss();
flowCorrection = coeffs.getFlowCorrection();
}
// Closes link flowing into full or out of empty tank
private void tankStatus(PropertiesMap pMap) throws ENException {
double q = flow;
SimulationNode n1 = getFirst();
SimulationNode n2 = getSecond();
// Make node n1 be the tank
if (!(n1 instanceof SimulationTank)) {
if (!(n2 instanceof SimulationTank))
return; // neither n1 or n2 is a tank
// N2 is a tank, swap !
SimulationNode n = n1;
n1 = n2;
n2 = n;
q = -q;
}
double h = n1.getSimHead() - n2.getSimHead();
SimulationTank tank = (SimulationTank) n1;
// Skip reservoirs & closed links
if (tank.getArea() == 0.0 || status.id <= StatType.CLOSED.id)
return;
// If tank full, then prevent flow into it
if (tank.getSimHead() >= tank.getHmax() - pMap.getHtol()) {
//Case 1: Link is a pump discharging into tank
if (getType() == LinkType.PUMP) {
if (getSecond() == n1)
status = StatType.TEMPCLOSED;
} else if (cvStatus(pMap, StatType.OPEN, h, q) == StatType.CLOSED) // Case 2: Downstream head > tank head
status = StatType.TEMPCLOSED;
}
// If tank empty, then prevent flow out of it
if (tank.getSimHead() <= tank.getHmin() + pMap.getHtol()) {
// Case 1: Link is a pump discharging from tank
if (getType() == LinkType.PUMP) {
if (getFirst() == n1)
status = StatType.TEMPCLOSED;
}
// Case 2: Tank head > downstream head
else if (cvStatus(pMap, StatType.CLOSED, h, q) == StatType.OPEN)
status = StatType.TEMPCLOSED;
}
}
// Updates status of a check valve.
private static StatType cvStatus(PropertiesMap pMap, StatType s, double dh, double q) throws ENException {
if (Math.abs(dh) > pMap.getHtol()) {
if (dh < -pMap.getHtol())
return (StatType.CLOSED);
else if (q < -pMap.getQtol())
return (StatType.CLOSED);
else
return (StatType.OPEN);
} else {
if (q < -pMap.getQtol())
return (StatType.CLOSED);
else
return (s);
}
}
// Determines new status for pumps, CVs, FCVs & pipes to tanks.
private boolean linkStatus(PropertiesMap pMap, FieldsMap fMap, Logger log) throws ENException {
boolean change = false;
double dh = first.getSimHead() - second.getSimHead();
StatType tStatus = status;
if (tStatus == StatType.XHEAD || tStatus == StatType.TEMPCLOSED)
status = StatType.OPEN;
if (getType() == LinkType.CV)
status = cvStatus(pMap, status, dh, flow);
if (this instanceof SimulationPump && status.id >= StatType.OPEN.id && setting > 0.0)
status = ((SimulationPump) this).pumpStatus(pMap, -dh);
if (getType() == LinkType.FCV && setting != Constants.MISSING)
status = ((SimulationValve) this).fcvStatus(pMap, tStatus);
if (first instanceof SimulationTank || second instanceof SimulationTank)
tankStatus(pMap);
if (tStatus != status) {
change = true;
if (pMap.getStatflag() == PropertiesMap.StatFlag.FULL)
logStatChange(fMap, log, this, tStatus, status);
}
return (change);
}
protected static void logStatChange(FieldsMap fMap, Logger logger, SimulationLink link, StatType s1, StatType s2) throws ENException {
if (s1 == s2) {
switch (link.getType()) {
case PRV:
case PSV:
case PBV:
link.setting *= fMap.getUnits(Type.PRESSURE);
break;
case FCV:
link.setting *= fMap.getUnits(Type.FLOW);
}
logger.finest(String.format(Utilities.getText("FMT56"), link.getType().parseStr, link.getLink().getId(), link.setting));
return;
}
StatType j1, j2;
if (s1 == StatType.ACTIVE)
j1 = StatType.ACTIVE;
else if (s1.ordinal() <= StatType.CLOSED.ordinal())
j1 = StatType.CLOSED;
else
j1 = StatType.OPEN;
if (s2 == StatType.ACTIVE) j2 = StatType.ACTIVE;
else if (s2.ordinal() <= StatType.CLOSED.ordinal())
j2 = StatType.CLOSED;
else
j2 = StatType.OPEN;
if (j1 != j2) {
logger.finest(String.format(Utilities.getText("FMT57"), link.getType().parseStr, link.getLink().getId(), j1.reportStr, j2.reportStr));
}
}
// Determines new status for pumps, CVs, FCVs & pipes to tanks.
public static boolean linkStatus(PropertiesMap pMap, FieldsMap fMap, Logger log, List<SimulationLink> links) throws ENException {
boolean change = false;
for (SimulationLink link : links) {
if (link.linkStatus(pMap, fMap, log))
change = true;
}
return change;
}
// Computes solution matrix coefficients for links
public static void computeMatrixCoeffs(final FieldsMap fMap,
final PropertiesMap pMap,
final PipeHeadModel hlModel,
final List<SimulationLink> links, final Curve[] curves,
final SparseMatrix smat, final LSVariables ls) throws ENException {
for (final SimulationLink link : links) {
link.computeMatrixCoeff(fMap, pMap, hlModel, curves, smat, ls);
}
}
}