Skip to main content

ParallelCsr32Operator

Struct ParallelCsr32Operator 

Source
pub struct ParallelCsr32Operator<'a> { /* private fields */ }
Expand description

Read-only parallel CSR operator wrapper. It shares the validated CSR storage and only changes the row execution policy used by apply.

Implementations§

Source§

impl<'a> ParallelCsr32Operator<'a>

Source

pub fn new(matrix: &'a Csr32Matrix) -> ParallelCsr32Operator<'a>

Examples found in repository?
examples/fem_structural_auto.rs (line 389)
304fn main() -> Result<(), Box<dyn Error>> {
305    let args = Args::parse()?;
306    println!(
307        "HyBIT {} structural-auto FEM benchmark",
308        env!("CARGO_PKG_VERSION")
309    );
310    println!("matrix             : {}", args.matrix.display());
311    println!("coordinates        : {}", args.coordinates.display());
312
313    let load_start = Instant::now();
314    let (matrix, mm) = read_matrix_market(&args.matrix)?;
315    let matrix_load_seconds = load_start.elapsed().as_secs_f64();
316    let coord_start = Instant::now();
317    let coordinates = read_coordinates(&args.coordinates)?;
318    let coord_load_seconds = coord_start.elapsed().as_secs_f64();
319    let profile = analyze_csr32(&matrix)?;
320
321    println!(
322        "Matrix Market      : {:?}, {} input entries -> {} CSR nnz",
323        mm.symmetry, mm.input_entries, mm.csr_nnz
324    );
325    println!("dimensions         : {} x {}", profile.nrows, profile.ncols);
326    println!("nnz                : {}", profile.nnz);
327    println!(
328        "CSR storage        : {:.3} MiB",
329        mib(matrix.storage_bytes())
330    );
331    println!("matrix load        : {:.3} ms", matrix_load_seconds * 1.0e3);
332    println!("coordinate nodes   : {}", coordinates.len());
333    println!("coordinate load    : {:.3} ms", coord_load_seconds * 1.0e3);
334
335    if !profile.square || !profile.full_diagonal || !profile.positive_diagonal {
336        return Err(
337            "rigid-body two-level PCG requires a square matrix with a complete positive diagonal"
338                .into(),
339        );
340    }
341    if matrix.nrows() != coordinates.len() * 3 {
342        return Err(format!(
343            "matrix/coordinate mismatch: {} matrix rows != {} coordinate nodes * 3",
344            matrix.nrows(),
345            coordinates.len()
346        )
347        .into());
348    }
349
350    let generated_rhs = args.rhs.is_none();
351    let b = if let Some(path) = args.rhs.as_deref() {
352        println!("RHS                : {}", path.display());
353        load_rhs(path, matrix.nrows())?
354    } else {
355        println!("RHS                : generated as b=A*1 (known exact solution)");
356        let ones = vec![1.0; matrix.ncols()];
357        matrix.spmv(&ones)?
358    };
359    let mut solver = HybitSolver::new();
360    solver.set_options(SolverOptions {
361        relative_tolerance: args.relative_tolerance,
362        absolute_tolerance: 0.0,
363        max_iterations: args.max_iterations,
364    })?;
365    solver.set_structural_options(StructuralOptions {
366        target_coarse_dimension: args.target_coarse_dimension,
367        aggregation: args.aggregation,
368        spmv_policy: args.spmv_policy,
369        preconditioner_policy: args.preconditioner_policy,
370        pcg_vector_policy: args.pcg_vector_policy,
371    })?;
372
373    let analysis = solver.analyze_csr32(&matrix)?;
374    let mut prepared = solver.prepare_structural_csr32(&matrix, &analysis, &coordinates)?;
375    println!("policy             : StructuralAuto/RigidBodyTwoLevel");
376    println!("aggregation        : {:?}", prepared.aggregation());
377    println!("SpMV policy        : {:?}", prepared.spmv_policy());
378    println!(
379        "precond policy     : {:?}",
380        prepared.structural_preconditioner_policy()
381    );
382    println!("PCG vector policy  : {:?}", prepared.pcg_vector_policy());
383    if prepared.parallel_spmv_enabled()
384        || prepared.parallel_preconditioner_enabled()
385        || prepared.parallel_pcg_vectors_enabled()
386    {
387        println!(
388            "Rayon threads      : {}",
389            ParallelCsr32Operator::new(&matrix).rayon_threads()
390        );
391    }
392    if prepared.parallel_preconditioner_enabled() {
393        println!(
394            "parallel index     : {:.3} MiB",
395            mib(prepared.parallel_preconditioner_index_bytes())
396        );
397    }
398    println!("target coarse dim  : {}", args.target_coarse_dimension);
399    println!(
400        "aggregate nodes    : {} (auto-selected)",
401        prepared.aggregate_nodes()
402    );
403    println!("fine block size    : 3");
404    println!("aggregate count    : {}", prepared.aggregate_count());
405    println!(
406        "aggregate min/max  : {} / {} nodes",
407        prepared.min_aggregate_nodes(),
408        prepared.max_aggregate_nodes()
409    );
410    println!("modes/aggregate    : 6");
411    println!("coarse dimension   : {}", prepared.coarse_dimension());
412    println!(
413        "base factor        : {:.3} MiB",
414        mib(prepared.base_factor_bytes())
415    );
416    println!(
417        "coarse factor      : {:.3} MiB",
418        mib(prepared.coarse_factor_bytes())
419    );
420    println!(
421        "geometry storage   : {:.3} MiB",
422        mib(prepared.geometry_bytes())
423    );
424    println!(
425        "total prec storage : {:.3} MiB",
426        mib(prepared.preconditioner_bytes())
427    );
428    println!(
429        "analysis           : {:.3} ms",
430        prepared.analysis_seconds() * 1.0e3
431    );
432    println!(
433        "prepare            : {:.3} ms",
434        prepared.prepare_seconds() * 1.0e3
435    );
436
437    let mut x = vec![0.0; matrix.ncols()];
438    let report = prepared.solve(&matrix, &b, &mut x)?;
439    let verified = verified_relative_residual(&matrix, &b, &x)?;
440
441    println!();
442    println!("Structural Auto API: 3x3 Block-Jacobi + six rigid-body coarse modes");
443    println!("status             : {:?}", report.status);
444    println!("preconditioner     : {:?}", report.preconditioner);
445    println!("iterations         : {}", report.iterations);
446    println!("reported residual  : {:.6e}", report.relative_residual);
447    println!("verified residual  : {:.6e}", verified);
448    if generated_rhs {
449        println!("relative x error   : {:.6e}", relative_error_to_ones(&x));
450    }
451    println!(
452        "solve time         : {:.3} ms",
453        report.solve_seconds * 1.0e3
454    );
455    println!(
456        "total setup+solve  : {:.3} ms",
457        (report.setup_seconds + report.solve_seconds) * 1.0e3
458    );
459
460    if !verified.is_finite() {
461        return Err("non-finite independently verified residual".into());
462    }
463    Ok(())
464}
More examples
Hide additional examples
examples/fem_structural_pcg_parallel.rs (line 324)
245fn main() -> Result<(), Box<dyn Error>> {
246    let args = Args::parse()?;
247    println!(
248        "HyBIT {} serial vs parallel/fused PCG vector benchmark",
249        env!("CARGO_PKG_VERSION")
250    );
251    println!("matrix             : {}", args.matrix.display());
252    println!("coordinates        : {}", args.coordinates.display());
253
254    let load_start = Instant::now();
255    let (matrix, mm) = read_matrix_market(&args.matrix)?;
256    let matrix_load = load_start.elapsed().as_secs_f64();
257    let coord_start = Instant::now();
258    let coordinates = read_coordinates(&args.coordinates)?;
259    let coord_load = coord_start.elapsed().as_secs_f64();
260    let matrix_profile = analyze_csr32(&matrix)?;
261
262    println!(
263        "Matrix Market      : {:?}, {} input entries -> {} CSR nnz",
264        mm.symmetry, mm.input_entries, mm.csr_nnz
265    );
266    println!(
267        "dimensions         : {} x {}",
268        matrix_profile.nrows, matrix_profile.ncols
269    );
270    println!("nnz                : {}", matrix_profile.nnz);
271    println!(
272        "CSR storage        : {:.3} MiB",
273        mib(matrix.storage_bytes())
274    );
275    println!("matrix load        : {:.3} ms", matrix_load * 1.0e3);
276    println!("coordinate nodes   : {}", coordinates.len());
277    println!("coordinate load    : {:.3} ms", coord_load * 1.0e3);
278    println!("aggregation        : {:?}", args.aggregation);
279    println!("target coarse dim  : {}", args.target_coarse_dimension);
280
281    if !matrix_profile.square || !matrix_profile.full_diagonal || !matrix_profile.positive_diagonal
282    {
283        return Err(
284            "structural PCG benchmark requires a square matrix with a complete positive diagonal"
285                .into(),
286        );
287    }
288    if matrix.nrows() != coordinates.len() * 3 {
289        return Err(format!(
290            "matrix/coordinate mismatch: {} matrix rows != {} coordinate nodes * 3",
291            matrix.nrows(),
292            coordinates.len()
293        )
294        .into());
295    }
296
297    let b = if let Some(path) = args.rhs.as_deref() {
298        println!("RHS                : {}", path.display());
299        load_rhs(path, matrix.nrows())?
300    } else {
301        println!("RHS                : generated as b=A*1 (known exact solution)");
302        matrix.spmv(&vec![1.0; matrix.ncols()])?
303    };
304
305    let aggregate_nodes =
306        recommend_rigid_body_aggregate_nodes(coordinates.len(), args.target_coarse_dimension)?;
307    let setup_start = Instant::now();
308    let preconditioner = match args.aggregation {
309        RigidBodyAggregation::Graph => {
310            RigidBodyTwoLevelBlockJacobiPreconditioner::from_csr32_graph(
311                &matrix,
312                &coordinates,
313                aggregate_nodes,
314            )?
315        }
316        RigidBodyAggregation::Contiguous => RigidBodyTwoLevelBlockJacobiPreconditioner::from_csr32(
317            &matrix,
318            &coordinates,
319            aggregate_nodes,
320        )?,
321        RigidBodyAggregation::Auto => unreachable!(),
322    };
323    let parallel_preconditioner = ParallelRigidBodyTwoLevelPreconditioner::new(&preconditioner)?;
324    let parallel_operator = ParallelCsr32Operator::new(&matrix);
325    let setup_seconds = setup_start.elapsed().as_secs_f64();
326
327    println!("aggregate target   : {} nodes", aggregate_nodes);
328    println!("aggregate count    : {}", preconditioner.aggregate_count());
329    println!("coarse dimension   : {}", preconditioner.coarse_dimension());
330    println!(
331        "prec storage       : {:.3} MiB",
332        mib(preconditioner.factor_bytes())
333    );
334    println!(
335        "parallel index     : {:.3} MiB",
336        mib(parallel_preconditioner.index_storage_bytes())
337    );
338    println!("setup              : {:.3} ms", setup_seconds * 1.0e3);
339    println!(
340        "Rayon threads      : {}",
341        parallel_preconditioner.rayon_threads()
342    );
343    println!("vector chunk       : {} values", PARALLEL_PCG_VECTOR_CHUNK);
344
345    let options = SolverOptions {
346        relative_tolerance: args.relative_tolerance,
347        absolute_tolerance: 0.0,
348        max_iterations: args.max_iterations,
349    };
350    let b_norm = norm2(&b).max(f64::MIN_POSITIVE);
351
352    let mut x_serial = vec![0.0; matrix.ncols()];
353    let mut ws_serial = PcgWorkspace::new(matrix.nrows());
354    let serial_start = Instant::now();
355    let serial_out = pcg_with_workspace(
356        &parallel_operator,
357        &parallel_preconditioner,
358        &b,
359        &mut x_serial,
360        options,
361        &mut ws_serial,
362    )?;
363    let serial_wall = serial_start.elapsed().as_secs_f64();
364    let serial_verified = verified_relative_residual(&matrix, &b, &x_serial)?;
365
366    let mut x_parallel = vec![0.0; matrix.ncols()];
367    let mut ws_parallel = PcgWorkspace::new(matrix.nrows());
368    let parallel_start = Instant::now();
369    let parallel_out = pcg_with_workspace_parallel_vectors(
370        &parallel_operator,
371        &parallel_preconditioner,
372        &b,
373        &mut x_parallel,
374        options,
375        &mut ws_parallel,
376    )?;
377    let parallel_wall = parallel_start.elapsed().as_secs_f64();
378    let parallel_verified = verified_relative_residual(&matrix, &b, &x_parallel)?;
379
380    println!();
381    println!("[1/2] Production PCG vector kernels");
382    println!("status             : {:?}", serial_out.status);
383    println!("iterations         : {}", serial_out.iterations);
384    println!(
385        "reported residual  : {:.6e}",
386        serial_out.final_residual / b_norm
387    );
388    println!("verified residual  : {:.6e}", serial_verified);
389    println!("solve time         : {:.3} ms", serial_wall * 1.0e3);
390
391    println!();
392    println!("[2/2] Parallel/fused PCG vector kernels");
393    println!("status             : {:?}", parallel_out.status);
394    println!("iterations         : {}", parallel_out.iterations);
395    println!(
396        "reported residual  : {:.6e}",
397        parallel_out.final_residual / b_norm
398    );
399    println!("verified residual  : {:.6e}", parallel_verified);
400    println!("solve time         : {:.3} ms", parallel_wall * 1.0e3);
401
402    println!();
403    println!("Comparison");
404    println!(
405        "iteration ratio    : {:.3} (parallel vectors / production)",
406        parallel_out.iterations as f64 / (serial_out.iterations.max(1) as f64)
407    );
408    println!(
409        "solve-time ratio   : {:.3} (parallel vectors / production)",
410        parallel_wall / serial_wall.max(f64::MIN_POSITIVE)
411    );
412    println!(
413        "PCG speedup        : {:.3}x",
414        serial_wall / parallel_wall.max(f64::MIN_POSITIVE)
415    );
416    println!(
417        "solution delta     : {:.6e} relative L2",
418        relative_difference(&x_serial, &x_parallel)
419    );
420    println!("note               : sparse operator and preconditioner are identical in both runs");
421
422    Ok(())
423}
examples/fem_structural_spmv.rs (line 319)
243fn main() -> Result<(), Box<dyn Error>> {
244    let args = Args::parse()?;
245    println!(
246        "HyBIT {} serial vs parallel CSR structural benchmark",
247        env!("CARGO_PKG_VERSION")
248    );
249    println!("matrix             : {}", args.matrix.display());
250    println!("coordinates        : {}", args.coordinates.display());
251
252    let load_start = Instant::now();
253    let (matrix, mm) = read_matrix_market(&args.matrix)?;
254    let matrix_load = load_start.elapsed().as_secs_f64();
255    let coord_start = Instant::now();
256    let coordinates = read_coordinates(&args.coordinates)?;
257    let coord_load = coord_start.elapsed().as_secs_f64();
258    let profile = analyze_csr32(&matrix)?;
259
260    println!(
261        "Matrix Market      : {:?}, {} input entries -> {} CSR nnz",
262        mm.symmetry, mm.input_entries, mm.csr_nnz
263    );
264    println!("dimensions         : {} x {}", profile.nrows, profile.ncols);
265    println!("nnz                : {}", profile.nnz);
266    println!(
267        "CSR storage        : {:.3} MiB",
268        mib(matrix.storage_bytes())
269    );
270    println!("matrix load        : {:.3} ms", matrix_load * 1.0e3);
271    println!("coordinate nodes   : {}", coordinates.len());
272    println!("coordinate load    : {:.3} ms", coord_load * 1.0e3);
273    println!("aggregation        : {:?}", args.aggregation);
274    println!("target coarse dim  : {}", args.target_coarse_dimension);
275    println!("kernel repeats     : {}", args.kernel_repeats);
276
277    if !profile.square || !profile.full_diagonal || !profile.positive_diagonal {
278        return Err(
279            "structural PCG benchmark requires a square matrix with a complete positive diagonal"
280                .into(),
281        );
282    }
283    if matrix.nrows() != coordinates.len() * 3 {
284        return Err(format!(
285            "matrix/coordinate mismatch: {} matrix rows != {} coordinate nodes * 3",
286            matrix.nrows(),
287            coordinates.len()
288        )
289        .into());
290    }
291
292    let b = if let Some(path) = args.rhs.as_deref() {
293        println!("RHS                : {}", path.display());
294        load_rhs(path, matrix.nrows())?
295    } else {
296        println!("RHS                : generated as b=A*1 (known exact solution)");
297        matrix.spmv(&vec![1.0; matrix.ncols()])?
298    };
299
300    let aggregate_nodes =
301        recommend_rigid_body_aggregate_nodes(coordinates.len(), args.target_coarse_dimension)?;
302    let setup_start = Instant::now();
303    let preconditioner = match args.aggregation {
304        RigidBodyAggregation::Graph => {
305            RigidBodyTwoLevelBlockJacobiPreconditioner::from_csr32_graph(
306                &matrix,
307                &coordinates,
308                aggregate_nodes,
309            )?
310        }
311        RigidBodyAggregation::Contiguous => RigidBodyTwoLevelBlockJacobiPreconditioner::from_csr32(
312            &matrix,
313            &coordinates,
314            aggregate_nodes,
315        )?,
316        RigidBodyAggregation::Auto => unreachable!(),
317    };
318    let setup_seconds = setup_start.elapsed().as_secs_f64();
319    let parallel = ParallelCsr32Operator::new(&matrix);
320
321    println!("aggregate target   : {} nodes", aggregate_nodes);
322    println!("aggregate count    : {}", preconditioner.aggregate_count());
323    println!("coarse dimension   : {}", preconditioner.coarse_dimension());
324    println!(
325        "prec storage       : {:.3} MiB",
326        mib(preconditioner.factor_bytes())
327    );
328    println!("setup              : {:.3} ms", setup_seconds * 1.0e3);
329    println!("Rayon threads      : {}", parallel.rayon_threads());
330
331    let mut ys = vec![0.0; matrix.nrows()];
332    let mut yp = vec![0.0; matrix.nrows()];
333    matrix.apply(&b, &mut ys)?;
334    parallel.apply(&b, &mut yp)?;
335    if ys != yp {
336        return Err("serial and parallel CSR kernels produced different results".into());
337    }
338
339    let serial_start = Instant::now();
340    for _ in 0..args.kernel_repeats {
341        matrix.apply(&b, &mut ys)?;
342    }
343    let serial_spmv = serial_start.elapsed().as_secs_f64();
344
345    let parallel_start = Instant::now();
346    for _ in 0..args.kernel_repeats {
347        parallel.apply(&b, &mut yp)?;
348    }
349    let parallel_spmv = parallel_start.elapsed().as_secs_f64();
350
351    println!();
352    println!("CSR SpMV microbenchmark");
353    println!(
354        "serial / call      : {:.3} ms",
355        ms_per(serial_spmv, args.kernel_repeats)
356    );
357    println!(
358        "parallel / call    : {:.3} ms",
359        ms_per(parallel_spmv, args.kernel_repeats)
360    );
361    println!(
362        "SpMV speedup       : {:.3}x",
363        serial_spmv / parallel_spmv.max(f64::MIN_POSITIVE)
364    );
365
366    let options = SolverOptions {
367        relative_tolerance: args.relative_tolerance,
368        absolute_tolerance: 0.0,
369        max_iterations: args.max_iterations,
370    };
371
372    let mut xs = vec![0.0; matrix.ncols()];
373    let mut ws = PcgWorkspace::new(matrix.nrows());
374    let serial_solve_start = Instant::now();
375    let serial_out = pcg_with_workspace(&matrix, &preconditioner, &b, &mut xs, options, &mut ws)?;
376    let serial_solve = serial_solve_start.elapsed().as_secs_f64();
377    let serial_verified = verified_relative_residual(&matrix, &b, &xs)?;
378
379    let mut xp = vec![0.0; matrix.ncols()];
380    let mut wp = PcgWorkspace::new(matrix.nrows());
381    let parallel_solve_start = Instant::now();
382    let parallel_out =
383        pcg_with_workspace(&parallel, &preconditioner, &b, &mut xp, options, &mut wp)?;
384    let parallel_solve = parallel_solve_start.elapsed().as_secs_f64();
385    let parallel_verified = verified_relative_residual(&matrix, &b, &xp)?;
386
387    println!();
388    println!("[1/2] Serial CSR PCG");
389    println!("status             : {:?}", serial_out.status);
390    println!("iterations         : {}", serial_out.iterations);
391    println!(
392        "reported residual  : {:.6e}",
393        serial_out.final_residual / norm2(&b).max(f64::MIN_POSITIVE)
394    );
395    println!("verified residual  : {:.6e}", serial_verified);
396    println!("solve time         : {:.3} ms", serial_solve * 1.0e3);
397
398    println!();
399    println!("[2/2] Parallel CSR PCG");
400    println!("status             : {:?}", parallel_out.status);
401    println!("iterations         : {}", parallel_out.iterations);
402    println!(
403        "reported residual  : {:.6e}",
404        parallel_out.final_residual / norm2(&b).max(f64::MIN_POSITIVE)
405    );
406    println!("verified residual  : {:.6e}", parallel_verified);
407    println!("solve time         : {:.3} ms", parallel_solve * 1.0e3);
408
409    println!();
410    println!("Comparison");
411    println!(
412        "iteration ratio    : {:.3} (parallel / serial)",
413        parallel_out.iterations as f64 / serial_out.iterations.max(1) as f64
414    );
415    println!(
416        "solve-time ratio   : {:.3} (parallel / serial)",
417        parallel_solve / serial_solve.max(f64::MIN_POSITIVE)
418    );
419    println!(
420        "PCG speedup        : {:.3}x",
421        serial_solve / parallel_solve.max(f64::MIN_POSITIVE)
422    );
423
424    if !serial_verified.is_finite() || !parallel_verified.is_finite() {
425        return Err("non-finite independently verified residual".into());
426    }
427    Ok(())
428}
examples/fem_structural_pcg_profile.rs (line 557)
478fn main() -> Result<(), Box<dyn Error>> {
479    let args = Args::parse()?;
480    println!(
481        "HyBIT {} PCG vector-kernel profile",
482        env!("CARGO_PKG_VERSION")
483    );
484    println!("matrix             : {}", args.matrix.display());
485    println!("coordinates        : {}", args.coordinates.display());
486
487    let load_start = Instant::now();
488    let (matrix, mm) = read_matrix_market(&args.matrix)?;
489    let matrix_load = load_start.elapsed().as_secs_f64();
490    let coord_start = Instant::now();
491    let coordinates = read_coordinates(&args.coordinates)?;
492    let coord_load = coord_start.elapsed().as_secs_f64();
493    let matrix_profile = analyze_csr32(&matrix)?;
494
495    println!(
496        "Matrix Market      : {:?}, {} input entries -> {} CSR nnz",
497        mm.symmetry, mm.input_entries, mm.csr_nnz
498    );
499    println!(
500        "dimensions         : {} x {}",
501        matrix_profile.nrows, matrix_profile.ncols
502    );
503    println!("nnz                : {}", matrix_profile.nnz);
504    println!(
505        "CSR storage        : {:.3} MiB",
506        mib(matrix.storage_bytes())
507    );
508    println!("matrix load        : {:.3} ms", matrix_load * 1.0e3);
509    println!("coordinate nodes   : {}", coordinates.len());
510    println!("coordinate load    : {:.3} ms", coord_load * 1.0e3);
511    println!("aggregation        : {:?}", args.aggregation);
512    println!("target coarse dim  : {}", args.target_coarse_dimension);
513
514    if !matrix_profile.square || !matrix_profile.full_diagonal || !matrix_profile.positive_diagonal
515    {
516        return Err(
517            "structural PCG benchmark requires a square matrix with a complete positive diagonal"
518                .into(),
519        );
520    }
521    if matrix.nrows() != coordinates.len() * 3 {
522        return Err(format!(
523            "matrix/coordinate mismatch: {} matrix rows != {} coordinate nodes * 3",
524            matrix.nrows(),
525            coordinates.len()
526        )
527        .into());
528    }
529
530    let b = if let Some(path) = args.rhs.as_deref() {
531        println!("RHS                : {}", path.display());
532        load_rhs(path, matrix.nrows())?
533    } else {
534        println!("RHS                : generated as b=A*1 (known exact solution)");
535        matrix.spmv(&vec![1.0; matrix.ncols()])?
536    };
537
538    let aggregate_nodes =
539        recommend_rigid_body_aggregate_nodes(coordinates.len(), args.target_coarse_dimension)?;
540    let setup_start = Instant::now();
541    let preconditioner = match args.aggregation {
542        RigidBodyAggregation::Graph => {
543            RigidBodyTwoLevelBlockJacobiPreconditioner::from_csr32_graph(
544                &matrix,
545                &coordinates,
546                aggregate_nodes,
547            )?
548        }
549        RigidBodyAggregation::Contiguous => RigidBodyTwoLevelBlockJacobiPreconditioner::from_csr32(
550            &matrix,
551            &coordinates,
552            aggregate_nodes,
553        )?,
554        RigidBodyAggregation::Auto => unreachable!(),
555    };
556    let parallel_preconditioner = ParallelRigidBodyTwoLevelPreconditioner::new(&preconditioner)?;
557    let parallel_operator = ParallelCsr32Operator::new(&matrix);
558    let setup_seconds = setup_start.elapsed().as_secs_f64();
559
560    println!("aggregate target   : {} nodes", aggregate_nodes);
561    println!("aggregate count    : {}", preconditioner.aggregate_count());
562    println!("coarse dimension   : {}", preconditioner.coarse_dimension());
563    println!(
564        "prec storage       : {:.3} MiB",
565        mib(preconditioner.factor_bytes())
566    );
567    println!(
568        "parallel index     : {:.3} MiB",
569        mib(parallel_preconditioner.index_storage_bytes())
570    );
571    println!("setup              : {:.3} ms", setup_seconds * 1.0e3);
572    println!(
573        "Rayon threads      : {}",
574        parallel_preconditioner.rayon_threads()
575    );
576
577    let options = SolverOptions {
578        relative_tolerance: args.relative_tolerance,
579        absolute_tolerance: 0.0,
580        max_iterations: args.max_iterations,
581    };
582
583    let mut x_profiled = vec![0.0; matrix.ncols()];
584    let (profiled_out, stages, profiled_wall) = profiled_pcg(
585        &parallel_operator,
586        &parallel_preconditioner,
587        &b,
588        &mut x_profiled,
589        options,
590    )?;
591    let profiled_verified = verified_relative_residual(&matrix, &b, &x_profiled)?;
592
593    let mut x_control = vec![0.0; matrix.ncols()];
594    let mut workspace = PcgWorkspace::new(matrix.nrows());
595    let control_start = Instant::now();
596    let control_out = pcg_with_workspace(
597        &parallel_operator,
598        &parallel_preconditioner,
599        &b,
600        &mut x_control,
601        options,
602        &mut workspace,
603    )?;
604    let control_wall = control_start.elapsed().as_secs_f64();
605    let control_verified = verified_relative_residual(&matrix, &b, &x_control)?;
606
607    println!();
608    println!("Profiled PCG");
609    println!("status             : {:?}", profiled_out.status);
610    println!("iterations         : {}", profiled_out.iterations);
611    println!(
612        "reported residual  : {:.6e}",
613        profiled_out.final_residual / norm2(&b).max(f64::MIN_POSITIVE)
614    );
615    println!("verified residual  : {:.6e}", profiled_verified);
616    println!("profiled wall      : {:.3} ms", profiled_wall * 1.0e3);
617    println!(
618        "observed / iter    : {:.3} ms",
619        if profiled_out.iterations == 0 {
620            0.0
621        } else {
622            profiled_wall * 1.0e3 / profiled_out.iterations as f64
623        }
624    );
625    println!();
626    println!("Actual nested PCG stage timings");
627    print_stage(
628        "operator A*x",
629        stages.operator_seconds,
630        stages.operator_calls,
631        profiled_wall,
632    );
633    print_stage(
634        "preconditioner",
635        stages.preconditioner_seconds,
636        stages.preconditioner_calls,
637        profiled_wall,
638    );
639    print_stage(
640        "dot reductions",
641        stages.dot_seconds,
642        stages.dot_calls,
643        profiled_wall,
644    );
645    print_stage(
646        "norm reductions",
647        stages.norm_seconds,
648        stages.norm_calls,
649        profiled_wall,
650    );
651    print_stage(
652        "x/r fused update",
653        stages.update_x_r_seconds,
654        stages.update_x_r_calls,
655        profiled_wall,
656    );
657    print_stage(
658        "p update",
659        stages.update_p_seconds,
660        stages.update_p_calls,
661        profiled_wall,
662    );
663    print_stage(
664        "initial residual",
665        stages.residual_init_seconds,
666        1,
667        profiled_wall,
668    );
669    print_stage("initial p copy", stages.copy_p_seconds, 1, profiled_wall);
670    let accounted = stages.accounted_seconds();
671    let unaccounted = (profiled_wall - accounted).max(0.0);
672    println!(
673        "accounted total        : {:9.3} ms  {:5.1}%",
674        accounted * 1.0e3,
675        100.0 * accounted / profiled_wall.max(f64::MIN_POSITIVE)
676    );
677    println!(
678        "timer/control overhead : {:9.3} ms  {:5.1}%",
679        unaccounted * 1.0e3,
680        100.0 * unaccounted / profiled_wall.max(f64::MIN_POSITIVE)
681    );
682
683    println!();
684    println!("Uninstrumented control");
685    println!("status             : {:?}", control_out.status);
686    println!("iterations         : {}", control_out.iterations);
687    println!(
688        "reported residual  : {:.6e}",
689        control_out.final_residual / norm2(&b).max(f64::MIN_POSITIVE)
690    );
691    println!("verified residual  : {:.6e}", control_verified);
692    println!("solve time         : {:.3} ms", control_wall * 1.0e3);
693    println!(
694        "profile/control    : {:.3}x",
695        profiled_wall / control_wall.max(f64::MIN_POSITIVE)
696    );
697    println!("note               : profile uses the same serial PCG vector kernels as production; only nested timers are added");
698
699    Ok(())
700}
examples/fem_structural_precond_parallel.rs (line 322)
245fn main() -> Result<(), Box<dyn Error>> {
246    let args = Args::parse()?;
247    println!(
248        "HyBIT {} serial vs parallel rigid-body preconditioner benchmark",
249        env!("CARGO_PKG_VERSION")
250    );
251    println!("matrix             : {}", args.matrix.display());
252    println!("coordinates        : {}", args.coordinates.display());
253
254    let load_start = Instant::now();
255    let (matrix, mm) = read_matrix_market(&args.matrix)?;
256    let matrix_load = load_start.elapsed().as_secs_f64();
257    let coord_start = Instant::now();
258    let coordinates = read_coordinates(&args.coordinates)?;
259    let coord_load = coord_start.elapsed().as_secs_f64();
260    let profile = analyze_csr32(&matrix)?;
261
262    println!(
263        "Matrix Market      : {:?}, {} input entries -> {} CSR nnz",
264        mm.symmetry, mm.input_entries, mm.csr_nnz
265    );
266    println!("dimensions         : {} x {}", profile.nrows, profile.ncols);
267    println!("nnz                : {}", profile.nnz);
268    println!(
269        "CSR storage        : {:.3} MiB",
270        mib(matrix.storage_bytes())
271    );
272    println!("matrix load        : {:.3} ms", matrix_load * 1.0e3);
273    println!("coordinate nodes   : {}", coordinates.len());
274    println!("coordinate load    : {:.3} ms", coord_load * 1.0e3);
275    println!("aggregation        : {:?}", args.aggregation);
276    println!("target coarse dim  : {}", args.target_coarse_dimension);
277    println!("kernel repeats     : {}", args.kernel_repeats);
278
279    if !profile.square || !profile.full_diagonal || !profile.positive_diagonal {
280        return Err(
281            "structural PCG benchmark requires a square matrix with a complete positive diagonal"
282                .into(),
283        );
284    }
285    if matrix.nrows() != coordinates.len() * 3 {
286        return Err(format!(
287            "matrix/coordinate mismatch: {} matrix rows != {} coordinate nodes * 3",
288            matrix.nrows(),
289            coordinates.len()
290        )
291        .into());
292    }
293
294    let b = if let Some(path) = args.rhs.as_deref() {
295        println!("RHS                : {}", path.display());
296        load_rhs(path, matrix.nrows())?
297    } else {
298        println!("RHS                : generated as b=A*1 (known exact solution)");
299        matrix.spmv(&vec![1.0; matrix.ncols()])?
300    };
301
302    let aggregate_nodes =
303        recommend_rigid_body_aggregate_nodes(coordinates.len(), args.target_coarse_dimension)?;
304    let setup_start = Instant::now();
305    let preconditioner = match args.aggregation {
306        RigidBodyAggregation::Graph => {
307            RigidBodyTwoLevelBlockJacobiPreconditioner::from_csr32_graph(
308                &matrix,
309                &coordinates,
310                aggregate_nodes,
311            )?
312        }
313        RigidBodyAggregation::Contiguous => RigidBodyTwoLevelBlockJacobiPreconditioner::from_csr32(
314            &matrix,
315            &coordinates,
316            aggregate_nodes,
317        )?,
318        RigidBodyAggregation::Auto => unreachable!(),
319    };
320    let setup_seconds = setup_start.elapsed().as_secs_f64();
321    let parallel_preconditioner = ParallelRigidBodyTwoLevelPreconditioner::new(&preconditioner)?;
322    let parallel_operator = ParallelCsr32Operator::new(&matrix);
323
324    println!("aggregate target   : {} nodes", aggregate_nodes);
325    println!("aggregate count    : {}", preconditioner.aggregate_count());
326    println!("coarse dimension   : {}", preconditioner.coarse_dimension());
327    println!(
328        "prec storage       : {:.3} MiB",
329        mib(preconditioner.factor_bytes())
330    );
331    println!(
332        "parallel index     : {:.3} MiB",
333        mib(parallel_preconditioner.index_storage_bytes())
334    );
335    println!("setup              : {:.3} ms", setup_seconds * 1.0e3);
336    println!(
337        "Rayon threads      : {}",
338        parallel_preconditioner.rayon_threads()
339    );
340
341    let mut zs = vec![0.0; matrix.nrows()];
342    let mut zp = vec![0.0; matrix.nrows()];
343    preconditioner.apply(&b, &mut zs)?;
344    parallel_preconditioner.apply(&b, &mut zp)?;
345    let scale = zs.iter().fold(1.0f64, |m, &v| m.max(v.abs()));
346    let max_diff = zs
347        .iter()
348        .zip(&zp)
349        .fold(0.0f64, |m, (&a, &b)| m.max((a - b).abs()));
350    if max_diff > 1.0e-11 * scale {
351        return Err(format!(
352            "serial/parallel preconditioner mismatch: max diff={max_diff:e}, scale={scale:e}"
353        )
354        .into());
355    }
356
357    let serial_prec_start = Instant::now();
358    for _ in 0..args.kernel_repeats {
359        preconditioner.apply(&b, &mut zs)?;
360    }
361    let serial_prec = serial_prec_start.elapsed().as_secs_f64();
362
363    let parallel_prec_start = Instant::now();
364    for _ in 0..args.kernel_repeats {
365        parallel_preconditioner.apply(&b, &mut zp)?;
366    }
367    let parallel_prec = parallel_prec_start.elapsed().as_secs_f64();
368
369    println!();
370    println!("Preconditioner microbenchmark");
371    println!(
372        "serial / apply     : {:.3} ms",
373        ms_per(serial_prec, args.kernel_repeats)
374    );
375    println!(
376        "parallel / apply   : {:.3} ms",
377        ms_per(parallel_prec, args.kernel_repeats)
378    );
379    println!(
380        "precond speedup    : {:.3}x",
381        serial_prec / parallel_prec.max(f64::MIN_POSITIVE)
382    );
383
384    let options = SolverOptions {
385        relative_tolerance: args.relative_tolerance,
386        absolute_tolerance: 0.0,
387        max_iterations: args.max_iterations,
388    };
389
390    let mut xs = vec![0.0; matrix.ncols()];
391    let mut ws = PcgWorkspace::new(matrix.nrows());
392    let serial_solve_start = Instant::now();
393    let serial_out = pcg_with_workspace(
394        &parallel_operator,
395        &preconditioner,
396        &b,
397        &mut xs,
398        options,
399        &mut ws,
400    )?;
401    let serial_solve = serial_solve_start.elapsed().as_secs_f64();
402    let serial_verified = verified_relative_residual(&matrix, &b, &xs)?;
403
404    let mut xp = vec![0.0; matrix.ncols()];
405    let mut wp = PcgWorkspace::new(matrix.nrows());
406    let parallel_solve_start = Instant::now();
407    let parallel_out = pcg_with_workspace(
408        &parallel_operator,
409        &parallel_preconditioner,
410        &b,
411        &mut xp,
412        options,
413        &mut wp,
414    )?;
415    let parallel_solve = parallel_solve_start.elapsed().as_secs_f64();
416    let parallel_verified = verified_relative_residual(&matrix, &b, &xp)?;
417
418    println!();
419    println!("[1/2] Parallel CSR + serial preconditioner");
420    println!("status             : {:?}", serial_out.status);
421    println!("iterations         : {}", serial_out.iterations);
422    println!(
423        "reported residual  : {:.6e}",
424        serial_out.final_residual / norm2(&b).max(f64::MIN_POSITIVE)
425    );
426    println!("verified residual  : {:.6e}", serial_verified);
427    println!("solve time         : {:.3} ms", serial_solve * 1.0e3);
428
429    println!();
430    println!("[2/2] Parallel CSR + parallel preconditioner");
431    println!("status             : {:?}", parallel_out.status);
432    println!("iterations         : {}", parallel_out.iterations);
433    println!(
434        "reported residual  : {:.6e}",
435        parallel_out.final_residual / norm2(&b).max(f64::MIN_POSITIVE)
436    );
437    println!("verified residual  : {:.6e}", parallel_verified);
438    println!("solve time         : {:.3} ms", parallel_solve * 1.0e3);
439
440    println!();
441    println!("Comparison");
442    println!(
443        "iteration ratio    : {:.3} (parallel prec / serial prec)",
444        parallel_out.iterations as f64 / serial_out.iterations.max(1) as f64
445    );
446    println!(
447        "solve-time ratio   : {:.3} (parallel prec / serial prec)",
448        parallel_solve / serial_solve.max(f64::MIN_POSITIVE)
449    );
450    println!(
451        "PCG speedup        : {:.3}x",
452        serial_solve / parallel_solve.max(f64::MIN_POSITIVE)
453    );
454
455    if !serial_verified.is_finite() || !parallel_verified.is_finite() {
456        return Err("non-finite independently verified residual".into());
457    }
458    Ok(())
459}
examples/fem_structural_prepared.rs (line 402)
311fn main() -> Result<(), Box<dyn Error>> {
312    let args = Args::parse()?;
313    println!(
314        "HyBIT {} prepared structural solve-many FEM benchmark",
315        env!("CARGO_PKG_VERSION")
316    );
317    println!("matrix             : {}", args.matrix.display());
318    println!("coordinates        : {}", args.coordinates.display());
319
320    let load_start = Instant::now();
321    let (matrix, mm) = read_matrix_market(&args.matrix)?;
322    let matrix_load_seconds = load_start.elapsed().as_secs_f64();
323    let coord_start = Instant::now();
324    let coordinates = read_coordinates(&args.coordinates)?;
325    let coord_load_seconds = coord_start.elapsed().as_secs_f64();
326    let profile = analyze_csr32(&matrix)?;
327
328    println!(
329        "Matrix Market      : {:?}, {} input entries -> {} CSR nnz",
330        mm.symmetry, mm.input_entries, mm.csr_nnz
331    );
332    println!("dimensions         : {} x {}", profile.nrows, profile.ncols);
333    println!("nnz                : {}", profile.nnz);
334    println!(
335        "CSR storage        : {:.3} MiB",
336        mib(matrix.storage_bytes())
337    );
338    println!("matrix load        : {:.3} ms", matrix_load_seconds * 1.0e3);
339    println!("coordinate nodes   : {}", coordinates.len());
340    println!("coordinate load    : {:.3} ms", coord_load_seconds * 1.0e3);
341
342    if !profile.square || !profile.full_diagonal || !profile.positive_diagonal {
343        return Err(
344            "prepared structural PCG requires a square matrix with a complete positive diagonal"
345                .into(),
346        );
347    }
348    if matrix.nrows() != coordinates.len() * 3 {
349        return Err(format!(
350            "matrix/coordinate mismatch: {} matrix rows != {} coordinate nodes * 3",
351            matrix.nrows(),
352            coordinates.len()
353        )
354        .into());
355    }
356
357    let generated_rhs = args.rhs.is_none();
358    let b = if let Some(path) = args.rhs.as_deref() {
359        println!("RHS                : {}", path.display());
360        load_rhs(path, matrix.nrows())?
361    } else {
362        println!("RHS                : generated as b=A*1 (known exact solution)");
363        let ones = vec![1.0; matrix.ncols()];
364        matrix.spmv(&ones)?
365    };
366    println!(
367        "repeats            : {} (fresh zero initial guess each solve)",
368        args.repeats
369    );
370
371    let mut solver = HybitSolver::new();
372    solver.set_options(SolverOptions {
373        relative_tolerance: args.relative_tolerance,
374        absolute_tolerance: 0.0,
375        max_iterations: args.max_iterations,
376    })?;
377    solver.set_structural_options(StructuralOptions {
378        target_coarse_dimension: args.target_coarse_dimension,
379        aggregation: args.aggregation,
380        spmv_policy: args.spmv_policy,
381        preconditioner_policy: args.preconditioner_policy,
382        pcg_vector_policy: args.pcg_vector_policy,
383    })?;
384
385    let analysis = solver.analyze_csr32(&matrix)?;
386    let mut prepared = solver.prepare_structural_csr32(&matrix, &analysis, &coordinates)?;
387
388    println!("policy             : StructuralAuto/RigidBodyTwoLevel/Prepared");
389    println!("aggregation        : {:?}", prepared.aggregation());
390    println!("SpMV policy        : {:?}", prepared.spmv_policy());
391    println!(
392        "precond policy     : {:?}",
393        prepared.structural_preconditioner_policy()
394    );
395    println!("PCG vector policy  : {:?}", prepared.pcg_vector_policy());
396    if prepared.parallel_spmv_enabled()
397        || prepared.parallel_preconditioner_enabled()
398        || prepared.parallel_pcg_vectors_enabled()
399    {
400        println!(
401            "Rayon threads      : {}",
402            ParallelCsr32Operator::new(&matrix).rayon_threads()
403        );
404    }
405    if prepared.parallel_preconditioner_enabled() {
406        println!(
407            "parallel index     : {:.3} MiB",
408            mib(prepared.parallel_preconditioner_index_bytes())
409        );
410    }
411    println!("target coarse dim  : {}", args.target_coarse_dimension);
412    println!(
413        "aggregate nodes    : {} (auto-selected)",
414        prepared.aggregate_nodes()
415    );
416    println!("aggregate count    : {}", prepared.aggregate_count());
417    println!(
418        "aggregate min/max  : {} / {} nodes",
419        prepared.min_aggregate_nodes(),
420        prepared.max_aggregate_nodes()
421    );
422    println!("modes/aggregate    : 6");
423    println!("coarse dimension   : {}", prepared.coarse_dimension());
424    println!(
425        "base factor        : {:.3} MiB",
426        mib(prepared.base_factor_bytes())
427    );
428    println!(
429        "coarse factor      : {:.3} MiB",
430        mib(prepared.coarse_factor_bytes())
431    );
432    println!(
433        "geometry storage   : {:.3} MiB",
434        mib(prepared.geometry_bytes())
435    );
436    println!(
437        "total prec storage : {:.3} MiB",
438        mib(prepared.preconditioner_bytes())
439    );
440    println!(
441        "Krylov workspace   : {:.3} MiB",
442        mib(prepared.krylov_workspace_bytes())
443    );
444    println!(
445        "analysis once      : {:.3} ms",
446        prepared.analysis_seconds() * 1.0e3
447    );
448    println!(
449        "prepare once       : {:.3} ms",
450        prepared.prepare_seconds() * 1.0e3
451    );
452
453    let mut total_solve_seconds = 0.0f64;
454    for repetition in 1..=args.repeats {
455        let mut x = vec![0.0; matrix.ncols()];
456        let wall_start = Instant::now();
457        let report = prepared.solve(&matrix, &b, &mut x)?;
458        let wall_seconds = wall_start.elapsed().as_secs_f64();
459        let verified = verified_relative_residual(&matrix, &b, &x)?;
460        total_solve_seconds += report.solve_seconds;
461
462        println!();
463        println!("Solve #{repetition}");
464        println!("status             : {:?}", report.status);
465        println!("preconditioner     : {:?}", report.preconditioner);
466        println!("reused             : {}", report.preconditioner_reused);
467        println!("sequence           : {}", report.solve_sequence);
468        println!("iterations         : {}", report.iterations);
469        println!("reported residual  : {:.6e}", report.relative_residual);
470        println!("verified residual  : {:.6e}", verified);
471        if generated_rhs {
472            println!("relative x error   : {:.6e}", relative_error_to_ones(&x));
473        }
474        println!(
475            "analysis charged   : {:.3} ms",
476            report.analysis_seconds * 1.0e3
477        );
478        println!(
479            "prepare charged    : {:.3} ms",
480            report.prepare_seconds * 1.0e3
481        );
482        println!(
483            "solve time         : {:.3} ms",
484            report.solve_seconds * 1.0e3
485        );
486        println!("call wall          : {:.3} ms", wall_seconds * 1.0e3);
487
488        if repetition == 1 && report.preconditioner_reused {
489            return Err("first prepared structural solve unexpectedly reported reuse".into());
490        }
491        if repetition > 1 && !report.preconditioner_reused {
492            return Err(
493                "subsequent prepared structural solve did not report preconditioner reuse".into(),
494            );
495        }
496        if repetition > 1 && (report.analysis_seconds != 0.0 || report.prepare_seconds != 0.0) {
497            return Err(
498                "subsequent prepared structural solve was charged reusable setup cost".into(),
499            );
500        }
501        if !verified.is_finite() {
502            return Err("non-finite independently verified residual".into());
503        }
504    }
505
506    println!();
507    println!("Prepared reuse summary");
508    println!("solve count        : {}", prepared.solve_count());
509    println!(
510        "one-time setup     : {:.3} ms",
511        (prepared.analysis_seconds() + prepared.prepare_seconds()) * 1.0e3
512    );
513    println!("sum solve time     : {:.3} ms", total_solve_seconds * 1.0e3);
514    println!(
515        "amortized total   : {:.3} ms/solve",
516        ((prepared.analysis_seconds() + prepared.prepare_seconds() + total_solve_seconds)
517            / args.repeats as f64)
518            * 1.0e3
519    );
520
521    Ok(())
522}
Source

pub fn matrix(&self) -> &'a Csr32Matrix

Source

pub fn rayon_threads(&self) -> usize

Examples found in repository?
examples/fem_structural_auto.rs (line 389)
304fn main() -> Result<(), Box<dyn Error>> {
305    let args = Args::parse()?;
306    println!(
307        "HyBIT {} structural-auto FEM benchmark",
308        env!("CARGO_PKG_VERSION")
309    );
310    println!("matrix             : {}", args.matrix.display());
311    println!("coordinates        : {}", args.coordinates.display());
312
313    let load_start = Instant::now();
314    let (matrix, mm) = read_matrix_market(&args.matrix)?;
315    let matrix_load_seconds = load_start.elapsed().as_secs_f64();
316    let coord_start = Instant::now();
317    let coordinates = read_coordinates(&args.coordinates)?;
318    let coord_load_seconds = coord_start.elapsed().as_secs_f64();
319    let profile = analyze_csr32(&matrix)?;
320
321    println!(
322        "Matrix Market      : {:?}, {} input entries -> {} CSR nnz",
323        mm.symmetry, mm.input_entries, mm.csr_nnz
324    );
325    println!("dimensions         : {} x {}", profile.nrows, profile.ncols);
326    println!("nnz                : {}", profile.nnz);
327    println!(
328        "CSR storage        : {:.3} MiB",
329        mib(matrix.storage_bytes())
330    );
331    println!("matrix load        : {:.3} ms", matrix_load_seconds * 1.0e3);
332    println!("coordinate nodes   : {}", coordinates.len());
333    println!("coordinate load    : {:.3} ms", coord_load_seconds * 1.0e3);
334
335    if !profile.square || !profile.full_diagonal || !profile.positive_diagonal {
336        return Err(
337            "rigid-body two-level PCG requires a square matrix with a complete positive diagonal"
338                .into(),
339        );
340    }
341    if matrix.nrows() != coordinates.len() * 3 {
342        return Err(format!(
343            "matrix/coordinate mismatch: {} matrix rows != {} coordinate nodes * 3",
344            matrix.nrows(),
345            coordinates.len()
346        )
347        .into());
348    }
349
350    let generated_rhs = args.rhs.is_none();
351    let b = if let Some(path) = args.rhs.as_deref() {
352        println!("RHS                : {}", path.display());
353        load_rhs(path, matrix.nrows())?
354    } else {
355        println!("RHS                : generated as b=A*1 (known exact solution)");
356        let ones = vec![1.0; matrix.ncols()];
357        matrix.spmv(&ones)?
358    };
359    let mut solver = HybitSolver::new();
360    solver.set_options(SolverOptions {
361        relative_tolerance: args.relative_tolerance,
362        absolute_tolerance: 0.0,
363        max_iterations: args.max_iterations,
364    })?;
365    solver.set_structural_options(StructuralOptions {
366        target_coarse_dimension: args.target_coarse_dimension,
367        aggregation: args.aggregation,
368        spmv_policy: args.spmv_policy,
369        preconditioner_policy: args.preconditioner_policy,
370        pcg_vector_policy: args.pcg_vector_policy,
371    })?;
372
373    let analysis = solver.analyze_csr32(&matrix)?;
374    let mut prepared = solver.prepare_structural_csr32(&matrix, &analysis, &coordinates)?;
375    println!("policy             : StructuralAuto/RigidBodyTwoLevel");
376    println!("aggregation        : {:?}", prepared.aggregation());
377    println!("SpMV policy        : {:?}", prepared.spmv_policy());
378    println!(
379        "precond policy     : {:?}",
380        prepared.structural_preconditioner_policy()
381    );
382    println!("PCG vector policy  : {:?}", prepared.pcg_vector_policy());
383    if prepared.parallel_spmv_enabled()
384        || prepared.parallel_preconditioner_enabled()
385        || prepared.parallel_pcg_vectors_enabled()
386    {
387        println!(
388            "Rayon threads      : {}",
389            ParallelCsr32Operator::new(&matrix).rayon_threads()
390        );
391    }
392    if prepared.parallel_preconditioner_enabled() {
393        println!(
394            "parallel index     : {:.3} MiB",
395            mib(prepared.parallel_preconditioner_index_bytes())
396        );
397    }
398    println!("target coarse dim  : {}", args.target_coarse_dimension);
399    println!(
400        "aggregate nodes    : {} (auto-selected)",
401        prepared.aggregate_nodes()
402    );
403    println!("fine block size    : 3");
404    println!("aggregate count    : {}", prepared.aggregate_count());
405    println!(
406        "aggregate min/max  : {} / {} nodes",
407        prepared.min_aggregate_nodes(),
408        prepared.max_aggregate_nodes()
409    );
410    println!("modes/aggregate    : 6");
411    println!("coarse dimension   : {}", prepared.coarse_dimension());
412    println!(
413        "base factor        : {:.3} MiB",
414        mib(prepared.base_factor_bytes())
415    );
416    println!(
417        "coarse factor      : {:.3} MiB",
418        mib(prepared.coarse_factor_bytes())
419    );
420    println!(
421        "geometry storage   : {:.3} MiB",
422        mib(prepared.geometry_bytes())
423    );
424    println!(
425        "total prec storage : {:.3} MiB",
426        mib(prepared.preconditioner_bytes())
427    );
428    println!(
429        "analysis           : {:.3} ms",
430        prepared.analysis_seconds() * 1.0e3
431    );
432    println!(
433        "prepare            : {:.3} ms",
434        prepared.prepare_seconds() * 1.0e3
435    );
436
437    let mut x = vec![0.0; matrix.ncols()];
438    let report = prepared.solve(&matrix, &b, &mut x)?;
439    let verified = verified_relative_residual(&matrix, &b, &x)?;
440
441    println!();
442    println!("Structural Auto API: 3x3 Block-Jacobi + six rigid-body coarse modes");
443    println!("status             : {:?}", report.status);
444    println!("preconditioner     : {:?}", report.preconditioner);
445    println!("iterations         : {}", report.iterations);
446    println!("reported residual  : {:.6e}", report.relative_residual);
447    println!("verified residual  : {:.6e}", verified);
448    if generated_rhs {
449        println!("relative x error   : {:.6e}", relative_error_to_ones(&x));
450    }
451    println!(
452        "solve time         : {:.3} ms",
453        report.solve_seconds * 1.0e3
454    );
455    println!(
456        "total setup+solve  : {:.3} ms",
457        (report.setup_seconds + report.solve_seconds) * 1.0e3
458    );
459
460    if !verified.is_finite() {
461        return Err("non-finite independently verified residual".into());
462    }
463    Ok(())
464}
More examples
Hide additional examples
examples/fem_structural_spmv.rs (line 329)
243fn main() -> Result<(), Box<dyn Error>> {
244    let args = Args::parse()?;
245    println!(
246        "HyBIT {} serial vs parallel CSR structural benchmark",
247        env!("CARGO_PKG_VERSION")
248    );
249    println!("matrix             : {}", args.matrix.display());
250    println!("coordinates        : {}", args.coordinates.display());
251
252    let load_start = Instant::now();
253    let (matrix, mm) = read_matrix_market(&args.matrix)?;
254    let matrix_load = load_start.elapsed().as_secs_f64();
255    let coord_start = Instant::now();
256    let coordinates = read_coordinates(&args.coordinates)?;
257    let coord_load = coord_start.elapsed().as_secs_f64();
258    let profile = analyze_csr32(&matrix)?;
259
260    println!(
261        "Matrix Market      : {:?}, {} input entries -> {} CSR nnz",
262        mm.symmetry, mm.input_entries, mm.csr_nnz
263    );
264    println!("dimensions         : {} x {}", profile.nrows, profile.ncols);
265    println!("nnz                : {}", profile.nnz);
266    println!(
267        "CSR storage        : {:.3} MiB",
268        mib(matrix.storage_bytes())
269    );
270    println!("matrix load        : {:.3} ms", matrix_load * 1.0e3);
271    println!("coordinate nodes   : {}", coordinates.len());
272    println!("coordinate load    : {:.3} ms", coord_load * 1.0e3);
273    println!("aggregation        : {:?}", args.aggregation);
274    println!("target coarse dim  : {}", args.target_coarse_dimension);
275    println!("kernel repeats     : {}", args.kernel_repeats);
276
277    if !profile.square || !profile.full_diagonal || !profile.positive_diagonal {
278        return Err(
279            "structural PCG benchmark requires a square matrix with a complete positive diagonal"
280                .into(),
281        );
282    }
283    if matrix.nrows() != coordinates.len() * 3 {
284        return Err(format!(
285            "matrix/coordinate mismatch: {} matrix rows != {} coordinate nodes * 3",
286            matrix.nrows(),
287            coordinates.len()
288        )
289        .into());
290    }
291
292    let b = if let Some(path) = args.rhs.as_deref() {
293        println!("RHS                : {}", path.display());
294        load_rhs(path, matrix.nrows())?
295    } else {
296        println!("RHS                : generated as b=A*1 (known exact solution)");
297        matrix.spmv(&vec![1.0; matrix.ncols()])?
298    };
299
300    let aggregate_nodes =
301        recommend_rigid_body_aggregate_nodes(coordinates.len(), args.target_coarse_dimension)?;
302    let setup_start = Instant::now();
303    let preconditioner = match args.aggregation {
304        RigidBodyAggregation::Graph => {
305            RigidBodyTwoLevelBlockJacobiPreconditioner::from_csr32_graph(
306                &matrix,
307                &coordinates,
308                aggregate_nodes,
309            )?
310        }
311        RigidBodyAggregation::Contiguous => RigidBodyTwoLevelBlockJacobiPreconditioner::from_csr32(
312            &matrix,
313            &coordinates,
314            aggregate_nodes,
315        )?,
316        RigidBodyAggregation::Auto => unreachable!(),
317    };
318    let setup_seconds = setup_start.elapsed().as_secs_f64();
319    let parallel = ParallelCsr32Operator::new(&matrix);
320
321    println!("aggregate target   : {} nodes", aggregate_nodes);
322    println!("aggregate count    : {}", preconditioner.aggregate_count());
323    println!("coarse dimension   : {}", preconditioner.coarse_dimension());
324    println!(
325        "prec storage       : {:.3} MiB",
326        mib(preconditioner.factor_bytes())
327    );
328    println!("setup              : {:.3} ms", setup_seconds * 1.0e3);
329    println!("Rayon threads      : {}", parallel.rayon_threads());
330
331    let mut ys = vec![0.0; matrix.nrows()];
332    let mut yp = vec![0.0; matrix.nrows()];
333    matrix.apply(&b, &mut ys)?;
334    parallel.apply(&b, &mut yp)?;
335    if ys != yp {
336        return Err("serial and parallel CSR kernels produced different results".into());
337    }
338
339    let serial_start = Instant::now();
340    for _ in 0..args.kernel_repeats {
341        matrix.apply(&b, &mut ys)?;
342    }
343    let serial_spmv = serial_start.elapsed().as_secs_f64();
344
345    let parallel_start = Instant::now();
346    for _ in 0..args.kernel_repeats {
347        parallel.apply(&b, &mut yp)?;
348    }
349    let parallel_spmv = parallel_start.elapsed().as_secs_f64();
350
351    println!();
352    println!("CSR SpMV microbenchmark");
353    println!(
354        "serial / call      : {:.3} ms",
355        ms_per(serial_spmv, args.kernel_repeats)
356    );
357    println!(
358        "parallel / call    : {:.3} ms",
359        ms_per(parallel_spmv, args.kernel_repeats)
360    );
361    println!(
362        "SpMV speedup       : {:.3}x",
363        serial_spmv / parallel_spmv.max(f64::MIN_POSITIVE)
364    );
365
366    let options = SolverOptions {
367        relative_tolerance: args.relative_tolerance,
368        absolute_tolerance: 0.0,
369        max_iterations: args.max_iterations,
370    };
371
372    let mut xs = vec![0.0; matrix.ncols()];
373    let mut ws = PcgWorkspace::new(matrix.nrows());
374    let serial_solve_start = Instant::now();
375    let serial_out = pcg_with_workspace(&matrix, &preconditioner, &b, &mut xs, options, &mut ws)?;
376    let serial_solve = serial_solve_start.elapsed().as_secs_f64();
377    let serial_verified = verified_relative_residual(&matrix, &b, &xs)?;
378
379    let mut xp = vec![0.0; matrix.ncols()];
380    let mut wp = PcgWorkspace::new(matrix.nrows());
381    let parallel_solve_start = Instant::now();
382    let parallel_out =
383        pcg_with_workspace(&parallel, &preconditioner, &b, &mut xp, options, &mut wp)?;
384    let parallel_solve = parallel_solve_start.elapsed().as_secs_f64();
385    let parallel_verified = verified_relative_residual(&matrix, &b, &xp)?;
386
387    println!();
388    println!("[1/2] Serial CSR PCG");
389    println!("status             : {:?}", serial_out.status);
390    println!("iterations         : {}", serial_out.iterations);
391    println!(
392        "reported residual  : {:.6e}",
393        serial_out.final_residual / norm2(&b).max(f64::MIN_POSITIVE)
394    );
395    println!("verified residual  : {:.6e}", serial_verified);
396    println!("solve time         : {:.3} ms", serial_solve * 1.0e3);
397
398    println!();
399    println!("[2/2] Parallel CSR PCG");
400    println!("status             : {:?}", parallel_out.status);
401    println!("iterations         : {}", parallel_out.iterations);
402    println!(
403        "reported residual  : {:.6e}",
404        parallel_out.final_residual / norm2(&b).max(f64::MIN_POSITIVE)
405    );
406    println!("verified residual  : {:.6e}", parallel_verified);
407    println!("solve time         : {:.3} ms", parallel_solve * 1.0e3);
408
409    println!();
410    println!("Comparison");
411    println!(
412        "iteration ratio    : {:.3} (parallel / serial)",
413        parallel_out.iterations as f64 / serial_out.iterations.max(1) as f64
414    );
415    println!(
416        "solve-time ratio   : {:.3} (parallel / serial)",
417        parallel_solve / serial_solve.max(f64::MIN_POSITIVE)
418    );
419    println!(
420        "PCG speedup        : {:.3}x",
421        serial_solve / parallel_solve.max(f64::MIN_POSITIVE)
422    );
423
424    if !serial_verified.is_finite() || !parallel_verified.is_finite() {
425        return Err("non-finite independently verified residual".into());
426    }
427    Ok(())
428}
examples/fem_structural_prepared.rs (line 402)
311fn main() -> Result<(), Box<dyn Error>> {
312    let args = Args::parse()?;
313    println!(
314        "HyBIT {} prepared structural solve-many FEM benchmark",
315        env!("CARGO_PKG_VERSION")
316    );
317    println!("matrix             : {}", args.matrix.display());
318    println!("coordinates        : {}", args.coordinates.display());
319
320    let load_start = Instant::now();
321    let (matrix, mm) = read_matrix_market(&args.matrix)?;
322    let matrix_load_seconds = load_start.elapsed().as_secs_f64();
323    let coord_start = Instant::now();
324    let coordinates = read_coordinates(&args.coordinates)?;
325    let coord_load_seconds = coord_start.elapsed().as_secs_f64();
326    let profile = analyze_csr32(&matrix)?;
327
328    println!(
329        "Matrix Market      : {:?}, {} input entries -> {} CSR nnz",
330        mm.symmetry, mm.input_entries, mm.csr_nnz
331    );
332    println!("dimensions         : {} x {}", profile.nrows, profile.ncols);
333    println!("nnz                : {}", profile.nnz);
334    println!(
335        "CSR storage        : {:.3} MiB",
336        mib(matrix.storage_bytes())
337    );
338    println!("matrix load        : {:.3} ms", matrix_load_seconds * 1.0e3);
339    println!("coordinate nodes   : {}", coordinates.len());
340    println!("coordinate load    : {:.3} ms", coord_load_seconds * 1.0e3);
341
342    if !profile.square || !profile.full_diagonal || !profile.positive_diagonal {
343        return Err(
344            "prepared structural PCG requires a square matrix with a complete positive diagonal"
345                .into(),
346        );
347    }
348    if matrix.nrows() != coordinates.len() * 3 {
349        return Err(format!(
350            "matrix/coordinate mismatch: {} matrix rows != {} coordinate nodes * 3",
351            matrix.nrows(),
352            coordinates.len()
353        )
354        .into());
355    }
356
357    let generated_rhs = args.rhs.is_none();
358    let b = if let Some(path) = args.rhs.as_deref() {
359        println!("RHS                : {}", path.display());
360        load_rhs(path, matrix.nrows())?
361    } else {
362        println!("RHS                : generated as b=A*1 (known exact solution)");
363        let ones = vec![1.0; matrix.ncols()];
364        matrix.spmv(&ones)?
365    };
366    println!(
367        "repeats            : {} (fresh zero initial guess each solve)",
368        args.repeats
369    );
370
371    let mut solver = HybitSolver::new();
372    solver.set_options(SolverOptions {
373        relative_tolerance: args.relative_tolerance,
374        absolute_tolerance: 0.0,
375        max_iterations: args.max_iterations,
376    })?;
377    solver.set_structural_options(StructuralOptions {
378        target_coarse_dimension: args.target_coarse_dimension,
379        aggregation: args.aggregation,
380        spmv_policy: args.spmv_policy,
381        preconditioner_policy: args.preconditioner_policy,
382        pcg_vector_policy: args.pcg_vector_policy,
383    })?;
384
385    let analysis = solver.analyze_csr32(&matrix)?;
386    let mut prepared = solver.prepare_structural_csr32(&matrix, &analysis, &coordinates)?;
387
388    println!("policy             : StructuralAuto/RigidBodyTwoLevel/Prepared");
389    println!("aggregation        : {:?}", prepared.aggregation());
390    println!("SpMV policy        : {:?}", prepared.spmv_policy());
391    println!(
392        "precond policy     : {:?}",
393        prepared.structural_preconditioner_policy()
394    );
395    println!("PCG vector policy  : {:?}", prepared.pcg_vector_policy());
396    if prepared.parallel_spmv_enabled()
397        || prepared.parallel_preconditioner_enabled()
398        || prepared.parallel_pcg_vectors_enabled()
399    {
400        println!(
401            "Rayon threads      : {}",
402            ParallelCsr32Operator::new(&matrix).rayon_threads()
403        );
404    }
405    if prepared.parallel_preconditioner_enabled() {
406        println!(
407            "parallel index     : {:.3} MiB",
408            mib(prepared.parallel_preconditioner_index_bytes())
409        );
410    }
411    println!("target coarse dim  : {}", args.target_coarse_dimension);
412    println!(
413        "aggregate nodes    : {} (auto-selected)",
414        prepared.aggregate_nodes()
415    );
416    println!("aggregate count    : {}", prepared.aggregate_count());
417    println!(
418        "aggregate min/max  : {} / {} nodes",
419        prepared.min_aggregate_nodes(),
420        prepared.max_aggregate_nodes()
421    );
422    println!("modes/aggregate    : 6");
423    println!("coarse dimension   : {}", prepared.coarse_dimension());
424    println!(
425        "base factor        : {:.3} MiB",
426        mib(prepared.base_factor_bytes())
427    );
428    println!(
429        "coarse factor      : {:.3} MiB",
430        mib(prepared.coarse_factor_bytes())
431    );
432    println!(
433        "geometry storage   : {:.3} MiB",
434        mib(prepared.geometry_bytes())
435    );
436    println!(
437        "total prec storage : {:.3} MiB",
438        mib(prepared.preconditioner_bytes())
439    );
440    println!(
441        "Krylov workspace   : {:.3} MiB",
442        mib(prepared.krylov_workspace_bytes())
443    );
444    println!(
445        "analysis once      : {:.3} ms",
446        prepared.analysis_seconds() * 1.0e3
447    );
448    println!(
449        "prepare once       : {:.3} ms",
450        prepared.prepare_seconds() * 1.0e3
451    );
452
453    let mut total_solve_seconds = 0.0f64;
454    for repetition in 1..=args.repeats {
455        let mut x = vec![0.0; matrix.ncols()];
456        let wall_start = Instant::now();
457        let report = prepared.solve(&matrix, &b, &mut x)?;
458        let wall_seconds = wall_start.elapsed().as_secs_f64();
459        let verified = verified_relative_residual(&matrix, &b, &x)?;
460        total_solve_seconds += report.solve_seconds;
461
462        println!();
463        println!("Solve #{repetition}");
464        println!("status             : {:?}", report.status);
465        println!("preconditioner     : {:?}", report.preconditioner);
466        println!("reused             : {}", report.preconditioner_reused);
467        println!("sequence           : {}", report.solve_sequence);
468        println!("iterations         : {}", report.iterations);
469        println!("reported residual  : {:.6e}", report.relative_residual);
470        println!("verified residual  : {:.6e}", verified);
471        if generated_rhs {
472            println!("relative x error   : {:.6e}", relative_error_to_ones(&x));
473        }
474        println!(
475            "analysis charged   : {:.3} ms",
476            report.analysis_seconds * 1.0e3
477        );
478        println!(
479            "prepare charged    : {:.3} ms",
480            report.prepare_seconds * 1.0e3
481        );
482        println!(
483            "solve time         : {:.3} ms",
484            report.solve_seconds * 1.0e3
485        );
486        println!("call wall          : {:.3} ms", wall_seconds * 1.0e3);
487
488        if repetition == 1 && report.preconditioner_reused {
489            return Err("first prepared structural solve unexpectedly reported reuse".into());
490        }
491        if repetition > 1 && !report.preconditioner_reused {
492            return Err(
493                "subsequent prepared structural solve did not report preconditioner reuse".into(),
494            );
495        }
496        if repetition > 1 && (report.analysis_seconds != 0.0 || report.prepare_seconds != 0.0) {
497            return Err(
498                "subsequent prepared structural solve was charged reusable setup cost".into(),
499            );
500        }
501        if !verified.is_finite() {
502            return Err("non-finite independently verified residual".into());
503        }
504    }
505
506    println!();
507    println!("Prepared reuse summary");
508    println!("solve count        : {}", prepared.solve_count());
509    println!(
510        "one-time setup     : {:.3} ms",
511        (prepared.analysis_seconds() + prepared.prepare_seconds()) * 1.0e3
512    );
513    println!("sum solve time     : {:.3} ms", total_solve_seconds * 1.0e3);
514    println!(
515        "amortized total   : {:.3} ms/solve",
516        ((prepared.analysis_seconds() + prepared.prepare_seconds() + total_solve_seconds)
517            / args.repeats as f64)
518            * 1.0e3
519    );
520
521    Ok(())
522}

Trait Implementations§

Source§

impl<'a> Clone for ParallelCsr32Operator<'a>

Source§

fn clone(&self) -> ParallelCsr32Operator<'a>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<'a> Copy for ParallelCsr32Operator<'a>

Source§

impl<'a> Debug for ParallelCsr32Operator<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl LinearOperator for ParallelCsr32Operator<'_>

Source§

fn rows(&self) -> usize

Source§

fn cols(&self) -> usize

Source§

fn apply(&self, x: &[f64], y: &mut [f64]) -> Result<(), HybitError>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.