differt-core 0.4.1__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {differt_core-0.4.1 → differt_core-0.6.0}/Cargo.lock +1 -1
- {differt_core-0.4.1 → differt_core-0.6.0}/PKG-INFO +3 -4
- {differt_core-0.4.1 → differt_core-0.6.0}/README.md +1 -1
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/Cargo.toml +3 -2
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/README.md +1 -1
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/_differt_core/rt/graph.pyi +3 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/src/geometry/triangle_mesh.rs +2 -2
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/src/rt/graph.rs +206 -5
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/tests/rt/test_graph.py +71 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/pyproject.toml +1 -2
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/_differt_core/rt/graph.pyi +3 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/src/geometry/triangle_mesh.rs +2 -2
- {differt_core-0.4.1 → differt_core-0.6.0}/src/rt/graph.rs +206 -5
- {differt_core-0.4.1 → differt_core-0.6.0}/Cargo.toml +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/LICENSE.md +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/LICENSE.md +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/benches/bench_main.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/benches/benchmarks/graph_iterators.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/benches/benchmarks/mod.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/__init__.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/_differt_core/__init__.pyi +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/_differt_core/geometry/triangle_mesh.pyi +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/_differt_core/rt/__init__.pyi +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/_differt_core/scene/sionna.pyi +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/_differt_core/scene/triangle_scene.pyi +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/geometry/__init__.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/geometry/_triangle_mesh.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/py.typed +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/rt/__init__.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/rt/_graph.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/scene/__init__.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/scene/_sionna.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/scene/_triangle_scene.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/src/geometry/mod.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/src/lib.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/src/rt/mod.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/src/scene/mod.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/src/scene/sionna.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/src/scene/triangle_scene.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/tests/__init__.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/tests/rt/__init__.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/differt-core/tests/test_version.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/__init__.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/_differt_core/__init__.pyi +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/_differt_core/geometry/triangle_mesh.pyi +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/_differt_core/rt/__init__.pyi +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/_differt_core/scene/sionna.pyi +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/_differt_core/scene/triangle_scene.pyi +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/geometry/__init__.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/geometry/_triangle_mesh.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/py.typed +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/rt/__init__.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/rt/_graph.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/scene/__init__.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/scene/_sionna.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/scene/_triangle_scene.py +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/src/geometry/mod.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/src/lib.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/src/rt/mod.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/src/scene/mod.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/src/scene/sionna.rs +0 -0
- {differt_core-0.4.1 → differt_core-0.6.0}/src/scene/triangle_scene.rs +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: differt-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
|
5
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
6
5
|
Classifier: Programming Language :: Python :: 3.11
|
|
7
6
|
Classifier: Programming Language :: Python :: 3.12
|
|
8
7
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -17,7 +16,7 @@ License-File: LICENSE.md
|
|
|
17
16
|
Summary: Core backend of DiffeRT implemented in Rust
|
|
18
17
|
Keywords: ray tracing,differentiable,propagation,radio,jax
|
|
19
18
|
Author-email: Jérome Eertmans <jeertmans@icloud.com>
|
|
20
|
-
Requires-Python: >=3.
|
|
19
|
+
Requires-Python: >=3.11
|
|
21
20
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
22
21
|
|
|
23
22
|
<div align="center">
|
|
@@ -36,7 +35,7 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
|
36
35
|
|
|
37
36
|
This package contains the core backend of
|
|
38
37
|
[DiffeRT](https://pypi.org/project/DiffeRT/),
|
|
39
|
-
implemented in Rust for
|
|
38
|
+
implemented in Rust for performance.
|
|
40
39
|
|
|
41
40
|
As a result, both `differt` and `differt-core` will
|
|
42
41
|
share the same version, and `differt` directly depends on `differt-core`.
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
This package contains the core backend of
|
|
16
16
|
[DiffeRT](https://pypi.org/project/DiffeRT/),
|
|
17
|
-
implemented in Rust for
|
|
17
|
+
implemented in Rust for performance.
|
|
18
18
|
|
|
19
19
|
As a result, both `differt` and `differt-core` will
|
|
20
20
|
share the same version, and `differt` directly depends on `differt-core`.
|
|
@@ -10,7 +10,7 @@ ndarray = {version = "0.16", features = ["rayon"]}
|
|
|
10
10
|
numpy = "0.25"
|
|
11
11
|
obj-rs = "0.7.1"
|
|
12
12
|
ply-rs = "0.1.3"
|
|
13
|
-
pyo3 = {version = "0.25", features = ["abi3-
|
|
13
|
+
pyo3 = {version = "0.25", features = ["abi3-py311", "indexmap", "generate-import-lib"]}
|
|
14
14
|
pyo3-log = "0.12.4"
|
|
15
15
|
quick-xml = {version = "0.37.2", features = ["serialize", "serde-types"]}
|
|
16
16
|
serde = {version = "1.0", features = ["derive"]}
|
|
@@ -37,4 +37,5 @@ unexpected_cfgs = {level = "warn", check-cfg = ['cfg(tarpaulin_include)']}
|
|
|
37
37
|
edition = "2021"
|
|
38
38
|
name = "differt-core"
|
|
39
39
|
rust-version = "1.78.0"
|
|
40
|
-
version = "0.
|
|
40
|
+
version = "0.6.0"
|
|
41
|
+
readme = "README.md"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
This package contains the core backend of
|
|
16
16
|
[DiffeRT](https://pypi.org/project/DiffeRT/),
|
|
17
|
-
implemented in Rust for
|
|
17
|
+
implemented in Rust for performance.
|
|
18
18
|
|
|
19
19
|
As a result, both `differt` and `differt-core` will
|
|
20
20
|
share the same version, and `differt` directly depends on `differt-core`.
|
|
@@ -52,6 +52,9 @@ class DiGraph:
|
|
|
52
52
|
to_adjacency: Bool[np.ndarray, " num_nodes"],
|
|
53
53
|
) -> tuple[int, int]: ...
|
|
54
54
|
def disconnect_nodes(self, *nodes: int, fast_mode: bool = True) -> None: ...
|
|
55
|
+
def filter_by_mask(
|
|
56
|
+
self, mask: Bool[np.ndarray, " num_nodes"], fast_mode: bool = True
|
|
57
|
+
) -> None: ...
|
|
55
58
|
def all_paths(
|
|
56
59
|
self,
|
|
57
60
|
from_: int,
|
|
@@ -155,7 +155,7 @@ impl TriangleMesh {
|
|
|
155
155
|
///
|
|
156
156
|
/// The array contains the material indices,
|
|
157
157
|
/// with a special placeholder value of ``-1``.
|
|
158
|
-
///
|
|
158
|
+
/// To obtain the name of the material, see :attr:`material_names`.
|
|
159
159
|
/// This attribute is :data:`None` if all face materials are unset.
|
|
160
160
|
#[getter]
|
|
161
161
|
fn face_materials<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyArray1<isize>>> {
|
|
@@ -168,7 +168,7 @@ impl TriangleMesh {
|
|
|
168
168
|
/// :class:`Int[np.ndarray, 'num_objects 2']<jaxtyping.Int>` | :data:`None`: The array of object indices.
|
|
169
169
|
///
|
|
170
170
|
/// If the present mesh contains multiple objects, usually as a result of
|
|
171
|
-
/// appending multiple meshes together, this array
|
|
171
|
+
/// appending multiple meshes together, this array contains start and end
|
|
172
172
|
/// indices for each sub mesh.
|
|
173
173
|
#[getter]
|
|
174
174
|
fn object_bounds<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyArray2<usize>>> {
|
|
@@ -6,7 +6,11 @@ use numpy::{
|
|
|
6
6
|
IntoPyArray, PyArray1, PyArray2, PyReadonlyArray1, PyReadonlyArray2,
|
|
7
7
|
ndarray::{Array2, ArrayView1, ArrayView2, Axis, parallel::prelude::*},
|
|
8
8
|
};
|
|
9
|
-
use pyo3::{
|
|
9
|
+
use pyo3::{
|
|
10
|
+
exceptions::{PyIndexError, PyValueError},
|
|
11
|
+
prelude::*,
|
|
12
|
+
types::PyType,
|
|
13
|
+
};
|
|
10
14
|
|
|
11
15
|
/// NodeId type.
|
|
12
16
|
pub type NodeId = usize;
|
|
@@ -116,7 +120,7 @@ pub mod complete {
|
|
|
116
120
|
|
|
117
121
|
/// A complete graph, i.e.,
|
|
118
122
|
/// a simple undirected graph in which every pair of
|
|
119
|
-
///
|
|
123
|
+
/// distinct nodes is connected by a unique edge.
|
|
120
124
|
///
|
|
121
125
|
/// Args:
|
|
122
126
|
/// num_nodes (int): The number of nodes.
|
|
@@ -820,11 +824,18 @@ pub mod directed {
|
|
|
820
824
|
/// from the nodes is sufficient, and faster to perform,
|
|
821
825
|
/// but can lead to a slower graph traversal when
|
|
822
826
|
/// generating all possible paths.
|
|
827
|
+
///
|
|
828
|
+
/// Raises:
|
|
829
|
+
/// IndexError: If any of the specified nodes is not part
|
|
830
|
+
/// of the graph.
|
|
823
831
|
#[pyo3(signature = (*nodes, fast_mode=true))]
|
|
824
832
|
#[pyo3(text_signature = "(self, *nodes, fast_mode=True)")]
|
|
825
|
-
pub fn disconnect_nodes(&mut self, mut nodes: Vec<usize>, fast_mode: bool) {
|
|
833
|
+
pub fn disconnect_nodes(&mut self, mut nodes: Vec<usize>, fast_mode: bool) -> PyResult<()> {
|
|
826
834
|
for i in nodes.iter() {
|
|
827
|
-
self.edges_list
|
|
835
|
+
self.edges_list
|
|
836
|
+
.get_mut(*i)
|
|
837
|
+
.ok_or_else(|| PyIndexError::new_err(format!("Node {i} is out-of-bounds")))?
|
|
838
|
+
.clear();
|
|
828
839
|
}
|
|
829
840
|
|
|
830
841
|
if !fast_mode {
|
|
@@ -834,6 +845,68 @@ pub mod directed {
|
|
|
834
845
|
edge.retain(|node| nodes.binary_search(node).is_err());
|
|
835
846
|
}
|
|
836
847
|
}
|
|
848
|
+
|
|
849
|
+
Ok(())
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/// Disconnect all nodes where the mask is False, keeping only
|
|
853
|
+
/// nodes where the mask is True.
|
|
854
|
+
///
|
|
855
|
+
/// This is a more efficient version of :meth:`disconnect_nodes` when
|
|
856
|
+
/// working with NumPy boolean arrays, as it avoids creating
|
|
857
|
+
/// intermediate Vec<usize> collections.
|
|
858
|
+
///
|
|
859
|
+
/// This has two effects:
|
|
860
|
+
///
|
|
861
|
+
/// - all paths from nodes where mask is False will be removed;
|
|
862
|
+
/// - and all paths to nodes where mask is False will be removed.
|
|
863
|
+
///
|
|
864
|
+
/// Args:
|
|
865
|
+
/// mask (:class:`Bool[ndarray, "num_nodes"]<jaxtyping.Bool>`): A boolean mask array where
|
|
866
|
+
/// :data:`True` means the node should remain connected, and
|
|
867
|
+
/// :data:`False` means the node should be disconnected.
|
|
868
|
+
/// fast_mode (bool): If set to :data:`True` (default),
|
|
869
|
+
/// only disconnecting all paths (i.e., edges)
|
|
870
|
+
/// from the nodes is sufficient, and faster to perform,
|
|
871
|
+
/// but can lead to a slower graph traversal when
|
|
872
|
+
/// generating all possible paths.
|
|
873
|
+
///
|
|
874
|
+
/// Raises:
|
|
875
|
+
/// ValueError: If the length of ``mask`` is larger
|
|
876
|
+
/// from the number of nodes in the graph.
|
|
877
|
+
#[pyo3(signature = (mask, fast_mode=true))]
|
|
878
|
+
#[pyo3(text_signature = "(self, mask, fast_mode=True)")]
|
|
879
|
+
pub fn filter_by_mask(
|
|
880
|
+
&mut self,
|
|
881
|
+
mask: PyReadonlyArray1<bool>,
|
|
882
|
+
fast_mode: bool,
|
|
883
|
+
) -> PyResult<()> {
|
|
884
|
+
let mask = mask.as_array();
|
|
885
|
+
|
|
886
|
+
if mask.len() > self.num_nodes() {
|
|
887
|
+
return Err(PyValueError::new_err(format!(
|
|
888
|
+
"'mask' length ({}) must be smaller than or equal to the number of nodes in \
|
|
889
|
+
the graph ({})",
|
|
890
|
+
mask.len(),
|
|
891
|
+
self.num_nodes()
|
|
892
|
+
)));
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Clear edges from nodes that should be disconnected
|
|
896
|
+
for (i, &keep_node) in mask.iter().enumerate() {
|
|
897
|
+
if !keep_node {
|
|
898
|
+
self.edges_list[i].clear();
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if !fast_mode {
|
|
903
|
+
// Remove references to disconnected nodes from all other nodes
|
|
904
|
+
for edge_list in self.edges_list.iter_mut() {
|
|
905
|
+
edge_list.retain(|&node| if node < mask.len() { mask[node] } else { true });
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
Ok(())
|
|
837
910
|
}
|
|
838
911
|
|
|
839
912
|
/// Return an iterator over all paths of length ``depth``
|
|
@@ -1135,7 +1208,7 @@ pub(crate) fn graph(m: Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
1135
1208
|
mod tests {
|
|
1136
1209
|
use std::cmp::Ordering;
|
|
1137
1210
|
|
|
1138
|
-
use ndarray::array;
|
|
1211
|
+
use numpy::{PyArrayMethods, ToPyArray, ndarray::array, pyo3::Python};
|
|
1139
1212
|
use rstest::*;
|
|
1140
1213
|
|
|
1141
1214
|
use super::{complete::CompleteGraph, directed::DiGraph, *};
|
|
@@ -1502,4 +1575,132 @@ mod tests {
|
|
|
1502
1575
|
|
|
1503
1576
|
assert!(complete_iter.eq(di_iter));
|
|
1504
1577
|
}
|
|
1578
|
+
|
|
1579
|
+
#[rstest]
|
|
1580
|
+
#[case(true)]
|
|
1581
|
+
#[case(false)]
|
|
1582
|
+
fn test_di_graph_disconnect_node(#[case] fast_mode: bool) {
|
|
1583
|
+
let mut graph: DiGraph = CompleteGraph::new(10).into();
|
|
1584
|
+
let (from, to) = graph.insert_from_and_to_nodes(true, None, None);
|
|
1585
|
+
|
|
1586
|
+
// Count paths before disconnecting any nodes
|
|
1587
|
+
let paths_before = graph.all_paths(from, to, 3, false).count();
|
|
1588
|
+
|
|
1589
|
+
// Disconnect nodes 2 and 5
|
|
1590
|
+
let disconnected_nodes = vec![2, 5];
|
|
1591
|
+
graph
|
|
1592
|
+
.disconnect_nodes(disconnected_nodes.clone(), fast_mode)
|
|
1593
|
+
.unwrap();
|
|
1594
|
+
|
|
1595
|
+
// Count paths after disconnecting nodes
|
|
1596
|
+
let paths_after: Vec<_> = graph.all_paths(from, to, 3, false).collect();
|
|
1597
|
+
|
|
1598
|
+
// Verify that no path contains any of the disconnected nodes
|
|
1599
|
+
for path in &paths_after {
|
|
1600
|
+
for &node in &disconnected_nodes {
|
|
1601
|
+
assert!(
|
|
1602
|
+
!path.contains(&node),
|
|
1603
|
+
"Path {:?} should not contain disconnected node {}",
|
|
1604
|
+
path,
|
|
1605
|
+
node
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// Verify that we have fewer paths after disconnection
|
|
1611
|
+
assert!(
|
|
1612
|
+
paths_after.len() < paths_before,
|
|
1613
|
+
"Should have fewer paths after disconnecting nodes"
|
|
1614
|
+
);
|
|
1615
|
+
|
|
1616
|
+
// Test disconnecting a single node
|
|
1617
|
+
let mut graph2: DiGraph = CompleteGraph::new(5).into();
|
|
1618
|
+
let (from2, to2) = graph2.insert_from_and_to_nodes(true, None, None);
|
|
1619
|
+
|
|
1620
|
+
let paths_before2 = graph2.all_paths(from2, to2, 4, false).count();
|
|
1621
|
+
graph2.disconnect_nodes(vec![1], fast_mode).unwrap();
|
|
1622
|
+
let paths_after2: Vec<_> = graph2.all_paths(from2, to2, 4, false).collect();
|
|
1623
|
+
|
|
1624
|
+
// Verify node 1 is not in any path
|
|
1625
|
+
for path in &paths_after2 {
|
|
1626
|
+
assert!(
|
|
1627
|
+
!path.contains(&1),
|
|
1628
|
+
"Path {:?} should not contain disconnected node 1",
|
|
1629
|
+
path
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
assert!(paths_after2.len() < paths_before2);
|
|
1634
|
+
|
|
1635
|
+
// Test disconnecting all intermediate nodes (should result in no paths for depth > 2)
|
|
1636
|
+
let mut graph3: DiGraph = CompleteGraph::new(3).into();
|
|
1637
|
+
let (from3, to3) = graph3.insert_from_and_to_nodes(false, None, None); // No direct path
|
|
1638
|
+
|
|
1639
|
+
// Disconnect all intermediate nodes (0, 1, 2)
|
|
1640
|
+
graph3.disconnect_nodes(vec![0, 1, 2], fast_mode).unwrap();
|
|
1641
|
+
let paths_after3 = graph3.all_paths(from3, to3, 4, false).count();
|
|
1642
|
+
|
|
1643
|
+
// Should have no paths since all intermediate nodes are disconnected
|
|
1644
|
+
assert_eq!(
|
|
1645
|
+
paths_after3, 0,
|
|
1646
|
+
"Should have no paths when all intermediate nodes are disconnected"
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
#[rstest]
|
|
1651
|
+
#[case(true)]
|
|
1652
|
+
#[case(false)]
|
|
1653
|
+
fn test_di_graph_filter_by_mask(#[case] fast_mode: bool) {
|
|
1654
|
+
Python::with_gil(|py| {
|
|
1655
|
+
let mut graph: DiGraph = CompleteGraph::new(8).into();
|
|
1656
|
+
let (from, to) = graph.insert_from_and_to_nodes(true, None, None);
|
|
1657
|
+
|
|
1658
|
+
// Count paths before filtering
|
|
1659
|
+
let paths_before = graph.all_paths(from, to, 3, false).count();
|
|
1660
|
+
|
|
1661
|
+
// Create a mask that keeps nodes 0, 2, 4, 6 and disconnects 1, 3, 5, 7
|
|
1662
|
+
let mask = array![true, false, true, false, true, false, true, false].to_pyarray(py);
|
|
1663
|
+
|
|
1664
|
+
// Apply the mask filter
|
|
1665
|
+
graph.filter_by_mask(mask.readonly(), fast_mode).unwrap();
|
|
1666
|
+
|
|
1667
|
+
// Count paths after filtering
|
|
1668
|
+
let paths_after: Vec<_> = graph.all_paths(from, to, 3, false).collect();
|
|
1669
|
+
|
|
1670
|
+
// Verify that no path contains any of the disconnected nodes (1, 3, 5, 7)
|
|
1671
|
+
let disconnected_nodes = vec![1, 3, 5, 7];
|
|
1672
|
+
for path in &paths_after {
|
|
1673
|
+
for &node in &disconnected_nodes {
|
|
1674
|
+
assert!(
|
|
1675
|
+
!path.contains(&node),
|
|
1676
|
+
"Path {:?} should not contain disconnected node {}",
|
|
1677
|
+
path,
|
|
1678
|
+
node
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// Verify that we have fewer paths after filtering
|
|
1684
|
+
assert!(
|
|
1685
|
+
paths_after.len() < paths_before,
|
|
1686
|
+
"Should have fewer paths after filtering by mask"
|
|
1687
|
+
);
|
|
1688
|
+
|
|
1689
|
+
// Test with a mask that disconnects all nodes (should result in no paths)
|
|
1690
|
+
let mut graph2: DiGraph = CompleteGraph::new(4).into();
|
|
1691
|
+
let (from2, to2) = graph2.insert_from_and_to_nodes(false, None, None);
|
|
1692
|
+
|
|
1693
|
+
// Disconnect all intermediate nodes
|
|
1694
|
+
let all_false_mask = ndarray::array![false, false, false, false].to_pyarray(py);
|
|
1695
|
+
graph2
|
|
1696
|
+
.filter_by_mask(all_false_mask.readonly(), fast_mode)
|
|
1697
|
+
.unwrap();
|
|
1698
|
+
|
|
1699
|
+
let paths_after2 = graph2.all_paths(from2, to2, 4, false).count();
|
|
1700
|
+
assert_eq!(
|
|
1701
|
+
paths_after2, 0,
|
|
1702
|
+
"Should have no paths when all nodes are disconnected"
|
|
1703
|
+
);
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1505
1706
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import re
|
|
2
3
|
|
|
3
4
|
import numpy as np
|
|
4
5
|
import pytest
|
|
@@ -46,6 +47,76 @@ class TestDiGraph:
|
|
|
46
47
|
for node in nodes:
|
|
47
48
|
assert node not in path
|
|
48
49
|
|
|
50
|
+
@pytest.mark.parametrize("fast_mode", [True, False])
|
|
51
|
+
def test_filter_by_mask(self, fast_mode: bool) -> None:
|
|
52
|
+
graph = DiGraph.from_complete_graph(CompleteGraph(8))
|
|
53
|
+
from_, to = graph.insert_from_and_to_nodes()
|
|
54
|
+
|
|
55
|
+
# Create a mask that disconnects nodes 1, 3, 5, 7 (keeps 0, 2, 4, 6)
|
|
56
|
+
mask = np.array([True, False, True, False, True, False, True, False])
|
|
57
|
+
disconnected_nodes = [1, 3, 5, 7]
|
|
58
|
+
|
|
59
|
+
# Count paths before filtering
|
|
60
|
+
paths_before = len(
|
|
61
|
+
list(graph.all_paths(from_, to, 3, include_from_and_to=False))
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Apply the mask filter
|
|
65
|
+
graph.filter_by_mask(mask, fast_mode=fast_mode)
|
|
66
|
+
|
|
67
|
+
# Count paths after filtering and verify no disconnected nodes appear
|
|
68
|
+
paths_after = []
|
|
69
|
+
for path in graph.all_paths(from_, to, 3, include_from_and_to=False):
|
|
70
|
+
paths_after.append(path)
|
|
71
|
+
for node in disconnected_nodes:
|
|
72
|
+
assert node not in path, (
|
|
73
|
+
f"Path {path} should not contain disconnected node {node}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Verify that we have fewer paths after filtering
|
|
77
|
+
assert len(paths_after) < paths_before, (
|
|
78
|
+
"Should have fewer paths after filtering by mask"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@pytest.mark.parametrize("fast_mode", [True, False])
|
|
82
|
+
def test_filter_by_mask_all_disconnected(self, fast_mode: bool) -> None:
|
|
83
|
+
graph = DiGraph.from_complete_graph(CompleteGraph(4))
|
|
84
|
+
from_, to = graph.insert_from_and_to_nodes(direct_path=False)
|
|
85
|
+
|
|
86
|
+
# Disconnect all intermediate nodes
|
|
87
|
+
mask = np.array([False, False, False, False])
|
|
88
|
+
graph.filter_by_mask(mask, fast_mode=fast_mode)
|
|
89
|
+
|
|
90
|
+
# Should have no paths when all intermediate nodes are disconnected
|
|
91
|
+
paths = list(graph.all_paths(from_, to, 4, include_from_and_to=False))
|
|
92
|
+
assert len(paths) == 0, "Should have no paths when all nodes are disconnected"
|
|
93
|
+
|
|
94
|
+
def test_filter_by_mask_wrong_size(self) -> None:
|
|
95
|
+
graph = DiGraph.from_complete_graph(CompleteGraph(5))
|
|
96
|
+
|
|
97
|
+
# Test with mask of wrong size
|
|
98
|
+
wrong_mask = np.array([True, False, True]) # Only 3 elements for 5 nodes
|
|
99
|
+
# Ok to use a smaller mask
|
|
100
|
+
graph.filter_by_mask(wrong_mask, fast_mode=True)
|
|
101
|
+
|
|
102
|
+
graph = DiGraph.from_complete_graph(CompleteGraph(5))
|
|
103
|
+
|
|
104
|
+
wrong_mask = np.array([
|
|
105
|
+
True,
|
|
106
|
+
False,
|
|
107
|
+
True,
|
|
108
|
+
False,
|
|
109
|
+
False,
|
|
110
|
+
False,
|
|
111
|
+
]) # 6 elements for 5 nodes
|
|
112
|
+
with pytest.raises(
|
|
113
|
+
ValueError,
|
|
114
|
+
match=re.escape(
|
|
115
|
+
"'mask' length (6) must be smaller than or equal to the number of nodes in the graph (5)"
|
|
116
|
+
),
|
|
117
|
+
):
|
|
118
|
+
graph.filter_by_mask(wrong_mask, fast_mode=True)
|
|
119
|
+
|
|
49
120
|
@pytest.mark.parametrize("fast_mode", [True, False])
|
|
50
121
|
def test_disconnect_nodes_equivalence(self, fast_mode: bool) -> None:
|
|
51
122
|
complete_graph = CompleteGraph(3)
|
|
@@ -8,7 +8,6 @@ authors = [
|
|
|
8
8
|
]
|
|
9
9
|
classifiers = [
|
|
10
10
|
"Programming Language :: Python :: 3",
|
|
11
|
-
"Programming Language :: Python :: 3.10",
|
|
12
11
|
"Programming Language :: Python :: 3.11",
|
|
13
12
|
"Programming Language :: Python :: 3.12",
|
|
14
13
|
"Programming Language :: Python :: 3.13",
|
|
@@ -24,7 +23,7 @@ description = "Core backend of DiffeRT implemented in Rust"
|
|
|
24
23
|
dynamic = ["license", "readme", "version"]
|
|
25
24
|
keywords = ["ray tracing", "differentiable", "propagation", "radio", "jax"]
|
|
26
25
|
name = "differt-core"
|
|
27
|
-
requires-python = ">= 3.
|
|
26
|
+
requires-python = ">= 3.11"
|
|
28
27
|
|
|
29
28
|
[tool.maturin]
|
|
30
29
|
bindings = "pyo3"
|
|
@@ -52,6 +52,9 @@ class DiGraph:
|
|
|
52
52
|
to_adjacency: Bool[np.ndarray, " num_nodes"],
|
|
53
53
|
) -> tuple[int, int]: ...
|
|
54
54
|
def disconnect_nodes(self, *nodes: int, fast_mode: bool = True) -> None: ...
|
|
55
|
+
def filter_by_mask(
|
|
56
|
+
self, mask: Bool[np.ndarray, " num_nodes"], fast_mode: bool = True
|
|
57
|
+
) -> None: ...
|
|
55
58
|
def all_paths(
|
|
56
59
|
self,
|
|
57
60
|
from_: int,
|
|
@@ -155,7 +155,7 @@ impl TriangleMesh {
|
|
|
155
155
|
///
|
|
156
156
|
/// The array contains the material indices,
|
|
157
157
|
/// with a special placeholder value of ``-1``.
|
|
158
|
-
///
|
|
158
|
+
/// To obtain the name of the material, see :attr:`material_names`.
|
|
159
159
|
/// This attribute is :data:`None` if all face materials are unset.
|
|
160
160
|
#[getter]
|
|
161
161
|
fn face_materials<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyArray1<isize>>> {
|
|
@@ -168,7 +168,7 @@ impl TriangleMesh {
|
|
|
168
168
|
/// :class:`Int[np.ndarray, 'num_objects 2']<jaxtyping.Int>` | :data:`None`: The array of object indices.
|
|
169
169
|
///
|
|
170
170
|
/// If the present mesh contains multiple objects, usually as a result of
|
|
171
|
-
/// appending multiple meshes together, this array
|
|
171
|
+
/// appending multiple meshes together, this array contains start and end
|
|
172
172
|
/// indices for each sub mesh.
|
|
173
173
|
#[getter]
|
|
174
174
|
fn object_bounds<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyArray2<usize>>> {
|
|
@@ -6,7 +6,11 @@ use numpy::{
|
|
|
6
6
|
IntoPyArray, PyArray1, PyArray2, PyReadonlyArray1, PyReadonlyArray2,
|
|
7
7
|
ndarray::{Array2, ArrayView1, ArrayView2, Axis, parallel::prelude::*},
|
|
8
8
|
};
|
|
9
|
-
use pyo3::{
|
|
9
|
+
use pyo3::{
|
|
10
|
+
exceptions::{PyIndexError, PyValueError},
|
|
11
|
+
prelude::*,
|
|
12
|
+
types::PyType,
|
|
13
|
+
};
|
|
10
14
|
|
|
11
15
|
/// NodeId type.
|
|
12
16
|
pub type NodeId = usize;
|
|
@@ -116,7 +120,7 @@ pub mod complete {
|
|
|
116
120
|
|
|
117
121
|
/// A complete graph, i.e.,
|
|
118
122
|
/// a simple undirected graph in which every pair of
|
|
119
|
-
///
|
|
123
|
+
/// distinct nodes is connected by a unique edge.
|
|
120
124
|
///
|
|
121
125
|
/// Args:
|
|
122
126
|
/// num_nodes (int): The number of nodes.
|
|
@@ -820,11 +824,18 @@ pub mod directed {
|
|
|
820
824
|
/// from the nodes is sufficient, and faster to perform,
|
|
821
825
|
/// but can lead to a slower graph traversal when
|
|
822
826
|
/// generating all possible paths.
|
|
827
|
+
///
|
|
828
|
+
/// Raises:
|
|
829
|
+
/// IndexError: If any of the specified nodes is not part
|
|
830
|
+
/// of the graph.
|
|
823
831
|
#[pyo3(signature = (*nodes, fast_mode=true))]
|
|
824
832
|
#[pyo3(text_signature = "(self, *nodes, fast_mode=True)")]
|
|
825
|
-
pub fn disconnect_nodes(&mut self, mut nodes: Vec<usize>, fast_mode: bool) {
|
|
833
|
+
pub fn disconnect_nodes(&mut self, mut nodes: Vec<usize>, fast_mode: bool) -> PyResult<()> {
|
|
826
834
|
for i in nodes.iter() {
|
|
827
|
-
self.edges_list
|
|
835
|
+
self.edges_list
|
|
836
|
+
.get_mut(*i)
|
|
837
|
+
.ok_or_else(|| PyIndexError::new_err(format!("Node {i} is out-of-bounds")))?
|
|
838
|
+
.clear();
|
|
828
839
|
}
|
|
829
840
|
|
|
830
841
|
if !fast_mode {
|
|
@@ -834,6 +845,68 @@ pub mod directed {
|
|
|
834
845
|
edge.retain(|node| nodes.binary_search(node).is_err());
|
|
835
846
|
}
|
|
836
847
|
}
|
|
848
|
+
|
|
849
|
+
Ok(())
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/// Disconnect all nodes where the mask is False, keeping only
|
|
853
|
+
/// nodes where the mask is True.
|
|
854
|
+
///
|
|
855
|
+
/// This is a more efficient version of :meth:`disconnect_nodes` when
|
|
856
|
+
/// working with NumPy boolean arrays, as it avoids creating
|
|
857
|
+
/// intermediate Vec<usize> collections.
|
|
858
|
+
///
|
|
859
|
+
/// This has two effects:
|
|
860
|
+
///
|
|
861
|
+
/// - all paths from nodes where mask is False will be removed;
|
|
862
|
+
/// - and all paths to nodes where mask is False will be removed.
|
|
863
|
+
///
|
|
864
|
+
/// Args:
|
|
865
|
+
/// mask (:class:`Bool[ndarray, "num_nodes"]<jaxtyping.Bool>`): A boolean mask array where
|
|
866
|
+
/// :data:`True` means the node should remain connected, and
|
|
867
|
+
/// :data:`False` means the node should be disconnected.
|
|
868
|
+
/// fast_mode (bool): If set to :data:`True` (default),
|
|
869
|
+
/// only disconnecting all paths (i.e., edges)
|
|
870
|
+
/// from the nodes is sufficient, and faster to perform,
|
|
871
|
+
/// but can lead to a slower graph traversal when
|
|
872
|
+
/// generating all possible paths.
|
|
873
|
+
///
|
|
874
|
+
/// Raises:
|
|
875
|
+
/// ValueError: If the length of ``mask`` is larger
|
|
876
|
+
/// from the number of nodes in the graph.
|
|
877
|
+
#[pyo3(signature = (mask, fast_mode=true))]
|
|
878
|
+
#[pyo3(text_signature = "(self, mask, fast_mode=True)")]
|
|
879
|
+
pub fn filter_by_mask(
|
|
880
|
+
&mut self,
|
|
881
|
+
mask: PyReadonlyArray1<bool>,
|
|
882
|
+
fast_mode: bool,
|
|
883
|
+
) -> PyResult<()> {
|
|
884
|
+
let mask = mask.as_array();
|
|
885
|
+
|
|
886
|
+
if mask.len() > self.num_nodes() {
|
|
887
|
+
return Err(PyValueError::new_err(format!(
|
|
888
|
+
"'mask' length ({}) must be smaller than or equal to the number of nodes in \
|
|
889
|
+
the graph ({})",
|
|
890
|
+
mask.len(),
|
|
891
|
+
self.num_nodes()
|
|
892
|
+
)));
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Clear edges from nodes that should be disconnected
|
|
896
|
+
for (i, &keep_node) in mask.iter().enumerate() {
|
|
897
|
+
if !keep_node {
|
|
898
|
+
self.edges_list[i].clear();
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if !fast_mode {
|
|
903
|
+
// Remove references to disconnected nodes from all other nodes
|
|
904
|
+
for edge_list in self.edges_list.iter_mut() {
|
|
905
|
+
edge_list.retain(|&node| if node < mask.len() { mask[node] } else { true });
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
Ok(())
|
|
837
910
|
}
|
|
838
911
|
|
|
839
912
|
/// Return an iterator over all paths of length ``depth``
|
|
@@ -1135,7 +1208,7 @@ pub(crate) fn graph(m: Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
1135
1208
|
mod tests {
|
|
1136
1209
|
use std::cmp::Ordering;
|
|
1137
1210
|
|
|
1138
|
-
use ndarray::array;
|
|
1211
|
+
use numpy::{PyArrayMethods, ToPyArray, ndarray::array, pyo3::Python};
|
|
1139
1212
|
use rstest::*;
|
|
1140
1213
|
|
|
1141
1214
|
use super::{complete::CompleteGraph, directed::DiGraph, *};
|
|
@@ -1502,4 +1575,132 @@ mod tests {
|
|
|
1502
1575
|
|
|
1503
1576
|
assert!(complete_iter.eq(di_iter));
|
|
1504
1577
|
}
|
|
1578
|
+
|
|
1579
|
+
#[rstest]
|
|
1580
|
+
#[case(true)]
|
|
1581
|
+
#[case(false)]
|
|
1582
|
+
fn test_di_graph_disconnect_node(#[case] fast_mode: bool) {
|
|
1583
|
+
let mut graph: DiGraph = CompleteGraph::new(10).into();
|
|
1584
|
+
let (from, to) = graph.insert_from_and_to_nodes(true, None, None);
|
|
1585
|
+
|
|
1586
|
+
// Count paths before disconnecting any nodes
|
|
1587
|
+
let paths_before = graph.all_paths(from, to, 3, false).count();
|
|
1588
|
+
|
|
1589
|
+
// Disconnect nodes 2 and 5
|
|
1590
|
+
let disconnected_nodes = vec![2, 5];
|
|
1591
|
+
graph
|
|
1592
|
+
.disconnect_nodes(disconnected_nodes.clone(), fast_mode)
|
|
1593
|
+
.unwrap();
|
|
1594
|
+
|
|
1595
|
+
// Count paths after disconnecting nodes
|
|
1596
|
+
let paths_after: Vec<_> = graph.all_paths(from, to, 3, false).collect();
|
|
1597
|
+
|
|
1598
|
+
// Verify that no path contains any of the disconnected nodes
|
|
1599
|
+
for path in &paths_after {
|
|
1600
|
+
for &node in &disconnected_nodes {
|
|
1601
|
+
assert!(
|
|
1602
|
+
!path.contains(&node),
|
|
1603
|
+
"Path {:?} should not contain disconnected node {}",
|
|
1604
|
+
path,
|
|
1605
|
+
node
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// Verify that we have fewer paths after disconnection
|
|
1611
|
+
assert!(
|
|
1612
|
+
paths_after.len() < paths_before,
|
|
1613
|
+
"Should have fewer paths after disconnecting nodes"
|
|
1614
|
+
);
|
|
1615
|
+
|
|
1616
|
+
// Test disconnecting a single node
|
|
1617
|
+
let mut graph2: DiGraph = CompleteGraph::new(5).into();
|
|
1618
|
+
let (from2, to2) = graph2.insert_from_and_to_nodes(true, None, None);
|
|
1619
|
+
|
|
1620
|
+
let paths_before2 = graph2.all_paths(from2, to2, 4, false).count();
|
|
1621
|
+
graph2.disconnect_nodes(vec![1], fast_mode).unwrap();
|
|
1622
|
+
let paths_after2: Vec<_> = graph2.all_paths(from2, to2, 4, false).collect();
|
|
1623
|
+
|
|
1624
|
+
// Verify node 1 is not in any path
|
|
1625
|
+
for path in &paths_after2 {
|
|
1626
|
+
assert!(
|
|
1627
|
+
!path.contains(&1),
|
|
1628
|
+
"Path {:?} should not contain disconnected node 1",
|
|
1629
|
+
path
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
assert!(paths_after2.len() < paths_before2);
|
|
1634
|
+
|
|
1635
|
+
// Test disconnecting all intermediate nodes (should result in no paths for depth > 2)
|
|
1636
|
+
let mut graph3: DiGraph = CompleteGraph::new(3).into();
|
|
1637
|
+
let (from3, to3) = graph3.insert_from_and_to_nodes(false, None, None); // No direct path
|
|
1638
|
+
|
|
1639
|
+
// Disconnect all intermediate nodes (0, 1, 2)
|
|
1640
|
+
graph3.disconnect_nodes(vec![0, 1, 2], fast_mode).unwrap();
|
|
1641
|
+
let paths_after3 = graph3.all_paths(from3, to3, 4, false).count();
|
|
1642
|
+
|
|
1643
|
+
// Should have no paths since all intermediate nodes are disconnected
|
|
1644
|
+
assert_eq!(
|
|
1645
|
+
paths_after3, 0,
|
|
1646
|
+
"Should have no paths when all intermediate nodes are disconnected"
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
#[rstest]
|
|
1651
|
+
#[case(true)]
|
|
1652
|
+
#[case(false)]
|
|
1653
|
+
fn test_di_graph_filter_by_mask(#[case] fast_mode: bool) {
|
|
1654
|
+
Python::with_gil(|py| {
|
|
1655
|
+
let mut graph: DiGraph = CompleteGraph::new(8).into();
|
|
1656
|
+
let (from, to) = graph.insert_from_and_to_nodes(true, None, None);
|
|
1657
|
+
|
|
1658
|
+
// Count paths before filtering
|
|
1659
|
+
let paths_before = graph.all_paths(from, to, 3, false).count();
|
|
1660
|
+
|
|
1661
|
+
// Create a mask that keeps nodes 0, 2, 4, 6 and disconnects 1, 3, 5, 7
|
|
1662
|
+
let mask = array![true, false, true, false, true, false, true, false].to_pyarray(py);
|
|
1663
|
+
|
|
1664
|
+
// Apply the mask filter
|
|
1665
|
+
graph.filter_by_mask(mask.readonly(), fast_mode).unwrap();
|
|
1666
|
+
|
|
1667
|
+
// Count paths after filtering
|
|
1668
|
+
let paths_after: Vec<_> = graph.all_paths(from, to, 3, false).collect();
|
|
1669
|
+
|
|
1670
|
+
// Verify that no path contains any of the disconnected nodes (1, 3, 5, 7)
|
|
1671
|
+
let disconnected_nodes = vec![1, 3, 5, 7];
|
|
1672
|
+
for path in &paths_after {
|
|
1673
|
+
for &node in &disconnected_nodes {
|
|
1674
|
+
assert!(
|
|
1675
|
+
!path.contains(&node),
|
|
1676
|
+
"Path {:?} should not contain disconnected node {}",
|
|
1677
|
+
path,
|
|
1678
|
+
node
|
|
1679
|
+
);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// Verify that we have fewer paths after filtering
|
|
1684
|
+
assert!(
|
|
1685
|
+
paths_after.len() < paths_before,
|
|
1686
|
+
"Should have fewer paths after filtering by mask"
|
|
1687
|
+
);
|
|
1688
|
+
|
|
1689
|
+
// Test with a mask that disconnects all nodes (should result in no paths)
|
|
1690
|
+
let mut graph2: DiGraph = CompleteGraph::new(4).into();
|
|
1691
|
+
let (from2, to2) = graph2.insert_from_and_to_nodes(false, None, None);
|
|
1692
|
+
|
|
1693
|
+
// Disconnect all intermediate nodes
|
|
1694
|
+
let all_false_mask = ndarray::array![false, false, false, false].to_pyarray(py);
|
|
1695
|
+
graph2
|
|
1696
|
+
.filter_by_mask(all_false_mask.readonly(), fast_mode)
|
|
1697
|
+
.unwrap();
|
|
1698
|
+
|
|
1699
|
+
let paths_after2 = graph2.all_paths(from2, to2, 4, false).count();
|
|
1700
|
+
assert_eq!(
|
|
1701
|
+
paths_after2, 0,
|
|
1702
|
+
"Should have no paths when all nodes are disconnected"
|
|
1703
|
+
);
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1505
1706
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{differt_core-0.4.1 → differt_core-0.6.0}/differt-core/benches/benchmarks/graph_iterators.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/geometry/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/scene/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{differt_core-0.4.1 → differt_core-0.6.0}/differt-core/python/differt_core/scene/_triangle_scene.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/_differt_core/scene/sionna.pyi
RENAMED
|
File without changes
|
{differt_core-0.4.1 → differt_core-0.6.0}/python/differt_core/_differt_core/scene/triangle_scene.pyi
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|