-
Notifications
You must be signed in to change notification settings - Fork 0
Feat add matrix market load #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we differ matrix formats and queries formats? Maybe it'll be good refactoring: |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,233 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! MatrixMarket directory format loader. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! An edge-labeled graph is stored in a directory with the following layout: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! ```text | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! <dir>/ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! vertices.txt — one line per node: `<node_name> <1-based-index>` | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! edges.txt — one line per label: `<label_name> <1-based-index>` | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! 1.txt — MM adjacency matrix for the label with index 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! 2.txt — MM adjacency matrix for the label with index 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! … | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! # Example | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! ```no_run | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! use pathrex::graph::{Graph, InMemory, GraphDecomposition}; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! use pathrex::formats::mm::MatrixMarket; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! let graph = Graph::<InMemory>::try_from( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! MatrixMarket::from_dir("path/to/graph/dir") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! ).unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! println!("Nodes: {}", graph.num_nodes()); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| //! ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::collections::HashMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::ffi::CString; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::fs::File; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::io::{BufRead, BufReader}; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::os::fd::IntoRawFd; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::path::{Path, PathBuf}; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| use crate::formats::FormatError; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use crate::graph::{GraphError, ensure_grb_init}; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use crate::la_ok; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use crate::lagraph_sys::{FILE, GrB_Matrix, LAGraph_MMRead}; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Read a single MatrixMarket file and return the raw [`GrB_Matrix`]. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn load_mm_file(path: impl AsRef<Path>) -> Result<GrB_Matrix, FormatError> { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let path = path.as_ref(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| ensure_grb_init().map_err(|e| match e { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| GraphError::LAGraph(info, msg) => FormatError::MatrixMarket { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| code: info, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| message: msg, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| _ => FormatError::MatrixMarket { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| code: crate::lagraph_sys::GrB_Info::GrB_PANIC, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| message: "Failed to initialize GraphBLAS".to_string(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| })?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| let file = File::open(path)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let fd = file.into_raw_fd(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| let c_mode = CString::new("r").unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let f = unsafe { libc::fdopen(fd, c_mode.as_ptr()) }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if f.is_null() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| unsafe { libc::close(fd) }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return Err(std::io::Error::last_os_error().into()); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut matrix: GrB_Matrix = std::ptr::null_mut(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| let err = la_ok!(LAGraph_MMRead(&mut matrix, f as *mut FILE)); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| unsafe { libc::fclose(f) }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| match err { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(_) => Ok(matrix), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Err(GraphError::LAGraph(info, msg)) => Err(FormatError::MatrixMarket { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| code: info, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| message: msg, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| _ => unreachable!("should be either mm read error or ok"), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Parse a `<name> <1-based-index>` mapping file. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| pub(crate) fn parse_index_map( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| path: &Path, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> Result<(HashMap<usize, String>, HashMap<String, usize>), FormatError> { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let file_name = path | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .file_name() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(|n| n.to_string_lossy().into_owned()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or_else(|| path.display().to_string()); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| let reader = BufReader::new(File::open(path)?); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut by_idx: HashMap<usize, String> = HashMap::new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut by_name: HashMap<String, usize> = HashMap::new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| for (line_no, line) in reader.lines().enumerate() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let line = line?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let line = line.trim(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if line.is_empty() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| let (name, idx_str) = | ||||||||||||||||||||||||||||||||||||||||||||||||||
| line.rsplit_once(char::is_whitespace) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .ok_or_else(|| FormatError::InvalidFormat { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| file: file_name.clone(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| line: line_no + 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| reason: "expected '<name> <index>' but found no whitespace".into(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| })?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| let idx: usize = idx_str | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .trim() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .parse() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| .map_err(|_| FormatError::InvalidFormat { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| file: file_name.clone(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| line: line_no + 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| reason: format!("index '{}' is not a valid positive integer", idx_str.trim()), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| })?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| })?; | |
| })?; | |
| if idx == 0 { | |
| return Err(FormatError::InvalidFormat { | |
| file: file_name.clone(), | |
| line: line_no + 1, | |
| reason: format!( | |
| "index '{}' must be >= 1 (1-based indexing)", | |
| idx_str.trim() | |
| ), | |
| }); | |
| } |
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parse_index_map() overwrites entries on duplicate names or indices (HashMap::insert), which can silently corrupt the mapping files. It would be safer to detect duplicates and return FormatError::InvalidFormat identifying the conflicting line.
| let name = name.trim().to_owned(); | |
| let name = name.trim().to_owned(); | |
| if by_idx.contains_key(&idx) { | |
| return Err(FormatError::InvalidFormat { | |
| file: file_name.clone(), | |
| line: line_no + 1, | |
| reason: format!( | |
| "duplicate index '{}' encountered; each index must be unique", | |
| idx | |
| ), | |
| }); | |
| } | |
| if by_name.contains_key(&name) { | |
| return Err(FormatError::InvalidFormat { | |
| file: file_name.clone(), | |
| line: line_no + 1, | |
| reason: format!( | |
| "duplicate name '{}' encountered; each name must be unique", | |
| name | |
| ), | |
| }); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AGENTS.md references several
src/formats/mm.rsandsrc/graph/inmemory.rsline numbers (e.g.,MatrixMarketatsrc/formats/mm.rs:160,load_mm_fileat:64,GraphSourceimpl atsrc/graph/inmemory.rs:429) that no longer match the current code. Please update these pointers so the doc remains navigable.