tess 1.0.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
tactical_assignment.h
1#pragma once
2
3#include <tess/core/shape.h>
4
5#include <algorithm>
6#include <cstddef>
7#include <cstdint>
8#include <functional>
9#include <limits>
10#include <span>
11#include <vector>
12
13namespace tess {
14
17 std::uint64_t requester = 0;
18 Coord3 origin{};
19 std::uint32_t priority = 0;
20};
21
24 std::uint64_t candidate = 0;
25 Coord3 position{};
26 std::uint32_t capacity = 1;
27};
28
31 bool feasible = false;
32 std::int64_t value = 0;
33};
34
37 std::uint64_t requester = 0;
38 std::uint64_t candidate = 0;
39 Coord3 position{};
40 std::int64_t score = 0;
41 bool assigned = false;
42};
43
45enum class TacticalAssignmentStatus : std::uint8_t {
46 Complete,
47 Partial,
48 InvalidInput,
49};
50
53 TacticalAssignmentStatus status = TacticalAssignmentStatus::Complete;
54 std::size_t assigned_count = 0;
55 std::span<const TacticalAssignment> assignments;
56};
57
60 public:
61 void reserve(std::size_t request_count, std::size_t candidate_count) {
62 request_order_.reserve(request_count);
63 candidate_order_.reserve(candidate_count);
64 remaining_capacity_.reserve(candidate_count);
65 assignments_.reserve(request_count);
66 }
67
68 private:
69 template <typename ScoreFn>
71 std::span<const TacticalRequest> requests,
72 std::span<const TacticalCandidate> candidates, ScoreFn&& score,
74
75 std::vector<std::size_t> request_order_;
76 std::vector<std::size_t> candidate_order_;
77 std::vector<std::uint32_t> remaining_capacity_;
78 std::vector<TacticalAssignment> assignments_;
79};
80
82template <typename ScoreFn>
84 std::span<const TacticalRequest> requests,
85 std::span<const TacticalCandidate> candidates, ScoreFn&& score,
87 scratch.assignments_.clear();
88 scratch.request_order_.resize(requests.size());
89 for (std::size_t i = 0; i < requests.size(); ++i) {
90 scratch.request_order_[i] = i;
91 }
92 std::sort(scratch.request_order_.begin(), scratch.request_order_.end(),
93 [&](std::size_t lhs, std::size_t rhs) {
94 return requests[lhs].requester < requests[rhs].requester;
95 });
96 for (std::size_t i = 1; i < scratch.request_order_.size(); ++i) {
97 if (requests[scratch.request_order_[i - 1]].requester ==
98 requests[scratch.request_order_[i]].requester) {
99 return {TacticalAssignmentStatus::InvalidInput, 0, {}};
100 }
101 }
102 scratch.candidate_order_.resize(candidates.size());
103 for (std::size_t i = 0; i < candidates.size(); ++i) {
104 scratch.candidate_order_[i] = i;
105 }
106 std::sort(scratch.candidate_order_.begin(), scratch.candidate_order_.end(),
107 [&](std::size_t lhs, std::size_t rhs) {
108 return candidates[lhs].candidate < candidates[rhs].candidate;
109 });
110 for (std::size_t i = 1; i < scratch.candidate_order_.size(); ++i) {
111 if (candidates[scratch.candidate_order_[i - 1]].candidate ==
112 candidates[scratch.candidate_order_[i]].candidate) {
113 return {TacticalAssignmentStatus::InvalidInput, 0, {}};
114 }
115 }
116
117 scratch.assignments_.resize(requests.size());
118 for (std::size_t i = 0; i < requests.size(); ++i) {
119 scratch.assignments_[i].requester = requests[i].requester;
120 }
121 std::sort(scratch.request_order_.begin(), scratch.request_order_.end(),
122 [&](std::size_t lhs, std::size_t rhs) {
123 if (requests[lhs].priority != requests[rhs].priority) {
124 return requests[lhs].priority > requests[rhs].priority;
125 }
126 return requests[lhs].requester < requests[rhs].requester;
127 });
128 scratch.remaining_capacity_.resize(candidates.size());
129 for (std::size_t i = 0; i < candidates.size(); ++i) {
130 scratch.remaining_capacity_[i] = candidates[i].capacity;
131 }
132
133 auto assigned_count = std::size_t{0};
134 for (const auto request_index : scratch.request_order_) {
135 auto best = candidates.size();
136 auto best_score = std::numeric_limits<std::int64_t>::max();
137 for (std::size_t candidate_index = 0; candidate_index < candidates.size();
138 ++candidate_index) {
139 if (scratch.remaining_capacity_[candidate_index] == 0) {
140 continue;
141 }
142 const auto candidate_score = static_cast<TacticalScore>(std::invoke(
143 score, requests[request_index], candidates[candidate_index]));
144 if (!candidate_score.feasible) {
145 continue;
146 }
147 if (best == candidates.size() || candidate_score.value < best_score ||
148 (candidate_score.value == best_score &&
149 candidates[candidate_index].candidate <
150 candidates[best].candidate)) {
151 best = candidate_index;
152 best_score = candidate_score.value;
153 }
154 }
155 if (best == candidates.size()) {
156 continue;
157 }
158 --scratch.remaining_capacity_[best];
159 scratch.assignments_[request_index] = TacticalAssignment{
160 requests[request_index].requester,
161 candidates[best].candidate,
162 candidates[best].position,
163 best_score,
164 true,
165 };
166 ++assigned_count;
167 }
168
169 const auto status = assigned_count == requests.size()
170 ? TacticalAssignmentStatus::Complete
171 : TacticalAssignmentStatus::Partial;
172 return {status, assigned_count,
173 std::span<const TacticalAssignment>{scratch.assignments_}};
174}
175
176} // namespace tess
Reusable storage for deterministic tactical assignment.
Definition tactical_assignment.h:59
friend auto assign_tactical_candidates_greedy(std::span< const TacticalRequest > requests, std::span< const TacticalCandidate > candidates, ScoreFn &&score, TacticalAssignmentScratch &scratch) -> TacticalAssignmentResult
Greedily assigns scarce candidates in priority and stable-ID order.
Definition tactical_assignment.h:83
Definition shape.h:46
Scratch-borrowing result from assign_tactical_candidates_greedy.
Definition tactical_assignment.h:52
One result aligned with the corresponding input request.
Definition tactical_assignment.h:36
One caller-owned position/resource with bounded concurrent capacity.
Definition tactical_assignment.h:23
One caller-owned subject seeking a tactical candidate.
Definition tactical_assignment.h:16
Feasibility and lower-is-better caller score for one request/candidate pair.
Definition tactical_assignment.h:30