/* * 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.ProcessFlow; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import com.jaamsim.Graphics.DisplayEntity; import com.jaamsim.ProbabilityDistributions.Distribution; import com.jaamsim.Samples.SampleConstant; import com.jaamsim.Samples.SampleInput; import com.jaamsim.basicsim.Entity; import com.jaamsim.datatypes.DoubleVector; import com.jaamsim.input.BooleanInput; import com.jaamsim.input.InputErrorException; import com.jaamsim.input.Keyword; import com.jaamsim.input.Output; import com.jaamsim.units.DimensionlessUnit; import com.jaamsim.units.TimeUnit; public class Resource extends DisplayEntity { @Keyword(description = "The number of equivalent resource units that are available.\n" + "The input can be a constant value, a time series, or an expression.", exampleList = {"3", "TimeSeries1", "this.attrib1"}) private final SampleInput capacity; @Keyword(description = "If TRUE, the next entity to seize the resource will be chosen " + "strictly on the basis of priority and waiting time. If this entity " + "is unable to seize the resource because of other restrictions such as " + "an OperatingThreshold input or the unavailability of other resources " + "it needs to seize at the same time, then other entities with lower " + "priority or shorter waiting time will NOT be allowed to seize the " + "resource. If FALSE, the entities will be tested in the same order of " + "priority and waiting time, but the first entity that is able to seize " + "the resource will be allowed to do so.", exampleList = {"TRUE"}) private final BooleanInput strictOrder; private int unitsInUse; // number of resource units that are being used at present private ArrayList<Seize> seizeList; // Seize objects that require this resource // Statistics protected double timeOfLastUpdate; // time at which the statistics were last updated protected double startOfStatisticsCollection; // time at which statistics collection was started protected int minUnitsInUse; // minimum observed number of units in use protected int maxUnitsInUse; // maximum observed number of units in use protected double unitSeconds; // total time that units have been used protected double squaredUnitSeconds; // total time for the square of the number of units in use protected int unitsSeized; // number of units that have been seized protected int unitsReleased; // number of units that have been released protected DoubleVector unitsInUseDist; // entry at position n is the total time that n units have been in use { attributeDefinitionList.setHidden(false); capacity = new SampleInput("Capacity", "Key Inputs", new SampleConstant(1.0)); capacity.setUnitType(DimensionlessUnit.class); capacity.setEntity(this); capacity.setValidRange(0, Double.POSITIVE_INFINITY); this.addInput(capacity); strictOrder = new BooleanInput("StrictOrder", "Key Inputs", false); this.addInput(strictOrder); } public Resource() { unitsInUseDist = new DoubleVector(); seizeList = new ArrayList<>(); } @Override public void validate() { boolean found = false; for (Seize ent : Entity.getClonesOfIterator(Seize.class)) { if( ent.requiresResource(this) ) found = true; } if (!found) throw new InputErrorException( "At least one Seize object must use this resource." ); if( capacity.getValue() instanceof Distribution ) throw new InputErrorException( "The Capacity keyword cannot accept a probability distribution."); } @Override public void earlyInit() { super.earlyInit(); unitsInUse = 0; // Clear statistics startOfStatisticsCollection = 0.0; timeOfLastUpdate = 0.0; minUnitsInUse = 0; maxUnitsInUse = 0; unitSeconds = 0.0; squaredUnitSeconds = 0.0; unitsSeized = 0; unitsReleased = 0; unitsInUseDist.clear(); // Prepare a list of the Seize objects that use this resource seizeList.clear(); for (Seize ent : Entity.getClonesOfIterator(Seize.class)) { if( ent.requiresResource(this) ) seizeList.add(ent); } } /** * Return the number of units that are available for use at the present time * @return */ public int getAvailableUnits() { return (int) capacity.getValue().getNextSample(this.getSimTime()) - unitsInUse; } /** * Seize the given number of units from the resource. * @param n = number of units to seize */ public void seize(int n) { this.updateStatistics(unitsInUse, unitsInUse+n); unitsInUse += n; unitsSeized += n; } /** * Release the given number of units back to the resource. * @param n = number of units to release */ public void release(int m) { int n = Math.min(m, unitsInUse); this.updateStatistics(unitsInUse, unitsInUse-n); unitsInUse -= n; unitsReleased += n; } /** * Notify all the Seize object that the number of available units of this Resource has increased. */ public void notifySeizeObjects() { // Is there capacity available? int cap = (int) capacity.getValue().getNextSample(this.getSimTime()); if (cap <= unitsInUse) return; // Prepare a sorted list of the Seize objects that have a waiting entity ArrayList<Seize> list = new ArrayList<>(seizeList.size()); for (Seize s : seizeList) { if (!s.getQueue().isEmpty()) { list.add(s); } } Collections.sort(list, seizeCompare); // Find the Seize object(s) that can use the released units while (true) { // Find the first Seize object that can seize the Resource Seize selection = null; for (Seize s : list) { if (s.isReadyToStart()) { selection = s; break; } // In StrictOrder mode, only the highest priority/longest waiting time entity is // eligible to seize the Resource if (strictOrder.getValue()) return; } // If none of the Seize objects can seize the Resource, then we are done if (selection == null) return; // Seize the resource selection.startProcessing(getSimTime()); // Is additional capacity available? if (cap <= unitsInUse) return; // If the selected Seize object has no more entities, remove it from the list if (selection.getQueue().isEmpty()) { list.remove(selection); } // If it does have more entities, re-sort the list to account for the next entity else { Collections.sort(list, seizeCompare); } } } /** * Sorts the Seize objects by the priority and waiting time of the first entity in each queue */ private static class SeizeCompare implements Comparator<Seize> { @Override public int compare(Seize s1, Seize s2) { // Chose the Seize object whose Queue contains the highest priority entity // (lowest numerical value, i.e. 1 is higher priority than 2) Queue que1 = s1.getQueue(); Queue que2 = s2.getQueue(); int ret = Integer.compare(que1.getFirstPriority(), que2.getFirstPriority()); // If the priorities are the same, choose the one with the longest waiting time if (ret == 0) { return Double.compare(que2.getQueueTime(), que1.getQueueTime()); } return ret; } } private SeizeCompare seizeCompare = new SeizeCompare(); // ******************************************************************************************************* // STATISTICS // ******************************************************************************************************* @Override public void clearStatistics() { super.clearStatistics(); double simTime = this.getSimTime(); startOfStatisticsCollection = simTime; timeOfLastUpdate = simTime; minUnitsInUse = unitsInUse; maxUnitsInUse = unitsInUse; unitSeconds = 0.0; squaredUnitSeconds = 0.0; unitsSeized = 0; unitsReleased = 0; for (int i=0; i<unitsInUseDist.size(); i++) { unitsInUseDist.set(i, 0.0d); } } public void updateStatistics( int oldValue, int newValue) { minUnitsInUse = Math.min(newValue, minUnitsInUse); maxUnitsInUse = Math.max(newValue, maxUnitsInUse); // Add the necessary number of additional bins to the queue length distribution int n = newValue + 1 - unitsInUseDist.size(); for( int i=0; i<n; i++ ) { unitsInUseDist.add(0.0); } double simTime = this.getSimTime(); double dt = simTime - timeOfLastUpdate; if( dt > 0.0 ) { unitSeconds += dt * oldValue; squaredUnitSeconds += dt * oldValue * oldValue; unitsInUseDist.addAt(dt,oldValue); // add dt to the entry at index queueSize timeOfLastUpdate = simTime; } } // ****************************************************************************************************** // OUTPUT METHODS // ****************************************************************************************************** @Output(name = "UnitsSeized", description = "The total number of resource units that have been seized.", unitType = DimensionlessUnit.class, reportable = true, sequence = 0) public int getUnitsSeized(double simTime) { return unitsSeized; } @Output(name = "UnitsReleased", description = "The total number of resource units that have been released.", unitType = DimensionlessUnit.class, reportable = true, sequence = 1) public int getUnitsReleased(double simTime) { return unitsReleased; } @Output(name = "UnitsInUse", description = "The present number of resource units that are in use.", unitType = DimensionlessUnit.class, sequence = 2) public int getUnitsInUse(double simTime) { return unitsInUse; } @Output(name = "UnitsInUseAverage", description = "The average number of resource units that are in use.", unitType = DimensionlessUnit.class, reportable = true, sequence = 3) public double getUnitsInUseAverage(double simTime) { double dt = simTime - timeOfLastUpdate; double totalTime = simTime - startOfStatisticsCollection; if( totalTime > 0.0 ) { return (unitSeconds + dt*unitsInUse)/totalTime; } return 0.0; } @Output(name = "UnitsInUseStandardDeviation", description = "The standard deviation of the number of resource units that are in use.", unitType = DimensionlessUnit.class, reportable = true, sequence = 4) public double getUnitsInUseStandardDeviation(double simTime) { double dt = simTime - timeOfLastUpdate; double mean = this.getUnitsInUseAverage(simTime); double totalTime = simTime - startOfStatisticsCollection; if( totalTime > 0.0 ) { return Math.sqrt( (squaredUnitSeconds + dt*unitsInUse*unitsInUse)/totalTime - mean*mean ); } return 0.0; } @Output(name = "UnitsInUseMinimum", description = "The minimum number of resource units that are in use.", unitType = DimensionlessUnit.class, reportable = true, sequence = 5) public int getUnitsInUseMinimum(double simTime) { return minUnitsInUse; } @Output(name = "UnitsInUseMaximum", description = "The maximum number of resource units that are in use.", unitType = DimensionlessUnit.class, reportable = true, sequence = 6) public int getUnitsInUseMaximum(double simTime) { // A unit that is seized and released immediately // does not count as a non-zero maximum in use if( maxUnitsInUse == 1 && unitsInUseDist.get(1) == 0.0 ) return 0; return maxUnitsInUse; } @Output(name = "UnitsInUseTimes", description = "The total time that the number of resource units in use was 0, 1, 2, etc.", unitType = TimeUnit.class, reportable = true, sequence = 7) public DoubleVector getUnitsInUseDistribution(double simTime) { DoubleVector ret = new DoubleVector(unitsInUseDist); double dt = simTime - timeOfLastUpdate; if(ret.size() == 0) ret.add(0.0); ret.addAt(dt, unitsInUse); // adds dt to the entry at index unitsInUse return ret; } }