/* * JaamSim Discrete Event Simulation * Copyright (C) 2013 Ausenco Engineering Canada Inc. * Copyright (C) 2016 JaamSim Software Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jaamsim.CalculationObjects; import com.jaamsim.Samples.SampleInput; import com.jaamsim.input.Input; import com.jaamsim.input.Keyword; import com.jaamsim.input.Output; import com.jaamsim.input.UnitTypeInput; import com.jaamsim.input.ValueInput; import com.jaamsim.units.TimeUnit; import com.jaamsim.units.Unit; import com.jaamsim.units.UserSpecifiedUnit; /** * The PIDController simulates a Proportional-Integral-Differential type Controller. * Error = SetPoint - ProcessVariable * Output = (ProportionalGain / ProcessVariableScale) * [ Error + (Integral/IntegralTime) + (DerivativeTime*Derivative) ] * @author Harry King * */ public class PIDController extends DoubleCalculation { @Keyword(description = "The set point for the PID controller. The unit type for the set point " + "is given by the UnitType keyword.\n" + "The input can be a number or an entity that returns a number, such as " + "a CalculationObject, an Expression, a ProbabilityDistribution, or a " + "TimeSeries.", exampleList = {"1.2 m", "TimeSeries1", "'1[m] + 2*[TimeSeries1].Value'"}) private final SampleInput setPoint; @Keyword(description = "The process variable feedback to the PID controller. The unit type " + "for the process variable is given by the UnitType keyword.\n" + "The input can be a number or an entity that returns a number, such as " + "a CalculationObject, an Expression, a ProbabilityDistribution, or a " + "TimeSeries.", exampleList = {"Process", "'1[m] + [Process].Value'"}) private final SampleInput processVariable; @Keyword(description = "A constant with the same unit type as the process variable and the " + "set point. The difference between the process variable and the set " + "point is divided by this quantity to make a dimensionless variable.", exampleList = {"1.0 kg"}) private final ValueInput processVariableScale; @Keyword(description = "The unit type for the output from the PID controller.", exampleList = {"DistanceUnit"}) protected final UnitTypeInput outputUnitType; @Keyword(description = "The coefficient applied to the proportional feedback loop. " + "Its unit type must be the same as the controller's output.", exampleList = {"1.3 m"}) private final ValueInput proportionalGain; @Keyword(description = "The time scale applied to the integral feedback loop.", exampleList = {"1.0 s"}) private final ValueInput integralTime; @Keyword(description = "The time scale applied to the differential feedback loop.", exampleList = {"1.0 s"}) private final ValueInput derivativeTime; @Keyword(description = "The lower limit for the output signal.", exampleList = {"0.0 m"}) private final ValueInput outputLow; @Keyword(description = "The upper limit for the output signal.", exampleList = {"1.0 m"}) private final ValueInput outputHigh; private double lastUpdateTime; // The time at which the last update was performed private double lastError; // The previous value for the error signal private double integral; // The integral of the error signal private double derivative; // The derivative of the error signal { inputValue.setHidden(true); setPoint = new SampleInput("SetPoint", "Key Inputs", null); setPoint.setUnitType(UserSpecifiedUnit.class); setPoint.setEntity(this); setPoint.setRequired(true); this.addInput(setPoint); processVariable = new SampleInput("ProcessVariable", "Key Inputs", null); processVariable.setUnitType(UserSpecifiedUnit.class); processVariable.setEntity(this); processVariable.setRequired(true); this.addInput(processVariable); processVariableScale = new ValueInput("ProcessVariableScale", "Key Inputs", 1.0d); processVariableScale.setValidRange(0.0d, Double.POSITIVE_INFINITY); processVariableScale.setUnitType(UserSpecifiedUnit.class); this.addInput(processVariableScale); outputUnitType = new UnitTypeInput("OutputUnitType", "Key Inputs", UserSpecifiedUnit.class); outputUnitType.setRequired(true); this.addInput(outputUnitType); proportionalGain = new ValueInput("ProportionalGain", "Key Inputs", 1.0d); proportionalGain.setValidRange(0.0d, Double.POSITIVE_INFINITY); proportionalGain.setUnitType(UserSpecifiedUnit.class); this.addInput(proportionalGain); integralTime = new ValueInput("IntegralTime", "Key Inputs", 1.0d); integralTime.setValidRange(1.0e-10, Double.POSITIVE_INFINITY); integralTime.setUnitType(TimeUnit.class ); this.addInput(integralTime); derivativeTime = new ValueInput("DerivativeTime", "Key Inputs", 1.0d); derivativeTime.setValidRange(0.0d, Double.POSITIVE_INFINITY); derivativeTime.setUnitType(TimeUnit.class ); this.addInput(derivativeTime); outputLow = new ValueInput("OutputLow", "Key Inputs", Double.NEGATIVE_INFINITY); outputLow.setUnitType(UserSpecifiedUnit.class); this.addInput(outputLow); outputHigh = new ValueInput("OutputHigh", "Key Inputs", Double.POSITIVE_INFINITY); outputHigh.setUnitType(UserSpecifiedUnit.class); this.addInput(outputHigh); } public PIDController() {} @Override public void updateForInput( Input<?> in ) { super.updateForInput( in ); if (in == outputUnitType) { outUnitType = outputUnitType.getUnitType(); outputLow.setUnitType(outUnitType); outputHigh.setUnitType(outUnitType); proportionalGain.setUnitType(outUnitType); return; } } @Override protected void setUnitType(Class<? extends Unit> ut) { super.setUnitType(ut); setPoint.setUnitType(ut); processVariable.setUnitType(ut); processVariableScale.setUnitType(ut); } @Override public void earlyInit() { super.earlyInit(); lastError = 0.0; integral = 0.0; derivative = 0.0; lastUpdateTime = 0.0; } public double getError(double simTime) { return setPoint.getValue().getNextSample(simTime) - processVariable.getValue().getNextSample(simTime); } @Override protected double calculateValue(double simTime, double inputVal, double lastTime, double lastInputVal, double lastVal) { // Calculate the elapsed time double dt = simTime - lastTime; // Calculate the error signal double error = this.getError(simTime); // Calculate integral and differential terms double intgrl = integral + error*dt; double deriv = 0.0; if (dt > 0.0) deriv = (error - lastError)/dt; // Calculate the output value double val = error; val += intgrl / integralTime.getValue(); val += derivativeTime.getValue() * deriv; val *= proportionalGain.getValue() / processVariableScale.getValue(); // Condition the output value val = Math.max(val, outputLow.getValue()); val = Math.min(val, outputHigh.getValue()); return val; } @Override public void update(double simTime) { super.update(simTime); double dt = simTime - lastUpdateTime; double error = this.getError(simTime); integral += error * dt; lastError = error; lastUpdateTime = simTime; return; } @Output(name = "ProportionalValue", description = "The proportional component of the output value.", unitType = UserSpecifiedUnit.class, sequence = 0) public double getProportionalValue(double simTime) { return this.getError(simTime) * proportionalGain.getValue() / processVariableScale.getValue(); } @Output(name = "IntegralValue", description = "The integral component of the output value.", unitType = UserSpecifiedUnit.class, sequence = 1) public double getIntegralValue(double simTime) { return integral / integralTime.getValue() * proportionalGain.getValue() / processVariableScale.getValue(); } @Output(name = "DerivativeValue", description = "The derivative component of the output value.", unitType = UserSpecifiedUnit.class, sequence = 2) public double getDifferentialValue(double simTime) { return derivativeTime.getValue() * derivative * proportionalGain.getValue()/ processVariableScale.getValue(); } }