Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ A built model can be saved to disk and loaded back. `load()` validates the M, M0
index.dump("model.bin")?;
let loaded: Apotheosis<SimpleTlshRecord, TlshDistance> = Apotheosis::load("model.bin")?;

// export::gexf::draw() writes model_layer0.gexf, model_layer1.gexf, ... next to the given base path.
apotheosis2::export::gexf::draw(&index, "model");
// export::gexf::draw() also returns Result and writes model_layer0.gexf, model_layer1.gexf, ...
apotheosis2::export::gexf::draw(&index, "model")?;
```


Expand Down
13 changes: 8 additions & 5 deletions docs/architecture/interfaz-apotheosis.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,10 @@ The bound `where Self: serde::de::DeserializeOwned` imposes the same restriction

```rust
// free function in the export::gexf module, not a facade method
pub fn draw<R, D, /* const params */, P: AsRef<Path>>(model: &Apotheosis<...>, path: P)
pub fn draw<R, D, /* const params */, P: AsRef<Path>>(
model: &Apotheosis<...>,
path: P,
) -> Result<(), Box<dyn std::error::Error>>
```

**Semantics**
Expand All @@ -218,7 +221,7 @@ Each GEXF file contains nodes (one per entry in that HNSW layer, identified by i

**Error Handling**

Does not return `Result`. I/O errors when writing each GEXF file are silently discarded: the code uses `let _ = save_gexf(...)`. GEXF XML serialization errors (`gexf.to_string().unwrap()`) produce a panic.
Returns `Err` on I/O errors when writing a GEXF file and on GEXF XML serialization errors. The first failing layer aborts the export; files already written remain on disk.



Expand Down Expand Up @@ -545,15 +548,15 @@ The crate does not adopt a uniform error-handling strategy: different methods us

#### `Result<_, Box<dyn std::error::Error>>`

Used by `dump`, `load`, and the `create` record constructors. The error type is a dynamic trait object that can wrap any I/O or serialization error. The caller must match on or propagate the error with `?`. There are no structured error types that would allow programmatic distinction between the possible causes (I/O failure, parameter mismatch, malformed file).
Used by `dump`, `load`, `export::gexf::draw`, and the `create` record constructors. The error type is a dynamic trait object that can wrap any I/O or serialization error. The caller must match on or propagate the error with `?`. There are no structured error types that would allow programmatic distinction between the possible causes (I/O failure, parameter mismatch, malformed file).

#### Panics

`export::gexf::draw` can panic if the internal GEXF XML serialization fails (`gexf.to_string().unwrap()` in `save_gexf`). No public facade method has identifiable panics in the normal flow; the `create` record constructors return `Err` on malformed input instead of panicking.
No public method has identifiable panics in the normal flow: `export::gexf::draw` returns `Err` instead of panicking on serialization failures, and the `create` record constructors return `Err` on malformed input.

#### Absence of error return

`insert` returns `bool` rather than `Result`: duplicate keys are signaled with `false` and a warning, without propagating an error to the caller. `export::gexf::draw` has no explicit error paths: I/O errors when writing each GEXF file are silently discarded via `let _ = save_gexf(...)`.
`insert` returns `bool` rather than `Result`: duplicate keys are signaled with `false` and a warning, without propagating an error to the caller.

---

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/views/cc/vista-cc.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ The course notes define *attachment* as the association between component ports
| `apo↔radix-lookup` | `<<call-return>>` | `Apotheosis::index-radix` | `RadixTree::lookup` | Input: `key: &[u8]` / Output: `Option<&RadixNode>` (Apotheosis extracts `Option<usize>` from `node.data`). If `Some(index)`: fast-path; if `None`: ANN path | First operation in every `search()` call. Its result determines which path is taken |
| `apo→radix-insert` | `<<call-return>>` | `Apotheosis::index-radix` | `RadixTree::insert` | Input: `(key: Vec<u8>, index: usize)` / no return | Executed on every new insertion, always alongside `hnsw-insert` |
| `apo↔fs-io` | `<<call-return>>` | `Apotheosis::persistence` | `FileSystem::io` | `dump(path)` writes to FS; `load(path)` reads from FS into Apotheosis | Bidirectional. `load` reconstructs the full model in memory |
| `apo→fs-gexf` | `<<call-return>>` | `Apotheosis::persistence` | `FileSystem::gexf-out` | `export::gexf::draw(&model, path)` writes one `.gexf` file per HNSW layer with pattern `<stem>_layer<N>.gexf` | Write only. No return value |
| `apo→fs-gexf` | `<<call-return>>` | `Apotheosis::persistence` | `FileSystem::gexf-out` | `export::gexf::draw(&model, path)` writes one `.gexf` file per HNSW layer with pattern `<stem>_layer<N>.gexf` | Write only. Returns `Result`: the first failed write or serialization aborts the export |
| `fs→gephi` | `<<file-read>>` | `FileSystem::gexf-out` | `Gephi::gexf-in` | `.gexf` file | Asynchronous with respect to APOTHEOSIS 2. Gephi opens the file independently; no runtime coupling |

### 2.3 Element Interfaces
Expand Down
15 changes: 11 additions & 4 deletions src/export/gexf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ use std::path::{Path, PathBuf};
pub fn draw<R, D, const M: usize, const M0: usize, const EF: usize, const HEURISTIC: bool, P>(
model: &Apotheosis<R, D, M, M0, EF, HEURISTIC>,
path: P,
) where
) -> Result<(), Box<dyn std::error::Error>>
where
R: ApotheosisRecord,
D: DistanceAlgorithm<R::MetricId> + Default,
P: AsRef<Path>,
Expand Down Expand Up @@ -54,11 +55,17 @@ pub fn draw<R, D, const M: usize, const M0: usize, const EF: usize, const HEURIS
);
}

let _ = save_gexf(base_path, layer_idx, &gexf);
save_gexf(base_path, layer_idx, &gexf)?;
}

Ok(())
}

fn save_gexf(base_path: &Path, layer_idx: usize, gexf: &Gexf) -> std::io::Result<()> {
fn save_gexf(
base_path: &Path,
layer_idx: usize,
gexf: &Gexf,
) -> Result<(), Box<dyn std::error::Error>> {
let mut file_path = PathBuf::from(base_path);

if let Some(stem) = file_path.file_stem().and_then(|s| s.to_str()) {
Expand All @@ -70,6 +77,6 @@ fn save_gexf(base_path: &Path, layer_idx: usize, gexf: &Gexf) -> std::io::Result
file_path = PathBuf::from(filename);
}

fs::write(file_path, gexf.to_string().unwrap())?;
fs::write(file_path, gexf.to_string()?)?;
Ok(())
}
2 changes: 1 addition & 1 deletion tests/api_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ fn draw_produces_one_gexf_file_per_layer() {
}

let layer_count = idx.draw_model().len();
apotheosis2::export::gexf::draw(&idx, &base);
apotheosis2::export::gexf::draw(&idx, &base).expect("draw() must succeed for this dataset");

for n in 0..layer_count {
let f = PathBuf::from(format!("{}/model_layer{}.gexf", dir, n));
Expand Down
Loading