BPMN-OS
BPMN for optimization and simulation
Loading...
Searching...
No Matches
BisectionalChoice.cpp
Go to the documentation of this file.
1#include "BisectionalChoice.h"
6#include <cassert>
7#include <stdexcept>
8#include <functional>
9#include <limits>
10#include <queue>
11//#include <iostream>
12
13using namespace BPMNOS::Execution;
14
16 : GreedyDispatcher(evaluator)
17 , enumeratedChoice(evaluator)
18{
19}
20
27
28
29void BisectionalChoice::notice(const Observable* observable) {
31 assert( dynamic_cast<const DecisionRequest*>(observable) );
32 auto request = static_cast<const DecisionRequest*>(observable);
33 // create pseudo decision
34 auto decision = std::make_shared<ChoiceDecision>(request->token, std::vector<number>{}, evaluator);
35 decisionsWithoutEvaluation.emplace_back( request->token->weak_from_this(), request->weak_from_this(), decision );
36 }
37 else {
38 GreedyDispatcher::notice(observable);
39 }
40}
41
42std::shared_ptr<Event> BisectionalChoice::dispatchEvent( [[maybe_unused]] const SystemState* systemState ) {
43//std::cout << "BisectionalChoice::dispatchEvent" << std::endl;
44 if ( systemState->currentTime > timestamp ) {
45 timestamp = systemState->currentTime;
46 clockTick();
47 }
48
49 for ( auto& [ token_ptr, request_ptr, _ ] : decisionsWithoutEvaluation ) {
50 auto request = request_ptr.lock();
51 assert( request );
52 // forget previous decision and find new best decision for the request
53 auto decision = determineBestChoices( request );
54 if ( decision ) {
55 addEvaluation( token_ptr, request_ptr, std::move(decision), decision->reward );
56 }
57 }
59
60 for ( auto [ cost, token_ptr, request_ptr, event_ptr ] : evaluatedDecisions ) {
61//std::cerr << "Best choice decision " << event_ptr.lock()->jsonify() << " evaluated with " << cost << std::endl;
62 return event_ptr.lock();
63 }
64
65//std::cerr << "No evaluated choice decision" << std::endl;
66 return nullptr;
67}
68
69std::shared_ptr<Decision> BisectionalChoice::determineBestChoices(std::shared_ptr<const DecisionRequest> request) {
70 auto token = request->token;
71 assert( token->node );
72 assert( token->node->represents<BPMNOS::Model::DecisionTask>() );
73 assert( token->node->extensionElements->represents<BPMNOS::Model::ExtensionElements>() );
74 auto extensionElements = token->node->extensionElements->as<BPMNOS::Model::ExtensionElements>();
75 assert( extensionElements->choices.size() );
76
77 if ( extensionElements->choices.size() > 1 ) {
78 // delegate to BestEnumeratedChoice
79//std::cerr << "delegate multiple choices" << std::endl;
80 return enumeratedChoice.determineBestChoices(request);
81 }
82
83 auto& choice = extensionElements->choices[0];
84
85 if ( !choice->enumeration.empty() ) {
86//std::cerr << "delegate enumeration" << std::endl;
87 // delegate to BestEnumeratedChoice
88 return enumeratedChoice.determineBestChoices(request);
89 }
90
91 if ( !choice->lowerBound && !choice->upperBound ) {
92 throw std::runtime_error("BisectionalChoice: choice requires bounds");
93 }
94
95 // Single choice with bounds (no enumeration) → use bisection
96 if ( choice->multipleOf ) {
97 return discreteBisection(request, choice.get());
98 }
99 else {
100// throw std::runtime_error("BisectionalChoice: continuous bisection is not yet supported");
101 // delegate to BestEnumeratedChoice
102 return enumeratedChoice.determineBestChoices(request);
103 }
104}
105
106BisectionalChoice::Candidate BisectionalChoice::evaluate(size_t index) {
107//std::cerr << "Evaluate " << index << ": " << values[index] << std::endl;
108 auto decision = std::make_shared<ChoiceDecision>(token, std::vector<number>{ values[index] }, evaluator);
109 decision->evaluate();
110 return { index, decision };
111}
112
113std::tuple<size_t, BisectionalChoice::Candidate, size_t> BisectionalChoice::findFeasible(size_t first, size_t last) {
114 const size_t npos = std::numeric_limits<size_t>::max();
115 if ( first > last ) return { npos, { npos, nullptr }, npos };
116
117 // BFS on intervals to find any feasible
118 std::queue<std::pair<size_t, size_t>> intervals;
119 intervals.push({first, last});
120
121 while ( !intervals.empty() ) {
122 auto [l, r] = intervals.front();
123 intervals.pop();
124
125 size_t mid = l + (r - l) / 2;
126 auto candidate = evaluate(mid);
127 if ( candidate.isFeasible() ) {
128 // Found feasible - return with nearest known infeasible bounds
129 size_t nearestLeft = (mid > first) ? l : npos;
130 size_t nearestRight = (mid < last) ? r : npos;
131 return { nearestLeft, candidate, nearestRight };
132 }
133
134 // mid is infeasible - continue searching both halves
135 if ( mid > l ) intervals.push({l, mid - 1});
136 if ( mid < r ) intervals.push({mid + 1, r});
137 }
138
139 return { npos, { npos, nullptr }, npos };
140}
141
142void BisectionalChoice::findBetweenFeasibleAndFeasible(Candidate left, Candidate right) {
143 if ( right.index <= left.index + 1 ) return;
144
145 size_t mid = left.index + (right.index - left.index) / 2;
146 auto candidate = evaluate(mid);
147
148 if ( !candidate.isFeasible() ) {
149 // Mid is infeasible - search both sides
150 findBetweenFeasibleAndInfeasible(left, mid);
151 findBetweenInfeasibleAndFeasible(mid, right);
152 }
153 else {
154 // Mid is feasible
155 if ( candidate.reward() > best.reward() ) {
156 best = candidate;
157 }
158 // Check if mid is worse than both boundaries -> peak is at a boundary
159 if ( candidate.reward() <= left.reward() && candidate.reward() <= right.reward() ) {
160 return; // Peak is at one of the boundaries, already checked
161 }
162 // Continue toward better side
163 if ( left.reward() >= right.reward() ) {
164 findBetweenFeasibleAndFeasible(left, candidate);
165 }
166 else {
167 findBetweenFeasibleAndFeasible(candidate, right);
168 }
169 }
170}
171
172void BisectionalChoice::findBetweenFeasibleAndInfeasible(Candidate feasible, size_t infeasibleIndex) {
173 while ( infeasibleIndex > feasible.index + 1 ) {
174 size_t mid = feasible.index + (infeasibleIndex - feasible.index + 1) / 2;
175 auto candidate = evaluate(mid);
176
177 if ( !candidate.isFeasible() ) {
178 // Still infeasible - narrow right boundary
179 infeasibleIndex = mid;
180 }
181 else if ( candidate.reward() <= best.reward() ) {
182 // Feasible but worse - done (past peak)
183 break;
184 }
185 else {
186 // Feasible and better - new best, continue toward infeasible
187 best = candidate;
188 feasible = candidate;
189 }
190 }
191}
192
193void BisectionalChoice::findBetweenInfeasibleAndFeasible(size_t infeasibleIndex, Candidate feasible) {
194 while ( feasible.index > infeasibleIndex + 1 ) {
195 size_t mid = infeasibleIndex + (feasible.index - infeasibleIndex) / 2;
196 auto candidate = evaluate(mid);
197
198 if ( !candidate.isFeasible() ) {
199 // Still infeasible - narrow left boundary
200 infeasibleIndex = mid;
201 }
202 else if ( candidate.reward() <= best.reward() ) {
203 // Feasible but worse - done (past peak)
204 break;
205 }
206 else {
207 // Feasible and better - new best, continue toward infeasible
208 best = candidate;
209 feasible = candidate;
210 }
211 }
212}
213
214std::shared_ptr<Decision> BisectionalChoice::discreteBisection(std::shared_ptr<const DecisionRequest> request, const BPMNOS::Model::Choice* choice) {
215 const size_t npos = std::numeric_limits<size_t>::max();
216
217 // Set member state for this search
218 token = request->token;
219 values = choice->getEnumeration(token->status, *token->data, token->globals);
220 best = { npos, nullptr };
221
222 if ( values.empty() ) {
223 return nullptr;
224 }
225
226 // Check both boundaries
227 auto left = evaluate(0);
228 auto right = (values.size() > 1) ? evaluate(values.size() - 1) : Candidate{ npos, nullptr };
229
230
231 if ( left.isFeasible() ) {
232 best = left;
233 }
234 if ( right.isFeasible() && (!best.isFeasible() || right.reward() > best.reward()) ) {
235 best = right;
236 }
237
238 if ( left.isFeasible() && right.isFeasible() ) {
239 // Both feasible - search between them
240 findBetweenFeasibleAndFeasible(left, right);
241 }
242 else if ( left.isFeasible() && !right.isFeasible() ) {
243 // Left feasible, right infeasible - search toward right
244 findBetweenFeasibleAndInfeasible(left, values.size() - 1);
245 }
246 else if ( !left.isFeasible() && right.isFeasible() ) {
247 // Left infeasible, right feasible - search toward left
248 findBetweenInfeasibleAndFeasible(0, right);
249 }
250 else {
251 // Both infeasible - find any feasible first
252 if ( values.size() <= 2 ) {
253 return nullptr;
254 }
255 auto [leftInfeasible, candidate, rightInfeasible] = findFeasible(1, values.size() - 2);
256 if ( !candidate.isFeasible() ) {
257 return nullptr;
258 }
259 best = candidate;
260
261 // Search both directions from feasible point
262 if ( leftInfeasible != npos ) {
263 findBetweenInfeasibleAndFeasible(leftInfeasible, best);
264 }
265 if ( rightInfeasible != npos ) {
266 findBetweenFeasibleAndInfeasible(best, rightInfeasible);
267 }
268 }
269
270//std::cerr << "Best " << best.index << std::endl;
271 return best.decision;
272}
273
std::shared_ptr< Decision > determineBestChoices(std::shared_ptr< const DecisionRequest > request)
std::shared_ptr< Decision > determineBestChoices(std::shared_ptr< const DecisionRequest > request)
std::shared_ptr< Event > dispatchEvent(const SystemState *systemState) override
void notice(const Observable *observable) override
void connect(Mediator *mediator) override
Represents an abstract base class for an evaluator determining feasibility and reward of a decision.
Definition Evaluator.h:15
Class for dispatching the event with the best evaluation.
void addEvaluation(WeakPtrs..., std::shared_ptr< Decision > decision, std::optional< double > reward)
void notice(const Observable *observable) override
void connect(Mediator *mediator) override
void addSubscriber(Observer *subscriber, ObservableTypes... observableTypes)
Definition Notifier.h:17
A class representing the state that the execution or simulation of a given scenario is in.
Definition SystemState.h:21
const BPMN::FlowNode * node
Definition Token.h:46
SharedValues * data
Pointer to the data of the owner or owned state machine subprocesses)
Definition Token.h:58
Class representing a choice to be made within a BPMNOS::Model::DecisionTask.
Definition Choice.h:21
std::vector< BPMNOS::number > getEnumeration(const BPMNOS::Values &status, const DataType &data, const BPMNOS::Values &globals) const
Returns the allowed values the attribute may take.
Definition Choice.cpp:188
Class representing a task in which one or more choices have to be made.
Class holding extension elements representing execution data for nodes.
std::unique_ptr< ExtensionElements > extensionElements
Definition bpmn++.h:16299
T * represents()
Attempts to cast the element to the specified type T.
Definition bpmn++.h:16236
Represents a pending decision.
Represents an abstract base class for a class that is an event listener and notifier.
Definition Mediator.h:13
virtual constexpr Type getObservableType() const =0