rust-pgn-reader-python-binding 3.2.0__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/Cargo.lock +3 -3
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/Cargo.toml +3 -1
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/PKG-INFO +1 -1
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/benches/parquet_bench.rs +1 -1
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/rust_pgn_reader_python_binding.pyi +13 -8
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/lib.rs +120 -58
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/test.py +73 -16
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/.github/workflows/CI.yml +0 -0
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/.gitignore +0 -0
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/README.md +0 -0
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/pyproject.toml +0 -0
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/bench.py +0 -0
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/bench_parquet.py +0 -0
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/bench_parquet_arrow.py +0 -0
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/bench_parquet_parallel.py +0 -0
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/comment_parsing.rs +0 -0
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/example_rowgroup.py +0 -0
- {rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/example_str.py +0 -0
|
@@ -840,9 +840,9 @@ dependencies = [
|
|
|
840
840
|
|
|
841
841
|
[[package]]
|
|
842
842
|
name = "libc"
|
|
843
|
-
version = "0.2.
|
|
843
|
+
version = "0.2.174"
|
|
844
844
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
845
|
-
checksum = "
|
|
845
|
+
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
|
846
846
|
|
|
847
847
|
[[package]]
|
|
848
848
|
name = "libm"
|
|
@@ -1386,7 +1386,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|
|
1386
1386
|
|
|
1387
1387
|
[[package]]
|
|
1388
1388
|
name = "rust_pgn_reader_python_binding"
|
|
1389
|
-
version = "3.
|
|
1389
|
+
version = "3.3.0"
|
|
1390
1390
|
dependencies = [
|
|
1391
1391
|
"arrow",
|
|
1392
1392
|
"arrow-array",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rust_pgn_reader_python_binding
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
@@ -43,7 +43,7 @@ pub fn bench_parquet() {
|
|
|
43
43
|
// Measure start time
|
|
44
44
|
let start = Instant::now();
|
|
45
45
|
|
|
46
|
-
let result = parse_multiple_games_native(&movetexts, None);
|
|
46
|
+
let result = parse_multiple_games_native(&movetexts, None, false);
|
|
47
47
|
|
|
48
48
|
let duration = start.elapsed();
|
|
49
49
|
println!("Time taken: {:?}", duration);
|
|
@@ -29,21 +29,26 @@ class PositionStatus:
|
|
|
29
29
|
class MoveExtractor:
|
|
30
30
|
moves: List[PyUciMove]
|
|
31
31
|
valid_moves: bool
|
|
32
|
-
comments: List[str]
|
|
33
|
-
evals: List[float]
|
|
34
|
-
clock_times: List[Tuple[int, int, float]]
|
|
32
|
+
comments: List[Optional[str]]
|
|
33
|
+
evals: List[Optional[float]]
|
|
34
|
+
clock_times: List[Optional[Tuple[int, int, float]]]
|
|
35
35
|
outcome: Optional[str]
|
|
36
36
|
headers: List[Tuple[str, str]]
|
|
37
|
-
castling_rights: List[Tuple[bool, bool, bool, bool]]
|
|
37
|
+
castling_rights: List[Optional[Tuple[bool, bool, bool, bool]]]
|
|
38
38
|
position_status: Optional[PositionStatus]
|
|
39
39
|
|
|
40
|
-
def __init__(self) -> None: ...
|
|
40
|
+
def __init__(self, store_legal_moves: bool = False) -> None: ...
|
|
41
41
|
def turn(self) -> bool: ...
|
|
42
|
+
def update_position_status(self) -> None: ...
|
|
43
|
+
@property
|
|
44
|
+
def legal_moves(self) -> List[List[PyUciMove]]: ...
|
|
42
45
|
|
|
43
|
-
def parse_game(pgn: str) -> MoveExtractor: ...
|
|
46
|
+
def parse_game(pgn: str, store_legal_moves: bool = False) -> MoveExtractor: ...
|
|
44
47
|
def parse_games(
|
|
45
|
-
pgns: List[str], num_threads: Optional[int] = None
|
|
48
|
+
pgns: List[str], num_threads: Optional[int] = None, store_legal_moves: bool = False
|
|
46
49
|
) -> List[MoveExtractor]: ...
|
|
47
50
|
def parse_game_moves_arrow_chunked_array(
|
|
48
|
-
pgn_chunked_array: pyarrow.ChunkedArray,
|
|
51
|
+
pgn_chunked_array: pyarrow.ChunkedArray,
|
|
52
|
+
num_threads: Optional[int] = None,
|
|
53
|
+
store_legal_moves: bool = False,
|
|
49
54
|
) -> List[MoveExtractor]: ...
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
use crate::comment_parsing::{
|
|
1
|
+
use crate::comment_parsing::{CommentContent, ParsedTag, parse_comments};
|
|
2
2
|
use arrow_array::{Array, LargeStringArray, StringArray};
|
|
3
3
|
use pgn_reader::{KnownOutcome, Outcome, RawComment, RawTag, Reader, SanPlus, Skip, Visitor};
|
|
4
4
|
use pyo3::prelude::*;
|
|
5
5
|
use pyo3_arrow::PyChunkedArray;
|
|
6
|
-
use rayon::prelude::*;
|
|
7
6
|
use rayon::ThreadPoolBuilder;
|
|
7
|
+
use rayon::prelude::*;
|
|
8
8
|
use shakmaty::Color;
|
|
9
|
-
use shakmaty::{
|
|
9
|
+
use shakmaty::{Chess, Position, Role, Square, uci::UciMove};
|
|
10
10
|
use std::io::Cursor;
|
|
11
11
|
use std::ops::ControlFlow;
|
|
12
12
|
|
|
@@ -111,17 +111,21 @@ pub struct MoveExtractor {
|
|
|
111
111
|
#[pyo3(get)]
|
|
112
112
|
moves: Vec<PyUciMove>,
|
|
113
113
|
|
|
114
|
+
store_legal_moves: bool,
|
|
115
|
+
flat_legal_moves: Vec<PyUciMove>,
|
|
116
|
+
legal_moves_offsets: Vec<usize>,
|
|
117
|
+
|
|
114
118
|
#[pyo3(get)]
|
|
115
119
|
valid_moves: bool,
|
|
116
120
|
|
|
117
121
|
#[pyo3(get)]
|
|
118
|
-
comments: Vec<String
|
|
122
|
+
comments: Vec<Option<String>>,
|
|
119
123
|
|
|
120
124
|
#[pyo3(get)]
|
|
121
|
-
evals: Vec<f64
|
|
125
|
+
evals: Vec<Option<f64>>,
|
|
122
126
|
|
|
123
127
|
#[pyo3(get)]
|
|
124
|
-
clock_times: Vec<(u32, u8, f64)
|
|
128
|
+
clock_times: Vec<Option<(u32, u8, f64)>>,
|
|
125
129
|
|
|
126
130
|
#[pyo3(get)]
|
|
127
131
|
outcome: Option<String>,
|
|
@@ -130,7 +134,7 @@ pub struct MoveExtractor {
|
|
|
130
134
|
headers: Vec<(String, String)>,
|
|
131
135
|
|
|
132
136
|
#[pyo3(get)]
|
|
133
|
-
castling_rights: Vec<(bool, bool, bool, bool)
|
|
137
|
+
castling_rights: Vec<Option<(bool, bool, bool, bool)>>,
|
|
134
138
|
|
|
135
139
|
#[pyo3(get)]
|
|
136
140
|
position_status: Option<PositionStatus>,
|
|
@@ -141,9 +145,13 @@ pub struct MoveExtractor {
|
|
|
141
145
|
#[pymethods]
|
|
142
146
|
impl MoveExtractor {
|
|
143
147
|
#[new]
|
|
144
|
-
|
|
148
|
+
#[pyo3(signature = (store_legal_moves = false))]
|
|
149
|
+
fn new(store_legal_moves: bool) -> MoveExtractor {
|
|
145
150
|
MoveExtractor {
|
|
146
151
|
moves: Vec::with_capacity(100),
|
|
152
|
+
store_legal_moves,
|
|
153
|
+
flat_legal_moves: Vec::with_capacity(if store_legal_moves { 100 * 30 } else { 0 }), // Pre-allocate for moves
|
|
154
|
+
legal_moves_offsets: Vec::with_capacity(if store_legal_moves { 100 } else { 0 }), // Pre-allocate for offsets
|
|
147
155
|
pos: Chess::default(),
|
|
148
156
|
valid_moves: true,
|
|
149
157
|
comments: Vec::with_capacity(100),
|
|
@@ -172,7 +180,31 @@ impl MoveExtractor {
|
|
|
172
180
|
castling_bitboard.contains(shakmaty::Square::H8),
|
|
173
181
|
);
|
|
174
182
|
|
|
175
|
-
self.castling_rights.push(castling_rights);
|
|
183
|
+
self.castling_rights.push(Some(castling_rights));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
fn push_legal_moves(&mut self) {
|
|
187
|
+
// Record the starting offset for the current position's legal moves.
|
|
188
|
+
self.legal_moves_offsets.push(self.flat_legal_moves.len());
|
|
189
|
+
|
|
190
|
+
let legal_moves_for_pos = self.pos.legal_moves();
|
|
191
|
+
self.flat_legal_moves.reserve(legal_moves_for_pos.len());
|
|
192
|
+
|
|
193
|
+
for m in legal_moves_for_pos {
|
|
194
|
+
let uci_move_obj = UciMove::from_standard(m);
|
|
195
|
+
if let UciMove::Normal {
|
|
196
|
+
from,
|
|
197
|
+
to,
|
|
198
|
+
promotion: promo_opt,
|
|
199
|
+
} = uci_move_obj
|
|
200
|
+
{
|
|
201
|
+
self.flat_legal_moves.push(PyUciMove {
|
|
202
|
+
from_square: from as u8,
|
|
203
|
+
to_square: to as u8,
|
|
204
|
+
promotion: promo_opt.map(|p_role| p_role as u8),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
176
208
|
}
|
|
177
209
|
|
|
178
210
|
fn update_position_status(&mut self) {
|
|
@@ -192,6 +224,27 @@ impl MoveExtractor {
|
|
|
192
224
|
},
|
|
193
225
|
});
|
|
194
226
|
}
|
|
227
|
+
|
|
228
|
+
#[getter]
|
|
229
|
+
fn legal_moves(&self) -> Vec<Vec<PyUciMove>> {
|
|
230
|
+
let mut result = Vec::with_capacity(self.legal_moves_offsets.len());
|
|
231
|
+
if self.legal_moves_offsets.is_empty() {
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for i in 0..self.legal_moves_offsets.len() - 1 {
|
|
236
|
+
let start = self.legal_moves_offsets[i];
|
|
237
|
+
let end = self.legal_moves_offsets[i + 1];
|
|
238
|
+
result.push(self.flat_legal_moves[start..end].to_vec());
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Handle the last chunk
|
|
242
|
+
if let Some(&start) = self.legal_moves_offsets.last() {
|
|
243
|
+
result.push(self.flat_legal_moves[start..].to_vec());
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
result
|
|
247
|
+
}
|
|
195
248
|
}
|
|
196
249
|
|
|
197
250
|
impl Visitor for MoveExtractor {
|
|
@@ -219,25 +272,36 @@ impl Visitor for MoveExtractor {
|
|
|
219
272
|
fn begin_movetext(&mut self, tags: Self::Tags) -> ControlFlow<Self::Output, Self::Movetext> {
|
|
220
273
|
self.headers = tags;
|
|
221
274
|
self.moves.clear();
|
|
275
|
+
self.flat_legal_moves.clear();
|
|
276
|
+
self.legal_moves_offsets.clear();
|
|
222
277
|
self.pos = Chess::default();
|
|
223
278
|
self.valid_moves = true;
|
|
224
279
|
self.comments.clear();
|
|
225
280
|
self.evals.clear();
|
|
226
281
|
self.clock_times.clear();
|
|
282
|
+
self.castling_rights.clear();
|
|
227
283
|
|
|
228
284
|
self.push_castling_bitboards();
|
|
285
|
+
if self.store_legal_moves {
|
|
286
|
+
self.push_legal_moves();
|
|
287
|
+
}
|
|
229
288
|
ControlFlow::Continue(())
|
|
230
289
|
}
|
|
231
290
|
|
|
291
|
+
// Roughly half the time during parsing is spent here in san()
|
|
232
292
|
fn san(
|
|
233
293
|
&mut self,
|
|
234
294
|
_movetext: &mut Self::Movetext,
|
|
235
295
|
san_plus: SanPlus,
|
|
236
296
|
) -> ControlFlow<Self::Output> {
|
|
237
297
|
if self.valid_moves {
|
|
298
|
+
// Most of the function time is spent calculating to_move()
|
|
238
299
|
match san_plus.san.to_move(&self.pos) {
|
|
239
300
|
Ok(m) => {
|
|
240
301
|
self.pos.play_unchecked(m);
|
|
302
|
+
if self.store_legal_moves {
|
|
303
|
+
self.push_legal_moves();
|
|
304
|
+
}
|
|
241
305
|
let uci_move_obj = UciMove::from_standard(m);
|
|
242
306
|
|
|
243
307
|
match uci_move_obj {
|
|
@@ -253,12 +317,20 @@ impl Visitor for MoveExtractor {
|
|
|
253
317
|
};
|
|
254
318
|
self.moves.push(py_uci_move);
|
|
255
319
|
self.push_castling_bitboards();
|
|
320
|
+
|
|
321
|
+
// Push placeholders to keep vectors in sync
|
|
322
|
+
self.comments.push(None);
|
|
323
|
+
self.evals.push(None);
|
|
324
|
+
self.clock_times.push(None);
|
|
256
325
|
}
|
|
257
326
|
_ => {
|
|
258
327
|
// This case handles UciMove::Put and UciMove::Null,
|
|
259
328
|
// which are not expected from standard PGN moves
|
|
260
329
|
// that PyUciMove is designed to represent.
|
|
261
|
-
eprintln!(
|
|
330
|
+
eprintln!(
|
|
331
|
+
"Unexpected UCI move type from standard PGN move: {:?}. Game moves might be invalid.",
|
|
332
|
+
uci_move_obj
|
|
333
|
+
);
|
|
262
334
|
self.valid_moves = false;
|
|
263
335
|
}
|
|
264
336
|
}
|
|
@@ -283,60 +355,47 @@ impl Visitor for MoveExtractor {
|
|
|
283
355
|
eprintln!("Unparsed remaining input: {:?}", remaining_input);
|
|
284
356
|
return ControlFlow::Continue(());
|
|
285
357
|
}
|
|
286
|
-
|
|
287
|
-
let mut clk_time_encountered = false;
|
|
358
|
+
|
|
288
359
|
let mut move_comments = String::new();
|
|
289
360
|
|
|
290
361
|
for content in parsed_comments {
|
|
291
362
|
match content {
|
|
292
363
|
CommentContent::Text(text) => {
|
|
293
364
|
if !text.trim().is_empty() {
|
|
365
|
+
if !move_comments.is_empty() {
|
|
366
|
+
move_comments.push(' ');
|
|
367
|
+
}
|
|
294
368
|
move_comments.push_str(&text);
|
|
295
369
|
}
|
|
296
370
|
}
|
|
297
371
|
CommentContent::Tag(tag_content) => match tag_content {
|
|
298
372
|
ParsedTag::Eval(eval_value) => {
|
|
299
|
-
if
|
|
300
|
-
|
|
301
|
-
"Multiple Eval values found in comment: {:?}",
|
|
302
|
-
_comment
|
|
303
|
-
);
|
|
304
|
-
// Potentially skip this eval or handle as an error
|
|
305
|
-
continue;
|
|
373
|
+
if let Some(last_eval) = self.evals.last_mut() {
|
|
374
|
+
*last_eval = Some(eval_value);
|
|
306
375
|
}
|
|
307
|
-
eval_encountered = true;
|
|
308
|
-
self.evals.push(eval_value);
|
|
309
376
|
}
|
|
310
377
|
ParsedTag::Mate(mate_value) => {
|
|
311
|
-
// For now, add mate info to the general comments string
|
|
312
|
-
// A dedicated field could be added to MoveExtractor if needed
|
|
313
378
|
if !move_comments.is_empty() && !move_comments.ends_with(' ') {
|
|
314
379
|
move_comments.push(' ');
|
|
315
380
|
}
|
|
316
381
|
move_comments.push_str(&format!("[Mate {}]", mate_value));
|
|
317
|
-
// If evals should also store mate, convert mate_value to f64
|
|
318
|
-
// e.g., self.evals.push(mate_value as f64 * 1000.0); // Or some indicator
|
|
319
382
|
}
|
|
320
383
|
ParsedTag::ClkTime {
|
|
321
384
|
hours,
|
|
322
385
|
minutes,
|
|
323
386
|
seconds,
|
|
324
387
|
} => {
|
|
325
|
-
if
|
|
326
|
-
|
|
327
|
-
"Multiple ClkTime values found in comment: {:?}",
|
|
328
|
-
_comment
|
|
329
|
-
);
|
|
330
|
-
// Potentially skip this clk or handle as an error
|
|
331
|
-
continue;
|
|
388
|
+
if let Some(last_clk) = self.clock_times.last_mut() {
|
|
389
|
+
*last_clk = Some((hours, minutes, seconds));
|
|
332
390
|
}
|
|
333
|
-
clk_time_encountered = true;
|
|
334
|
-
self.clock_times.push((hours, minutes, seconds));
|
|
335
391
|
}
|
|
336
392
|
},
|
|
337
393
|
}
|
|
338
394
|
}
|
|
339
|
-
|
|
395
|
+
|
|
396
|
+
if let Some(last_comment) = self.comments.last_mut() {
|
|
397
|
+
*last_comment = Some(move_comments);
|
|
398
|
+
}
|
|
340
399
|
}
|
|
341
400
|
Err(e) => {
|
|
342
401
|
eprintln!("Error parsing comment: {:?}", e);
|
|
@@ -374,9 +433,12 @@ impl Visitor for MoveExtractor {
|
|
|
374
433
|
}
|
|
375
434
|
|
|
376
435
|
// --- Native Rust versions (no PyResult) ---
|
|
377
|
-
pub fn parse_single_game_native(
|
|
436
|
+
pub fn parse_single_game_native(
|
|
437
|
+
pgn: &str,
|
|
438
|
+
store_legal_moves: bool,
|
|
439
|
+
) -> Result<MoveExtractor, String> {
|
|
378
440
|
let mut reader = Reader::new(Cursor::new(pgn));
|
|
379
|
-
let mut extractor = MoveExtractor::new();
|
|
441
|
+
let mut extractor = MoveExtractor::new(store_legal_moves);
|
|
380
442
|
match reader.read_game(&mut extractor) {
|
|
381
443
|
Ok(Some(_)) => Ok(extractor),
|
|
382
444
|
Ok(None) => Err("No game found in PGN".to_string()),
|
|
@@ -387,6 +449,7 @@ pub fn parse_single_game_native(pgn: &str) -> Result<MoveExtractor, String> {
|
|
|
387
449
|
pub fn parse_multiple_games_native(
|
|
388
450
|
pgns: &Vec<String>,
|
|
389
451
|
num_threads: Option<usize>,
|
|
452
|
+
store_legal_moves: bool,
|
|
390
453
|
) -> Result<Vec<MoveExtractor>, String> {
|
|
391
454
|
let num_threads = num_threads.unwrap_or_else(num_cpus::get);
|
|
392
455
|
|
|
@@ -397,25 +460,16 @@ pub fn parse_multiple_games_native(
|
|
|
397
460
|
.expect("Failed to build Rayon thread pool");
|
|
398
461
|
|
|
399
462
|
thread_pool.install(|| {
|
|
400
|
-
|
|
401
|
-
.
|
|
402
|
-
.
|
|
403
|
-
.collect();
|
|
404
|
-
|
|
405
|
-
let mut extractors: Vec<MoveExtractor> = Vec::with_capacity(results.len());
|
|
406
|
-
for res in results {
|
|
407
|
-
match res {
|
|
408
|
-
Ok(extractor) => extractors.push(extractor),
|
|
409
|
-
Err(e) => return Err(e),
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
Ok(extractors)
|
|
463
|
+
pgns.par_iter()
|
|
464
|
+
.map(|pgn| parse_single_game_native(pgn, store_legal_moves))
|
|
465
|
+
.collect()
|
|
413
466
|
})
|
|
414
467
|
}
|
|
415
468
|
|
|
416
469
|
fn _parse_game_moves_from_arrow_chunks_native(
|
|
417
470
|
pgn_chunked_array: &PyChunkedArray,
|
|
418
471
|
num_threads: Option<usize>,
|
|
472
|
+
store_legal_moves: bool,
|
|
419
473
|
) -> Result<Vec<MoveExtractor>, String> {
|
|
420
474
|
let num_threads = num_threads.unwrap_or_else(num_cpus::get);
|
|
421
475
|
let thread_pool = ThreadPoolBuilder::new()
|
|
@@ -452,7 +506,7 @@ fn _parse_game_moves_from_arrow_chunks_native(
|
|
|
452
506
|
thread_pool.install(|| {
|
|
453
507
|
pgn_str_slices
|
|
454
508
|
.par_iter()
|
|
455
|
-
.map(|&pgn_s| parse_single_game_native(pgn_s))
|
|
509
|
+
.map(|&pgn_s| parse_single_game_native(pgn_s, store_legal_moves))
|
|
456
510
|
.collect::<Result<Vec<MoveExtractor>, String>>()
|
|
457
511
|
})
|
|
458
512
|
}
|
|
@@ -461,25 +515,33 @@ fn _parse_game_moves_from_arrow_chunks_native(
|
|
|
461
515
|
// TODO check if I can call py.allow_threads and release GIL
|
|
462
516
|
// see https://docs.rs/pyo3-arrow/0.10.1/pyo3_arrow/
|
|
463
517
|
#[pyfunction]
|
|
518
|
+
#[pyo3(signature = (pgn, store_legal_moves = false))]
|
|
464
519
|
/// Parses a single PGN game string.
|
|
465
|
-
fn parse_game(pgn: &str) -> PyResult<MoveExtractor> {
|
|
466
|
-
parse_single_game_native(pgn)
|
|
520
|
+
fn parse_game(pgn: &str, store_legal_moves: bool) -> PyResult<MoveExtractor> {
|
|
521
|
+
parse_single_game_native(pgn, store_legal_moves)
|
|
522
|
+
.map_err(pyo3::exceptions::PyValueError::new_err)
|
|
467
523
|
}
|
|
468
524
|
|
|
469
525
|
/// In parallel, parse a set of games
|
|
470
526
|
#[pyfunction]
|
|
471
|
-
#[pyo3(signature = (pgns, num_threads=None))]
|
|
472
|
-
fn parse_games(
|
|
473
|
-
|
|
527
|
+
#[pyo3(signature = (pgns, num_threads=None, store_legal_moves=false))]
|
|
528
|
+
fn parse_games(
|
|
529
|
+
pgns: Vec<String>,
|
|
530
|
+
num_threads: Option<usize>,
|
|
531
|
+
store_legal_moves: bool,
|
|
532
|
+
) -> PyResult<Vec<MoveExtractor>> {
|
|
533
|
+
parse_multiple_games_native(&pgns, num_threads, store_legal_moves)
|
|
534
|
+
.map_err(pyo3::exceptions::PyValueError::new_err)
|
|
474
535
|
}
|
|
475
536
|
|
|
476
537
|
#[pyfunction]
|
|
477
|
-
#[pyo3(signature = (pgn_chunked_array, num_threads=None))]
|
|
538
|
+
#[pyo3(signature = (pgn_chunked_array, num_threads=None, store_legal_moves=false))]
|
|
478
539
|
fn parse_game_moves_arrow_chunked_array(
|
|
479
540
|
pgn_chunked_array: PyChunkedArray,
|
|
480
541
|
num_threads: Option<usize>,
|
|
542
|
+
store_legal_moves: bool,
|
|
481
543
|
) -> PyResult<Vec<MoveExtractor>> {
|
|
482
|
-
_parse_game_moves_from_arrow_chunks_native(&pgn_chunked_array, num_threads)
|
|
544
|
+
_parse_game_moves_from_arrow_chunks_native(&pgn_chunked_array, num_threads, store_legal_moves)
|
|
483
545
|
.map_err(pyo3::exceptions::PyValueError::new_err)
|
|
484
546
|
}
|
|
485
547
|
|
|
@@ -78,16 +78,28 @@ class TestPgnExtraction(unittest.TestCase):
|
|
|
78
78
|
"f5f8",
|
|
79
79
|
"h8h7",
|
|
80
80
|
]
|
|
81
|
-
|
|
81
|
+
|
|
82
|
+
comments_reference = [
|
|
83
|
+
"asdf",
|
|
84
|
+
None,
|
|
85
|
+
None,
|
|
86
|
+
None,
|
|
87
|
+
None,
|
|
88
|
+
None,
|
|
89
|
+
"hello",
|
|
90
|
+
] + [None for _ in range(57)]
|
|
91
|
+
|
|
82
92
|
valid_reference = True
|
|
83
|
-
evals_reference = []
|
|
84
|
-
clock_times_reference = []
|
|
93
|
+
evals_reference = [None for _ in range(len(moves_reference))]
|
|
94
|
+
clock_times_reference = [None for _ in range(len(moves_reference))]
|
|
85
95
|
|
|
86
96
|
self.assertTrue([str(move) for move in extractor.moves] == moves_reference)
|
|
87
97
|
self.assertTrue(extractor.comments == comments_reference)
|
|
88
98
|
self.assertTrue(extractor.valid_moves == valid_reference)
|
|
89
99
|
self.assertTrue(extractor.evals == evals_reference)
|
|
90
100
|
self.assertTrue(extractor.clock_times == clock_times_reference)
|
|
101
|
+
|
|
102
|
+
assert extractor.position_status is not None # appease the type checker
|
|
91
103
|
self.assertFalse(extractor.position_status.is_checkmate)
|
|
92
104
|
self.assertFalse(extractor.position_status.is_stalemate)
|
|
93
105
|
self.assertFalse(extractor.position_status.is_game_over)
|
|
@@ -183,10 +195,10 @@ class TestPgnExtraction(unittest.TestCase):
|
|
|
183
195
|
"a4a5",
|
|
184
196
|
"b3d5",
|
|
185
197
|
]
|
|
186
|
-
comments_reference = []
|
|
198
|
+
comments_reference = [None for _ in range(len(moves_reference))]
|
|
187
199
|
valid_reference = True
|
|
188
|
-
evals_reference = []
|
|
189
|
-
clock_times_reference = []
|
|
200
|
+
evals_reference = [None for _ in range(len(moves_reference))]
|
|
201
|
+
clock_times_reference = [None for _ in range(len(moves_reference))]
|
|
190
202
|
headers_reference = [
|
|
191
203
|
("Event", "Rated Classical game"),
|
|
192
204
|
("Site", "https://lichess.org/lhy6ehiv"),
|
|
@@ -211,6 +223,8 @@ class TestPgnExtraction(unittest.TestCase):
|
|
|
211
223
|
self.assertTrue(extractor.evals == evals_reference)
|
|
212
224
|
self.assertTrue(extractor.clock_times == clock_times_reference)
|
|
213
225
|
self.assertTrue(extractor.headers == headers_reference)
|
|
226
|
+
|
|
227
|
+
assert extractor.position_status is not None # appease the type checker
|
|
214
228
|
self.assertFalse(extractor.position_status.is_checkmate)
|
|
215
229
|
self.assertFalse(extractor.position_status.is_stalemate)
|
|
216
230
|
self.assertFalse(extractor.position_status.is_game_over)
|
|
@@ -326,6 +340,8 @@ class TestPgnExtraction(unittest.TestCase):
|
|
|
326
340
|
self.assertTrue(extractor.valid_moves == valid_reference)
|
|
327
341
|
self.assertTrue(extractor.evals == evals_reference)
|
|
328
342
|
self.assertTrue(extractor.clock_times == clock_times_reference)
|
|
343
|
+
|
|
344
|
+
assert extractor.position_status is not None # appease the type checker
|
|
329
345
|
self.assertFalse(extractor.position_status.is_checkmate)
|
|
330
346
|
self.assertFalse(extractor.position_status.is_stalemate)
|
|
331
347
|
self.assertFalse(extractor.position_status.is_game_over)
|
|
@@ -337,11 +353,6 @@ class TestPgnExtraction(unittest.TestCase):
|
|
|
337
353
|
]
|
|
338
354
|
extractor = rust_pgn_reader_python_binding.parse_games(pgns)
|
|
339
355
|
|
|
340
|
-
comments_reference = [[], ["asdf", "hello"]]
|
|
341
|
-
|
|
342
|
-
self.assertTrue(extractor[0].comments == comments_reference[0])
|
|
343
|
-
self.assertTrue(extractor[1].comments == comments_reference[1])
|
|
344
|
-
|
|
345
356
|
moves_reference = [
|
|
346
357
|
[
|
|
347
358
|
"g1f3",
|
|
@@ -431,12 +442,37 @@ class TestPgnExtraction(unittest.TestCase):
|
|
|
431
442
|
],
|
|
432
443
|
]
|
|
433
444
|
|
|
445
|
+
comments_reference = [
|
|
446
|
+
[None for _ in range(len(moves_reference[0]))],
|
|
447
|
+
[
|
|
448
|
+
"asdf",
|
|
449
|
+
None,
|
|
450
|
+
None,
|
|
451
|
+
None,
|
|
452
|
+
None,
|
|
453
|
+
None,
|
|
454
|
+
"hello",
|
|
455
|
+
None,
|
|
456
|
+
None,
|
|
457
|
+
None,
|
|
458
|
+
None,
|
|
459
|
+
None,
|
|
460
|
+
None,
|
|
461
|
+
None,
|
|
462
|
+
],
|
|
463
|
+
]
|
|
464
|
+
|
|
465
|
+
self.assertTrue(extractor[0].comments == comments_reference[0])
|
|
466
|
+
self.assertTrue(extractor[1].comments == comments_reference[1])
|
|
467
|
+
|
|
434
468
|
self.assertTrue(
|
|
435
469
|
[str(move) for move in extractor[0].moves] == moves_reference[0]
|
|
436
470
|
)
|
|
437
471
|
self.assertTrue(
|
|
438
472
|
[str(move) for move in extractor[1].moves] == moves_reference[1]
|
|
439
473
|
)
|
|
474
|
+
assert extractor[0].position_status is not None # appease the type checker
|
|
475
|
+
|
|
440
476
|
self.assertTrue(extractor[0].position_status.is_checkmate)
|
|
441
477
|
self.assertFalse(extractor[0].position_status.is_stalemate)
|
|
442
478
|
self.assertTrue(extractor[0].position_status.is_game_over)
|
|
@@ -446,6 +482,7 @@ class TestPgnExtraction(unittest.TestCase):
|
|
|
446
482
|
|
|
447
483
|
self.assertTrue(extractor[1].position_status is None)
|
|
448
484
|
extractor[1].update_position_status()
|
|
485
|
+
assert extractor[1].position_status is not None # appease the type checker
|
|
449
486
|
self.assertFalse(extractor[1].position_status.is_checkmate)
|
|
450
487
|
self.assertFalse(extractor[1].position_status.is_stalemate)
|
|
451
488
|
self.assertFalse(extractor[1].position_status.is_game_over)
|
|
@@ -496,11 +533,6 @@ class TestPgnExtraction(unittest.TestCase):
|
|
|
496
533
|
)
|
|
497
534
|
)
|
|
498
535
|
|
|
499
|
-
comments_reference = [[], ["asdf", "hello"]]
|
|
500
|
-
|
|
501
|
-
self.assertTrue(extractors[0].comments == comments_reference[0])
|
|
502
|
-
self.assertTrue(extractors[1].comments == comments_reference[1])
|
|
503
|
-
|
|
504
536
|
moves_reference = [
|
|
505
537
|
[
|
|
506
538
|
"g1f3",
|
|
@@ -597,7 +629,31 @@ class TestPgnExtraction(unittest.TestCase):
|
|
|
597
629
|
[str(move) for move in extractors[1].moves] == moves_reference[1]
|
|
598
630
|
)
|
|
599
631
|
|
|
632
|
+
comments_reference = [
|
|
633
|
+
[None for _ in range(len(moves_reference[0]))],
|
|
634
|
+
[
|
|
635
|
+
"asdf",
|
|
636
|
+
None,
|
|
637
|
+
None,
|
|
638
|
+
None,
|
|
639
|
+
None,
|
|
640
|
+
None,
|
|
641
|
+
"hello",
|
|
642
|
+
None,
|
|
643
|
+
None,
|
|
644
|
+
None,
|
|
645
|
+
None,
|
|
646
|
+
None,
|
|
647
|
+
None,
|
|
648
|
+
None,
|
|
649
|
+
],
|
|
650
|
+
]
|
|
651
|
+
|
|
652
|
+
self.assertTrue(extractors[0].comments == comments_reference[0])
|
|
653
|
+
self.assertTrue(extractors[1].comments == comments_reference[1])
|
|
654
|
+
|
|
600
655
|
extractors[0].update_position_status() # Ensure status is calculated
|
|
656
|
+
assert extractors[0].position_status is not None # appease the type checker
|
|
601
657
|
self.assertTrue(extractors[0].position_status.is_checkmate)
|
|
602
658
|
self.assertFalse(extractors[0].position_status.is_stalemate)
|
|
603
659
|
self.assertTrue(extractors[0].position_status.is_game_over)
|
|
@@ -613,6 +669,7 @@ class TestPgnExtraction(unittest.TestCase):
|
|
|
613
669
|
extractors[1].position_status is None
|
|
614
670
|
) # Not set by default for parse_game_moves_arrow_chunked_array
|
|
615
671
|
extractors[1].update_position_status()
|
|
672
|
+
assert extractors[1].position_status is not None # appease the type checker
|
|
616
673
|
self.assertFalse(extractors[1].position_status.is_checkmate)
|
|
617
674
|
self.assertFalse(extractors[1].position_status.is_stalemate)
|
|
618
675
|
self.assertFalse(extractors[1].position_status.is_game_over)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
{rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/bench_parquet.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/comment_parsing.rs
RENAMED
|
File without changes
|
|
File without changes
|
{rust_pgn_reader_python_binding-3.2.0 → rust_pgn_reader_python_binding-3.3.0}/src/example_str.py
RENAMED
|
File without changes
|