1use crate::types::{RowBatch, StageTemplate};
32
33#[derive(Debug, Default)]
56pub struct BakingScratch {
57 cut_nz_per_col: Vec<u32>,
59 col_list_start: Vec<u32>,
61 col_list_row: Vec<i32>,
63 col_list_val: Vec<f64>,
65 write_cursor: Vec<u32>,
67}
68
69impl BakingScratch {
70 #[must_use]
72 pub fn new() -> Self {
73 Self::default()
74 }
75}
76
77#[allow(clippy::too_many_lines)] pub fn bake_rows_into_template(
106 base: &StageTemplate,
107 rows: &RowBatch,
108 out: &mut StageTemplate,
109 scratch: &mut BakingScratch,
110) {
111 #[allow(clippy::cast_sign_loss)]
113 {
114 debug_assert_eq!(
115 base.col_starts.len(),
116 base.num_cols + 1,
117 "base.col_starts.len()={} but num_cols+1={}",
118 base.col_starts.len(),
119 base.num_cols + 1
120 );
121 debug_assert_eq!(
122 base.col_starts.last().copied().unwrap_or(0) as usize,
123 base.num_nz,
124 "base.col_starts[num_cols] != base.num_nz"
125 );
126 debug_assert_eq!(base.row_indices.len(), base.num_nz);
127 debug_assert_eq!(base.values.len(), base.num_nz);
128 debug_assert_eq!(base.col_lower.len(), base.num_cols);
129 debug_assert_eq!(base.col_upper.len(), base.num_cols);
130 debug_assert_eq!(base.objective.len(), base.num_cols);
131 debug_assert_eq!(base.row_lower.len(), base.num_rows);
132 debug_assert_eq!(base.row_upper.len(), base.num_rows);
133 debug_assert!(
134 base.col_scale.is_empty() || base.col_scale.len() == base.num_cols,
135 "base.col_scale must be empty or length num_cols"
136 );
137 debug_assert!(
138 base.row_scale.is_empty() || base.row_scale.len() == base.num_rows,
139 "base.row_scale must be empty or length num_rows"
140 );
141
142 if rows.num_rows > 0 {
143 debug_assert_eq!(
144 rows.row_starts.len(),
145 rows.num_rows + 1,
146 "rows.row_starts.len()={} but num_rows+1={}",
147 rows.row_starts.len(),
148 rows.num_rows + 1
149 );
150 debug_assert_eq!(
151 rows.row_starts[0], 0,
152 "RowBatch invariant: row_starts[0] must be 0"
153 );
154 debug_assert_eq!(rows.row_lower.len(), rows.num_rows);
155 debug_assert_eq!(rows.row_upper.len(), rows.num_rows);
156
157 let rows_nnz = rows.row_starts[rows.num_rows] as usize;
158 debug_assert_eq!(rows.col_indices.len(), rows_nnz);
159 debug_assert_eq!(rows.values.len(), rows_nnz);
160
161 #[cfg(debug_assertions)]
162 for &col in &rows.col_indices {
163 debug_assert!(
164 (col as usize) < base.num_cols,
165 "col_indices[k]={col} >= base.num_cols={}",
166 base.num_cols
167 );
168 }
169 }
170 }
171
172 #[allow(clippy::cast_sign_loss)]
174 let rows_nnz = if rows.num_rows > 0 {
175 rows.row_starts[rows.num_rows] as usize
176 } else {
177 0
178 };
179 let total_nnz = base.num_nz + rows_nnz;
180
181 #[allow(clippy::expect_used)]
182 let total_nnz_i32 = i32::try_from(total_nnz).expect("total nnz exceeds i32::MAX");
183
184 let num_cols = base.num_cols;
185 let num_rows = base.num_rows + rows.num_rows;
186
187 scratch.cut_nz_per_col.clear();
191 scratch.cut_nz_per_col.resize(num_cols, 0u32);
192 #[allow(clippy::cast_sign_loss)]
193 for &col in &rows.col_indices {
194 scratch.cut_nz_per_col[col as usize] += 1;
195 }
196
197 out.col_starts.clear();
199 out.row_indices.clear();
200 out.values.clear();
201 out.col_lower.clear();
202 out.col_upper.clear();
203 out.objective.clear();
204 out.col_scale.clear();
205 out.row_lower.clear();
206 out.row_upper.clear();
207 out.row_scale.clear();
208
209 out.num_cols = num_cols;
211 out.num_rows = num_rows;
212 out.num_nz = total_nnz;
213 out.n_state = base.n_state;
214 out.n_transfer = base.n_transfer;
215 out.n_dual_relevant = base.n_dual_relevant;
216 out.n_hydro = base.n_hydro;
217 out.max_par_order = base.max_par_order;
218
219 out.col_lower.extend_from_slice(&base.col_lower);
221 out.col_upper.extend_from_slice(&base.col_upper);
222 out.objective.extend_from_slice(&base.objective);
223 out.col_scale.extend_from_slice(&base.col_scale);
224
225 out.row_lower.extend_from_slice(&base.row_lower);
227 out.row_lower.extend_from_slice(&rows.row_lower);
228 out.row_upper.extend_from_slice(&base.row_upper);
229 out.row_upper.extend_from_slice(&rows.row_upper);
230
231 if !base.row_scale.is_empty() {
237 out.row_scale.extend_from_slice(&base.row_scale);
238 out.row_scale
239 .resize(out.row_scale.len() + rows.num_rows, 1.0_f64);
240 } else if rows.num_rows > 0 {
241 out.row_scale.resize(base.num_rows + rows.num_rows, 1.0_f64);
242 }
243
244 scratch.col_list_start.clear();
247 scratch.col_list_start.reserve(num_cols + 1);
248 let mut running = 0u32;
249 for &count in &scratch.cut_nz_per_col {
250 scratch.col_list_start.push(running);
251 running += count;
252 }
253 scratch.col_list_start.push(running);
254
255 scratch.col_list_row.clear();
259 scratch.col_list_row.resize(rows_nnz, 0i32);
260 scratch.col_list_val.clear();
261 scratch.col_list_val.resize(rows_nnz, 0.0f64);
262 scratch.write_cursor.clear();
263 scratch.write_cursor.resize(num_cols, 0u32);
264
265 #[allow(clippy::cast_sign_loss)]
267 for r in 0..rows.num_rows {
268 let start = rows.row_starts[r] as usize;
269 let end = rows.row_starts[r + 1] as usize;
270 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
271 let row_i32 = (base.num_rows + r) as i32;
272 for k in start..end {
273 let j = rows.col_indices[k] as usize;
274 let pos = (scratch.col_list_start[j] + scratch.write_cursor[j]) as usize;
275 scratch.col_list_row[pos] = row_i32;
276 scratch.col_list_val[pos] = rows.values[k];
277 scratch.write_cursor[j] += 1;
278 }
279 }
280
281 let mut nz_cursor: i32 = 0;
283 #[allow(clippy::cast_sign_loss)]
284 for j in 0..num_cols {
285 out.col_starts.push(nz_cursor);
286
287 let base_start = base.col_starts[j] as usize;
288 let base_end = base.col_starts[j + 1] as usize;
289 out.row_indices
290 .extend_from_slice(&base.row_indices[base_start..base_end]);
291 out.values
292 .extend_from_slice(&base.values[base_start..base_end]);
293
294 let list_start = scratch.col_list_start[j] as usize;
295 let list_end = scratch.col_list_start[j + 1] as usize;
296 out.row_indices
297 .extend_from_slice(&scratch.col_list_row[list_start..list_end]);
298 out.values
299 .extend_from_slice(&scratch.col_list_val[list_start..list_end]);
300
301 let col_len = (base_end - base_start) + (list_end - list_start);
302 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
303 {
304 nz_cursor += col_len as i32;
305 }
306 }
307 out.col_starts.push(total_nnz_i32);
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use crate::types::{RowBatch, SolverStatistics, StageTemplate};
314
315 fn make_fixture_stage_template() -> StageTemplate {
325 StageTemplate {
326 num_cols: 3,
327 num_rows: 2,
328 num_nz: 3,
329 col_starts: vec![0_i32, 2, 2, 3],
330 row_indices: vec![0_i32, 1, 1],
331 values: vec![1.0, 2.0, 1.0],
332 col_lower: vec![0.0, 0.0, 0.0],
333 col_upper: vec![10.0, f64::INFINITY, 8.0],
334 objective: vec![0.0, 1.0, 50.0],
335 row_lower: vec![6.0, 14.0],
336 row_upper: vec![6.0, 14.0],
337 n_state: 1,
338 n_transfer: 0,
339 n_dual_relevant: 1,
340 n_hydro: 1,
341 max_par_order: 0,
342 col_scale: Vec::new(),
343 row_scale: Vec::new(),
344 }
345 }
346
347 fn make_empty_row_batch() -> RowBatch {
349 RowBatch {
350 num_rows: 0,
351 row_starts: vec![0_i32],
352 col_indices: vec![],
353 values: vec![],
354 row_lower: vec![],
355 row_upper: vec![],
356 }
357 }
358
359 #[test]
364 fn test_bake_empty_rows_copies_base() {
365 let base = make_fixture_stage_template();
366 let rows = make_empty_row_batch();
367 let mut out = StageTemplate::empty();
368
369 bake_rows_into_template(&base, &rows, &mut out, &mut BakingScratch::default());
370
371 assert_eq!(out.num_cols, base.num_cols);
372 assert_eq!(out.num_rows, base.num_rows);
373 assert_eq!(out.num_nz, base.num_nz);
374 assert_eq!(out.col_starts, base.col_starts);
375 assert_eq!(out.row_indices, base.row_indices);
376 assert_eq!(out.values, base.values);
377 assert_eq!(out.col_lower, base.col_lower);
378 assert_eq!(out.col_upper, base.col_upper);
379 assert_eq!(out.objective, base.objective);
380 assert_eq!(out.row_lower, base.row_lower);
381 assert_eq!(out.row_upper, base.row_upper);
382 assert_eq!(out.n_state, base.n_state);
383 assert_eq!(out.n_transfer, base.n_transfer);
384 assert_eq!(out.n_dual_relevant, base.n_dual_relevant);
385 assert_eq!(out.n_hydro, base.n_hydro);
386 assert_eq!(out.max_par_order, base.max_par_order);
387 assert!(out.row_scale.is_empty());
389 }
390
391 #[test]
396 fn test_bake_single_row_appends_correct_column_entries() {
397 let base = make_fixture_stage_template();
400 let rows = RowBatch {
401 num_rows: 1,
402 row_starts: vec![0_i32, 2],
403 col_indices: vec![0_i32, 2],
404 values: vec![-1.5, 1.0],
405 row_lower: vec![10.0],
406 row_upper: vec![f64::INFINITY],
407 };
408 let mut out = StageTemplate::empty();
409
410 bake_rows_into_template(&base, &rows, &mut out, &mut BakingScratch::default());
411
412 assert_eq!(out.num_rows, 3);
413 assert_eq!(out.num_nz, 5);
414
415 assert_eq!(out.col_starts, vec![0_i32, 3, 3, 5]);
417
418 assert_eq!(&out.row_indices[0..3], &[0_i32, 1, 2]);
420 assert_eq!(&out.values[0..3], &[1.0_f64, 2.0, -1.5]);
421
422 assert_eq!(&out.row_indices[3..5], &[1_i32, 2]);
426 assert_eq!(&out.values[3..5], &[1.0_f64, 1.0]);
427
428 assert_eq!(out.row_lower, vec![6.0_f64, 14.0, 10.0]);
430 assert!(out.row_upper[2].is_infinite() && out.row_upper[2] > 0.0);
431 }
432
433 #[test]
438 fn test_bake_preserves_row_scale_and_defaults_cut_rows_to_one() {
439 let mut base = make_fixture_stage_template();
440 base.row_scale = vec![1.0, 2.0];
441
442 let rows = RowBatch {
443 num_rows: 1,
444 row_starts: vec![0_i32, 1],
445 col_indices: vec![0_i32],
446 values: vec![-1.0],
447 row_lower: vec![5.0],
448 row_upper: vec![f64::INFINITY],
449 };
450 let mut out = StageTemplate::empty();
451
452 bake_rows_into_template(&base, &rows, &mut out, &mut BakingScratch::default());
453
454 assert_eq!(out.row_scale.len(), 3);
455 assert_eq!(out.row_scale[0], 1.0);
456 assert_eq!(out.row_scale[1], 2.0);
457 assert_eq!(out.row_scale[2], 1.0); }
459
460 #[test]
465 fn test_bake_preserves_empty_row_scale_when_no_rows() {
466 let base = make_fixture_stage_template(); let rows = make_empty_row_batch();
468 let mut out = StageTemplate::empty();
469
470 bake_rows_into_template(&base, &rows, &mut out, &mut BakingScratch::default());
471
472 assert!(out.row_scale.is_empty());
473 assert_eq!(out.num_rows, base.num_rows);
474 }
475
476 #[test]
481 fn test_bake_reuses_out_buffer_capacity() {
482 let big_base = StageTemplate {
484 num_cols: 2,
485 num_rows: 5,
486 num_nz: 10,
487 col_starts: vec![0_i32, 5, 10],
488 row_indices: vec![0_i32, 1, 2, 3, 4, 0, 1, 2, 3, 4],
489 values: vec![1.0; 10],
490 col_lower: vec![0.0, 0.0],
491 col_upper: vec![f64::INFINITY, f64::INFINITY],
492 objective: vec![1.0, 1.0],
493 row_lower: vec![0.0; 5],
494 row_upper: vec![f64::INFINITY; 5],
495 n_state: 0,
496 n_transfer: 0,
497 n_dual_relevant: 0,
498 n_hydro: 0,
499 max_par_order: 0,
500 col_scale: Vec::new(),
501 row_scale: Vec::new(),
502 };
503 let empty_rows = make_empty_row_batch();
504 let mut out = StageTemplate::empty();
505
506 bake_rows_into_template(
507 &big_base,
508 &empty_rows,
509 &mut out,
510 &mut BakingScratch::default(),
511 );
512
513 let cap_col_starts = out.col_starts.capacity();
515 let cap_row_indices = out.row_indices.capacity();
516 let cap_values = out.values.capacity();
517 let cap_row_lower = out.row_lower.capacity();
518 let cap_row_upper = out.row_upper.capacity();
519
520 let small_base = StageTemplate {
522 num_cols: 2,
523 num_rows: 4,
524 num_nz: 8,
525 col_starts: vec![0_i32, 4, 8],
526 row_indices: vec![0_i32, 1, 2, 3, 0, 1, 2, 3],
527 values: vec![1.0; 8],
528 col_lower: vec![0.0, 0.0],
529 col_upper: vec![f64::INFINITY, f64::INFINITY],
530 objective: vec![1.0, 1.0],
531 row_lower: vec![0.0; 4],
532 row_upper: vec![f64::INFINITY; 4],
533 n_state: 0,
534 n_transfer: 0,
535 n_dual_relevant: 0,
536 n_hydro: 0,
537 max_par_order: 0,
538 col_scale: Vec::new(),
539 row_scale: Vec::new(),
540 };
541
542 bake_rows_into_template(
543 &small_base,
544 &empty_rows,
545 &mut out,
546 &mut BakingScratch::default(),
547 );
548
549 assert_eq!(out.num_rows, 4);
550 assert_eq!(out.num_nz, 8);
551
552 assert!(out.col_starts.capacity() >= cap_col_starts);
554 assert!(out.row_indices.capacity() >= cap_row_indices);
555 assert!(out.values.capacity() >= cap_values);
556 assert!(out.row_lower.capacity() >= cap_row_lower);
557 assert!(out.row_upper.capacity() >= cap_row_upper);
558 }
559
560 #[test]
565 fn test_bake_determinism() {
566 let base = make_fixture_stage_template();
567 let rows = RowBatch {
568 num_rows: 2,
569 row_starts: vec![0_i32, 2, 3],
570 col_indices: vec![0_i32, 2, 1],
571 values: vec![-1.0, 0.5, 3.0],
572 row_lower: vec![8.0, 12.0],
573 row_upper: vec![f64::INFINITY, f64::INFINITY],
574 };
575
576 let mut out1 = StageTemplate::empty();
577 let mut out2 = StageTemplate::empty();
578
579 bake_rows_into_template(&base, &rows, &mut out1, &mut BakingScratch::default());
580 bake_rows_into_template(&base, &rows, &mut out2, &mut BakingScratch::default());
581
582 assert_eq!(out1.col_starts, out2.col_starts);
583 assert_eq!(out1.row_indices, out2.row_indices);
584 assert_eq!(out1.values, out2.values);
585 assert_eq!(out1.row_lower, out2.row_lower);
586 assert_eq!(out1.row_upper, out2.row_upper);
587 }
588
589 #[test]
594 fn test_bake_multi_column_distribution() {
595 let base = StageTemplate {
602 num_cols: 4,
603 num_rows: 3,
604 num_nz: 6,
605 col_starts: vec![0_i32, 2, 3, 6, 6],
606 row_indices: vec![0_i32, 1, 2, 0, 1, 2],
607 values: vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
608 col_lower: vec![0.0; 4],
609 col_upper: vec![f64::INFINITY; 4],
610 objective: vec![0.0; 4],
611 row_lower: vec![0.0; 3],
612 row_upper: vec![f64::INFINITY; 3],
613 n_state: 2,
614 n_transfer: 1,
615 n_dual_relevant: 2,
616 n_hydro: 2,
617 max_par_order: 1,
618 col_scale: Vec::new(),
619 row_scale: Vec::new(),
620 };
621
622 let rows = RowBatch {
627 num_rows: 3,
628 row_starts: vec![0_i32, 2, 4, 7],
629 col_indices: vec![0_i32, 3, 1, 2, 0, 2, 3],
630 values: vec![-1.0, 1.0, -2.0, 2.0, -3.0, 3.0, -4.0],
631 row_lower: vec![10.0, 20.0, 30.0],
632 row_upper: vec![f64::INFINITY; 3],
633 };
634
635 let mut out = StageTemplate::empty();
636 bake_rows_into_template(&base, &rows, &mut out, &mut BakingScratch::default());
637
638 assert_eq!(out.num_rows, 6);
639 assert_eq!(out.num_nz, 13);
645 assert_eq!(out.col_starts, vec![0_i32, 4, 6, 11, 13]);
646
647 assert_eq!(&out.row_indices[0..4], &[0_i32, 1, 3, 5]);
649 assert_eq!(&out.values[0..4], &[1.0_f64, 2.0, -1.0, -3.0]);
650
651 assert_eq!(&out.row_indices[4..6], &[2_i32, 4]);
653 assert_eq!(&out.values[4..6], &[3.0_f64, -2.0]);
654
655 assert_eq!(&out.row_indices[6..11], &[0_i32, 1, 2, 4, 5]);
657 assert_eq!(&out.values[6..11], &[4.0_f64, 5.0, 6.0, 2.0, 3.0]);
658
659 assert_eq!(&out.row_indices[11..13], &[3_i32, 5]);
661 assert_eq!(&out.values[11..13], &[1.0_f64, -4.0]);
662
663 assert_eq!(&out.row_lower[3..6], &[10.0_f64, 20.0, 30.0]);
665 }
666
667 struct MockSolver {
674 last_loaded_num_rows: usize,
675 stats: SolverStatistics,
676 }
677
678 impl MockSolver {
679 fn new() -> Self {
680 Self {
681 last_loaded_num_rows: 0,
682 stats: SolverStatistics::default(),
683 }
684 }
685 }
686
687 impl crate::SolverInterface for MockSolver {
688 type Profile = crate::profile::MockProfile;
689
690 fn apply_profile(&mut self, _profile: &crate::profile::MockProfile) {}
691
692 fn load_model(&mut self, template: &StageTemplate) {
693 self.last_loaded_num_rows = template.num_rows;
694 self.stats.load_model_count += 1;
695 }
696
697 fn add_rows(&mut self, _rows: &RowBatch) {}
698
699 fn set_row_bounds(&mut self, _indices: &[usize], _lower: &[f64], _upper: &[f64]) {}
700
701 fn set_col_bounds(&mut self, _indices: &[usize], _lower: &[f64], _upper: &[f64]) {}
702
703 fn solve(
704 &mut self,
705 _basis: Option<&crate::types::Basis>,
706 ) -> Result<crate::types::SolutionView<'_>, crate::types::SolverError> {
707 Err(crate::types::SolverError::InternalError {
708 message: "mock".to_string(),
709 error_code: None,
710 })
711 }
712
713 fn get_basis(&mut self, _out: &mut crate::types::Basis) {}
714
715 fn statistics(&self) -> SolverStatistics {
716 self.stats.clone()
717 }
718
719 fn statistics_into(&self, out: &mut SolverStatistics) {
720 out.copy_from(&self.stats);
721 }
722
723 fn name(&self) -> &'static str {
724 "Mock"
725 }
726
727 fn solver_name_version(&self) -> String {
728 "MockSolver 0.0.0".to_string()
729 }
730 }
731
732 #[test]
733 fn test_bake_load_model_row_count() {
734 use crate::SolverInterface;
735
736 let base = make_fixture_stage_template();
737 let rows = RowBatch {
738 num_rows: 3,
739 row_starts: vec![0_i32, 1, 2, 3],
740 col_indices: vec![0_i32, 1, 2],
741 values: vec![-1.0, -1.0, -1.0],
742 row_lower: vec![5.0, 6.0, 7.0],
743 row_upper: vec![f64::INFINITY; 3],
744 };
745
746 let mut out = StageTemplate::empty();
747 bake_rows_into_template(&base, &rows, &mut out, &mut BakingScratch::default());
748
749 let expected_rows = base.num_rows + rows.num_rows; let mut solver = MockSolver::new();
752 let before = solver.statistics().load_model_count;
753 solver.load_model(&out);
754 let after = solver.statistics().load_model_count;
755
756 assert_eq!(after - before, 1);
757 assert_eq!(solver.last_loaded_num_rows, expected_rows);
758 }
759
760 #[test]
765 fn test_bake_empty_base_row_scale_with_cut_rows_appends_ones() {
766 let base = make_fixture_stage_template(); let rows = RowBatch {
768 num_rows: 2,
769 row_starts: vec![0_i32, 1, 2],
770 col_indices: vec![0_i32, 0],
771 values: vec![-1.0, -2.0],
772 row_lower: vec![5.0, 6.0],
773 row_upper: vec![f64::INFINITY; 2],
774 };
775 let mut out = StageTemplate::empty();
776
777 bake_rows_into_template(&base, &rows, &mut out, &mut BakingScratch::default());
778
779 assert_eq!(out.row_scale.len(), base.num_rows + rows.num_rows);
783 assert!(out.row_scale.iter().all(|&s| s == 1.0));
784 }
785
786 #[test]
804 #[cfg(not(debug_assertions))]
805 #[should_panic(expected = "total nnz exceeds i32::MAX")]
806 fn test_bake_panics_on_nnz_overflow() {
807 let large_num_nz = usize::try_from(i32::MAX).unwrap(); let base = StageTemplate {
813 num_cols: 0,
814 num_rows: 0,
815 num_nz: large_num_nz,
816 col_starts: vec![0_i32], row_indices: vec![], values: vec![],
819 col_lower: vec![],
820 col_upper: vec![],
821 objective: vec![],
822 row_lower: vec![],
823 row_upper: vec![],
824 n_state: 0,
825 n_transfer: 0,
826 n_dual_relevant: 0,
827 n_hydro: 0,
828 max_par_order: 0,
829 col_scale: Vec::new(),
830 row_scale: Vec::new(),
831 };
832 let rows = RowBatch {
838 num_rows: 1,
839 row_starts: vec![0_i32, 1],
840 col_indices: vec![0_i32],
841 values: vec![1.0],
842 row_lower: vec![0.0],
843 row_upper: vec![f64::INFINITY],
844 };
845 let mut out = StageTemplate::empty();
846 bake_rows_into_template(&base, &rows, &mut out, &mut BakingScratch::default());
847 }
848
849 #[test]
855 fn bake_twice_same_scratch_is_bit_identical() {
856 let base = StageTemplate {
858 num_cols: 4,
859 num_rows: 3,
860 num_nz: 6,
861 col_starts: vec![0_i32, 2, 3, 6, 6],
862 row_indices: vec![0_i32, 1, 2, 0, 1, 2],
863 values: vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
864 col_lower: vec![0.0; 4],
865 col_upper: vec![f64::INFINITY; 4],
866 objective: vec![0.0; 4],
867 row_lower: vec![0.0; 3],
868 row_upper: vec![f64::INFINITY; 3],
869 n_state: 2,
870 n_transfer: 1,
871 n_dual_relevant: 2,
872 n_hydro: 2,
873 max_par_order: 1,
874 col_scale: Vec::new(),
875 row_scale: Vec::new(),
876 };
877 let rows = RowBatch {
878 num_rows: 3,
879 row_starts: vec![0_i32, 2, 4, 7],
880 col_indices: vec![0_i32, 3, 1, 2, 0, 2, 3],
881 values: vec![-1.0, 1.0, -2.0, 2.0, -3.0, 3.0, -4.0],
882 row_lower: vec![10.0, 20.0, 30.0],
883 row_upper: vec![f64::INFINITY; 3],
884 };
885
886 let mut scratch = BakingScratch::default();
887
888 let mut out1 = StageTemplate::empty();
890 bake_rows_into_template(&base, &rows, &mut out1, &mut scratch);
891
892 let cap_cut_nz = scratch.cut_nz_per_col.capacity();
894 let cap_col_start = scratch.col_list_start.capacity();
895 let cap_col_row = scratch.col_list_row.capacity();
896 let cap_col_val = scratch.col_list_val.capacity();
897 let cap_write_cursor = scratch.write_cursor.capacity();
898
899 let mut out2 = StageTemplate::empty();
901 bake_rows_into_template(&base, &rows, &mut out2, &mut scratch);
902
903 let mut out_fresh = StageTemplate::empty();
905 bake_rows_into_template(&base, &rows, &mut out_fresh, &mut BakingScratch::default());
906
907 assert_eq!(out1.col_starts, out2.col_starts);
909 assert_eq!(out1.col_starts, out_fresh.col_starts);
910 assert_eq!(out1.row_indices, out2.row_indices);
911 assert_eq!(out1.row_indices, out_fresh.row_indices);
912 assert_eq!(out1.values, out2.values);
913 assert_eq!(out1.values, out_fresh.values);
914 assert_eq!(out1.row_lower, out2.row_lower);
915 assert_eq!(out1.row_lower, out_fresh.row_lower);
916 assert_eq!(out1.row_upper, out2.row_upper);
917 assert_eq!(out1.row_upper, out_fresh.row_upper);
918 assert_eq!(out1.row_scale, out2.row_scale);
919 assert_eq!(out1.row_scale, out_fresh.row_scale);
920
921 assert!(scratch.cut_nz_per_col.capacity() >= cap_cut_nz);
923 assert!(scratch.col_list_start.capacity() >= cap_col_start);
924 assert!(scratch.col_list_row.capacity() >= cap_col_row);
925 assert!(scratch.col_list_val.capacity() >= cap_col_val);
926 assert!(scratch.write_cursor.capacity() >= cap_write_cursor);
927 }
928}