pub fn solve_position_with_book(
bb: &Bitboard,
budget_s: f64,
book: Option<&OpeningBookDatabase>,
) -> Option<Value>Expand description
Like solve_position, but first probes book (when given) for a
stored solved reference at bb’s canonical key, short-circuiting the
minimax solve on a hit. On a fresh solve, the result is written back
through book (best-effort — a write failure is silently ignored, the
solve itself already succeeded and is returned regardless).
See crate::bench::book_export for the orientation caveat: both the
lookup and the write-back apply only when bb is its own canonical
representative — the stored moves are in that one orientation and
cannot be translated across symmetries yet.
Examples found in repository?
examples/depth4_survey.rs (line 140)
100fn main() {
101 let args = parse_args();
102
103 let started = Instant::now();
104 let mut states = enumerate_depth4();
105 println!(
106 "enumerated {} canonical nonterminal depth-4 states in {:.2}s",
107 states.len(),
108 started.elapsed().as_secs_f64()
109 );
110 if args.sample > 0 && args.sample < states.len() {
111 use rand::prelude::*;
112 let mut rng = StdRng::seed_from_u64(args.seed);
113 states.shuffle(&mut rng);
114 states.truncate(args.sample);
115 states.sort_by_key(|(bb, _)| bb.to_le_bytes());
116 println!(
117 "sampled {} of {} states (seed={})",
118 states.len(),
119 10946,
120 args.seed
121 );
122 } else if args.limit > 0 {
123 states.truncate(args.limit);
124 println!("limited to {} states for this run", states.len());
125 }
126
127 let book = OpeningBookDatabase::open(&OpeningBookConfig {
128 database_path: args.db.clone(),
129 ..Default::default()
130 })
131 .expect("open book");
132
133 let mut rows: Vec<serde_json::Value> = Vec::with_capacity(states.len());
134 let mut solved = 0usize;
135 let mut cutoff = 0usize;
136 let solve_started = Instant::now();
137
138 for (i, (bb, mult)) in states.iter().enumerate() {
139 let t0 = Instant::now();
140 let reference = solve_position_with_book(bb, args.budget_s, Some(&book));
141 let elapsed = t0.elapsed().as_secs_f64();
142 println!(
143 " [{}/{}] {} solved={} elapsed={:.3}s",
144 i + 1,
145 states.len(),
146 State::new(*bb).to_qfen(),
147 reference.is_some(),
148 elapsed
149 );
150
151 match &reference {
152 Some(r) => {
153 solved += 1;
154 rows.push(serde_json::json!({
155 "qfen": State::new(*bb).to_qfen(),
156 "multiplicity": mult,
157 "value": r["value"],
158 "nodes": r["nodes"],
159 "solve_time_s": elapsed,
160 "solver": r["solver"],
161 }));
162 }
163 None => {
164 cutoff += 1;
165 rows.push(serde_json::json!({
166 "qfen": State::new(*bb).to_qfen(),
167 "multiplicity": mult,
168 "value": null,
169 "nodes": null,
170 "solve_time_s": elapsed,
171 "solver": null,
172 }));
173 }
174 }
175
176 if (i + 1) % 25 == 0 || i + 1 == states.len() {
177 println!(
178 "progress: {}/{} solved={} cutoff={} elapsed_total={:.1}s",
179 i + 1,
180 states.len(),
181 solved,
182 cutoff,
183 solve_started.elapsed().as_secs_f64()
184 );
185 // Flush partial results after every progress line so a killed
186 // run still leaves a readable, if incomplete, artifact — the
187 // SQLite book itself is already durable per-row regardless.
188 let partial = serde_json::json!({
189 "budget_s": args.budget_s,
190 "total_canonical_depth4_states": states.len(),
191 "processed": i + 1,
192 "solved": solved,
193 "cutoff": cutoff,
194 "positions": rows,
195 });
196 std::fs::write(&args.out, serde_json::to_string_pretty(&partial).unwrap())
197 .expect("write partial output");
198 }
199 }
200
201 println!(
202 "done: {} solved, {} cutoff, total solve wall time {:.1}s -> {}",
203 solved,
204 cutoff,
205 solve_started.elapsed().as_secs_f64(),
206 args.out
207 );
208}