The Gaudi Framework  v36r16 (ea80daf8)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
PrecedenceSvc.cpp
Go to the documentation of this file.
1 /***********************************************************************************\
2 * (c) Copyright 1998-2019 CERN for the benefit of the LHCb and ATLAS collaborations *
3 * *
4 * This software is distributed under the terms of the Apache version 2 licence, *
5 * copied verbatim in the file "LICENSE". *
6 * *
7 * In applying this licence, CERN does not waive the privileges and immunities *
8 * granted to it by virtue of its status as an Intergovernmental Organization *
9 * or submit itself to any jurisdiction. *
10 \***********************************************************************************/
11 #include "PrecedenceSvc.h"
12 #include "EventSlot.h"
16 
17 #include <Gaudi/Algorithm.h>
18 #include <Gaudi/Sequence.h>
19 
20 #define ON_DEBUG if ( msgLevel( MSG::DEBUG ) )
21 #define ON_VERBOSE if ( msgLevel( MSG::VERBOSE ) )
22 
24 
25 // ============================================================================
26 // Initialization
27 // ============================================================================
28 StatusCode PrecedenceSvc::initialize() {
29  using namespace concurrency;
30 
31  auto sc = Service::initialize(); // parent class must be initialized first
32  if ( sc.isFailure() ) {
33  fatal() << "Base class failed to initialize" << endmsg;
34  return sc;
35  }
36 
37  // prepare a directory to dump precedence analysis files to.
38  if ( m_dumpPrecTrace || m_dumpPrecRules ) {
39  if ( !boost::filesystem::create_directory( m_dumpDirName ) ) {
40  error() << "Could not create directory " << m_dumpDirName
41  << "required "
42  "for task precedence tracing"
43  << endmsg;
44  return StatusCode::FAILURE;
45  }
46  }
47 
48  if ( m_dumpPrecRules ) m_PRGraph.enableAnalysis();
49 
50  // Get the algo resource pool
51  m_algResourcePool = serviceLocator()->service( "AlgResourcePool" );
52  if ( !m_algResourcePool.isValid() ) {
53  fatal() << "Error retrieving AlgoResourcePool" << endmsg;
54  return StatusCode::FAILURE;
55  }
56 
57  info() << "Assembling CF and DF task precedence rules" << endmsg;
58 
59  ON_DEBUG debug() << "Assembling CF precedence realm:" << endmsg;
60  // create the root CF node
61  m_PRGraph.addHeadNode( "RootDecisionHub", Concurrent{ true }, PromptDecision{ false }, ModeOr{ true },
62  AllPass{ true }, Inverted{ false } );
63  // assemble the CF rules
64  for ( const auto& ialgoPtr : m_algResourcePool->getTopAlgList() ) {
65  auto algorithm = dynamic_cast<Gaudi::Algorithm*>( ialgoPtr );
66  if ( !algorithm ) fatal() << "Conversion from IAlgorithm to Gaudi::Algorithm failed" << endmsg;
67  sc = assembleCFRules( algorithm, "RootDecisionHub" );
68  if ( sc.isFailure() ) {
69  fatal() << "Could not assemble the CF precedence realm" << endmsg;
70  return sc;
71  }
72  }
73 
74  if ( m_ignoreDFRules ) {
75  warning() << "Ignoring DF precedence rules, disabling all associated features" << endmsg;
76  return StatusCode::SUCCESS;
77  }
78 
79  ON_DEBUG debug() << "Assembling DF precedence realm:" << endmsg;
80  sc = m_PRGraph.initialize();
81  if ( sc.isFailure() ) {
82  fatal() << "Could not assemble the DF precedence realm" << endmsg;
83  return sc;
84  }
85 
86  // Rank algorithms if a prioritization rule is supplied
87  if ( m_mode == "PCE" ) {
89  m_PRGraph.rankAlgorithms( ranker );
90  } else if ( m_mode == "COD" ) {
92  m_PRGraph.rankAlgorithms( ranker );
93  } else if ( m_mode == "E" ) {
94  auto ranker = concurrency::RankerByEccentricity();
95  m_PRGraph.rankAlgorithms( ranker );
96  } else if ( m_mode == "T" ) {
97  auto ranker = concurrency::RankerByTiming();
98  m_PRGraph.rankAlgorithms( ranker );
99  } else if ( m_mode == "DRE" ) {
101  m_PRGraph.rankAlgorithms( ranker );
102  } else if ( !m_mode.empty() ) {
103  error() << "Requested prioritization rule '" << m_mode << "' is unknown" << endmsg;
104  return StatusCode::FAILURE;
105  }
106 
107  if ( m_showDataFlow ) { debug() << m_PRGraph.dumpDataFlow() << endmsg; }
108 
109  if ( m_verifyRules ) {
110  ON_DEBUG debug() << "Verifying task precedence rules" << endmsg;
111 
112  // Check if CF properties are self-consistent
113  auto propValidator = concurrency::NodePropertiesValidator();
114  m_PRGraph.accept( propValidator );
115  if ( !propValidator.passed() )
116  warning() << propValidator.reply() << endmsg;
117  else
118  ON_DEBUG debug() << propValidator.reply() << endmsg;
119 
120  // Check for violations in the DF topology
121  auto prodValidator = concurrency::ProductionAmbiguityFinder();
122  m_PRGraph.accept( prodValidator );
123  if ( !prodValidator.passed() ) {
124  error() << prodValidator.reply() << endmsg;
125  return StatusCode::FAILURE;
126  } else {
127  ON_DEBUG debug() << prodValidator.reply() << endmsg;
128  }
129 
130  auto sccFinder = concurrency::TarjanSCCFinder();
131  m_PRGraph.accept( sccFinder );
132  if ( !sccFinder.passed() ) {
133  error() << sccFinder.reply() << endmsg;
134  return StatusCode::FAILURE;
135  } else {
136  ON_DEBUG debug() << sccFinder.reply() << endmsg;
137  }
138  }
139 
140  if ( sc.isSuccess() ) info() << "PrecedenceSvc initialized successfully" << endmsg;
141 
142  return sc;
143 }
144 
145 // ============================================================================
147  unsigned int recursionDepth ) {
148  using namespace concurrency;
149 
151 
152  ++recursionDepth;
153 
154  bool isGaudiSequencer( false );
155  bool isAthSequencer( false );
156 
157  if ( !algo->isSequence() ) {
158  ON_DEBUG debug() << std::string( recursionDepth, ' ' ) << "Algorithm '" << algo->name() << "' discovered" << endmsg;
159  sc = m_PRGraph.addAlgorithmNode( algo, parentName, false, false );
160  return sc;
161  } else {
162  if ( algo->hasProperty( "ShortCircuit" ) )
163  isGaudiSequencer = true;
164  else if ( algo->hasProperty( "StopOverride" ) )
165  isAthSequencer = true;
166  }
167 
168  auto seq = dynamic_cast<Gaudi::Sequence*>( algo );
169  if ( seq == 0 ) {
170  error() << "Algorithm " << algo->name() << " has isSequence==true, but unable to dcast to Sequence" << endmsg;
171  return StatusCode::FAILURE;
172  }
173 
174  auto subAlgorithms = seq->subAlgorithms();
175 
176  // Recursively unroll
177  ON_DEBUG debug() << std::string( recursionDepth, ' ' ) << "Decision hub '" << algo->name() << "' discovered"
178  << endmsg;
179  bool modeOr = false;
180  bool allPass = false;
181  bool promptDecision = false;
182  bool isSequential = false;
183  bool isInverted = false;
184 
185  if ( isGaudiSequencer ) {
186  modeOr = ( algo->getProperty( "ModeOR" ).toString() == "True" );
187  allPass = ( algo->getProperty( "IgnoreFilterPassed" ).toString() == "True" );
188  promptDecision = ( algo->getProperty( "ShortCircuit" ).toString() == "True" );
189  isInverted = ( algo->getProperty( "Invert" ).toString() == "True" );
190  if ( allPass ) promptDecision = false; // standard GaudiSequencer behavior on all pass is to execute everything
191  isSequential = ( algo->hasProperty( "Sequential" ) && ( algo->getProperty( "Sequential" ).toString() == "True" ) );
192  } else if ( isAthSequencer ) {
193  modeOr = ( algo->getProperty( "ModeOR" ).toString() == "True" );
194  allPass = ( algo->getProperty( "IgnoreFilterPassed" ).toString() == "True" );
195  promptDecision = ( algo->getProperty( "StopOverride" ).toString() == "False" );
196  isSequential = ( algo->hasProperty( "Sequential" ) && ( algo->getProperty( "Sequential" ).toString() == "True" ) );
197  }
198  sc = m_PRGraph.addDecisionHubNode( algo, parentName, Concurrent{ !isSequential }, PromptDecision{ promptDecision },
199  ModeOr{ modeOr }, AllPass{ allPass }, Inverted{ isInverted } );
200  if ( sc.isFailure() ) {
201  error() << "Failed to add DecisionHub " << algo->name() << " to graph of precedence rules" << endmsg;
202  return sc;
203  }
204 
205  for ( auto subalgo : *subAlgorithms ) {
206  sc = assembleCFRules( subalgo, algo->name(), recursionDepth );
207  if ( sc.isFailure() ) {
208  error() << "Algorithm " << subalgo->name() << " could not be flattened" << endmsg;
209  return sc;
210  }
211  }
212  return sc;
213 }
214 
215 // ============================================================================
217 
218  if ( Cause::source::Task == cause.m_source ) {
219  ON_VERBOSE verbose() << "Triggering bottom-up traversal at node '" << cause.m_sourceName << "'" << endmsg;
220  auto visitor = concurrency::DecisionUpdater( slot, cause, m_dumpPrecTrace );
221  m_PRGraph.getAlgorithmNode( cause.m_sourceName )->accept( visitor );
222  } else {
223  ON_VERBOSE verbose() << "Triggering top-down traversal at the root node" << endmsg;
224  auto visitor = concurrency::Supervisor( slot, cause, m_dumpPrecTrace );
225  m_PRGraph.getHeadNode()->accept( visitor );
226  }
227 
228  if ( m_dumpPrecTrace )
229  if ( CFRulesResolved( slot.parentSlot ? *slot.parentSlot : slot ) )
230  dumpPrecedenceTrace( slot.parentSlot ? *slot.parentSlot : slot );
231 
232  if ( m_dumpPrecRules )
233  if ( CFRulesResolved( slot.parentSlot ? *slot.parentSlot : slot ) )
234  dumpPrecedenceRules( slot.parentSlot ? *slot.parentSlot : slot );
235 
236  return StatusCode::SUCCESS;
237 }
238 
239 // ============================================================================
241 
242  Cause cs = { Cause::source::Root, "RootDecisionHub" };
243  auto visitor = concurrency::RunSimulator( slot, cs );
244 
245  auto& nodeDecisions = slot.controlFlowState;
246 
247  std::vector<int> prevNodeDecisions;
248  int cntr = 0;
249  std::vector<int> counters;
250 
251  while ( !CFRulesResolved( slot ) ) {
252  cntr += 1;
253  int prevAlgosNum = visitor.m_nodesSucceeded;
254  ON_DEBUG debug() << " Proceeding with iteration #" << cntr << endmsg;
255  prevNodeDecisions = slot.controlFlowState;
256  m_PRGraph.getHeadNode()->accept( visitor );
257  if ( prevNodeDecisions == nodeDecisions ) {
258  error() << " No progress on iteration " << cntr << " detected, node decisions are:" << nodeDecisions << endmsg;
259  return StatusCode::FAILURE;
260  }
261  info() << " Iteration #" << cntr << " finished, total algorithms executed: " << visitor.m_nodesSucceeded
262  << endmsg;
263 
265  s << cntr << ", " << ( visitor.m_nodesSucceeded - prevAlgosNum ) << "\n";
266 
267  std::ofstream myfile;
268  myfile.open( "RunSimulation.csv", std::ios::app );
269  myfile << s.str();
270  myfile.close();
271 
272  if ( visitor.m_nodesSucceeded != prevAlgosNum ) counters.push_back( visitor.m_nodesSucceeded );
273  }
274 
275  info() << "Asymptotical intra-event speedup: " << (float)visitor.m_nodesSucceeded / (float)counters.size() << endmsg;
276 
277  // Reset algorithm states and node decisions
278  slot.algsStates.reset();
279  nodeDecisions.assign( nodeDecisions.size(), -1 );
280 
281  return StatusCode::SUCCESS;
282 }
283 
284 // ============================================================================
286  return ( -1 != slot.controlFlowState[m_PRGraph.getHeadNode()->getNodeIndex()] ? true : false );
287 }
288 
289 // ============================================================================
291 
292  info() << std::endl << "==================== Control Flow Configuration ==================" << std::endl << std::endl;
293  info() << m_PRGraph.dumpControlFlow() << endmsg;
294 }
295 // ============================================================================
297  info() << std::endl << "===================== Data Flow Configuration ====================" << std::endl;
298  info() << m_PRGraph.dumpDataFlow() << endmsg;
299 }
300 
301 // ============================================================================
303 
305  m_PRGraph.printState( ss, slot, 0 );
306  return ss.str();
307 }
308 
309 // ============================================================================
311 
312  if ( !m_dumpPrecRules ) {
313  warning() << "To trace temporal and topological aspects of execution flow, "
314  << "set DumpPrecedenceRules property to True " << endmsg;
315  return;
316  }
317 
318  ON_DEBUG debug() << "Dumping temporal precedence rules" << endmsg;
319 
320  std::string fileName;
321  if ( m_dumpPrecRulesFile.empty() ) {
322  const auto& eventID = slot.eventContext->eventID();
323  fileName = "rules_evt-" + std::to_string( eventID.event_number() ) + "_slot-" +
324  std::to_string( slot.eventContext->slot() ) + "_run-" + std::to_string( eventID.run_number() ) +
325  ".graphml";
326  } else {
327  fileName = m_dumpPrecRulesFile;
328  }
329 
331  pth.append( fileName );
332 
333  m_PRGraph.dumpPrecRules( pth, slot );
334 }
335 
336 // ============================================================================
338 
339  if ( !m_dumpPrecTrace ) {
340  warning() << "To trace task precedence patterns, set DumpPrecedenceTrace "
341  << "property to True " << endmsg;
342  return;
343  }
344 
345  ON_DEBUG debug() << "Dumping temporal precedence trace" << endmsg;
346 
347  std::string fileName;
348  if ( m_dumpPrecTraceFile.empty() ) {
349  const auto& eventID = slot.eventContext->eventID();
350  fileName = "trace_evt-" + std::to_string( eventID.event_number() ) + "_slot-" +
351  std::to_string( slot.eventContext->slot() ) + "_run-" + std::to_string( eventID.run_number() ) +
352  ".graphml";
353  } else {
354  fileName = m_dumpPrecTraceFile;
355  }
356 
358  pth.append( fileName );
359 
360  m_PRGraph.dumpPrecTrace( pth, slot );
361 }
362 
363 // ============================================================================
364 // Finalize
365 // ============================================================================
EventSlot::eventContext
std::unique_ptr< EventContext > eventContext
Cache for the eventContext.
Definition: EventSlot.h:83
PrecedenceSvc
A service to resolve the task execution precedence.
Definition: PrecedenceSvc.h:31
PrecedenceSvc::m_dumpPrecRules
Gaudi::Property< bool > m_dumpPrecRules
Definition: PrecedenceSvc.h:97
PrecedenceSvc::dumpPrecedenceTrace
void dumpPrecedenceTrace(const EventSlot &) override
Dump precedence trace (available only in DEBUG mode, and must be enabled with the corresponding servi...
Definition: PrecedenceSvc.cpp:337
Service::initialize
StatusCode initialize() override
Definition: Service.cpp:118
concurrency::PrecedenceRulesGraph::printState
void printState(std::stringstream &output, EventSlot &slot, const unsigned int &recursionLevel) const
Print a string representing the control flow state.
Definition: PrecedenceRulesGraph.cpp:260
std::string
STL class.
concurrency::PrecedenceRulesGraph::dumpPrecTrace
void dumpPrecTrace(const boost::filesystem::path &, const EventSlot &slot)
dump to file the precedence trace
Definition: PrecedenceRulesGraph.cpp:672
concurrency::Supervisor
Definition: Promoters.h:64
PrecedenceSvc::dumpPrecedenceRules
void dumpPrecedenceRules(const EventSlot &) override
Dump precedence rules (available only in DEBUG mode, and must be enabled with the corresponding servi...
Definition: PrecedenceSvc.cpp:310
Read.app
app
Definition: Read.py:36
PrecedenceSvc::m_PRGraph
concurrency::PrecedenceRulesGraph m_PRGraph
Graph of precedence rules.
Definition: PrecedenceSvc.h:82
AtlasMCRecoFullPrecedenceDump.path
path
Definition: AtlasMCRecoFullPrecedenceDump.py:49
Gaudi::Algorithm::name
const std::string & name() const override
The identifying name of the algorithm object.
Definition: Algorithm.cpp:528
PrecedenceSvc::m_dumpPrecTraceFile
Gaudi::Property< std::string > m_dumpPrecTraceFile
Definition: PrecedenceSvc.h:92
EventContext::eventID
const EventIDBase & eventID() const
Definition: EventContext.h:55
EventSlot.h
gaudirun.s
string s
Definition: gaudirun.py:348
std::vector< int >
std::vector::size
T size(T... args)
PrecedenceSvc::assembleCFRules
StatusCode assembleCFRules(Gaudi::Algorithm *, const std::string &, unsigned int recursionDepth=0)
Definition: PrecedenceSvc.cpp:146
concurrency::RankerByProductConsumption
Definition: Rankers.h:20
EventSlot
Class representing an event slot.
Definition: EventSlot.h:24
concurrency::PrecedenceRulesGraph::dumpPrecRules
void dumpPrecRules(const boost::filesystem::path &, const EventSlot &slot)
dump to file the precedence rules
Definition: PrecedenceRulesGraph.cpp:631
std::stringstream
STL class.
PropertyHolder::getProperty
StatusCode getProperty(Gaudi::Details::PropertyBase *p) const override
get the property
Definition: PropertyHolder.h:190
concurrency::ProductionAmbiguityFinder
Definition: Validators.h:132
PrecedenceSvc::printState
const std::string printState(EventSlot &) const override
Definition: PrecedenceSvc.cpp:302
Service::finalize
StatusCode finalize() override
Definition: Service.cpp:222
concurrency::AlgorithmNode::accept
bool accept(IGraphVisitor &visitor) override
Visitor entry point.
Definition: PrecedenceRulesGraph.cpp:191
std::vector::push_back
T push_back(T... args)
PrecedenceSvc::simulate
StatusCode simulate(EventSlot &) const override
Simulate execution flow.
Definition: PrecedenceSvc.cpp:240
PrecedenceSvc::dumpDataFlow
void dumpDataFlow() const override
Definition: PrecedenceSvc.cpp:296
ON_VERBOSE
#define ON_VERBOSE
Definition: PrecedenceSvc.cpp:21
StatusCode
Definition: StatusCode.h:65
Gaudi::tagged_bool_ns::tagged_bool
Definition: TaggedBool.h:16
PrecedenceSvc::m_dumpDirName
boost::filesystem::path m_dumpDirName
Precedence analysis facilities.
Definition: PrecedenceSvc.h:88
PrecedenceSvc.h
EventSlot::parentSlot
EventSlot * parentSlot
Pointer to parent slot (null for top level)
Definition: EventSlot.h:96
std::ofstream
STL class.
concurrency::PrecedenceRulesGraph::addAlgorithmNode
StatusCode addAlgorithmNode(Gaudi::Algorithm *daughterAlgo, const std::string &parentName, bool inverted, bool allPass)
Add algorithm node.
Definition: PrecedenceRulesGraph.cpp:372
EventContext::slot
ContextID_t slot() const
Definition: EventContext.h:51
Cause::m_source
source m_source
Definition: PrecedenceRulesGraph.h:402
Gaudi::Algorithm
Base class from which all concrete algorithm classes should be derived.
Definition: Algorithm.h:90
concurrency::RankerByEccentricity
Definition: Rankers.h:49
std::to_string
T to_string(T... args)
PrecedenceSvc::CFRulesResolved
bool CFRulesResolved(EventSlot &) const override
Check if the root CF decision is resolved.
Definition: PrecedenceSvc.cpp:285
PrecedenceSvc::m_dumpPrecTrace
Gaudi::Property< bool > m_dumpPrecTrace
Definition: PrecedenceSvc.h:90
Validators.h
Algorithm.h
HistogramsTiming.seq
seq
Definition: HistogramsTiming.py:24
std::ofstream::open
T open(T... args)
genconfuser.verbose
verbose
Definition: genconfuser.py:29
concurrency::PrecedenceRulesGraph::addDecisionHubNode
StatusCode addDecisionHubNode(Gaudi::Algorithm *daughterAlgo, const std::string &parentName, concurrency::Concurrent, concurrency::PromptDecision, concurrency::ModeOr, concurrency::AllPass, concurrency::Inverted)
Add a node, which aggregates decisions of direct daughter nodes.
Definition: PrecedenceRulesGraph.cpp:455
endmsg
MsgStream & endmsg(MsgStream &s)
MsgStream Modifier: endmsg. Calls the output method of the MsgStream.
Definition: MsgStream.h:203
AlgsExecutionStates::reset
void reset()
Definition: AlgsExecutionStates.h:63
Cause::source::Root
@ Root
concurrency::PrecedenceRulesGraph::dumpControlFlow
std::string dumpControlFlow() const
Print out control flow of Algorithms and Sequences.
Definition: PrecedenceRulesGraph.cpp:564
PrecedenceSvc::iterate
StatusCode iterate(EventSlot &, const Cause &) override
Infer the precedence effect caused by an execution flow event.
Definition: PrecedenceSvc.cpp:216
concurrency
Definition: PrecedenceRulesGraph.cpp:38
Rankers.h
StatusCode::isFailure
bool isFailure() const
Definition: StatusCode.h:129
concurrency::DecisionUpdater
Definition: Promoters.h:48
concurrency::PrecedenceRulesGraph::getAlgorithmNode
AlgorithmNode * getAlgorithmNode(const std::string &algoName) const
Get the AlgorithmNode from by algorithm name using graph index.
Definition: PrecedenceRulesGraph.h:666
concurrency::PrecedenceRulesGraph::dumpDataFlow
std::string dumpDataFlow() const
Print out all data origins and destinations, as reflected in the EF graph.
Definition: PrecedenceRulesGraph.cpp:597
StatusCode::SUCCESS
constexpr static const auto SUCCESS
Definition: StatusCode.h:100
Cause::source::Task
@ Task
std::endl
T endl(T... args)
concurrency::NodePropertiesValidator
Definition: Validators.h:28
DECLARE_COMPONENT
#define DECLARE_COMPONENT(type)
Definition: PluginServiceV1.h:46
Promoters.h
PrecedenceSvc::m_dumpPrecRulesFile
Gaudi::Property< std::string > m_dumpPrecRulesFile
Definition: PrecedenceSvc.h:98
Sequence.h
PrecedenceSvc::dumpControlFlow
void dumpControlFlow() const override
Dump precedence rules.
Definition: PrecedenceSvc.cpp:290
std::stringstream::str
T str(T... args)
ON_DEBUG
#define ON_DEBUG
Definition: PrecedenceSvc.cpp:20
concurrency::RankerByCummulativeOutDegree
Definition: Rankers.h:28
EventSlot::controlFlowState
std::vector< int > controlFlowState
State of the control flow.
Definition: EventSlot.h:87
concurrency::DecisionNode::accept
bool accept(IGraphVisitor &visitor) override
Visitor entry point.
Definition: PrecedenceRulesGraph.cpp:65
StatusCode::FAILURE
constexpr static const auto FAILURE
Definition: StatusCode.h:101
Gaudi::Sequence
Definition: Sequence.h:18
concurrency::TarjanSCCFinder
Definition: Validators.h:169
concurrency::PrecedenceRulesGraph::getHeadNode
DecisionNode * getHeadNode() const
Get head node.
Definition: PrecedenceRulesGraph.h:661
EventSlot::algsStates
AlgsExecutionStates algsStates
Vector of algorithms states.
Definition: EventSlot.h:85
Cause
Definition: PrecedenceRulesGraph.h:399
concurrency::RankerByTiming
Definition: Rankers.h:41
Gaudi::Algorithm::isSequence
bool isSequence() const override
Are we a Sequence?
Definition: Algorithm.h:198
concurrency::RunSimulator
Definition: Promoters.h:87
concurrency::ControlFlowNode::getNodeIndex
const unsigned int & getNodeIndex() const
Get node index.
Definition: PrecedenceRulesGraph.h:430
PrecedenceSvc::finalize
StatusCode finalize() override
Finalize.
Definition: PrecedenceSvc.cpp:366
concurrency::RankerByDataRealmEccentricity
Definition: Rankers.h:57
Cause::m_sourceName
std::string m_sourceName
Definition: PrecedenceRulesGraph.h:403
PropertyHolder::hasProperty
bool hasProperty(std::string_view name) const override
Return true if we have a property with the given name.
Definition: PropertyHolder.h:227