LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_fluid_refusal.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_SN_SN_FLUID_REFUSAL_H
6#define LINE_API_SN_SN_FLUID_REFUSAL_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * Port of matlab/src/api/sn/sn_fluid_refusal.m.
12 *
13 * The structural rules of a Fluid Stochastic Petri Net, shared by every solver that declares FluidPlace. A
14 * fluid place holds a level, not tokens, so a token arc on it, an initial token marking or a Source routed into
15 * it is refused; a fluid arc must name a fluid place; an immediate mode carries no flow, since it runs for zero
16 * time; and only an exponential clock can be re-timed by a level.
17 */
18
19#include <cmath>
20#include <cstddef>
21#include <string>
22#include <vector>
23
25#include "line/num/number.h"
26
27namespace line {
28namespace api {
29
30/** The verdict of `sn_fluid_refusal`: `ok` when no rule is broken, else the reason. */
32 bool ok = true;
33 std::string reason;
34};
35
36/**
37 * Whether the net breaks a structural rule of an FSPN. Always OK for a net with no fluid.
38 *
39 * @param sn the network structure
40 */
41template <class T>
44 const std::size_t I = sn.nodes.size(), K = sn.classes.size();
45 std::vector<bool> fluid(I + 1, false);
46 for (std::size_t ind = 1; ind <= I; ++ind) fluid[ind] = sn.is_fluid_node(ind);
47 for (std::size_t ind = 1; ind <= I; ++ind) {
48 if (!fluid[ind]) continue;
49 const auto im = sn.initmarking.find(ind);
50 if (im != sn.initmarking.end())
51 for (const T& tok : im->second)
52 if (num_traits<T>::to_double(tok) > 0.0) {
53 v.ok = false;
54 v.reason = "fluid place " + sn.nodes[ind - 1].name +
55 " holds an initial token marking; a fluid place holds a level, set with "
56 "setFluidLevel";
57 return v;
58 }
59 for (std::size_t src = 1; src <= I; ++src) {
60 if (sn.nodes[src - 1].nodetype != lang::NodeType::Source) continue;
61 bool linked = false;
62 if (sn.rtnodes.rows() >= I * K && sn.rtnodes.cols() >= I * K)
63 for (std::size_t r = 0; r < K && !linked; ++r)
64 for (std::size_t s = 0; s < K && !linked; ++s)
65 if (num_traits<T>::to_double(sn.rtnodes((src - 1) * K + r, (ind - 1) * K + s)) > 0.0)
66 linked = true;
67 if (linked) {
68 v.ok = false;
69 v.reason = "Source " + sn.nodes[src - 1].name + " is routed into fluid place " +
70 sn.nodes[ind - 1].name +
71 "; a fluid place receives fluid only through setFluidRate and setFluidImpulse";
72 return v;
73 }
74 }
75 }
76 for (const auto& kv : sn.transparam) {
77 const std::size_t ind = kv.first;
78 const qn::TransitionParam<T>& tp = kv.second;
79 const std::string& tname = sn.nodes[ind - 1].name;
80 for (std::size_t m = 0; m < tp.nmodes; ++m) {
81 const std::string mname = (m < tp.modenames.size() && !tp.modenames[m].empty())
82 ? tp.modenames[m]
83 : ("Mode" + std::to_string(m + 1));
84 const std::vector<Matrix<T>>* arcs[3] = {&tp.enabling, &tp.inhibiting, &tp.firing};
85 const char* arcname[3] = {"enabling", "inhibiting", "firing"};
86 for (int w = 0; w < 3; ++w) {
87 if (m >= arcs[w]->size()) continue;
88 const Matrix<T>& A = (*arcs[w])[m];
89 for (std::size_t p = 0; p < A.rows() && p < I; ++p) {
90 if (!fluid[p + 1]) continue;
91 for (std::size_t r = 0; r < A.cols(); ++r) {
92 // an absent inhibitor arc is stored as Inf, not 0
93 const double a = num_traits<T>::to_double(A(p, r));
94 if (!std::isfinite(a) || a == 0.0) continue;
95 v.ok = false;
96 v.reason = "mode " + mname + " of transition " + tname + " has a token " + arcname[w] +
97 " arc on fluid place " + sn.nodes[p].name +
98 "; a fluid place is read through setFluidGuard and moved through "
99 "setFluidRate/setFluidImpulse";
100 return v;
101 }
102 }
103 }
104 const bool isimm = m < tp.timing.size() && tp.timing[m] == lang::TimingStrategy::IMMEDIATE;
105 if (isimm && !tp.fluid_rates(m).empty()) {
106 v.ok = false;
107 v.reason = "immediate mode " + mname + " of transition " + tname +
108 " carries a fluid flow; an immediate mode runs for zero time, so its flow moves "
109 "nothing. Use setFluidImpulse";
110 return v;
111 }
112 std::vector<std::size_t> named;
113 for (const auto& a : tp.fluid_rates(m)) named.push_back(a.node);
114 for (const auto& g : tp.fluid_guards(m)) named.push_back(g.node);
115 for (const auto& a : tp.fluid_impulses(m)) named.push_back(a.node);
116 for (const auto& d : tp.fluid_deps(m)) named.push_back(d.node);
117 for (const auto& fd : tp.fluid_flow_deps(m)) {
118 bool flows = false;
119 for (const auto& a : tp.fluid_rates(m)) flows = flows || a.node == fd.node;
120 if (!flows) {
121 v.ok = false;
122 v.reason = "mode " + mname + " of transition " + tname +
123 " has a fluid flow dependence on place " +
124 (fd.node >= 1 && fd.node <= I ? sn.nodes[fd.node - 1].name : std::to_string(fd.node)) +
125 " but no flow into or out of it; set the flow with setFluidRate first";
126 return v;
127 }
128 for (std::size_t rd : fd.read)
129 if (rd == 0 || rd > I || sn.nodes[rd - 1].nodetype != lang::NodeType::Place || fluid[rd]) {
130 v.ok = false;
131 v.reason = "mode " + mname + " of transition " + tname +
132 " has a fluid flow dependence reading a node that is not a token place; a flow "
133 "multiplier reads the marking, and a level is read by setFluidRateDependence";
134 return v;
135 }
136 }
137 for (std::size_t nd : named)
138 if (nd == 0 || nd > I || !fluid[nd]) {
139 v.ok = false;
140 v.reason = "mode " + mname + " of transition " + tname + " has a fluid arc on place " +
141 (nd >= 1 && nd <= I ? sn.nodes[nd - 1].name : std::to_string(nd)) +
142 ", which is not a fluid place; declare it with setFluid(true)";
143 return v;
144 }
145 if (!tp.fluid_deps(m).empty() &&
146 (m >= tp.firingproc.size() || tp.firingproc[m].disabled ||
147 tp.firingproc[m].type != lang::ProcessType::EXP)) {
148 v.ok = false;
149 v.reason = "mode " + mname + " of transition " + tname +
150 " has a fluid rate dependence on a non-exponential firing time; only an "
151 "exponential clock can be re-timed by the level";
152 return v;
153 }
154 }
155 }
156 return v;
157}
158
159} // namespace api
160} // namespace line
161
162#endif // LINE_API_SN_SN_FLUID_REFUSAL_H
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
A network plus its refreshed NetworkStruct.
SnFluidVerdict sn_fluid_refusal(const qn::NetworkStruct< T > &sn)
Whether the net breaks a structural rule of an FSPN.
@ IMMEDIATE
fires with zero delay, resolved by weight and priority
Definition lang_types.h:370
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
The verdict of sn_fluid_refusal: ok when no rule is broken, else the reason.
The parameters of a Transition node: MATLAB sn.nodeparam{ind} for an SPN transition,...
const std::vector< FluidArc > & fluid_rates(std::size_t m) const
std::vector< lang::TimingStrategy > timing
immediate or timed
std::vector< std::string > modenames
std::vector< lang::Distrib< T > > firingproc
firing distribution per mode
const std::vector< FluidArc > & fluid_impulses(std::size_t m) const
std::vector< Matrix< T > > firing
firing[m](p,r): class-r tokens mode m moves to/from place p when it fires.
std::vector< Matrix< T > > enabling
enabling[m](p,r): class-r tokens of place p (0-based node) mode m needs.
const std::vector< FluidDep > & fluid_deps(std::size_t m) const
const std::vector< FluidFlowDep > & fluid_flow_deps(std::size_t m) const
std::vector< Matrix< T > > inhibiting
inhibiting[m](p,r): class-r tokens of p that BLOCK mode m (Inf = never).
const std::vector< FluidGuard > & fluid_guards(std::size_t m) const