1classdef Transition < StatefulNode
2 % A
class for a stochastic Petri net transition
4 % Copyright (c) 2012-2026, Imperial College London
23 function self = Transition(model,name)
24 % TRANSITION(MODEL, NAME)
26 self@StatefulNode(name);
27 if model.isMatlabNative()
28 classes = model.getClasses();
29 self.input = Enabling(classes);
30 self.output = Firing(classes);
31 self.cap = Inf; % Compatible with other
nodes
34 self.model.addNode(self);
36 self.server = Timing();
38 self.enablingConditions = [];
39 self.inhibitingConditions = [];
41 self.numberOfServers = [];
42 self.timingStrategies = [];
43 self.distributions = {};
44 self.firingPriorities = [];
45 self.firingWeights = [];
46 self.firingOutcomes = [];
47 self.firingRateDependence = {};
48 elseif model.isJavaNative()
50 self.obj = jline.lang.nodes.Transition(model.obj, name);
51 self.index = model.obj.getNodeIndex(self.obj);
55 function self = init(self)
58 nclasses = length(self.model.getClasses());
59 nnodes = length(self.model.getNodes());
61 self.enablingConditions = cell(self.getNumberOfModes);
62 self.inhibitingConditions = cell(self.getNumberOfModes);
63 self.firingOutcomes = cell(self.getNumberOfModes);
64 for m=1:self.getNumberOfModes
65 self.enablingConditions{m} = zeros(nnodes,nclasses);
66 self.inhibitingConditions{m} = Inf*ones(nnodes,nclasses); % Inf = no inhibitor arc (never inhibits); zeros would inhibit at >=0 tokens, i.e. always
67 self.firingOutcomes{m} = zeros(nnodes,nclasses);
69 self.numberOfServers = ones(1,self.getNumberOfModes);
70 self.timingStrategies = repmat(TimingStrategy.TIMED,1,self.getNumberOfModes);
71 self.firingWeights = ones(1,self.getNumberOfModes);
72 self.firingPriorities = ones(1,self.getNumberOfModes);
73 self.distributions = cell(1, self.getNumberOfModes);
74 self.distributions(:) = {Exp(1)};
75 % Empty == unit multiplier (marking-independent firing rate).
76 self.firingRateDependence = cell(1, self.getNumberOfModes);
79 function mode = addMode(self, modeName)
80 nclasses = length(self.model.getClasses());
81 nnodes = length(self.model.getNodes());
82 self.modeNames{end+1} = modeName;
83 self.enablingConditions{end+1} = zeros(nnodes,nclasses);
84 self.inhibitingConditions{end+1} = Inf*ones(nnodes,nclasses);
85 self.numberOfServers(end+1) = 1;
86 self.timingStrategies(end+1) = TimingStrategy.TIMED;
87 self.firingWeights(end+1) = 1.0;
88 self.firingPriorities(end+1) = 1.0;
89 self.distributions{end+1} = Exp(1);
90 self.firingOutcomes{end+1} = zeros(nnodes,nclasses);
91 self.firingRateDependence{end+1} = [];
92 mode = Mode(self,modeName);
93 self.modes{end+1} = mode;
96 function self = setEnablingConditions(self, mode,
class, inputNode, enablingCondition)
97 % SELF = SETENABLINGCONDITIONS(MODE, CLASS, NODE, ENABLINGCONDITIONS)
99 if isa(inputNode,
'Place')
100 inputNode = self.model.getNodeIndex(inputNode.name);
101 self.enablingConditions{mode}(inputNode,
class) = enablingCondition;
103 error(
'Node must be a Place node.');
107 function self = setInhibitingConditions(self, mode,
class, inputNode, inhibitingCondition)
108 % SELF = SETINHIBITINGCONDITIONS(MODE, CLASS, NODE, INHIBITINGCONDITIONS)
110 if isa(inputNode,
'Place')
111 inputNode = self.model.getNodeIndex(inputNode.name);
112 self.inhibitingConditions{mode}(inputNode,
class) = inhibitingCondition;
114 error(
'Node must be a Place node.');
118 function self = setModeNames(self, mode, modeName)
119 % SELF = SETMODENAMES(MODE, MODENAMES)
121 self.modeNames{mode} = modeName;
124 function self = setNumberOfServers(self, mode, numberOfServers)
125 % SELF = SETNUMBEROFSERVERS(MODE, NUMOFSERVERS)
127 self.numberOfServers(mode) = numberOfServers;
130 function self = setTimingStrategy(self, mode, timingStrategy)
131 % SELF = SETTIMINGSTRATEGY(MODE, TIMINGSTRATEGY)
133 self.timingStrategies(mode) = timingStrategy;
136 function self = setFiringPriorities(self, mode, firingPriority)
137 % SELF = SETFIRINGPRIORITIES(MODE, FIRINGPRIORITIES)
139 self.firingPriorities(mode) = firingPriority;
142 function self = setFiringWeights(self, mode, firingWeight)
143 % SELF = SETFIRINGWEIGHTS(MODE, FIRINGWEIGHTS)
145 self.firingWeights(mode) = firingWeight;
148 function self = setFiringOutcome(self, mode,
class, node, firingOutcome)
149 % SELF = SETFIRINGOUTCOMES(MODE, NODE, CLASS,FIRINGOUTCOME)
150 ind = self.model.getNodeIndex(node);
151 self.firingOutcomes{mode}(ind,
class) = firingOutcome;
154 function self = setDistribution(self, mode, distribution)
155 self.distributions{mode} = distribution;
158 function self = setFiringRateDependence(self, mode, g)
159 % SELF = SETFIRINGRATEDEPENDENCE(MODE, G)
160 % Marking-dependent firing-rate multiplier
for a timed mode.
161 % G
is a function handle g(m) of the input-place marking matrix m
162 % (shaped like enablingConditions{mode}, nnodes x nclasses),
163 % returning a positive scalar. The effective firing rate of an
164 % enabled
binding becomes rate_base(mode)*g(m). Empty G restores
165 % the unit (marking-independent) multiplier.
167 % Exact only
for memoryless firing: the mode must be TIMED (not
168 % IMMEDIATE, which uses firingWeights) and exponentially
169 % distributed. This mirrors the PS/FCFS-only restriction on
170 % station load/
class dependence.
171 if ~isempty(g) && ~isa(g,
'function_handle')
172 line_error(mfilename,'Firing-rate dependence must be a function handle g(m) or empty.');
175 if self.timingStrategies(mode) == TimingStrategy.IMMEDIATE
176 line_error(mfilename,'Firing-rate dependence
is not supported for IMMEDIATE modes; use setFiringWeights for immediate transitions.');
178 if ~isa(self.distributions{mode},
'Exp')
179 line_error(mfilename,
'Firing-rate dependence is supported only for exponentially distributed (memoryless) firing; set an Exp distribution on the mode first.');
182 self.firingRateDependence{mode} = g;
185 function nmodes = getNumberOfModes(self)
186 nmodes = length(self.modeNames);
189 function modes = getModes(self)
193 function [
map,mu,phi] = getServiceRates(self)
194 % [PH,MU,PHI] = GETPHSERVICERATES()
196 map = cell(1,self.getNumberOfModes);
197 mu = cell(1,self.getNumberOfModes);
198 phi = cell(1,self.getNumberOfModes);
200 for r=1:self.getNumberOfModes
201 switch class(self.distributions{r})
202 case {
'Replayer',
'Trace'}
203 aph = self.distributions{r}.fitAPH;
204 map{r} =
aph.getProcess();
207 case {
'Exp',
'Coxian',
'Erlang',
'HyperExp',
'Markovian',
'APH',
'MAP'}
208 map{r} = self.distributions{r}.getProcess();
209 mu{r} = self.distributions{r}.getMu;
210 phi{r} = self.distributions{r}.getPhi;
212 map{r} = self.distributions{r}.getProcess();
213 mu{r} = self.distributions{r}.getMu;
214 phi{r} = self.distributions{r}.getPhi;
215 case {
'Det',
'Uniform',
'Pareto',
'Gamma',
'Weibull',
'Lognormal',
'Expolynomial'}
216 map{r} = self.distributions{r}.getProcess();
217 mu{r} = [self.distributions{r}.getRate];
220 map{r} = {[NaN],[NaN]};