337 const std::string&
name()
const {
return name_; }
343 const std::vector<WFNArc>&
arcs()
const {
return arcs_; }
364 check_new_name(
name);
365 places_.emplace_back(
new WFNPlace(
name, places_.size(), initial_marking));
366 place_map_[
name] = places_.size() - 1;
367 return *places_.back();
378 double servers = std::numeric_limits<double>::infinity(),
379 double priority = 1.0,
double weight = 1.0) {
389 double servers = std::numeric_limits<double>::infinity(),
390 double priority = 1.0,
double weight = 1.0) {
391 Dist d = distribution;
396 "Timed transition \"" +
name +
397 "\" cannot have an Immediate distribution; declare it TimingStrategy.IMMEDIATE.");
401 if (!(weight > 0) || !(priority > 0))
403 check_new_name(
name);
404 transitions_.emplace_back(
405 new WFNTransition(
name, transitions_.size(), d, timing, servers, priority, weight));
406 transition_map_[
name] = transitions_.size() - 1;
407 return *transitions_.back();
420 if ((src.place_ && tgt.place_) || (src.transition_ && tgt.transition_))
422 "Arcs must connect a place to a transition or vice versa, not two places or two "
424 if (!(weight >= 1) || weight != std::round(weight))
425 throw InputError(
ErrorKind(
"Model"),
"Arc " + element_name(src) +
" -> " + element_name(tgt) +
426 " has weight " + detail::num_g(weight) +
427 "; arc weights must be positive integers.");
429 arcs_.push_back(
WFNArc(
true, src.place_, tgt.transition_, weight));
431 arcs_.push_back(
WFNArc(
false, tgt.place_, src.transition_, weight));
454 return set_resource_impl(
transition,
name, &servers, &scheduling);
465 void set_arrival_process(
const std::string& type,
double rate) { set_arrival_typed(type, &rate,
nullptr); }
471 if (!(k >= 0) || k != std::round(k) || std::isinf(k))
472 throw InputError(
ErrorKind(
"Argument"),
"The number of closed cases must be a non-negative integer.");
473 closed_cases_ =
static_cast<long>(k);
478 if (!(cap >= 1))
throw InputError(
ErrorKind(
"Argument"),
"The state cap must be a positive number.");
484 const auto it = place_map_.find(
name);
486 return *places_[it->second];
491 const auto it = transition_map_.find(
name);
492 if (it == transition_map_.end())
494 return *transitions_[it->second];
499 const Inc inc = incidence();
500 Matrix<double> Pre(inc.nP, inc.nT, 0.0), Post(inc.nP, inc.nT, 0.0);
501 for (std::size_t p = 0; p < inc.nP; ++p)
502 for (std::size_t t = 0; t < inc.nT; ++t) {
503 Pre(p, t) =
static_cast<double>(inc.pre[p][t]);
504 Post(p, t) =
static_cast<double>(inc.post[p][t]);
506 return std::make_pair(Pre, Post);
523 if (transitions_.empty())
525 const Inc inc = incidence();
526 const std::size_t nP = inc.nP, nT = inc.nT;
527 for (std::size_t t = 0; t < nT; ++t) {
528 bool in =
false, out =
false;
529 for (std::size_t p = 0; p < nP; ++p) {
530 in = in || inc.pre[p][t] != 0;
531 out = out || inc.post[p][t] != 0;
535 "\" has no input place, so it is not on a path from the "
539 "\" has no output place, so it is not on a path to the "
542 std::vector<std::size_t> src_cand, snk_cand;
543 for (std::size_t p = 0; p < nP; ++p) {
544 long in = 0, out = 0;
545 for (std::size_t t = 0; t < nT; ++t) {
546 in += inc.post[p][t];
547 out += inc.pre[p][t];
549 if (in == 0) src_cand.push_back(p);
550 if (out == 0) snk_cand.push_back(p);
552 if (src_cand.size() != 1)
554 "A WF-net has exactly one source place (a place without incoming arcs); found " +
555 std::to_string(src_cand.size()) +
": " + place_list(src_cand) +
".");
556 if (snk_cand.size() != 1)
558 "A WF-net has exactly one sink place (a place without outgoing arcs); found " +
559 std::to_string(snk_cand.size()) +
": " + place_list(snk_cand) +
".");
560 const std::size_t s = src_cand[0], o = snk_cand[0];
561 if (source_ >= 0 &&
static_cast<std::size_t
>(source_) != s)
563 "\" was declared the source, but \"" + places_[s]->
name() +
564 "\" is the only place without incoming arcs.");
565 if (sink_ >= 0 &&
static_cast<std::size_t
>(sink_) != o)
567 "\" was declared the sink, but \"" + places_[o]->
name() +
568 "\" is the only place without outgoing arcs.");
570 const std::size_t n = nP + nT;
571 std::vector<std::vector<std::size_t>> fwd_adj(n), bwd_adj(n);
572 for (std::size_t p = 0; p < nP; ++p)
573 for (std::size_t t = 0; t < nT; ++t) {
574 if (inc.pre[p][t] > 0) {
575 fwd_adj[p].push_back(nP + t);
576 bwd_adj[nP + t].push_back(p);
578 if (inc.post[p][t] > 0) {
579 fwd_adj[nP + t].push_back(p);
580 bwd_adj[p].push_back(nP + t);
583 const std::vector<bool> fwd = bfs(fwd_adj, std::vector<std::size_t>(1, s));
584 const std::vector<bool> bwd = bfs(bwd_adj, std::vector<std::size_t>(1, o));
586 for (std::size_t v = 0; v < n; ++v) {
587 if (fwd[v] && bwd[v])
continue;
588 if (!off.empty()) off +=
", ";
589 off +=
"\"" + (v < nP ? places_[v]->name() : transitions_[v - nP]->
name()) +
"\"";
592 throw InputError(
ErrorKind(
"Model"),
"Not a WF-net: " + off +
" not on a path from source \"" +
593 places_[s]->
name() +
"\" to sink \"" + places_[o]->
name() +
595 source_ =
static_cast<long>(s);
596 sink_ =
static_cast<long>(o);
615 if (!(k >= 1) || k != std::round(k) || std::isinf(k))
618 const Inc inc = incidence();
619 const std::size_t nP = inc.nP, nT = inc.nT;
620 const long kk =
static_cast<long>(k);
621 Marking m0(nP, 0), mf(nP, 0);
629 Reach rg = untimed_reachability(inc, m0,
state_cap);
631 if (rg.status == Reach::UNBOUNDED) {
635 report.
witnesses.push_back(
"unbounded: " + marking_string(rg.witness_cover) +
636 " strictly covers its ancestor " + marking_string(rg.witness_ancestor));
637 report.
sound =
false;
640 if (rg.status == Reach::CAP) {
644 " markings reached before the reachability graph was complete");
645 report.
sound =
false;
649 const std::size_t n = rg.M.size();
650 std::vector<bool> is_final(n,
false);
651 bool any_final =
false;
652 for (std::size_t i = 0; i < n; ++i) {
653 is_final[i] = rg.M[i] == mf;
654 any_final = any_final || is_final[i];
656 std::vector<std::size_t> outdeg(n, 0);
657 std::vector<std::vector<std::size_t>> radj(n);
658 std::vector<bool> fired(nT,
false);
659 for (
const ReachEdge& e : rg.E) {
661 radj[e.to].push_back(e.from);
664 for (std::size_t i = 0; i < n; ++i)
665 if (outdeg[i] == 0 && !is_final[i]) {
667 report.
witnesses.push_back(
"deadlock: " + marking_string(rg.M[i]) +
" enables no transition");
669 for (std::size_t i = 0; i < n; ++i)
670 if (rg.M[i][sink_] >= kk && !is_final[i]) {
672 report.
witnesses.push_back(
"improper completion: " + marking_string(rg.M[i]));
674 std::vector<bool> coreach(n,
false);
676 std::vector<std::size_t> finals;
677 for (std::size_t i = 0; i < n; ++i)
678 if (is_final[i]) finals.push_back(i);
679 coreach = bfs(radj, finals);
681 for (std::size_t i = 0; i < n; ++i)
685 report.
witnesses.push_back(
"no option to complete: from " + marking_string(rg.M[i]) +
686 " the final marking is unreachable");
688 for (std::size_t t = 0; t < nT; ++t)
691 report.
witnesses.push_back(
"dead transition: \"" + transitions_[t]->
name() +
"\" never fires");
702 return std::to_string(report.
k) +
"-sound, " + std::to_string(report.
state_space_size) +
703 " reachable markings";
705 for (std::size_t i = 0; i < report.
witnesses.size(); ++i) {
709 return str.empty() ? report.
verdict : str;
729 const Chain ch = single_case_chain();
730 const std::size_t n = ch.Q.
rows();
732 for (
double a : ch.alpha) mass += a;
735 "Every case of \"" + name_ +
736 "\" completes in zero time, so its cycle time has no phase-type law.");
738 for (std::size_t i = 0; i < n; ++i)
739 for (std::size_t j = 0; j < n; ++j) negQ(i, j) = -ch.Q(i, j);
741 const detail::Factor fT(detail::transpose(negQ));
742 const std::vector<double> Nrow = fT.solve(ch.alpha);
743 std::vector<double> m(3, 0.0), v = ch.alpha;
745 for (
int j = 1; j <= 3; ++j) {
749 for (
double x : v) s += x;
753 an.
ph = PH(ch.alpha, ch.Q);
761 const std::size_t nT = transitions_.
size(), nP = places_.size();
762 for (std::size_t t = 0; t < nT; ++t) {
763 double f = ch.firings0[t];
764 for (std::size_t s = 0; s < n; ++s) f += Nrow[s] * (ch.Rfire(s, t) + ch.Gfire(s, t));
768 for (std::size_t p = 0; p < nP; ++p) {
770 for (std::size_t s = 0; s < n; ++s) x += Nrow[s] * static_cast<double>(ch.Mtang[s][p]);
788 for (std::size_t r = 0; r < resources_.size(); ++r) {
790 for (std::size_t t = 0; t < transitions_.size(); ++t)
791 if (transitions_[t]->resource_ ==
static_cast<long>(r))
792 d += f[t] * transitions_[t]->distribution().mean;
794 loads.
Servers.push_back(resources_[r]->servers());
797 for (std::size_t t = 0; t < transitions_.size(); ++t) {
799 if (
tr.is_timed() && std::isfinite(
tr.servers())) {
805 for (std::size_t j = 0; j < loads.
Resource.size(); ++j) {
814 if (!has_arrival_)
throw InputError(
ErrorKind(
"Argument"),
"Give LAMBDA or set an arrival process.");
834 detail::TreeNode tree;
836 return reduce_to_tree(tree, reason);
852 detail::TreeNode tree;
854 if (!reduce_to_tree(tree, reason))
856 "toWorkflow requires a block-structured WF-net: " + reason);
859 wf_branch(tree, st, h, tl);
886 throw InputError(
ErrorKind(
"Argument"),
"Target must be specified as 'forkjoin' or 'spn'.");
887 if (closed_cases_ == 0 && !has_arrival_)
888 throw InputError(
ErrorKind(
"Model"),
"Set an arrival process (open analysis) or setClosedCases(k) "
889 "(closed analysis) before compiling to a Network.");
890 if (closed_cases_ > 0 && has_arrival_)
892 "Both an arrival process and closed cases are set; clear one of them "
893 "(setArrivalProcess('none') or setClosedCases(0)).");
896 std::string tg = target;
897 std::transform(tg.begin(), tg.end(), tg.begin(), [](
unsigned char c) { return std::tolower(c); });
898 if (tg ==
"forkjoin")
return compile_to_fork_join();
899 if (tg ==
"spn")
return compile_to_spn();
910 if (!compiled_ || &solver.
model() != compiled_->network.get())
911 throw InputError(
ErrorKind(
"Argument"),
"The solver is not built on the network returned by the last "
912 "toNetwork call of this WorkflowNet.");
913 const Compiled& c = *compiled_;
916 metrics.
target = c.target;
917 metrics.
mode = c.mode;
918 if (c.target ==
"forkjoin") {
920 if (c.mode ==
"open") {
922 std::size_t jidx = 0;
923 metrics.
cycle_time = fj_tree_time(c.tree, c, tab, jidx);
926 X = detail::table_cell(tab, tab.
Tput, c.restart,
"Case");
927 metrics.
wip =
static_cast<double>(c.k);
928 metrics.
cycle_time =
static_cast<double>(c.k) / X;
933 metrics.
tasks.
RespT.push_back(detail::table_cell(tab, tab.
RespT, task.station, task.cls));
941 metrics.
wip = detail::table_sum(tab, tab.
QLen, c.counter);
942 const double X = c.mode ==
"open" ? c.lambda : detail::table_sum(tab, tab.
Tput, c.counter);
945 for (
const std::string& p : c.places) {
947 metrics.
places.
QLen.push_back(detail::table_sum(tab, tab.
QLen, p));
955 for (std::size_t b : res.
busy) busy += detail::table_sum(tab, tab.
QLen, c.busy[b].place);
975 std::shared_ptr<Network> model = build_plain_network(1);
998 if (
sn.classes.size() != 1)
1000 "A WF-net has uncoloured tokens; the Network must have exactly one class.");
1002 const std::size_t nn =
sn.nodes.size();
1003 for (std::size_t i = 0; i < nn; ++i) {
1007 const auto it =
sn.initmarking.find(i + 1);
1008 if (it !=
sn.initmarking.end())
1009 for (
double x : it->second) st += x;
1014 "; a WF-net holds only places and transitions.");
1017 for (std::size_t i = 0; i < nn; ++i) {
1019 const std::string& tname =
sn.nodes[i].name;
1020 const auto it =
sn.transparam.find(i + 1);
1021 const std::size_t nmodes = it ==
sn.transparam.end() ? 0 : it->second.nmodes;
1024 std::to_string(nmodes) +
1025 " modes; a WF-net transition has exactly one "
1031 for (std::size_t r = 0; r < Inh.
rows(); ++r)
1032 for (std::size_t c = 0; c < Inh.
cols(); ++c)
1033 if (std::isfinite(Inh(r, c)) && Inh(r, c) > 0)
1035 "\" has inhibitor arcs, which WF-nets do "
1039 "Transition \"" + tname +
1040 "\" has a marking-dependent firing rate, which WF-net analysis does not "
1045 for (std::size_t j = 0; j < Ein.
rows() && j < nn; ++j)
1046 if (Ein(j, 0) > 0) net.
add_arc(
sn.nodes[j].name, tname, Ein(j, 0));
1047 for (std::size_t j = 0; j < Eout.
rows() && j < nn; ++j) {
1048 if (!(Eout(j, 0) > 0))
continue;
1051 sn.nodes[j].name +
"\", which is not a place.");
1052 net.
add_arc(tname,
sn.nodes[j].name, Eout(j, 0));
1060 typedef std::vector<long> Marking;
1063 std::size_t nP = 0, nT = 0;
1064 std::vector<std::vector<long>> pre, post;
1068 std::size_t from, to, t;
1072 enum Status { COMPLETE, CAP, UNBOUNDED };
1073 std::vector<Marking> M;
1074 std::vector<ReachEdge> E;
1075 Status status = COMPLETE;
1076 Marking witness_cover, witness_ancestor;
1081 std::vector<double> alpha;
1083 Matrix<double> Rfire, Gfire;
1084 std::vector<Marking> Mtang;
1085 std::vector<double> firings0;
1089 workflow::Workflow<double> wf;
1095 std::shared_ptr<Network> network;
1096 std::string target, mode;
1098 double lambda = std::numeric_limits<double>::quiet_NaN();
1099 std::string restart;
1103 std::vector<Task> tasks;
1104 detail::TreeNode tree;
1105 std::vector<std::string> joins;
1106 std::vector<std::string> task_station;
1107 std::vector<std::string> task_class;
1114 std::vector<Res> resources;
1115 std::string counter;
1116 std::vector<std::string> places;
1120 std::vector<Busy> busy;
1124 std::vector<std::unique_ptr<WFNPlace>> places_;
1125 std::vector<std::unique_ptr<WFNTransition>> transitions_;
1126 std::vector<WFNArc> arcs_;
1127 std::vector<std::unique_ptr<WFNResource>> resources_;
1128 std::map<std::string, std::size_t> place_map_, transition_map_;
1129 long source_ = -1, sink_ = -1;
1131 bool has_arrival_ =
false;
1132 long closed_cases_ = 0;
1133 double state_cap_ = 1e5;
1134 std::shared_ptr<Compiled> compiled_;
1140 void check_new_name(
const std::string&
name)
const {
1141 if (place_map_.count(
name) || transition_map_.count(
name))
1143 "\" already exists in workflow net \"" + name_ +
"\".");
1145 if (
name.compare(0, pre.size(), pre) == 0)
1147 "\" are reserved for compiled networks: \"" +
name +
"\".");
1152 if (!ref.by_name_)
return ref;
1153 const auto ip = place_map_.find(ref.name_);
1154 if (ip != place_map_.end())
return ElementRef(*places_[ip->second]);
1155 const auto it = transition_map_.find(ref.name_);
1156 if (it != transition_map_.end())
return ElementRef(*transitions_[it->second]);
1157 throw InputError(
ErrorKind(
"Model"),
"No place or transition named \"" + ref.name_ +
"\" found.");
1160 void check_owned(
const ElementRef& ref)
const {
1163 ok = ref.place_->index() < places_.size() && places_[ref.place_->index()].get() == ref.place_;
1164 else if (ref.transition_)
1165 ok = ref.transition_->index() < transitions_.size() &&
1166 transitions_[ref.transition_->index()].get() == ref.transition_;
1169 "Element is not a place or transition of workflow net \"" + name_ +
"\".");
1172 static std::string element_name(
const ElementRef& ref) {
1173 return ref.place_ ? ref.place_->name() : ref.transition_->name();
1176 long declared_place(
const ElementRef& ref,
const char* what)
const {
1179 if (!r.place_)
throw InputError(
ErrorKind(
"Model"), std::string(what) +
" must be a WFNPlace object.");
1181 return static_cast<long>(r.place_->index());
1184 WFNResource& set_resource_impl(
const ElementRef&
transition,
const std::string&
name,
const double* servers,
1188 const auto it = transition_map_.find(
transition.name_);
1189 if (it == transition_map_.end())
1193 if (!r.transition_)
throw InputError(
ErrorKind(
"Argument"),
"First argument must be a transition.");
1195 WFNTransition& tr = *transitions_[r.transition_->index()];
1196 if (tr.is_immediate())
1198 "\" takes no time and cannot hold a resource.");
1199 if (std::isfinite(tr.servers()))
1201 "Transition \"" + tr.name() +
"\" already limits its own concurrency to " +
1202 detail::num_d(tr.servers()) +
1203 " servers; a task is constrained either by its own servers or by a shared "
1204 "resource, not both.");
1206 for (std::size_t i = 0; i < resources_.size(); ++i)
1207 if (resources_[i]->
name() ==
name) {
1208 idx =
static_cast<long>(i);
1212 resources_.emplace_back(
new WFNResource(
name, servers ? *servers : 1.0,
1214 idx =
static_cast<long>(resources_.size()) - 1;
1216 if (servers) resources_[idx]->set_servers(*servers);
1217 if (scheduling) resources_[idx]->set_scheduling(*scheduling);
1220 return *resources_[idx];
1223 void set_arrival_typed(
const std::string& type,
const double* rate,
const Dist* map) {
1224 std::string tp = type;
1225 std::transform(tp.begin(), tp.end(), tp.begin(), [](
unsigned char c) { return std::tolower(c); });
1227 has_arrival_ =
false;
1229 }
else if (tp ==
"poisson") {
1230 if (!rate || !(*rate > 0))
1232 arrival_ =
Exp(*rate);
1233 has_arrival_ =
true;
1234 }
else if (tp ==
"map") {
1235 const bool modulated =
1238 if (!modulated)
throw InputError(
ErrorKind(
"Argument"),
"A MAP stream needs a MAP object.");
1240 has_arrival_ =
true;
1246 std::string place_list(
const std::vector<std::size_t>& idx)
const {
1248 for (std::size_t j = 0; j < idx.size(); ++j) s += (j ?
", \"" :
"\"") + places_[idx[j]]->name() +
"\"";
1249 return s.empty() ?
"none" : s;
1252 std::string transition_list(
const std::vector<std::size_t>& idx)
const {
1254 for (std::size_t j = 0; j < idx.size(); ++j)
1255 s += (j ?
", \"" :
"\"") + transitions_[idx[j]]->name() +
"\"";
1256 return s.empty() ?
"none" : s;
1259 std::string marking_string(
const Marking& m)
const {
1260 std::string s =
"[";
1262 for (std::size_t p = 0; p < m.size(); ++p) {
1263 if (m[p] == 0)
continue;
1264 if (!first) s +=
", ";
1265 s += places_[p]->name() +
":" + std::to_string(m[p]);
1271 Inc incidence()
const {
1273 inc.nP = places_.size();
1274 inc.nT = transitions_.size();
1275 inc.pre.assign(inc.nP, std::vector<long>(inc.nT, 0));
1276 inc.post.assign(inc.nP, std::vector<long>(inc.nT, 0));
1277 for (
const WFNArc& a : arcs_) {
1278 const long w =
static_cast<long>(std::llround(a.weight()));
1280 inc.pre[a.place().index()][a.transition().index()] += w;
1282 inc.post[a.place().index()][a.transition().index()] += w;
1287 static std::vector<bool> bfs(
const std::vector<std::vector<std::size_t>>& adj,
1288 const std::vector<std::size_t>& starts) {
1289 std::vector<bool> visited(adj.size(),
false);
1290 std::deque<std::size_t> queue;
1291 for (std::size_t s : starts) {
1295 while (!queue.empty()) {
1296 const std::size_t cur = queue.front();
1298 for (std::size_t nxt : adj[cur])
1299 if (!visited[nxt]) {
1300 visited[nxt] =
true;
1301 queue.push_back(nxt);
1307 static std::vector<double> enabling_degree(
const Inc& inc,
const Marking& m) {
1308 std::vector<double> deg(inc.nT, std::numeric_limits<double>::infinity());
1309 for (std::size_t t = 0; t < inc.nT; ++t) {
1310 for (std::size_t p = 0; p < inc.nP; ++p)
1311 if (inc.pre[p][t] > 0)
1312 deg[t] = std::min(deg[t], std::floor(
static_cast<double>(m[p]) / inc.pre[p][t]));
1322 static Reach untimed_reachability(
const Inc& inc,
const Marking& m0,
double cap) {
1324 std::vector<std::size_t> parent;
1325 std::map<Marking, std::size_t> keys;
1327 parent.push_back(npos_parent());
1329 for (std::size_t head = 0; head < rg.M.size(); ++head) {
1330 const Marking m = rg.M[head];
1331 for (std::size_t t = 0; t < inc.nT; ++t) {
1333 for (std::size_t p = 0; p < inc.nP && en; ++p) en = m[p] >= inc.pre[p][t];
1336 for (std::size_t p = 0; p < inc.nP; ++p) m2[p] = m[p] + inc.post[p][t] - inc.pre[p][t];
1338 const auto it = keys.find(m2);
1339 if (it != keys.end()) {
1342 for (std::size_t a = head; a != npos_parent(); a = parent[a]) {
1343 bool ge =
true, gt =
false;
1344 for (std::size_t p = 0; p < inc.nP; ++p) {
1345 ge = ge && m2[p] >= rg.M[a][p];
1346 gt = gt || m2[p] > rg.M[a][p];
1349 rg.status = Reach::UNBOUNDED;
1350 rg.witness_cover = m2;
1351 rg.witness_ancestor = rg.M[a];
1355 if (
static_cast<double>(rg.M.size()) >= cap) {
1356 rg.status = Reach::CAP;
1360 parent.push_back(head);
1361 to = rg.M.size() - 1;
1364 rg.E.push_back(ReachEdge{head, to, t});
1370 static std::size_t npos_parent() {
return static_cast<std::size_t
>(-1); }
1372 void require_sound(
long k) {
1373 const SoundnessReport report =
check_soundness(
static_cast<double>(k));
1374 if (report.verdict ==
"unknown")
1375 throw NumericError(
ErrorKind(
"StateSpace"),
"Soundness of \"" + name_ +
"\" (k=" + std::to_string(k) +
1377 ". Raise stateCap, or skip the gate where the method "
1382 "-sound, so the case cycle time is undefined: " +
1387 static void ph_representation(
const Dist& d,
const std::string&
name, std::vector<double>& alpha,
1388 Matrix<double>& S) {
1394 if (!ph_family || d.disabled || !d.has_map())
1397 " firing law; the exact single-case analysis needs a phase-type law (Exp, "
1398 "Erlang, HyperExp, Coxian, Cox2, APH, PH). Compile with toNetwork('spn') and "
1399 "use a simulator (SolverSSA, SolverLDES) for other laws.");
1400 const std::size_t n = d.D0.rows();
1402 alpha.assign(n, 0.0);
1404 alpha.assign(d.params.begin(), d.params.end());
1407 for (std::size_t i = 0; i < n; ++i) {
1409 for (std::size_t j = 0; j < n; ++j) out += d.D1(i, j);
1410 if (!(out > 0))
continue;
1411 for (std::size_t j = 0; j < n; ++j) alpha[j] = d.D1(i, j) / out;
1416 bool negative =
false;
1417 for (
double a : alpha) {
1423 "\" has a defective or negative initial vector.");
1427 static std::vector<long> after_firing(
const Inc& inc,
const std::vector<long>& x, std::size_t t,
1428 const std::vector<std::size_t>& ord) {
1429 const std::size_t nP = inc.nP, nT = inc.nT;
1431 for (std::size_t p = 0; p < nP; ++p) m2[p] = x[p] + inc.post[p][t] - inc.pre[p][t];
1432 const std::vector<double> deg = enabling_degree(inc, m2);
1433 std::vector<long> y(nP + nT);
1434 for (std::size_t p = 0; p < nP; ++p) y[p] = m2[p];
1435 for (std::size_t u = 0; u < nT; ++u) {
1436 long ph = x[nP + u];
1437 if (deg[u] == 0 || u == t || ord[u] <= 1) ph = 0;
1451 Chain single_case_chain() {
1453 const Inc inc = incidence();
1454 const std::size_t nP = inc.nP, nT = inc.nT;
1455 std::vector<bool> is_imm(nT,
false);
1456 std::vector<double> prio(nT), wgt(nT), srv(nT);
1457 std::vector<std::vector<double>> pha(nT);
1458 std::vector<Matrix<double>> phS(nT);
1459 std::vector<std::size_t> ord(nT, 0);
1460 for (std::size_t t = 0; t < nT; ++t) {
1461 const WFNTransition& tr = *transitions_[t];
1462 is_imm[t] = tr.is_immediate();
1463 prio[t] = tr.priority();
1464 wgt[t] = tr.weight();
1465 srv[t] = tr.servers();
1467 ph_representation(tr.distribution(), tr.name(), pha[t], phS[t]);
1468 ord[t] = pha[t].size();
1473 std::vector<long> x0(nP + nT, 0);
1477 std::size_t from, to;
1481 std::map<std::vector<long>, std::size_t> keys;
1482 std::vector<std::vector<long>> S;
1483 std::vector<int> kind;
1484 std::vector<Edge> edges;
1487 for (std::size_t head = 0; head < S.size(); ++head) {
1488 const std::vector<long> x = S[head];
1489 const Marking m(x.begin(), x.begin() +
static_cast<long>(nP));
1490 const std::vector<double> deg = enabling_degree(inc, m);
1491 std::vector<std::vector<long>> succ;
1492 std::vector<double> val;
1493 std::vector<long> lab;
1494 bool imm_enabled =
false;
1495 for (std::size_t t = 0; t < nT; ++t) imm_enabled = imm_enabled || (is_imm[t] && deg[t] > 0);
1499 }
else if (imm_enabled) {
1501 double pmax = -std::numeric_limits<double>::infinity();
1502 for (std::size_t t = 0; t < nT; ++t)
1503 if (is_imm[t] && deg[t] > 0) pmax = std::max(pmax, prio[t]);
1504 std::vector<std::size_t> en;
1506 for (std::size_t t = 0; t < nT; ++t)
1507 if (is_imm[t] && deg[t] > 0 && prio[t] == pmax) {
1511 for (std::size_t t : en) {
1512 succ.push_back(after_firing(inc, x, t, ord));
1513 val.push_back(wgt[t] / wsum);
1514 lab.push_back(
static_cast<long>(t));
1517 std::vector<double> act(nT);
1518 for (std::size_t t = 0; t < nT; ++t) act[t] = std::min(deg[t], srv[t]);
1519 for (std::size_t t = 0; t < nT; ++t)
1520 if (!is_imm[t] && ord[t] > 1 && act[t] > 1)
1523 "Transition \"" + transitions_[t]->
name() +
"\" is enabled " + detail::num_d(act[t]) +
1524 " times at once with a " + std::to_string(ord[t]) +
1525 "-phase firing law; the exact single-case analysis keeps one phase per transition, "
1526 "so it supports concurrent firings only for exponential laws.");
1527 std::size_t need = nT;
1528 for (std::size_t t = 0; t < nT && need == nT; ++t)
1529 if (!is_imm[t] && ord[t] > 1 && act[t] > 0 && x[nP + t] == 0) need = t;
1532 for (std::size_t j = 0; j < pha[need].size(); ++j)
1533 if (pha[need][j] > 0) {
1534 std::vector<long> y = x;
1535 y[nP + need] =
static_cast<long>(j + 1);
1537 val.push_back(pha[need][j]);
1543 for (std::size_t t = 0; t < nT; ++t) {
1544 if (is_imm[t] || !(act[t] > 0))
continue;
1547 succ.push_back(after_firing(inc, x, t, ord));
1548 val.push_back(-phS[t](0, 0) * act[t]);
1549 lab.push_back(
static_cast<long>(t));
1551 const std::size_t j =
static_cast<std::size_t
>(x[nP + t] - 1);
1552 for (std::size_t j2 = 0; j2 < ord[t]; ++j2)
1553 if (j2 != j && phS[t](j, j2) > 0) {
1554 std::vector<long> y = x;
1555 y[nP + t] =
static_cast<long>(j2 + 1);
1557 val.push_back(phS[t](j, j2));
1560 double exit_rate = 0.0;
1561 for (std::size_t j2 = 0; j2 < ord[t]; ++j2) exit_rate -= phS[t](j, j2);
1563 succ.push_back(after_firing(inc, x, t, ord));
1564 val.push_back(exit_rate);
1565 lab.push_back(
static_cast<long>(t));
1571 "\" reached a dead marking " +
1572 marking_string(m) +
" in the timed chain.");
1576 for (std::size_t e = 0; e < succ.size(); ++e) {
1578 const auto it = keys.find(succ[e]);
1579 if (it != keys.end()) {
1583 if (
static_cast<double>(to + 1) > state_cap_)
1585 "\" exceeds the state cap of " +
1586 detail::num_d(state_cap_) +
" states.");
1587 S.push_back(succ[e]);
1590 edges.push_back(Edge{head, to, val[e], lab[e]});
1594 const std::size_t n = S.size();
1595 std::vector<std::size_t> pos(n, 0);
1596 std::size_t nZ = 0, nTT = 0;
1597 for (std::size_t i = 0; i < n; ++i) {
1598 if (kind[i] == 1) pos[i] = nZ++;
1599 else if (kind[i] == 2) pos[i] = nTT++;
1602 Matrix<double> IZ(nZ, nZ, 0.0), PZT(nZ, nTT, 0.0), CZ(nZ, nT, 0.0);
1603 for (std::size_t z = 0; z < nZ; ++z) IZ(z, z) = 1.0;
1604 for (
const Edge& e : edges) {
1605 if (kind[e.from] != 1)
continue;
1606 const std::size_t z = pos[e.from];
1607 if (kind[e.to] == 1) IZ(z, pos[e.to]) -= e.val;
1608 else if (kind[e.to] == 2) PZT(z, pos[e.to]) += e.val;
1609 if (e.lab >= 0) CZ(z,
static_cast<std::size_t
>(e.lab)) += e.val;
1611 Matrix<double> BT(nZ, nTT, 0.0), FZ(nZ, nT, 0.0);
1613 const detail::Factor f(IZ);
1614 for (std::size_t c = 0; c < nTT; ++c) {
1615 std::vector<double> b(nZ);
1616 for (std::size_t z = 0; z < nZ; ++z) b[z] = PZT(z, c);
1618 for (std::size_t z = 0; z < nZ; ++z) BT(z, c) = b[z];
1620 for (std::size_t c = 0; c < nT; ++c) {
1621 std::vector<double> b(nZ);
1622 for (std::size_t z = 0; z < nZ; ++z) b[z] = CZ(z, c);
1624 for (std::size_t z = 0; z < nZ; ++z) FZ(z, c) = b[z];
1629 ch.Q = Matrix<double>(nTT, nTT, 0.0);
1630 ch.Rfire = Matrix<double>(nTT, nT, 0.0);
1631 ch.Gfire = Matrix<double>(nTT, nT, 0.0);
1632 for (
const Edge& e : edges) {
1633 if (kind[e.from] != 2)
continue;
1634 const std::size_t s = pos[e.from];
1635 const double r = e.val;
1637 if (e.lab >= 0) ch.Rfire(s,
static_cast<std::size_t
>(e.lab)) += r;
1638 if (kind[e.to] == 2) {
1639 ch.Q(s, pos[e.to]) += r;
1640 }
else if (kind[e.to] == 1) {
1641 const std::size_t z = pos[e.to];
1642 for (std::size_t j = 0; j < nTT; ++j) ch.Q(s, j) += r * BT(z, j);
1643 for (std::size_t j = 0; j < nT; ++j) ch.Gfire(s, j) += r * FZ(z, j);
1646 for (std::size_t i = 0; i < n; ++i)
1647 if (kind[i] == 2) ch.Mtang.push_back(Marking(S[i].begin(), S[i].begin() +
static_cast<long>(nP)));
1648 ch.firings0.assign(nT, 0.0);
1650 ch.alpha.assign(nTT, 0.0);
1651 ch.alpha[pos[0]] = 1.0;
1652 }
else if (kind[0] == 1) {
1653 ch.alpha.assign(nTT, 0.0);
1654 for (std::size_t j = 0; j < nTT; ++j) ch.alpha[j] = BT(pos[0], j);
1655 for (std::size_t j = 0; j < nT; ++j) ch.firings0[j] = FZ(pos[0], j);
1661 bool choice_resolvable(
const std::vector<std::size_t>& comp,
const std::vector<bool>& head_imm,
1662 const std::vector<double>& head_prio,
const std::string& place_name,
1663 std::string& why)
const {
1665 for (std::size_t c : comp) ok = ok && head_imm[c] && head_prio[c] == head_prio[comp[0]];
1668 why =
"the choice at place \"" + place_name +
1669 "\" is not resolved by immediate transitions of equal priority, so it is a race or a priority "
1670 "choice that the block structure cannot express";
1680 bool reduce_to_tree(detail::TreeNode& tree, std::string& reason)
const {
1681 using detail::TreeNode;
1682 const Inc inc = incidence();
1684 const std::size_t nP = inc.nP, nT = inc.nT;
1685 for (std::size_t p = 0; p < nP; ++p)
1686 for (std::size_t t = 0; t < nT; ++t)
1687 if (inc.pre[p][t] > 1 || inc.post[p][t] > 1) {
1688 reason =
"an arc has weight greater than one";
1691 std::vector<std::vector<bool>> pre(nP, std::vector<bool>(nT)), post(nP, std::vector<bool>(nT));
1692 for (std::size_t p = 0; p < nP; ++p)
1693 for (std::size_t t = 0; t < nT; ++t) {
1694 pre[p][t] = inc.pre[p][t] > 0;
1695 post[p][t] = inc.post[p][t] > 0;
1697 std::vector<bool> alive_p(nP,
true), alive_t(nT,
true);
1698 const std::size_t src =
static_cast<std::size_t
>(source_);
1699 std::size_t snk =
static_cast<std::size_t
>(sink_);
1700 std::vector<TreeNode> ptree(nP, detail::tree_eps()), ttree(nT);
1701 std::vector<bool> head_imm(nT,
false);
1702 std::vector<double> head_w(nT), head_prio(nT);
1703 for (std::size_t t = 0; t < nT; ++t) {
1704 const WFNTransition& tr = *transitions_[t];
1705 if (tr.is_immediate()) {
1706 ttree[t] = detail::tree_eps();
1709 ttree[t].type = TreeNode::TASK;
1712 head_w[t] = tr.weight();
1713 head_prio[t] = tr.priority();
1715 auto col = [&](
const std::vector<std::vector<bool>>& A, std::size_t t) {
1716 std::vector<std::size_t> out;
1717 for (std::size_t p = 0; p < nP; ++p)
1718 if (A[p][t]) out.push_back(p);
1721 auto row = [&](
const std::vector<std::vector<bool>>& A, std::size_t p) {
1722 std::vector<std::size_t> out;
1723 for (std::size_t t = 0; t < nT; ++t)
1724 if (A[p][t]) out.push_back(t);
1727 auto alive = [](
const std::vector<bool>& a) {
1728 std::vector<std::size_t> out;
1729 for (std::size_t i = 0; i < a.size(); ++i)
1730 if (a[i]) out.push_back(i);
1733 auto one = [](std::size_t x) {
return std::vector<std::size_t>(1, x); };
1734 bool changed =
true;
1738 for (std::size_t t : alive(alive_t)) {
1739 const std::vector<std::size_t> ins = col(pre, t), outs = col(post, t);
1740 if (ins.size() == 1 && outs.size() == 1 && ins[0] != outs[0]) {
1741 const std::size_t p1 = ins[0], p2 = outs[0];
1742 if (row(pre, p1) == one(t) && row(post, p2) == one(t)) {
1743 ptree[p1] = detail::tree_seq({ptree[p1], ttree[t], ptree[p2]});
1745 pre[p2].assign(nT,
false);
1746 post[p2].assign(nT,
false);
1747 for (std::size_t p = 0; p < nP; ++p) pre[p][t] = post[p][t] =
false;
1749 alive_p[p2] =
false;
1750 if (p2 == snk) snk = p1;
1756 for (std::size_t p : alive(alive_p)) {
1757 if (p == src || p == snk)
continue;
1758 const std::vector<std::size_t> prod = row(post, p), cons = row(pre, p);
1759 if (prod.size() == 1 && cons.size() == 1 && prod[0] != cons[0]) {
1760 const std::size_t t1 = prod[0], t2 = cons[0];
1761 if (col(post, t1) == one(p) && col(pre, t2) == one(p)) {
1762 ttree[t1] = detail::tree_seq({ttree[t1], ptree[p], ttree[t2]});
1763 for (std::size_t q = 0; q < nP; ++q) {
1764 post[q][t1] = post[q][t2];
1765 pre[q][t2] = post[q][t2] =
false;
1767 pre[p].assign(nT,
false);
1768 post[p].assign(nT,
false);
1769 alive_t[t2] =
false;
1776 const std::vector<std::size_t> ap = alive(alive_p);
1777 for (std::size_t a : ap) {
1778 if (!alive_p[a] || a == src || a == snk)
continue;
1779 const std::vector<std::size_t> pa = row(post, a), ca = row(pre, a);
1780 if (pa.size() != 1 || ca.size() != 1)
continue;
1781 for (std::size_t b : ap) {
1782 if (b <= a || !alive_p[b] || b == src || b == snk)
continue;
1783 if (row(post, b) == pa && row(pre, b) == ca) {
1784 ptree[a] = detail::tree_and({ptree[a], ptree[b]});
1785 pre[b].assign(nT,
false);
1786 post[b].assign(nT,
false);
1793 const std::vector<std::size_t> at = alive(alive_t);
1794 for (std::size_t a : at) {
1795 if (!alive_t[a])
continue;
1796 const std::vector<std::size_t> ia = col(pre, a), oa = col(post, a);
1797 if (ia.size() != 1 || oa.size() != 1)
continue;
1798 for (std::size_t b : at) {
1799 if (b <= a || !alive_t[b])
continue;
1800 if (col(pre, b) == ia && col(post, b) == oa) {
1801 const std::vector<std::size_t> comp = row(pre, ia[0]);
1803 if (!choice_resolvable(comp, head_imm, head_prio, places_[ia[0]]->
name(), why)) {
1807 const double wa = head_w[a], wb = head_w[b];
1808 ttree[a] = detail::tree_xor({ttree[a], ttree[b]}, {wa / (wa + wb), wb / (wa + wb)});
1809 head_w[a] = wa + wb;
1810 for (std::size_t p = 0; p < nP; ++p) pre[p][b] = post[p][b] =
false;
1817 for (std::size_t t : alive(alive_t)) {
1818 const std::vector<std::size_t> ins = col(pre, t), outs = col(post, t);
1819 if (ins.size() == 1 && ins == outs) {
1820 const std::size_t p = ins[0];
1821 const std::vector<std::size_t> comp = row(pre, p);
1822 if (comp.size() < 2)
continue;
1824 if (!choice_resolvable(comp, head_imm, head_prio, places_[p]->
name(), why)) {
1829 for (std::size_t c : comp) wsum += head_w[c];
1830 ptree[p] = detail::tree_loop(ptree[p], ttree[t], head_w[t] / wsum);
1831 for (std::size_t q = 0; q < nP; ++q) pre[q][t] = post[q][t] =
false;
1837 const std::vector<std::size_t> left_t = alive(alive_t), left_p = alive(alive_p);
1838 const bool ok = left_t.empty() && left_p.size() == 1;
1840 tree = ptree[left_p[0]];
1841 }
else if (reason.empty()) {
1842 reason =
"the reduction to series, AND, XOR and loop blocks stops with transitions " +
1843 transition_list(left_t) +
" left, so the net is not block-structured";
1852 std::string wf_task(std::size_t t, WfState& st)
const {
1853 const WFNTransition& tr = *transitions_[t];
1855 st.wf.add_activity(tr.name(), tr.distribution());
1859 static std::string wf_aux(WfState& st) {
1861 const std::string
name = std::string(
ReservedPrefix) +
"aux_" + std::to_string(st.naux);
1862 st.wf.add_activity(
name, Immediate());
1867 void wf_branch(
const detail::TreeNode& node, WfState& st, std::string& h, std::string& tl)
const {
1868 using detail::TreeNode;
1869 switch (node.type) {
1870 case TreeNode::TASK:
1871 h = wf_task(node.t, st);
1874 case TreeNode::SEQ: {
1875 std::vector<TreeNode>
kids = node.kids;
1876 if (
kids.front().type == TreeNode::TASK) {
1877 h = wf_task(
kids.front().t, st);
1882 if (!
kids.empty() &&
kids.back().type == TreeNode::TASK) {
1883 tl = wf_task(
kids.back().t, st);
1885 }
else if (
kids.empty()) {
1890 if (h != tl) wf_emit(detail::tree_seq(kids), h, tl, st);
1900 wf_emit(node, h, tl, st);
1905 void wf_emit(
const detail::TreeNode& node,
const std::string& pre,
const std::string& post,
1906 WfState& st)
const {
1907 using detail::TreeNode;
1908 typedef workflow::Workflow<double> Wf;
1909 switch (node.type) {
1911 st.wf.add_precedence(Wf::Serial(pre, post));
1913 case TreeNode::TASK: {
1914 const std::string a = wf_task(node.t, st);
1915 st.wf.add_precedence(Wf::Serial(pre, a));
1916 st.wf.add_precedence(Wf::Serial(a, post));
1919 case TreeNode::SEQ: {
1920 const std::vector<TreeNode>&
kids = node.kids;
1921 std::string cur = pre;
1923 while (i <
kids.size()) {
1924 const TreeNode& k =
kids[i];
1925 if (k.type == TreeNode::TASK) {
1926 const std::string a = wf_task(k.t, st);
1927 st.wf.add_precedence(Wf::Serial(cur, a));
1933 if (i + 1 <
kids.size() && kids[i + 1].type == TreeNode::TASK) {
1934 nxt = wf_task(kids[i + 1].t, st);
1936 }
else if (i + 1 ==
kids.size()) {
1943 wf_emit(k, cur, nxt, st);
1948 if (cur != post) st.wf.add_precedence(Wf::Serial(cur, post));
1952 case TreeNode::XOR: {
1953 std::vector<std::string> heads(node.kids.size()), tails(node.kids.size());
1954 for (std::size_t j = 0; j < node.kids.size(); ++j) wf_branch(node.kids[j], st, heads[j], tails[j]);
1955 if (node.type == TreeNode::AND) {
1956 st.wf.add_precedence(Wf::AndFork(pre, heads));
1957 st.wf.add_precedence(Wf::AndJoin(tails, post));
1959 st.wf.add_precedence(Wf::OrFork(pre, heads, node.probs));
1960 st.wf.add_precedence(Wf::OrJoin(tails, post));
1964 case TreeNode::LOOP: {
1965 if (node.kids[1].type != TreeNode::EPS)
1967 "toWorkflow cannot express a loop with work on its return path: "
1968 "Workflow.Loop repeats a single body a geometric number of times. "
1969 "getCycleTime and toNetwork handle this net.");
1971 wf_branch(node.kids[0], st, h, tl);
1972 st.wf.add_precedence(Wf::Loop(pre, {h, post}, 1.0 / (1.0 - node.q)));
1985 std::size_t node = 0;
1986 std::size_t task = 0;
1993 std::vector<FjVertex> g;
1994 std::vector<FjEdge> E;
1995 std::size_t nfork = 0;
1996 std::vector<std::size_t> task_vertex;
1997 std::vector<std::size_t> res_node;
1998 std::vector<std::size_t> task_node;
1999 std::vector<std::string> joins;
2000 std::map<std::size_t, std::string> names;
2003 static std::size_t fj_vertex(FjGraph& fj,
const std::string& kind, std::size_t node, std::size_t task) {
2009 return fj.g.size() - 1;
2013 void fj_emit(
const detail::TreeNode& node, std::size_t vin, std::size_t vout, FjGraph& fj,
Network& net)
const {
2014 using detail::TreeNode;
2015 switch (node.type) {
2017 fj.E.push_back(FjEdge{vin, vout, 1.0});
2019 case TreeNode::TASK: {
2020 const std::size_t t = node.t;
2021 const WFNTransition& tr = *transitions_[t];
2023 if (tr.resource_ >= 0) {
2024 const std::size_t r =
static_cast<std::size_t
>(tr.resource_);
2025 if (fj.res_node[r] == 0) {
2026 const WFNResource& res = *resources_[r];
2027 fj.res_node[r] = net.add_queue(res.name(), res.scheduling());
2028 net.set_number_of_servers(fj.res_node[r], res.servers());
2029 fj.names[fj.res_node[r]] = res.name();
2031 nd = fj.res_node[r];
2032 }
else if (std::isfinite(tr.servers())) {
2034 net.set_number_of_servers(nd, tr.servers());
2035 fj.task_node[t] = nd;
2036 fj.names[nd] = tr.name();
2038 nd = net.add_delay(tr.name());
2039 fj.names[nd] = tr.name();
2041 const std::size_t v = fj_vertex(fj,
"station", nd, t + 1);
2042 fj.task_vertex[t] = v;
2043 fj.E.push_back(FjEdge{vin, v, 1.0});
2044 fj.E.push_back(FjEdge{v, vout, 1.0});
2047 case TreeNode::SEQ: {
2048 std::size_t cur = vin;
2049 for (std::size_t j = 0; j < node.kids.size(); ++j) {
2050 const std::size_t nxt = j + 1 == node.kids.size() ? vout : fj_vertex(fj,
"virt", 0, 0);
2051 fj_emit(node.kids[j], cur, nxt, fj, net);
2057 for (std::size_t j = 0; j < node.kids.size(); ++j) {
2058 const std::size_t b = fj_vertex(fj,
"virt", 0, 0);
2059 fj.E.push_back(FjEdge{vin, b, node.probs[j]});
2060 fj_emit(node.kids[j], b, vout, fj, net);
2063 case TreeNode::LOOP: {
2064 const std::size_t v1 = fj_vertex(fj,
"virt", 0, 0);
2065 const std::size_t v2 = fj_vertex(fj,
"virt", 0, 0);
2066 const std::size_t v3 = fj_vertex(fj,
"virt", 0, 0);
2067 fj.E.push_back(FjEdge{vin, v1, 1.0});
2068 fj_emit(node.kids[0], v1, v2, fj, net);
2069 fj.E.push_back(FjEdge{v2, v3, node.q});
2070 fj_emit(node.kids[1], v3, v1, fj, net);
2071 fj.E.push_back(FjEdge{v2, vout, 1.0 - node.q});
2074 case TreeNode::AND: {
2077 const std::size_t fk = net.add_fork(pre +
"fork" + std::to_string(fj.nfork));
2078 const std::string jname = pre +
"join" + std::to_string(fj.nfork);
2079 const std::size_t jn = net.add_join(jname, fk);
2080 fj.joins.push_back(jname);
2081 const std::size_t vf = fj_vertex(fj,
"fork", fk, 0);
2082 const std::size_t vj = fj_vertex(fj,
"join", jn, 0);
2083 fj.E.push_back(FjEdge{vin, vf, 1.0});
2084 for (std::size_t j = 0; j < node.kids.size(); ++j) {
2085 const std::size_t b = fj_vertex(fj,
"virt", 0, 0);
2086 const std::size_t bo = fj_vertex(fj,
"virt", 0, 0);
2087 fj.E.push_back(FjEdge{vf, b, 1.0});
2088 fj_emit(node.kids[j], b, bo, fj, net);
2089 fj.E.push_back(FjEdge{bo, vj, 1.0});
2091 fj.E.push_back(FjEdge{vj, vout, 1.0});
2102 static std::vector<FjEdge> fj_resolve(FjGraph& fj,
Network& net) {
2103 const std::size_t nv = fj.g.size();
2104 std::vector<bool> is_virt(nv);
2105 std::vector<std::size_t> iv, ip, vpos(nv, 0), ppos(nv, 0);
2106 for (std::size_t v = 0; v < nv; ++v) {
2107 is_virt[v] = fj.g[v].kind ==
"virt";
2109 vpos[v] = iv.size();
2112 ppos[v] = ip.size();
2116 const std::size_t ni = iv.size(), np = ip.size();
2117 Matrix<double> Iv(ni, ni, 0.0), Pvp(ni, np, 0.0);
2118 for (std::size_t i = 0; i < ni; ++i) Iv(i, i) = 1.0;
2119 for (
const FjEdge& e : fj.E) {
2120 if (!is_virt[e.u])
continue;
2122 Iv(vpos[e.u], vpos[e.v]) -= e.p;
2124 Pvp(vpos[e.u], ppos[e.v]) += e.p;
2126 const std::string loop_msg =
2127 "The net has a loop of zero-time choices that repeats with probability one, so a case never leaves it.";
2128 Matrix<double> Res(ni, np, 0.0);
2130 Matrix<double> inv(ni, ni, 0.0);
2132 const detail::Factor f(Iv);
2133 for (std::size_t c = 0; c < ni; ++c) {
2134 std::vector<double> b(ni, 0.0);
2137 for (std::size_t r = 0; r < ni; ++r) inv(r, c) = b[r];
2139 }
catch (
const NumericError&) {
2143 double n1 = 0.0, n1i = 0.0;
2144 for (std::size_t c = 0; c < ni; ++c) {
2145 double s = 0.0, si = 0.0;
2146 for (std::size_t r = 0; r < ni; ++r) {
2147 s += std::abs(Iv(r, c));
2148 si += std::abs(inv(r, c));
2150 n1 = std::max(n1, s);
2151 n1i = std::max(n1i, si);
2155 for (std::size_t r = 0; r < ni; ++r)
2156 for (std::size_t c = 0; c < np; ++c) {
2158 for (std::size_t k = 0; k < ni; ++k) s += inv(r, k) * Pvp(k, c);
2163 std::vector<FjEdge> dist;
2164 std::size_t nrouter = 0;
2165 for (std::size_t u : ip) {
2166 if (fj.g[u].kind ==
"sink")
continue;
2167 std::vector<double> d;
2168 if (fj.g[u].kind ==
"fork") {
2169 for (std::size_t ei = 0; ei < fj.E.size(); ++ei) {
2170 const FjEdge e = fj.E[ei];
2171 if (e.u != u)
continue;
2174 for (std::size_t j = 0; j < np; ++j) d[j] = Res(vpos[e.v], j);
2177 std::vector<std::size_t> tgt;
2178 for (std::size_t j = 0; j < np; ++j)
2179 if (d[j] > tol) tgt.push_back(j);
2180 if (tgt.size() == 1 && std::abs(d[tgt[0]] - 1) < tol) {
2181 dist.push_back(FjEdge{u, ip[tgt[0]], 1.0});
2184 const std::size_t rt =
2185 net.add_router(std::string(
ReservedPrefix) +
"router" + std::to_string(nrouter));
2186 const std::size_t r = fj_vertex(fj,
"router", rt, 0);
2187 dist.push_back(FjEdge{u, r, 1.0});
2188 for (std::size_t j : tgt) dist.push_back(FjEdge{r, ip[j], d[j]});
2193 for (
const FjEdge& e : fj.E) {
2194 if (e.u != u)
continue;
2196 for (std::size_t j = 0; j < np; ++j) d[j] += e.p * Res(vpos[e.v], j);
2198 d[ppos[e.v]] += e.p;
2200 for (std::size_t j = 0; j < np; ++j)
2201 if (d[j] > tol) dist.push_back(FjEdge{u, ip[j], d[j]});
2208 std::shared_ptr<Network> compile_to_fork_join() {
2209 detail::TreeNode tree;
2211 if (!reduce_to_tree(tree, reason))
2213 "WF-net: " + reason +
". Use toNetwork('spn').");
2214 const bool closed = closed_cases_ > 0;
2216 std::shared_ptr<Network> network = std::make_shared<Network>(name_ +
"_forkjoin");
2218 const std::size_t nT = transitions_.size(), nR = resources_.size();
2220 std::vector<std::size_t> use_count(nR, 0);
2221 for (std::size_t t = 0; t < nT; ++t)
2222 if (transitions_[t]->resource_ >= 0) ++use_count[
static_cast<std::size_t
>(transitions_[t]->resource_)];
2224 fj.task_vertex.assign(nT,
static_cast<std::size_t
>(-1));
2225 fj.res_node.assign(nR, 0);
2226 fj.task_node.assign(nT, 0);
2228 fj_vertex(fj,
"endpoint", net.add_delay(pre +
"restart"), 0);
2230 fj_vertex(fj,
"endpoint", net.add_source(pre +
"source"), 0);
2231 fj_vertex(fj,
"sink", net.add_sink(pre +
"sink"), 0);
2233 const std::size_t vin = fj_vertex(fj,
"virt", 0, 0);
2234 const std::size_t vout = fj_vertex(fj,
"virt", 0, 0);
2235 fj.E.push_back(FjEdge{0, vin, 1.0});
2236 fj.E.push_back(FjEdge{vout, closed ? 0u : 1u, 1.0});
2237 fj_emit(tree, vin, vout, fj, net);
2238 const std::vector<FjEdge> dist = fj_resolve(fj, net);
2240 const std::size_t endpoint = fj.g[0].node;
2241 const std::size_t case_class = closed
2242 ? net.add_closed_class(
"Case",
static_cast<double>(closed_cases_), endpoint, 0)
2243 : net.add_open_class(
"Case", 0);
2244 std::vector<std::size_t> cls_of(nT, case_class);
2245 std::vector<std::string> cls_name(nT,
"Case");
2246 for (std::size_t t = 0; t < nT; ++t) {
2247 const long r = transitions_[t]->resource_;
2248 if (r >= 0 && use_count[
static_cast<std::size_t
>(r)] > 1) {
2249 cls_name[t] =
"Case_" + transitions_[t]->name();
2250 cls_of[t] = closed ? net.add_closed_class(cls_name[t], 0.0, endpoint, 0)
2251 : net.add_open_class(cls_name[t], 0);
2254 std::vector<std::size_t> vcls(fj.g.size(), case_class);
2255 for (std::size_t v = 0; v < fj.g.size(); ++v)
2256 if (fj.g[v].task > 0) vcls[v] = cls_of[fj.g[v].task - 1];
2259 net.set_service(endpoint, case_class, Immediate());
2261 net.set_arrival(endpoint, case_class, arrival_);
2262 std::shared_ptr<Compiled> c = std::make_shared<Compiled>();
2263 c->task_station.assign(nT, std::string());
2264 for (std::size_t t = 0; t < nT; ++t) {
2265 if (fj.task_vertex[t] ==
static_cast<std::size_t
>(-1))
continue;
2266 const WFNTransition& tr = *transitions_[t];
2267 const std::size_t nd = fj.g[fj.task_vertex[t]].node;
2268 net.set_service(nd, cls_of[t], tr.distribution());
2269 const std::string station = fj.names.at(nd);
2270 c->tasks.push_back(
Compiled::Task{tr.name(), station, cls_name[t]});
2271 c->task_station[t] = station;
2275 for (
const FjEdge& e : dist) {
2276 const std::size_t r = vcls[e.u];
2277 const std::size_t s = fj.g[e.v].kind ==
"sink" ? r : vcls[e.v];
2278 const std::size_t nu = fj.g[e.u].node, nv = fj.g[e.v].node;
2279 P.set(r, s, nu, nv,
P.get(r, s, nu, nv) + e.p);
2282 for (std::size_t r = 0; r < nR; ++r)
2283 if (fj.res_node[r] != 0)
2284 c->resources.push_back(
Compiled::Res{resources_[r]->name(), resources_[r]->servers(),
2285 fj.names.at(fj.res_node[r]), {}});
2286 for (std::size_t t = 0; t < nT; ++t)
2287 if (fj.task_node[t] != 0)
2288 c->resources.push_back(
Compiled::Res{transitions_[t]->name(), transitions_[t]->servers(),
2289 fj.names.at(fj.task_node[t]), {}});
2290 c->network = network;
2291 c->target =
"forkjoin";
2292 c->k = closed_cases_;
2295 c->restart = pre +
"restart";
2298 c->lambda = 1.0 / arrival_.mean;
2301 c->joins = fj.joins;
2302 c->task_class = cls_name;
2315 static double fj_tree_time(
const detail::TreeNode& node,
const Compiled& c,
const AvgTable& tab,
2316 std::size_t& jidx) {
2317 using detail::TreeNode;
2318 switch (node.type) {
2321 case TreeNode::TASK:
2322 return detail::table_cell(tab, tab.RespT, c.task_station[node.t], c.task_class[node.t]);
2323 case TreeNode::SEQ: {
2325 for (
const TreeNode& k : node.kids) x += fj_tree_time(k, c, tab, jidx);
2328 case TreeNode::XOR: {
2330 for (std::size_t j = 0; j < node.kids.size(); ++j)
2331 x += node.probs[j] * fj_tree_time(node.kids[j], c, tab, jidx);
2334 case TreeNode::LOOP: {
2335 const double d = fj_tree_time(node.kids[0], c, tab, jidx);
2336 const double r = fj_tree_time(node.kids[1], c, tab, jidx);
2337 return (d + node.q * r) / (1.0 - node.q);
2339 case TreeNode::AND: {
2340 const std::string& jn = c.joins.at(jidx++);
2342 for (
const TreeNode& k : node.kids) y += fj_tree_time(k, c, tab, jidx);
2344 return y /
static_cast<double>(node.kids.size()) + detail::table_cell(tab, tab.RespT, jn,
"Case");
2355 static void set_transition_timing(
Network& net, std::size_t node, std::size_t mode,
const WFNTransition& w,
2357 if (w.is_immediate()) {
2359 net.set_firing_weight(node, mode, w.weight());
2360 net.set_firing_priority(node, mode, w.priority());
2363 net.set_mode_distribution(node, mode, w.distribution());
2364 net.set_mode_servers(node, mode, servers);
2365 net.set_firing_priority(node, mode, w.priority());
2377 std::shared_ptr<Network> compile_to_spn() {
2378 const bool closed = closed_cases_ > 0;
2379 const Inc inc = incidence();
2380 const std::size_t nP = inc.nP, nT = inc.nT, nR = resources_.size();
2381 for (std::size_t t = 0; t < nT; ++t) {
2382 const WFNTransition& tr = *transitions_[t];
2383 if (tr.resource_ < 0)
continue;
2384 for (std::size_t p = 0; p < nP; ++p) {
2385 if (inc.pre[p][t] == 0)
continue;
2386 std::vector<std::size_t> rivals;
2387 for (std::size_t u = 0; u < nT; ++u)
2388 if (u != t && inc.pre[p][u] > 0) rivals.push_back(u);
2389 if (!rivals.empty())
2391 "Task \"" + tr.name() +
"\" holds resource \"" +
2392 resources_[
static_cast<std::size_t
>(tr.resource_)]->name() +
2393 "\" and competes for place \"" + places_[p]->name() +
"\" with " +
2394 transition_list(rivals) +
2395 "; in the SPN the resource is acquired by an immediate transition, "
2396 "which would always win that race.");
2400 std::shared_ptr<Network> network = std::make_shared<Network>(name_ +
"_spn");
2402 std::size_t source = 0, sink = 0;
2404 source = net.add_source(pre +
"source");
2405 sink = net.add_sink(pre +
"sink");
2407 std::vector<std::size_t> pl(nP);
2408 for (std::size_t p = 0; p < nP; ++p) pl[p] = net.add_place(places_[p]->name());
2409 const std::size_t p_arr = net.add_place(pre +
"arrivals");
2410 const std::size_t p_cnt = net.add_place(pre +
"cases");
2411 std::vector<std::size_t> p_res(nR);
2412 for (std::size_t r = 0; r < nR; ++r) p_res[r] = net.add_place(pre +
"res_" + resources_[r]->name());
2413 std::vector<std::size_t> tr_(nT), acq(nT, 0), p_busy(nT, 0);
2414 for (std::size_t t = 0; t < nT; ++t) {
2415 tr_[t] = net.add_transition(transitions_[t]->
name());
2416 if (transitions_[t]->resource_ >= 0) {
2417 acq[t] = net.add_transition(pre +
"acq_" + transitions_[t]->
name());
2418 p_busy[t] = net.add_place(pre +
"busy_" + transitions_[t]->
name());
2421 const std::size_t t_start = net.add_transition(pre +
"start");
2422 const std::size_t t_end = net.add_transition(pre +
"end");
2427 for (std::size_t r = 0; r < nR; ++r) n_res += resources_[r]->servers();
2428 jc = net.add_closed_class(
"Case",
static_cast<double>(closed_cases_) + n_res, p_arr, 0);
2430 jc = net.add_open_class(
"Case", 0);
2431 net.set_arrival(source, jc, arrival_);
2434 auto arc = [&R](std::size_t a, std::size_t b) { R.set(1, 1, a, b, 1.0); };
2436 std::size_t m = net.add_mode(t_start,
"start");
2438 net.set_enabling_conditions(t_start, m, jc, p_arr, 1);
2439 net.set_firing_outcome(t_start, m, jc, pl[source_], 1);
2440 net.set_firing_outcome(t_start, m, jc, p_cnt, 1);
2441 arc(p_arr, t_start);
2442 arc(t_start, pl[source_]);
2443 arc(t_start, p_cnt);
2444 m = net.add_mode(t_end,
"end");
2446 net.set_enabling_conditions(t_end, m, jc, pl[sink_], 1);
2447 net.set_enabling_conditions(t_end, m, jc, p_cnt, 1);
2448 arc(pl[sink_], t_end);
2451 net.set_firing_outcome(t_end, m, jc, p_arr, 1);
2454 net.set_firing_outcome(t_end, m, jc, sink, 1);
2458 std::shared_ptr<Compiled> c = std::make_shared<Compiled>();
2459 for (std::size_t r = 0; r < nR; ++r)
2460 c->resources.push_back(
Compiled::Res{resources_[r]->name(), resources_[r]->servers(), std::string(), {}});
2461 for (std::size_t t = 0; t < nT; ++t) {
2462 const WFNTransition& w = *transitions_[t];
2463 if (w.resource_ < 0) {
2464 m = net.add_mode(tr_[t], w.name());
2465 set_transition_timing(net, tr_[t], m, w, w.servers());
2466 for (std::size_t p = 0; p < nP; ++p)
2467 if (inc.pre[p][t] > 0) {
2468 net.set_enabling_conditions(tr_[t], m, jc, pl[p],
static_cast<double>(inc.pre[p][t]));
2471 for (std::size_t p = 0; p < nP; ++p)
2472 if (inc.post[p][t] > 0) {
2473 net.set_firing_outcome(tr_[t], m, jc, pl[p],
static_cast<double>(inc.post[p][t]));
2477 const std::size_t r =
static_cast<std::size_t
>(w.resource_);
2478 const std::size_t ma = net.add_mode(acq[t],
"acq_" + w.name());
2480 net.set_firing_weight(acq[t], ma, w.weight());
2481 net.set_firing_priority(acq[t], ma, w.priority());
2482 for (std::size_t p = 0; p < nP; ++p)
2483 if (inc.pre[p][t] > 0) {
2484 net.set_enabling_conditions(acq[t], ma, jc, pl[p],
static_cast<double>(inc.pre[p][t]));
2487 net.set_enabling_conditions(acq[t], ma, jc, p_res[r], 1);
2488 arc(p_res[r], acq[t]);
2489 net.set_firing_outcome(acq[t], ma, jc, p_busy[t], 1);
2490 arc(acq[t], p_busy[t]);
2491 m = net.add_mode(tr_[t], w.name());
2492 set_transition_timing(net, tr_[t], m, w, std::numeric_limits<double>::infinity());
2493 net.set_enabling_conditions(tr_[t], m, jc, p_busy[t], 1);
2494 arc(p_busy[t], tr_[t]);
2495 for (std::size_t p = 0; p < nP; ++p)
2496 if (inc.post[p][t] > 0) {
2497 net.set_firing_outcome(tr_[t], m, jc, pl[p],
static_cast<double>(inc.post[p][t]));
2500 net.set_firing_outcome(tr_[t], m, jc, p_res[r], 1);
2501 arc(tr_[t], p_res[r]);
2502 c->busy.push_back(
Compiled::Busy{w.name(), pre +
"busy_" + w.name()});
2503 c->resources[r].busy.push_back(c->busy.size() - 1);
2507 for (std::size_t p = 0; p < nP; ++p) net.set_initial_marking(pl[p], {0.0});
2508 net.set_initial_marking(p_cnt, {0.0});
2509 for (std::size_t t = 0; t < nT; ++t)
2510 if (p_busy[t] != 0) net.set_initial_marking(p_busy[t], {0.0});
2511 for (std::size_t r = 0; r < nR; ++r) net.set_initial_marking(p_res[r], {resources_[r]->servers()});
2512 net.set_initial_marking(p_arr, {closed ?
static_cast<double>(closed_cases_) : 0.0});
2513 c->network = network;
2515 c->k = closed_cases_;
2520 c->lambda = 1.0 / arrival_.mean;
2522 c->counter = pre +
"cases";
2523 for (std::size_t p = 0; p < nP; ++p) c->places.push_back(places_[p]->
name());
2524 std::vector<Compiled::Res> kept;
2526 if (res.servers > 0 && !res.busy.empty()) kept.push_back(res);
2527 c->resources = kept;
2533 std::shared_ptr<Network> build_plain_network(
long k)
const {
2534 const Inc inc = incidence();
2535 const std::size_t nP = inc.nP, nT = inc.nT;
2536 std::shared_ptr<Network> model = std::make_shared<Network>(name_);
2538 std::vector<std::size_t> pl(nP), tr(nT);
2539 for (std::size_t p = 0; p < nP; ++p) pl[p] = net.add_place(places_[p]->name());
2540 for (std::size_t t = 0; t < nT; ++t) tr[t] = net.add_transition(transitions_[t]->name());
2541 const std::size_t jc = net.add_closed_class(
"Case",
static_cast<double>(k), pl[source_], 0);
2543 for (std::size_t t = 0; t < nT; ++t) {
2544 const WFNTransition& w = *transitions_[t];
2545 const std::size_t m = net.add_mode(tr[t], w.name());
2546 set_transition_timing(net, tr[t], m, w, w.servers());
2547 for (std::size_t p = 0; p < nP; ++p)
2548 if (inc.pre[p][t] > 0) {
2549 net.set_enabling_conditions(tr[t], m, jc, pl[p],
static_cast<double>(inc.pre[p][t]));
2550 R.set(1, 1, pl[p], tr[t], 1.0);
2552 for (std::size_t p = 0; p < nP; ++p)
2553 if (inc.post[p][t] > 0) {
2554 net.set_firing_outcome(tr[t], m, jc, pl[p],
static_cast<double>(inc.post[p][t]));
2555 R.set(1, 1, tr[t], pl[p], 1.0);
2559 for (std::size_t p = 0; p < nP; ++p)
2560 net.set_initial_marking(pl[p], {static_cast<double>(static_cast<long>(p) == source_ ? k : 0)});