1function linemodel_save(model, filename)
2% LINEMODEL_SAVE Save a LINE model to JSON.
4% LINEMODEL_SAVE(MODEL, FILENAME) saves the model to the specified JSON
5% file, conforming to the line-model.schema.json specification.
8% model - Network, LayeredNetwork, Workflow, or Environment
object
9% filename - output file path (should end in .json)
12% model = Network(
'M/M/1');
13% source = Source(model,
'Source');
14% queue = Queue(model,
'Queue', SchedStrategy.FCFS);
15% sink = Sink(model,
'Sink');
16% oclass = OpenClass(model,
'Class1');
17% source.setArrival(oclass, Exp(1.0));
18% queue.setService(oclass, Exp(2.0));
19%
P = model.initRoutingMatrix();
20%
P{1}(1,2) = 1;
P{1}(2,3) = 1;
22% linemodel_save(model,
'mm1.json');
24% Copyright (c) 2012-2026, Imperial College London
27if isa(model,
'LayeredNetwork')
28 modelMap = layered2json(model);
29elseif isa(model, 'Workflow')
30 modelMap = workflow2json(model);
31elseif isa(model, 'Environment')
32 modelMap = environment2json(model);
34 modelMap = network2json(model);
37% Build the full document
40sb{end+1} =
' "format": "line-model",';
41sb{end+1} =
' "version": "1.0",';
42sb{end+1} = [
' "model": ', encode_value(modelMap, 2)];
44jsonStr = strjoin(sb, newline);
46fid = fopen(filename,
'w');
48 error(
'linemodel_save:fileOpen',
'Cannot open file: %s', filename);
50cleanupObj = onCleanup(@() fclose(fid));
51fprintf(fid,
'%s\n', jsonStr);
55% =========================================================================
56% Network serialization
57% =========================================================================
59function result = network2json(model)
60% Convert a Network to a containers.Map (preserves key ordering/commas)
61result = containers.Map();
62result(
'type') =
'Network';
63result(
'name') = model.getName();
65nodes = model.getNodes();
75 % Skip implicit ClassSwitch
nodes (
auto-created by link())
76 if isa(node, 'ClassSwitch') && isprop(node, 'autoAdded') && node.autoAdded
80 nj = containers.Map();
81 nj('name') = node.name;
82 nj('type') = node_type_str(node);
86 nj('scheduling') = 'INF';
87 elseif isa(node, 'Queue')
88 sched = node.schedStrategy;
90 nj('scheduling') = sched_id_to_str(sched);
95 if isa(node, 'Queue') && ~isa(node, 'Delay')
96 ns = node.numberOfServers;
97 if isfinite(ns) && ns > 1
103 if isa(node, 'Queue')
105 if ~isempty(c) && isfinite(c) && c > 0
110 % Per-class buffer capacity
111 if isa(node, 'Queue') && ~isempty(node.classCap)
112 ccMap = containers.Map();
115 if r <= length(node.classCap) && isfinite(node.classCap(r))
116 ccMap(jc.name) = node.classCap(r);
120 nj(
'classCap') = ccMap;
125 if isa(node,
'Queue') && ~isempty(node.dropRule)
126 drMap = containers.Map();
129 if r <= length(node.dropRule)
130 dr = node.dropRule(r);
131 drStr = droprule_to_str(dr);
133 drMap(jc.name) = drStr;
138 nj('dropRule') = drMap;
142 % Load-dependent scaling
143 if isa(node, 'Station') && ~isempty(node.lldScaling)
144 ldMap = containers.Map();
145 ldMap('type') = 'loadDependent';
146 ldMap('scaling') = node.lldScaling(:)';
147 nj('loadDependence') = ldMap;
150 % Service / arrival distributions
151 svc = containers.Map();
155 if isa(node,
'Source')
157 dist = node.getArrivalProcess(jc);
161 elseif isa(node, 'Queue') || isa(node, 'Delay')
163 dist = node.getService(jc);
168 if ~isempty(dist) && ~isa(dist, 'Disabled')
169 dj = dist2json(dist);
180 if isa(node, 'ClassSwitch')
181 csm = node.server.csMatrix;
183 csDict = containers.Map();
185 row = containers.Map();
187 if ri <= size(csm,1) && ci <= size(csm,2) && csm(ri,ci) ~= 0
188 row(
classes{ci}.name) = csm(ri,ci);
192 csDict(
classes{ri}.name) = row;
196 nj(
'classSwitchMatrix') = csDict;
202 if isa(node,
'Cache')
203 cc = containers.Map();
204 cc('items') = node.items.nitems;
205 ilc = node.itemLevelCap;
207 cc('capacity') = ilc;
209 cc('capacity') = ilc(:)';
211 cc('replacement') = repl_to_str(node.replacestrategy);
213 % Hit/miss class mappings
214 hc = full(node.server.hitClass);
215 mc = full(node.server.missClass);
216 if ~isempty(hc) && any(hc > 0)
217 hitMap = containers.Map();
218 for hi = 1:length(hc)
219 if hc(hi) > 0 && hi <= K && hc(hi) <= K
224 cc(
'hitClass') = hitMap;
227 if ~isempty(mc) && any(mc > 0)
228 missMap = containers.Map();
229 for mi = 1:length(mc)
230 if mc(mi) > 0 && mi <= K && mc(mi) <= K
235 cc(
'missClass') = missMap;
239 % Read popularity distributions (setRead)
240 if ~isempty(node.popularity)
241 popMap = containers.Map();
242 for pi = 1:size(node.popularity, 1)
243 for pj = 1:size(node.popularity, 2)
244 if pi <= size(node.popularity, 1) && pj <= size(node.popularity, 2) ...
245 && ~isempty(node.popularity{pi, pj})
246 popDist = node.popularity{pi, pj};
247 dj = dist2json(popDist);
248 if ~isempty(dj) && pj <= K
255 cc(
'popularity') = popMap;
264 if ~isempty(node.output) && isprop(node.output, 'tasksPerLink') && node.output.tasksPerLink > 1
265 nj('tasksPerLink') = node.output.tasksPerLink;
269 % Join paired fork and join strategy
271 if ~isempty(node.joinOf)
272 nj('forkNode') = node.joinOf.name;
274 % Serialize per-class join strategy if non-default
275 if ~isempty(node.input) && isprop(node.input, 'joinStrategy') && ~isempty(node.input.joinStrategy)
278 if r <= length(node.input.joinStrategy) && ~isempty(node.input.joinStrategy{r})
279 js = node.input.joinStrategy{r};
280 if js ~= JoinStrategy.STD
281 if js == JoinStrategy.PARTIAL
282 nj(
'joinStrategy') =
'PARTIAL';
288 if ~isempty(node.input) && isprop(node.input,
'joinRequired') && ~isempty(node.input.joinRequired)
291 if r <= length(node.input.joinRequired) && ~isempty(node.input.joinRequired{r})
292 jq = node.input.joinRequired{r};
294 nj(
'joinQuorum') = jq;
301 % DPS scheduling parameters (weights per
class)
302 if isa(node,
'Queue') && ~isa(node,
'Delay')
303 sched = node.schedStrategy;
304 if ~isempty(sched) && (sched == SchedStrategy.DPS || sched == SchedStrategy.GPS)
305 sp = containers.Map();
309 w = node.schedStrategyPar(r);
310 if ~isempty(w) && isfinite(w) && w > 0
317 nj(
'schedParams') = sp;
323 if isa(node,
'Transition')
325 nModes = node.getNumberOfModes();
326 allNodes = model.getNodes();
328 mj = containers.Map();
329 if mi <= length(node.modeNames) && ~isempty(node.modeNames{mi})
330 mj('name') = node.modeNames{mi};
332 mj(
'name') = sprintf(
'Mode%d', mi);
335 if mi <= length(node.distributions) && ~isempty(node.distributions{mi})
336 dj = dist2json(node.distributions{mi});
338 mj(
'distribution') = dj;
342 if mi <= length(node.timingStrategies)
343 if node.timingStrategies(mi) == TimingStrategy.TIMED
344 mj('timingStrategy') = 'TIMED';
346 mj('timingStrategy') = 'IMMEDIATE';
350 if mi <= length(node.numberOfServers) && node.numberOfServers(mi) > 1
351 mj('numServers') = node.numberOfServers(mi);
354 if mi <= length(node.firingPriorities) && node.firingPriorities(mi) > 0
355 mj('firingPriority') = node.firingPriorities(mi);
358 if mi <= length(node.firingWeights) && node.firingWeights(mi) ~= 1.0
359 mj('firingWeight') = node.firingWeights(mi);
361 % Enabling conditions
362 if mi <= length(node.enablingConditions)
363 ecMat = node.enablingConditions{mi};
365 for ni = 1:size(ecMat, 1)
366 for ci = 1:size(ecMat, 2)
368 ec = containers.Map();
369 ec('node') = allNodes{ni}.name;
370 ec(
'class') =
classes{ci}.name;
371 ec(
'count') = ecMat(ni, ci);
372 ecList{end+1} = ec; %#ok<AGROW>
377 mj(
'enablingConditions') = ecList;
380 % Inhibiting conditions
381 if mi <= length(node.inhibitingConditions)
382 icMat = node.inhibitingConditions{mi};
384 for ni = 1:size(icMat, 1)
385 for ci = 1:size(icMat, 2)
386 if isfinite(icMat(ni, ci))
387 ic = containers.Map();
388 ic('node') = allNodes{ni}.name;
389 ic(
'class') =
classes{ci}.name;
390 ic(
'count') = icMat(ni, ci);
391 icList{end+1} = ic; %#ok<AGROW>
396 mj(
'inhibitingConditions') = icList;
400 if mi <= length(node.firingOutcomes)
401 foMat = node.firingOutcomes{mi};
403 for ni = 1:size(foMat, 1)
404 for ci = 1:size(foMat, 2)
405 if foMat(ni, ci) ~= 0
406 fo = containers.Map();
407 fo('node') = allNodes{ni}.name;
408 fo(
'class') =
classes{ci}.name;
409 fo(
'count') = foMat(ni, ci);
410 foList{end+1} = fo; %#ok<AGROW>
415 mj(
'firingOutcomes') = foList;
418 modesJson{end+1} = mj; %#ok<AGROW>
420 if ~isempty(modesJson)
421 nj(
'modes') = modesJson;
425 % Initial state
for Place
nodes (token counts)
426 if isa(node,
'Place') && ~isempty(node.state)
427 nj('initialState') = node.state(:)';
430 nodesJson{end+1} = nj; %#ok<AGROW>
432result(
'nodes') = nodesJson;
438 cj = containers.Map();
439 cj(
'name') = jc.name;
440 if isa(jc,
'OpenSignal')
441 cj('type') = 'Signal';
442 cj('openOrClosed') = 'Open';
443 cj('signalType') = SignalType.toText(jc.signalType);
444 if ~isempty(jc.targetJobClass)
445 cj('targetClass') = jc.targetJobClass.name;
447 if ~isempty(jc.removalDistribution)
448 cj('removalDistribution') = dist2json(jc.removalDistribution);
450 if ~isempty(jc.removalPolicy) && jc.removalPolicy ~= RemovalPolicy.RANDOM
451 cj('removalPolicy') = RemovalPolicy.toText(jc.removalPolicy);
453 elseif isa(jc, 'ClosedSignal')
454 cj('type') = 'Signal';
455 cj('openOrClosed') = 'Closed';
456 cj('signalType') = SignalType.toText(jc.signalType);
457 if ~isempty(jc.refstat) && isprop(jc.refstat, 'name')
458 cj('refNode') = jc.refstat.name;
460 if ~isempty(jc.targetJobClass)
461 cj('targetClass') = jc.targetJobClass.name;
463 if ~isempty(jc.removalDistribution)
464 cj('removalDistribution') = dist2json(jc.removalDistribution);
466 if ~isempty(jc.removalPolicy) && jc.removalPolicy ~= RemovalPolicy.RANDOM
467 cj('removalPolicy') = RemovalPolicy.toText(jc.removalPolicy);
469 elseif isa(jc, 'OpenClass')
471 elseif isa(jc, 'ClosedClass')
472 cj('type') = 'Closed';
473 cj('population') = jc.population;
474 if ~isempty(jc.refstat) && isprop(jc.refstat, 'name')
475 cj('refNode') = jc.refstat.name;
481 cj('priority') = jc.priority;
483 if isprop(jc, 'deadline') && isfinite(jc.deadline)
484 cj('deadline') = jc.deadline;
486 classesJson{end+1} = cj; %#ok<AGROW>
488result(
'classes') = classesJson;
491routingMap = containers.Map();
493 sn = model.getStruct();
494 % Prefer rtorig (original
P matrix before ClassSwitch expansion)
495 if ~isempty(sn) && isfield(sn,
'rtorig') && iscell(sn.rtorig) && ~isempty(sn.rtorig) && ~isempty(sn.rtorig{1,1})
497 M_orig = size(P_orig{1,1}, 1);
498 % Identify
explicit ClassSwitch node indices (not
auto-added)
499 nodes = model.getNodes();
500 explicit_cs =
false(1, M_orig);
501 for ii = 1:min(M_orig, length(
nodes))
502 if isa(
nodes{ii},
'ClassSwitch') && ~
nodes{ii}.autoAdded
503 explicit_cs(ii) =
true;
506 % For
explicit CS sources, compute same-
class routing:
507 % P_same(s,ii,jj) = sum_r P_orig{r,s}(ii,jj)
508 % This avoids saving cross-
class entries that would cause
509 %
double-switching on load.
510 cs_same = zeros(K, M_orig, M_orig);
518 if issparse(Prs); Prs = full(Prs); end
519 total = total + Prs(ii, jj);
521 cs_same(s, ii, jj) = total;
528 fromTo = containers.Map();
535 % For
explicit CS, use same-
class routing only
538 val = cs_same(s, ii, jj);
540 ni = sn.nodenames{ii};
541 njn = sn.nodenames{jj};
543 fromTo(ni) = containers.Map();
551 % Skip cross-
class entries from explicit CS
557 ni = sn.nodenames{ii};
558 njn = sn.nodenames{jj};
560 fromTo(ni) = containers.Map();
570 routingMap(key) = fromTo;
574 elseif ~isempty(sn) && isfield(sn,
'rtnodes') && ~isempty(sn.rtnodes)
575 % Fallback to rtnodes
if rtorig not available
580 fromTo = containers.Map();
583 val = rt((ii-1)*K+r, (jj-1)*K+s);
585 ni = sn.nodenames{ii};
586 njn = sn.nodenames{jj};
588 fromTo(ni) = containers.Map();
598 routingMap(key) = fromTo;
604 % If
struct not available, routing stays empty
607routing = containers.Map();
608routing(
'type') =
'matrix';
609routing(
'matrix') = routingMap;
610result(
'routing') = routing;
612% --- Routing Strategies ---
614 sn2 = model.getStruct();
615 if ~isempty(sn2) && isfield(sn2,
'routing') && ~isempty(sn2.routing)
616 routingStrategies = containers.Map();
617 stratNames = containers.Map('KeyType','int32','ValueType','
char');
618 stratNames(int32(RoutingStrategy.RAND)) = 'RAND';
619 stratNames(int32(RoutingStrategy.RROBIN)) = 'RROBIN';
620 stratNames(int32(RoutingStrategy.WRROBIN)) = 'WRROBIN';
621 stratNames(int32(RoutingStrategy.JSQ)) = 'JSQ';
622 stratNames(int32(RoutingStrategy.KCHOICES)) = 'KCHOICES';
623 stratNames(int32(RoutingStrategy.FIRING)) = 'FIRING';
624 stratNames(int32(RoutingStrategy.RL)) = 'RL';
625 stratNames(int32(RoutingStrategy.DISABLED)) = 'DISABLED';
627 nodeStrats = containers.Map();
629 routVal = int32(sn2.routing(i, r));
630 if routVal ~= int32(RoutingStrategy.PROB) && routVal ~= int32(RoutingStrategy.RAND) && stratNames.isKey(routVal)
631 nodeStrats(
classes{r}.name) = stratNames(routVal);
634 if nodeStrats.Count > 0
635 routingStrategies(sn2.nodenames{i}) = nodeStrats;
638 if routingStrategies.Count > 0
639 result(
'routingStrategies') = routingStrategies;
642 % Save WRROBIN weights
643 routingWeights = containers.Map();
645 stationIdx = sn2.nodeToStation(i);
646 if stationIdx < 1;
continue; end
647 nodeObj2 =
nodes{stationIdx};
648 nodeClassWeights = containers.Map();
650 if int32(sn2.routing(i, r)) == int32(RoutingStrategy.WRROBIN)
651 os = nodeObj2.output.outputStrategy;
654 % osEntry = {className, stratName, forwardLinks}
655 if length(osEntry) >= 3
656 fwdLinks = osEntry{3};
657 destWeights = containers.Map();
658 for fi = 1:length(fwdLinks)
660 % link = {destNode, weight}
661 if iscell(link) && length(link) >= 2 && isa(link{1},
'Node')
662 destWeights(link{1}.name) = link{2};
665 if destWeights.Count > 0
666 nodeClassWeights(
classes{r}.name) = destWeights;
672 if nodeClassWeights.Count > 0
673 routingWeights(sn2.nodenames{i}) = nodeClassWeights;
676 if routingWeights.Count > 0
677 result(
'routingWeights') = routingWeights;
683% --- Switchover Times ---
685 nodesCellTmp = result(
'nodes');
688 if isa(nodeObj,
'Queue')
690 if isprop(nodeObj,
'switchoverTimes') && ~isempty(nodeObj.switchoverTimes)
699 if ~isempty(dist) && ~isa(dist,
'Disabled')
700 so = containers.Map();
703 so(
'distribution') = dist2json(dist);
711 for nj_idx = 1:length(nodesCellTmp)
712 nj = nodesCellTmp{nj_idx};
713 if strcmp(nj(
'name'), nodeObj.name)
714 nj('switchoverTimes') = soTimes;
715 nodesCellTmp{nj_idx} = nj;
722 result(
'nodes') = nodesCellTmp;
726% --- Heterogeneous Server Types ---
728 nodesCellTmp = result(
'nodes');
731 if isa(nodeObj,
'Queue') && nodeObj.isHeterogeneous()
733 for ti = 1:length(nodeObj.serverTypes)
734 st = nodeObj.serverTypes{ti};
735 stj = containers.Map();
736 stj(
'name') = st.name;
737 stj(
'count') = st.numOfServers;
740 for cci = 1:length(st.compatibleClasses)
741 ccNames{end+1} = st.compatibleClasses{cci}.name; %#ok<AGROW>
744 stj(
'compatibleClasses') = ccNames;
746 % Per-
class service distributions
747 svcMap = containers.Map();
750 dist = nodeObj.getHeteroService(jc, st);
751 if ~isempty(dist) && ~isa(dist,
'Disabled')
752 svcMap(jc.name) = dist2json(dist);
756 stj('service') = svcMap;
758 stArr{end+1} = stj; %#ok<AGROW>
761 for nj_idx = 1:length(nodesCellTmp)
762 nj = nodesCellTmp{nj_idx};
763 if strcmp(nj(
'name'), nodeObj.name)
764 nj('serverTypes') = stArr;
766 policy = nodeObj.getHeteroSchedPolicy();
767 if ~isempty(policy) && policy ~= HeteroSchedPolicy.ORDER
768 nj('heteroSchedPolicy') = HeteroSchedPolicy.toText(policy);
770 nodesCellTmp{nj_idx} = nj;
777 result(
'nodes') = nodesCellTmp;
781% --- Balking, Retrial, Patience ---
783 nodesCellTmp = result(
'nodes');
784 for nj_idx = 1:length(nodesCellTmp)
785 nj = nodesCellTmp{nj_idx};
786 nodeName = nj(
'name');
787 nodeObj = node_map(nodeName);
788 if ~isa(nodeObj,
'Queue'),
continue; end
790 balkJson = containers.Map();
793 if nodeObj.hasBalking(jc)
794 [strategy, thresholds] = nodeObj.getBalking(jc);
795 bjc = containers.Map();
797 case BalkingStrategy.QUEUE_LENGTH, bjc(
'strategy') =
'QUEUE_LENGTH';
798 case BalkingStrategy.EXPECTED_WAIT, bjc(
'strategy') =
'EXPECTED_WAIT';
799 case BalkingStrategy.COMBINED, bjc(
'strategy') =
'COMBINED';
802 for ti = 1:length(thresholds)
804 tjson = containers.Map();
805 tjson(
'minJobs') = th{1};
807 tjson(
'maxJobs') = -1;
809 tjson(
'maxJobs') = th{2};
811 tjson(
'probability') = th{3};
812 thArr{end+1} = tjson;
814 bjc(
'thresholds') = thArr;
815 balkJson(jc.name) = bjc;
818 if balkJson.Count > 0
819 nj(
'balking') = balkJson;
822 retrialJson = containers.Map();
825 if nodeObj.hasRetrial(jc)
826 [delayDist, maxAttempts] = nodeObj.getRetrial(jc);
827 rjc = containers.Map();
828 rjc(
'delay') = dist2json(delayDist);
829 rjc(
'maxAttempts') = maxAttempts;
830 retrialJson(jc.name) = rjc;
833 if retrialJson.Count > 0
834 nj(
'retrial') = retrialJson;
837 patienceJson = containers.Map();
840 patDist = nodeObj.getPatience(jc);
841 if ~isempty(patDist) && ~isa(patDist,
'Disabled')
842 pjc = containers.Map();
843 pjc('distribution') = dist2json(patDist);
844 impType = nodeObj.getImpatienceType(jc);
846 pjc('impatienceType') = ImpatienceType.toText(impType);
848 patienceJson(jc.name) = pjc;
851 if patienceJson.Count > 0
852 nj('patience') = patienceJson;
854 nodesCellTmp{nj_idx} = nj;
856 result(
'nodes') = nodesCellTmp;
860% --- Finite Capacity Regions ---
862 regions = model.regions;
865 for ri = 1:length(regions)
867 rj = containers.Map();
868 rj(
'name') = reg.name;
869 % Stations with per-
class details
871 for ni = 1:length(reg.nodes)
872 sj = containers.Map();
873 sj('node') = reg.
nodes{ni}.name;
875 if isprop(reg, 'classMaxJobs') && ~isempty(reg.classMaxJobs)
876 ccMap = containers.Map();
879 if r <= length(reg.classMaxJobs) && isfinite(reg.classMaxJobs(r))
880 ccMap(jc.name) = reg.classMaxJobs(r);
884 sj(
'classCap') = ccMap;
887 % Per-
class classWeight
888 if isprop(reg, 'classWeight') && ~isempty(reg.classWeight)
889 cwMap = containers.Map();
892 if r <= length(reg.classWeight) && reg.classWeight(r) ~= 1
893 cwMap(jc.name) = reg.classWeight(r);
897 sj(
'classWeight') = cwMap;
900 % Per-
class classSize
901 if isprop(reg, 'classSize') && ~isempty(reg.classSize)
902 csMap = containers.Map();
905 if r <= length(reg.classSize) && reg.classSize(r) ~= 1
906 csMap(jc.name) = reg.classSize(r);
910 sj(
'classSize') = csMap;
913 stationsJson{end+1} = sj; %#ok<AGROW>
915 rj(
'stations') = stationsJson;
916 if isprop(reg,
'globalMaxJobs') && isfinite(reg.globalMaxJobs)
917 rj('globalMaxJobs') = reg.globalMaxJobs;
919 if isprop(reg, 'globalMaxMemory') && isfinite(reg.globalMaxMemory)
920 rj('globalMaxMemory') = reg.globalMaxMemory;
922 % Per-class classMaxJobs at region level
923 if isprop(reg, 'classMaxJobs') && ~isempty(reg.classMaxJobs)
924 cmjMap = containers.Map();
927 if r <= length(reg.classMaxJobs) && isfinite(reg.classMaxJobs(r))
928 cmjMap(jc.name) = reg.classMaxJobs(r);
932 rj(
'classMaxJobs') = cmjMap;
936 if isprop(reg,
'dropRule') && ~isempty(reg.dropRule)
937 drMap = containers.Map();
940 if r <= length(reg.dropRule)
941 drStr = droprule_to_str(reg.dropRule(r));
943 drMap(jc.name) = drStr;
948 rj('dropRule') = drMap;
951 fcrArray{end+1} = rj; %#ok<AGROW>
953 if ~isempty(fcrArray)
954 result(
'finiteCapacityRegions') = fcrArray;
962% =========================================================================
963% LayeredNetwork serialization
964% =========================================================================
966function result = layered2json(model)
967result = containers.Map();
968result(
'type') =
'LayeredNetwork';
969result(
'name') = model.getName();
974for i = 1:length(hosts)
976 pj = containers.Map();
978 mult = h.multiplicity;
979 if isfinite(mult) && mult > 1
980 pj(
'multiplicity') = mult;
982 schedStr = h.scheduling;
983 if ~isempty(schedStr) && ~strcmpi(schedStr,
'inf')
984 pj('scheduling') = upper(schedStr);
987 if q > 0 && q ~= 0.001
992 pj('speedFactor') = sf;
994 repl = h.replication;
996 pj('replication') = repl;
998 procsJson{end+1} = pj; %#ok<AGROW>
1000result(
'processors') = procsJson;
1004tasksList = model.tasks;
1005for i = 1:length(tasksList)
1007 tj = containers.Map();
1008 tj(
'name') = t.name;
1009 if ~isempty(t.parent)
1010 tj('processor') = t.parent.name;
1012 mult = t.multiplicity;
1013 if isfinite(mult) && mult > 1
1014 tj('multiplicity') = mult;
1016 schedStr = t.scheduling;
1017 if ~isempty(schedStr)
1018 tj('scheduling') = upper(schedStr);
1021 ttMean = t.thinkTimeMean;
1022 if ~isempty(ttMean) && ttMean > GlobalConstants.FineTol
1023 if ~isempty(t.thinkTime) && isa(t.thinkTime, 'Distribution')
1024 tj('thinkTime') = dist2json(t.thinkTime);
1026 params = containers.Map();
1027 params('lambda') = 1.0 / ttMean;
1028 dj = containers.Map();
1030 dj('params') = params;
1031 tj('thinkTime') = dj;
1035 if ~isempty(t.fanInSource) && ischar(t.fanInSource) && ~isempty(t.fanInSource)
1036 fi = containers.Map();
1037 fi(t.fanInSource) = t.fanInValue;
1041 if ~isempty(t.fanOutDest)
1042 fo = containers.Map();
1043 for fi_idx = 1:length(t.fanOutDest)
1044 fo(t.fanOutDest{fi_idx}) = t.fanOutValue(fi_idx);
1048 repl = t.replication;
1050 tj(
'replication') = repl;
1052 % FunctionTask detection
1053 if isa(t,
'FunctionTask')
1054 tj('taskType') = 'FunctionTask';
1056 % Setup time / delay-off time (on any Task)
1057 if ~isempty(t.setupTime) && isa(t.setupTime, 'Distribution')
1058 stMean = t.setupTimeMean;
1059 if stMean > GlobalConstants.FineTol
1060 tj('setupTime') = dist2json(t.setupTime);
1063 if ~isempty(t.delayOffTime) && isa(t.delayOffTime, 'Distribution')
1064 dotMean = t.delayOffTimeMean;
1065 if dotMean > GlobalConstants.FineTol
1066 tj('delayOffTime') = dist2json(t.delayOffTime);
1069 % CacheTask detection
1070 if isa(t, 'CacheTask')
1071 tj('taskType') = 'CacheTask';
1072 tj('totalItems') = t.items;
1073 tj('cacheCapacity') = t.itemLevelCap;
1074 rs = t.replacestrategy;
1075 rsNameMap = containers.Map({ReplacementStrategy.RR, ReplacementStrategy.FIFO, ...
1076 ReplacementStrategy.SFIFO, ReplacementStrategy.LRU}, ...
1077 {
'RR',
'FIFO',
'SFIFO',
'LRU'});
1078 if rsNameMap.isKey(rs)
1079 tj(
'replacementStrategy') = rsNameMap(rs);
1081 tj(
'replacementStrategy') =
'FIFO';
1084 tasksJson{end+1} = tj; %#ok<AGROW>
1086result(
'tasks') = tasksJson;
1090entriesList = model.entries;
1091for i = 1:length(entriesList)
1093 ej = containers.Map();
1094 ej(
'name') = e.name;
1095 if ~isempty(e.parent)
1096 ej('task') = e.parent.name;
1098 % Entry arrival distribution
1099 if ~isempty(e.arrival) && isa(e.arrival, 'Distribution')
1100 ej('arrival') = dist2json(e.arrival);
1102 % ItemEntry detection
1103 if isa(e, 'ItemEntry')
1104 ej('entryType') = 'ItemEntry';
1105 ej('totalItems') = e.cardinality;
1106 if ~isempty(e.popularity)
1107 if isa(e.popularity, 'Distribution')
1108 ej('accessProb') = dist2json(e.popularity);
1112 entriesJson{end+1} = ej; %#ok<AGROW>
1114result(
'entries') = entriesJson;
1116% --- Build reply
map: activityName -> entryName ---
1117replyMap = containers.Map();
1118for i = 1:length(entriesList)
1120 if ~isempty(e.replyActivity)
1121 for j = 1:length(e.replyActivity)
1122 replyMap(e.replyActivity{j}) = e.name;
1129actsList = model.activities;
1130for i = 1:length(actsList)
1132 aj = containers.Map();
1133 aj(
'name') = a.name;
1134 if ~isempty(a.parent)
1135 if isa(a.parent, 'Task') || isa(a.parent, 'Entry')
1136 aj('task') = a.parent.name;
1137 elseif ischar(a.parent) || isstring(a.parent)
1138 aj('task') =
char(a.parent);
1139 elseif ischar(a.parentName) && ~isempty(a.parentName)
1140 aj('task') = a.parentName;
1142 elseif ~isempty(a.parentName) && ischar(a.parentName)
1143 aj('task') = a.parentName;
1146 if ~isempty(a.hostDemand) && isa(a.hostDemand, 'Distribution')
1147 if ~isa(a.hostDemand, 'Immediate')
1148 aj('hostDemand') = dist2json(a.hostDemand);
1150 elseif ~isempty(a.hostDemandMean) && a.hostDemandMean > GlobalConstants.FineTol
1151 params = containers.Map();
1152 params('lambda') = 1.0 / a.hostDemandMean;
1153 dj = containers.Map();
1155 dj('params') = params;
1156 aj('hostDemand') = dj;
1159 if ~isempty(a.boundToEntry)
1160 aj('boundTo') = a.boundToEntry;
1163 if replyMap.isKey(a.name)
1164 aj('repliesTo') = replyMap(a.name);
1167 if ~isempty(a.syncCallDests)
1169 for j = 1:length(a.syncCallDests)
1170 sc = containers.Map();
1171 sc('entry') = a.syncCallDests{j};
1172 if j <= length(a.syncCallMeans) && a.syncCallMeans(j) ~= 1.0
1173 sc(
'mean') = a.syncCallMeans(j);
1175 synchCalls{end+1} = sc; %#ok<AGROW>
1177 aj(
'synchCalls') = synchCalls;
1180 if ~isempty(a.asyncCallDests)
1182 for j = 1:length(a.asyncCallDests)
1183 ac = containers.Map();
1184 ac('entry') = a.asyncCallDests{j};
1185 if j <= length(a.asyncCallMeans) && a.asyncCallMeans(j) ~= 1.0
1186 ac(
'mean') = a.asyncCallMeans(j);
1188 asynchCalls{end+1} = ac; %#ok<AGROW>
1190 aj(
'asynchCalls') = asynchCalls;
1192 actsJson{end+1} = aj; %#ok<AGROW>
1194result(
'activities') = actsJson;
1196% --- Precedences ---
1198for i = 1:length(tasksList)
1200 precs = t.precedences;
1201 if isempty(precs),
continue; end
1202 for j = 1:length(precs)
1204 pj = containers.Map();
1205 pj(
'task') = t.name;
1207 preType = p.preType;
1208 postType = p.postType;
1210 % Determine JSON precedence type and collect activity names
1211 if preType == ActivityPrecedenceType.PRE_SEQ && postType == ActivityPrecedenceType.POST_SEQ
1212 pj(
'type') =
'Serial';
1213 pj(
'activities') = [p.preActs, p.postActs];
1214 elseif preType == ActivityPrecedenceType.PRE_SEQ && postType == ActivityPrecedenceType.POST_AND
1215 pj(
'type') =
'AndFork';
1216 pj(
'activities') = [p.preActs, p.postActs];
1217 elseif preType == ActivityPrecedenceType.PRE_AND && postType == ActivityPrecedenceType.POST_SEQ
1218 pj(
'type') =
'AndJoin';
1219 pj(
'activities') = [p.preActs, p.postActs];
1220 elseif preType == ActivityPrecedenceType.PRE_SEQ && postType == ActivityPrecedenceType.POST_OR
1221 pj(
'type') =
'OrFork';
1222 pj(
'activities') = [p.preActs, p.postActs];
1223 if ~isempty(p.postParams)
1224 pj('probabilities') = p.postParams(:)';
1226 elseif preType == ActivityPrecedenceType.PRE_OR && postType == ActivityPrecedenceType.POST_SEQ
1227 pj('type') = 'OrJoin';
1228 pj('activities') = [p.preActs, p.postActs];
1229 elseif postType == ActivityPrecedenceType.POST_LOOP
1230 pj('type') = 'Loop';
1231 % For Loop, preActs
is the trigger, postActs
is the loop body
1232 pj('activities') = p.postActs;
1233 if ~isempty(p.preActs)
1234 pj('preActivity') = p.preActs{1};
1236 if ~isempty(p.postParams)
1237 pj('loopCount') = p.postParams(1);
1239 elseif preType == ActivityPrecedenceType.PRE_SEQ && postType == ActivityPrecedenceType.POST_CACHE
1240 pj('type') = 'CacheAccess';
1241 pj('activities') = [p.preActs, p.postActs];
1245 precsJson{end+1} = pj; %#ok<AGROW>
1248if ~isempty(precsJson)
1249 result(
'precedences') = precsJson;
1254% =========================================================================
1255% Workflow serialization
1256% =========================================================================
1258function result = workflow2json(model)
1259% Convert a Workflow to a containers.Map
for JSON output.
1260result = containers.Map();
1261result(
'type') =
'Workflow';
1262result(
'name') = model.getName();
1266acts = model.activities;
1267for i = 1:length(acts)
1269 aj = containers.Map();
1270 aj(
'name') = act.name;
1271 if ~isempty(act.hostDemand) && isa(act.hostDemand,
'Distribution')
1272 dj = dist2json(act.hostDemand);
1274 aj('hostDemand') = dj;
1277 actsJson{end+1} = aj; %#ok<AGROW>
1279result(
'activities') = actsJson;
1281% --- Precedences ---
1283precs = model.precedences;
1284for i = 1:length(precs)
1286 pj = containers.Map();
1290 for a = 1:length(p.preActs)
1291 preActsJson{end+1} = p.preActs{a}; %#ok<AGROW>
1293 pj(
'preActs') = preActsJson;
1297 for a = 1:length(p.postActs)
1298 postActsJson{end+1} = p.postActs{a}; %#ok<AGROW>
1300 pj(
'postActs') = postActsJson;
1302 % preType / postType - convert numeric IDs to JAR-compatible strings
1303 pj(
'preType') = prectype_to_str(p.preType);
1304 pj(
'postType') = prectype_to_str(p.postType);
1307 if ~isempty(p.preParams)
1308 pj('preParams') = p.preParams(:)';
1312 if ~isempty(p.postParams)
1313 pj('postParams') = p.postParams(:)';
1316 precsJson{end+1} = pj; %#ok<AGROW>
1318result(
'precedences') = precsJson;
1322% =========================================================================
1323% Environment serialization
1324% =========================================================================
1326function result = environment2json(model)
1327% Convert an Environment to a containers.Map
for JSON output.
1328result = containers.Map();
1329result(
'type') =
'Environment';
1330result(
'name') = model.getName();
1332E = height(model.envGraph.Nodes);
1333result(
'numStages') = E;
1338 sj = containers.Map();
1339 sj(
'name') = model.envGraph.Nodes.Name{e};
1340 % Serialize the stage
's Network model
1341 if e <= length(model.ensemble) && ~isempty(model.ensemble{e})
1342 sj('model
') = network2json(model.ensemble{e});
1344 stagesJson{end+1} = sj; %#ok<AGROW>
1346result('stages
') = stagesJson;
1348% --- Transitions ---
1352 if ~isempty(model.env) && e <= size(model.env, 1) && h <= size(model.env, 2) ...
1353 && ~isempty(model.env{e,h}) && ~isa(model.env{e,h}, 'Disabled
')
1354 tj = containers.Map();
1355 tj('from
') = e - 1; % Convert to 0-indexed for JAR compatibility
1356 tj('to
') = h - 1; % Convert to 0-indexed for JAR compatibility
1357 dj = dist2json(model.env{e,h});
1359 tj('distribution
') = dj;
1360 transJson{end+1} = tj; %#ok<AGROW>
1365result('transitions
') = transJson;
1369% =========================================================================
1370% Distribution serialization
1371% =========================================================================
1373function d = dist2json(dist)
1374% Convert a Distribution to a containers.Map for JSON output.
1379d = containers.Map();
1380cn = builtin('class', dist);
1383 d('type
') = 'Disabled
';
1385 d('type
') = 'Immediate
';
1388 params = containers.Map();
1389 params('lambda
') = dist.getParam(1).paramValue;
1390 d('params
') = params;
1393 params = containers.Map();
1394 params('value
') = dist.getParam(1).paramValue;
1395 d('params
') = params;
1397 d('type
') = 'Erlang
';
1398 params = containers.Map();
1399 params('lambda
') = dist.getParam(1).paramValue;
1400 params('k
') = dist.getParam(2).paramValue;
1401 d('params
') = params;
1403 d('type
') = 'HyperExp
';
1404 params = containers.Map();
1405 p = dist.getParam(1).paramValue;
1406 l1 = dist.getParam(2).paramValue;
1407 l2 = dist.getParam(3).paramValue;
1409 params('p
') = [p, 1-p];
1410 params('lambda
') = [l1, l2];
1412 params('p
') = p(:)';
1413 params(
'lambda') = [l1, l2];
1415 d(
'params') = params;
1417 d(
'type') =
'Gamma';
1418 params = containers.Map();
1419 params(
'alpha') = dist.getParam(1).paramValue;
1420 params(
'beta') = dist.getParam(2).paramValue;
1421 d(
'params') = params;
1423 d(
'type') =
'Lognormal';
1424 params = containers.Map();
1425 params(
'mu') = dist.getParam(1).paramValue;
1426 params(
'sigma') = dist.getParam(2).paramValue;
1427 d(
'params') = params;
1429 d(
'type') =
'Uniform';
1430 params = containers.Map();
1431 params(
'a') = dist.getParam(1).paramValue;
1432 params(
'b') = dist.getParam(2).paramValue;
1433 d(
'params') = params;
1436 params = containers.Map();
1437 params(
's') = dist.getParam(3).paramValue;
1438 params(
'n') = dist.getParam(4).paramValue;
1439 d(
'params') = params;
1441 d(
'type') =
'Pareto';
1442 params = containers.Map();
1443 params(
'alpha') = dist.getParam(1).paramValue;
1444 params(
'scale') = dist.getParam(2).paramValue;
1445 d(
'params') = params;
1447 d(
'type') =
'Weibull';
1448 params = containers.Map();
1449 params(
'alpha') = dist.getParam(1).paramValue;
1450 params(
'beta') = dist.getParam(2).paramValue;
1451 d(
'params') = params;
1453 d(
'type') =
'Normal';
1454 params = containers.Map();
1455 params(
'mu') = dist.getParam(1).paramValue;
1456 params(
'sigma') = dist.getParam(2).paramValue;
1457 d(
'params') = params;
1459 d(
'type') =
'Geometric';
1460 params = containers.Map();
1461 params(
'p') = dist.getParam(1).paramValue;
1462 d(
'params') = params;
1464 d(
'type') =
'Binomial';
1465 params = containers.Map();
1466 params(
'n') = dist.getParam(1).paramValue;
1467 params(
'p') = dist.getParam(2).paramValue;
1468 d(
'params') = params;
1470 d(
'type') =
'Poisson';
1471 params = containers.Map();
1472 params(
'lambda') = dist.getParam(1).paramValue;
1473 d(
'params') = params;
1475 d(
'type') =
'Bernoulli';
1476 params = containers.Map();
1477 params(
'p') = dist.getParam(1).paramValue;
1478 d(
'params') = params;
1479 case 'DiscreteUniform'
1480 d(
'type') =
'DiscreteUniform';
1481 params = containers.Map();
1482 params(
'min') = dist.getParam(1).paramValue;
1483 params(
'max') = dist.getParam(2).paramValue;
1484 d(
'params') = params;
1485 case {
'Coxian',
'Cox2'}
1486 d(
'type') =
'Coxian';
1487 params = containers.Map();
1488 params(
'mu') = dist.getMu()
';
1489 params('phi
') = dist.getPhi()';
1490 d(
'params') = params;
1493 ph = containers.Map();
1494 alpha = dist.getInitProb();
1495 T = dist.getSubgenerator();
1497 ph(
'alpha') = alpha(:)
';
1499 ph('alpha
') = alpha;
1505 mapSpec = containers.Map();
1506 mapSpec('D0
') = dist.getParam(1).paramValue;
1507 mapSpec('D1
') = dist.getParam(2).paramValue;
1510 d('type
') = 'MMPP2
';
1511 params = containers.Map();
1512 params('lambda0
') = dist.getParam(1).paramValue;
1513 params('lambda1
') = dist.getParam(2).paramValue;
1514 params('sigma0
') = dist.getParam(3).paramValue;
1515 params('sigma1
') = dist.getParam(4).paramValue;
1516 d('params
') = params;
1517 case 'DiscreteSampler
'
1518 d('type
') = 'DiscreteSampler
';
1519 params = containers.Map();
1520 params('p
') = dist.getParam(1).paramValue(:)';
1521 params(
'x') = dist.getParam(2).paramValue(:)
';
1522 d('params
') = params;
1524 d('type
') = 'Replayer
';
1525 params = containers.Map();
1526 params('fileName
') = dist.getParam(1).paramValue;
1528 params('mean
') = dist.getMean();
1531 d('params
') = params;
1532 % Save APH fit as fallback
1534 aphDist = dist.fitAPH();
1535 if ~isempty(aphDist) && isa(aphDist, 'Distribution
')
1536 ph = containers.Map();
1537 alpha = aphDist.getParam(1).paramValue;
1538 T = aphDist.getParam(2).paramValue;
1540 ph('alpha
') = alpha(:)';
1542 ph(
'alpha') = alpha;
1550 d(
'type') =
'Prior';
1552 for ai = 1:dist.getNumAlternatives()
1553 altDist = dist.getAlternative(ai);
1554 altJson = dist2json(altDist);
1555 if ~isempty(altJson)
1556 alts{end+1} = altJson; %#ok<AGROW>
1559 d(
'distributions') = alts;
1560 d(
'probabilities') = dist.probabilities(:)
';
1562 % Fallback: fit from mean
1566 fit = containers.Map();
1567 fit('method
') = 'fitMean
';
1577% =========================================================================
1579% =========================================================================
1581function s = encode_value(val, indent)
1582% Recursively encode a MATLAB value to JSON string.
1583if nargin < 2, indent = 0; end
1584pad = repmat(' ', 1, indent);
1585pad2 = repmat(' ', 1, indent + 2);
1587if isa(val, 'containers.Map
')
1592 parts = cell(1, length(ks));
1593 for i = 1:length(ks)
1596 parts{i} = sprintf('%s
"%s": %s
', pad2, json_escape(k), encode_value(v, indent + 2));
1598 s = sprintf('{\n%s\n%s}
', strjoin(parts, sprintf(',\n
')), pad);
1600elseif ischar(val) || isstring(val)
1601 s = sprintf('"%s"', json_escape(char(val)));
1602elseif islogical(val) && isscalar(val)
1603 if val, s = 'true'; else, s = 'false'; end
1604elseif isnumeric(val) && isscalar(val)
1608 if val > 0, s = '"Infinity"'; else, s = '"-Infinity"'; end
1609 elseif val == floor(val) && abs(val) < 1e15
1610 s = sprintf('%d
', val);
1612 s = sprintf('%.15g
', val);
1614elseif isnumeric(val) && isvector(val) && ~isscalar(val)
1615 parts = cell(1, length(val));
1616 for i = 1:length(val)
1617 parts{i} = encode_value(val(i), 0);
1619 s = ['[
', strjoin(parts, ',
'), ']
'];
1620elseif isnumeric(val) && ismatrix(val) && ~isvector(val)
1621 rows = cell(1, size(val, 1));
1622 for i = 1:size(val, 1)
1623 rows{i} = encode_value(val(i,:), 0);
1625 s = ['[
', strjoin(rows, ',
'), ']
'];
1630 parts = cell(1, length(val));
1631 for i = 1:length(val)
1632 parts{i} = sprintf('%s%s
', pad2, encode_value(val{i}, indent + 2));
1634 s = sprintf('[\n%s\n%s]
', strjoin(parts, sprintf(',\n
')), pad);
1636elseif isstruct(val) && isscalar(val)
1637 fnames = fieldnames(val);
1641 parts = cell(1, length(fnames));
1642 for i = 1:length(fnames)
1645 parts{i} = sprintf('%s
"%s": %s
', pad2, json_escape(fn), encode_value(fv, indent + 2));
1647 s = sprintf('{\n%s\n%s}
', strjoin(parts, sprintf(',\n
')), pad);
1654function s = json_escape(str)
1655% Escape special characters for JSON strings.
1656s = strrep(str, '\
', '\\
');
1657s = strrep(s, '"', '\"');
1658s = strrep(s, sprintf('\n'), '\n');
1659s = strrep(s, sprintf('\r'), '\r');
1660s = strrep(s, sprintf('\t'), '\t');
1664% =========================================================================
1666% =========================================================================
1668function s = node_type_str(node)
1669% Get the JSON node type string for a node object.
1670if isa(node, 'Source'), s = 'Source';
1671elseif isa(node, 'Sink'), s = 'Sink';
1672elseif isa(node, 'Delay'), s = 'Delay';
1673elseif isa(node, 'Cache'), s = 'Cache';
1674elseif isa(node, 'Place'), s = 'Place';
1675elseif isa(node, 'Transition'), s = 'Transition';
1676elseif isa(node, 'Queue'), s = 'Queue';
1677elseif isa(node, 'Fork'), s = 'Fork';
1678elseif isa(node, 'Join'), s = 'Join';
1679elseif isa(node, 'Router'), s = 'Router';
1680elseif isa(node, 'ClassSwitch'), s = 'ClassSwitch';
1685function s = sched_id_to_str(id)
1686% Map SchedStrategy numeric ID to schema-compatible string.
1687if id == SchedStrategy.INF, s = 'INF';
1688elseif id == SchedStrategy.FCFS, s = 'FCFS';
1689elseif id == SchedStrategy.LCFS, s = 'LCFS';
1690elseif id == SchedStrategy.LCFSPR, s = 'LCFSPR';
1691elseif id == SchedStrategy.PS, s = 'PS';
1692elseif id == SchedStrategy.DPS, s = 'DPS';
1693elseif id == SchedStrategy.GPS, s = 'GPS';
1694elseif id == SchedStrategy.SIRO, s = 'SIRO';
1695elseif id == SchedStrategy.SJF, s = 'SJF';
1696elseif id == SchedStrategy.LJF, s = 'LJF';
1697elseif id == SchedStrategy.SEPT, s = 'SEPT';
1698elseif id == SchedStrategy.LEPT, s = 'LEPT';
1699elseif id == SchedStrategy.HOL, s = 'HOL';
1700elseif id == SchedStrategy.FORK, s = 'FORK';
1701elseif id == SchedStrategy.EXT, s = 'EXT';
1702elseif id == SchedStrategy.REF, s = 'REF';
1703elseif id == SchedStrategy.POLLING, s = 'POLLING';
1704elseif id == SchedStrategy.PSPRIO, s = 'PSPRIO';
1705elseif id == SchedStrategy.DPSPRIO, s = 'DPSPRIO';
1706elseif id == SchedStrategy.GPSPRIO, s = 'GPSPRIO';
1707elseif id == SchedStrategy.FCFSPRIO, s = 'FCFSPRIO';
1712function s = repl_to_str(id)
1713% Map ReplacementStrategy numeric ID to schema string.
1714if id == ReplacementStrategy.LRU, s = 'LRU';
1715elseif id == ReplacementStrategy.FIFO, s = 'FIFO';
1716elseif id == ReplacementStrategy.RR, s = 'RR';
1717elseif id == ReplacementStrategy.SFIFO, s = 'SFIFO';
1722function s = droprule_to_str(id)
1723% Map DropStrategy numeric ID to schema-compatible string.
1724if id == DropStrategy.DROP, s = 'drop';
1725elseif id == DropStrategy.WAITQ, s = 'waitingQueue';
1726elseif id == DropStrategy.BAS, s = 'blockingAfterService';
1727elseif id == DropStrategy.RETRIAL, s = 'retrial';
1728elseif id == DropStrategy.RETRIAL_WITH_LIMIT, s = 'retrialWithLimit';
1733function s = prectype_to_str(id)
1734% Map ActivityPrecedenceType numeric ID to JAR-compatible string.
1735if id == ActivityPrecedenceType.PRE_SEQ, s = 'pre';
1736elseif id == ActivityPrecedenceType.PRE_AND, s = 'pre-AND';
1737elseif id == ActivityPrecedenceType.PRE_OR, s = 'pre-OR';
1738elseif id == ActivityPrecedenceType.POST_SEQ, s = 'post';
1739elseif id == ActivityPrecedenceType.POST_AND, s = 'post-AND';
1740elseif id == ActivityPrecedenceType.POST_OR, s = 'post-OR';
1741elseif id == ActivityPrecedenceType.POST_LOOP, s = 'post-LOOP';
1742elseif id == ActivityPrecedenceType.POST_CACHE, s = 'post-CACHE';