Skip to content

Commit

Permalink
Reorganize code into modules (Qiskit#396)
Browse files Browse the repository at this point in the history
The organization steps were:

 * Group code by topics in api.rst
 * For small topics, use topic.rs
 * For larger topics, create topic/mod.rs for the Python implementations and
   topic/xyz.rs for implementations

Details worth sharing are:

 * weight_callable and NodeRemoved stayed at lib.rs because they are
   omnipresent; because they are pure Rust code I thought it was a reasonable
   place
 * wrap_pyfunction(module_name::function_name) is currently not supported,
   so all functions had to be imported
 * Some modules from the "Others" section in the docs are small; they could be
   grouped together but other_algo did not feel very descriptive

Closes Qiskit#300

* Reorganize dag algorithms into new file

* Reorganize shortest path algorithms in new file

* Move astar to short path file

* Reorganize matching in new file

* Reorganize random_circuit in new file

* Run cargo fmt

* Move layout to its own module

* Run cargo fmt

* Move isomorphism to its own module

* Move matching to its own module

* Move shortest_path to its own module

* Remove weight_callable duplication

* Move floyd_warshall to its own file

* Move num_shortest_path_unweighted to its own file

* Move all_pairs_dijkstra to its own file

* Rename to dag_algo

* Move tree algorithms to its own file

* Move connectivity to its own file

* Move dag_algo to its own module

* Move traversal to its own module

* Temporarily allow module inception

* Rename isomorphism and layouts

* Move simple_path to its own file

* Move transitivity to its own file

* Move core_number to its own file

* Create representation_algo

* Move digraph_union to union

* Move graph_greedy_coloring to its own file

* Move code from traversal to dag_algo

* Revert "Rename isomorphism and layouts"

This reverts commit 22abf85.

* Move more items into connectivity module

* Rename random_circuit to random_graph

* Avoid module inception in isomorphism.rs

* Avoid module inception in layout.rs

* Simplify names in layout module

* Add details to CONTRIBUTING.md

* Fix tox -edocs issues

* Minor CONTRIBUTING.md fixes
  • Loading branch information
IvanIsCoding authored Aug 6, 2021
1 parent 0919144 commit 2f74f7f
Show file tree
Hide file tree
Showing 33 changed files with 5,830 additions and 5,151 deletions.
68 changes: 68 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,74 @@ with Qiskit; the general guidelines and advice still apply here.
In addition to the general guidelines there are specific details for
contributing to retworkx, these are documented below.

### Making changes to the code

Retworkx is implemented primarily in Rust with a thin layer of Python.
Because of that, most of your code changes will involve modifications to
Rust files in `src`. To understand which files you need to change, we invite
you for an overview of our simplified source tree:

```
├── src/
│ ├── lib.rs
│ ├── tiny.rs
│ ├── large/
│ │ ├── mod.rs
│ │ ├── pure_rust_code.rs
│ │ └── more_pure_rust_code.rs
```

#### Module exports in `lib.rs`

To add new functions, you will need to export them in `lib.rs`. `lib.rs` will
import functions defined in Rust modules (see the next section), and export
them to Python using `m.add_wrapped(wrap_pyfunction!(your_new_function))?;`

#### Adding and changing functions in modules

To add and change functions, you will need to modify module files. Modules contain pyfunctions
that will be exported, and can be defined either as a single file such as `tiny.rs` or as a
directory with `mod.rs` such as `large/`.

Rust functions that are exported to Python are annotated with `#[pyfunction]`. The
annotation gives them power to interact both with the Python interpreter and pure
Rust code. To change an existing function, search for its name and edit the code that
already exists.

If you want to add a new function, find the module you'd like to insert it in
or create a new one like `your_module.rs`. Then, start with the boilerplate bellow:

```rust
/// Docstring containing description of the function
#[pyfunction]
#[pyo3(text_signature = "(graph, /)")]
pub fn your_new_function(
py: Python,
graph: &graph::PyGraph,
) -> PyResult<()> {
/* Your code goes here */
}
```

> __NOTE:__ If you create a new `your_module.rs`, remember to declare and import it in `lib.rs`:
> ```rust
> mod your_module;
> use your_module::*;
> ```
#### Module directories: when a single file is not enough
Sometimes you will find that it is hard to organize a module in a tiny
file like `tiny.rs`. In those cases, we suggest moving the files to a directory
and splitting them following the structure of `large/`.
Module directories have a `mod.rs` file containing the pyfunctions. The pyfunctions
in that file then delegate most of logic by importing and calling pure Rust code from
`pure_rust_code.rs` and `more_pure_rust_code.rs`.
> __NOTE:__ Do you still have questions about making your contribution?
> Contact us at the [\#retworkx channel in Qiskit Slack](https://qiskit.slack.com/messages/retworkx/)
### Tests
Once you've made a code change, it is important to verify that your change
Expand Down
78 changes: 78 additions & 0 deletions src/coloring.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

#![allow(clippy::float_cmp)]

use crate::graph;

use ahash::RandomState;
use hashbrown::{HashMap, HashSet};
use indexmap::IndexMap;
use std::cmp::Reverse;

use pyo3::prelude::*;
use pyo3::types::PyDict;
use pyo3::Python;

use petgraph::graph::NodeIndex;
use petgraph::prelude::*;
use petgraph::visit::NodeCount;

use rayon::prelude::*;

/// Color a PyGraph using a largest_first strategy greedy graph coloring.
///
/// :param PyGraph: The input PyGraph object to color
///
/// :returns: A dictionary where keys are node indices and the value is
/// the color
/// :rtype: dict
#[pyfunction]
#[pyo3(text_signature = "(graph, /)")]
fn graph_greedy_color(
py: Python,
graph: &graph::PyGraph,
) -> PyResult<PyObject> {
let mut colors: IndexMap<usize, usize, RandomState> =
IndexMap::with_hasher(RandomState::default());
let mut node_vec: Vec<NodeIndex> = graph.graph.node_indices().collect();
let mut sort_map: HashMap<NodeIndex, usize> =
HashMap::with_capacity(graph.node_count());
for k in node_vec.iter() {
sort_map.insert(*k, graph.graph.edges(*k).count());
}
node_vec.par_sort_by_key(|k| Reverse(sort_map.get(k)));
for u_index in node_vec {
let mut neighbor_colors: HashSet<usize> = HashSet::new();
for edge in graph.graph.edges(u_index) {
let target = edge.target().index();
let existing_color = match colors.get(&target) {
Some(node) => node,
None => continue,
};
neighbor_colors.insert(*existing_color);
}
let mut count: usize = 0;
loop {
if !neighbor_colors.contains(&count) {
break;
}
count += 1;
}
colors.insert(u_index.index(), count);
}
let out_dict = PyDict::new(py);
for (index, color) in colors {
out_dict.set_item(index, color)?;
}
Ok(out_dict.into())
}
96 changes: 96 additions & 0 deletions src/connectivity/core_number.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

#![allow(clippy::float_cmp)]

use hashbrown::{HashMap, HashSet};

use pyo3::prelude::*;
use pyo3::types::PyDict;
use pyo3::Python;

use petgraph::graph::NodeIndex;
use petgraph::prelude::*;
use petgraph::EdgeType;

use rayon::prelude::*;

pub fn core_number<Ty>(
py: Python,
graph: &StableGraph<PyObject, PyObject, Ty>,
) -> PyResult<PyObject>
where
Ty: EdgeType,
{
let node_num = graph.node_count();
if node_num == 0 {
return Ok(PyDict::new(py).into());
}

let mut cores: HashMap<NodeIndex, usize> = HashMap::with_capacity(node_num);
let mut node_vec: Vec<NodeIndex> = graph.node_indices().collect();
let mut degree_map: HashMap<NodeIndex, usize> =
HashMap::with_capacity(node_num);
let mut nbrs: HashMap<NodeIndex, HashSet<NodeIndex>> =
HashMap::with_capacity(node_num);
let mut node_pos: HashMap<NodeIndex, usize> =
HashMap::with_capacity(node_num);

for k in node_vec.iter() {
let k_nbrs: HashSet<NodeIndex> =
graph.neighbors_undirected(*k).collect();
let k_deg = k_nbrs.len();

nbrs.insert(*k, k_nbrs);
cores.insert(*k, k_deg);
degree_map.insert(*k, k_deg);
}
node_vec.par_sort_by_key(|k| degree_map.get(k));

let mut bin_boundaries: Vec<usize> =
Vec::with_capacity(degree_map[&node_vec[node_num - 1]] + 1);
bin_boundaries.push(0);
let mut curr_degree = 0;
for (i, v) in node_vec.iter().enumerate() {
node_pos.insert(*v, i);
let v_degree = degree_map[v];
if v_degree > curr_degree {
for _ in 0..v_degree - curr_degree {
bin_boundaries.push(i);
}
curr_degree = v_degree;
}
}

for v_ind in 0..node_vec.len() {
let v = node_vec[v_ind];
let v_nbrs = nbrs[&v].clone();
for u in v_nbrs {
if cores[&u] > cores[&v] {
nbrs.get_mut(&u).unwrap().remove(&v);
let pos = node_pos[&u];
let bin_start = bin_boundaries[cores[&u]];
*node_pos.get_mut(&u).unwrap() = bin_start;
*node_pos.get_mut(&node_vec[bin_start]).unwrap() = pos;
node_vec.swap(bin_start, pos);
bin_boundaries[cores[&u]] += 1;
*cores.get_mut(&u).unwrap() -= 1;
}
}
}

let out_dict = PyDict::new(py);
for (v_index, core) in cores {
out_dict.set_item(v_index.index(), core)?;
}
Ok(out_dict.into())
}
Loading

0 comments on commit 2f74f7f

Please sign in to comment.