solverforge_solver/stats/
telemetry.rs1use std::time::Duration;
7
8#[derive(Debug, Clone, Default, PartialEq)]
33pub struct SelectorTelemetry {
34 pub selector_index: usize,
35 pub selector_label: String,
36 pub moves_generated: u64,
37 pub moves_evaluated: u64,
38 pub moves_accepted: u64,
39 pub moves_applied: u64,
40 pub moves_not_doable: u64,
41 pub moves_acceptor_rejected: u64,
42 pub moves_forager_ignored: u64,
43 pub moves_hard_improving: u64,
44 pub moves_hard_neutral: u64,
45 pub moves_hard_worse: u64,
46 pub conflict_repair_provider_generated: u64,
47 pub conflict_repair_duplicate_filtered: u64,
48 pub conflict_repair_illegal_filtered: u64,
49 pub conflict_repair_not_doable_filtered: u64,
50 pub conflict_repair_hard_improving: u64,
51 pub conflict_repair_exposed: u64,
52 pub generation_time: Duration,
53 pub evaluation_time: Duration,
54}
55
56#[derive(Debug, Clone, Default, PartialEq)]
57pub struct MoveTelemetry {
58 pub move_label: String,
59 pub moves_generated: u64,
60 pub moves_evaluated: u64,
61 pub moves_accepted: u64,
62 pub moves_applied: u64,
63 pub moves_not_doable: u64,
64 pub moves_acceptor_rejected: u64,
65 pub moves_forager_ignored: u64,
66 pub moves_score_improving: u64,
67 pub moves_score_equal: u64,
68 pub moves_score_worse: u64,
69 pub moves_rejected_improving: u64,
70 pub applied_score_improvement: f64,
71}
72
73#[derive(Debug, Clone, Copy, Default, PartialEq)]
74pub struct AppliedMoveTelemetry {
75 pub step_index: u64,
76 pub move_label: &'static str,
77 pub selected_candidate_index: usize,
78 pub moves_generated_this_step: u64,
79 pub moves_evaluated_this_step: u64,
80 pub moves_accepted_this_step: u64,
81 pub moves_forager_ignored_this_step: u64,
82 pub score_before: f64,
83 pub score_after: f64,
84 pub score_delta: f64,
85 pub hard_feasible_before: bool,
86 pub hard_feasible_after: bool,
87}
88
89#[derive(Debug, Clone, Default, PartialEq)]
90pub struct SolverTelemetry {
91 pub elapsed: Duration,
92 pub step_count: u64,
93 pub moves_generated: u64,
94 pub moves_evaluated: u64,
95 pub moves_accepted: u64,
96 pub moves_applied: u64,
97 pub moves_not_doable: u64,
98 pub moves_acceptor_rejected: u64,
99 pub moves_forager_ignored: u64,
100 pub moves_hard_improving: u64,
101 pub moves_hard_neutral: u64,
102 pub moves_hard_worse: u64,
103 pub conflict_repair_provider_generated: u64,
104 pub conflict_repair_duplicate_filtered: u64,
105 pub conflict_repair_illegal_filtered: u64,
106 pub conflict_repair_not_doable_filtered: u64,
107 pub conflict_repair_hard_improving: u64,
108 pub conflict_repair_exposed: u64,
109 pub score_calculations: u64,
110 pub construction_slots_assigned: u64,
111 pub construction_slots_kept: u64,
112 pub construction_slots_no_doable: u64,
113 pub scalar_assignment_required_remaining: u64,
114 pub generation_time: Duration,
115 pub evaluation_time: Duration,
116 pub selector_telemetry: Vec<SelectorTelemetry>,
117 pub move_telemetry: Vec<MoveTelemetry>,
118 pub applied_move_trace: Vec<AppliedMoveTelemetry>,
119}
120
121impl SolverTelemetry {
122 pub const fn new_const() -> Self {
123 Self {
124 elapsed: Duration::ZERO,
125 step_count: 0,
126 moves_generated: 0,
127 moves_evaluated: 0,
128 moves_accepted: 0,
129 moves_applied: 0,
130 moves_not_doable: 0,
131 moves_acceptor_rejected: 0,
132 moves_forager_ignored: 0,
133 moves_hard_improving: 0,
134 moves_hard_neutral: 0,
135 moves_hard_worse: 0,
136 conflict_repair_provider_generated: 0,
137 conflict_repair_duplicate_filtered: 0,
138 conflict_repair_illegal_filtered: 0,
139 conflict_repair_not_doable_filtered: 0,
140 conflict_repair_hard_improving: 0,
141 conflict_repair_exposed: 0,
142 score_calculations: 0,
143 construction_slots_assigned: 0,
144 construction_slots_kept: 0,
145 construction_slots_no_doable: 0,
146 scalar_assignment_required_remaining: 0,
147 generation_time: Duration::ZERO,
148 evaluation_time: Duration::ZERO,
149 selector_telemetry: Vec::new(),
150 move_telemetry: Vec::new(),
151 applied_move_trace: Vec::new(),
152 }
153 }
154}
155
156#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
157pub struct Throughput {
158 pub count: u64,
159 pub elapsed: Duration,
160}
161
162pub(crate) fn whole_units_per_second(count: u64, elapsed: Duration) -> u128 {
163 let nanos = elapsed.as_nanos();
164 if nanos == 0 {
165 0
166 } else {
167 u128::from(count)
168 .saturating_mul(1_000_000_000)
169 .checked_div(nanos)
170 .unwrap_or(0)
171 }
172}
173
174pub(crate) fn format_duration(duration: Duration) -> String {
175 let secs = duration.as_secs();
176 let nanos = duration.subsec_nanos();
177
178 if secs >= 60 {
179 let mins = secs / 60;
180 let rem_secs = secs % 60;
181 return format!("{mins}m {rem_secs}s");
182 }
183
184 if secs > 0 {
185 let millis = nanos / 1_000_000;
186 if millis == 0 {
187 return format!("{secs}s");
188 }
189 return format!("{secs}s {millis}ms");
190 }
191
192 let millis = nanos / 1_000_000;
193 if millis > 0 {
194 return format!("{millis}ms");
195 }
196
197 let micros = nanos / 1_000;
198 if micros > 0 {
199 return format!("{micros}us");
200 }
201
202 format!("{nanos}ns")
203}