diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..fa0f963c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement by reaching out to one of the contributors +of this repository. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..ebbb9d0e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contribution Guidelines + +## Issues +If you discover an issue with an algorithm, or test, open an issue to point out areas for improvement. +If you are comfortable with it, implement the fix and open a PR. + + +## Adding tests +See the [testing contributors guide](tests/CONTRIBUTING.md) + +## Coding Standards + +### Languages + +*GSQL* +- Follow the [GSQL Style Guide](https://docs.tigergraph.com/gsql-ref/current/appendix/gsql-style-guide) + +*Python* +- Use the [ruff formatter](https://docs.astral.sh/ruff/formatter/#the-ruff-formatter) to format your code +- tests: pytest and networkx wherever applicable + +*C/CPP* + + +## Pull Requests +- Make sure git knows your name and email address: + ``` + $ git config user.name "J. Random User" + $ git config user.email "j.random.user@example.com" + ``` +- The name and email address must be valid as we cannot accept anonymous contributions. +- Write good commit messages. + - Concise commit messages that describe your changes help us better understand your contributions. + +## General Guidelines + +Ensure your pull request (PR) adheres to the following guidelines: + +- Try to make the name concise and descriptive. +- Give a good description of the change being made. Since this is very subjective, see the [Updating Your Pull Request (PR)](#updating-your-pull-request-pr) section below for further details. +- Every pull request should be associated with one or more issues. If no issue exists yet, please create your own. +- Make sure that all applicable issues are mentioned somewhere in the PR description. This can be done by typing # to bring up a list of issues. + +### Updating Your Pull Request (PR) + +A lot of times, making a PR adhere to the standards above can be difficult. If the maintainers notice anything that we'd like changed, we'll ask you to edit your PR before we merge it. +This applies to both the content documented in the PR and the changed contained within the branch being merged. There's no need to open a new PR. Just edit the existing one. + +--- + +Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. + diff --git a/tests/CONTRIBUTING.md b/tests/CONTRIBUTING.md new file mode 100644 index 00000000..5a021193 --- /dev/null +++ b/tests/CONTRIBUTING.md @@ -0,0 +1,165 @@ +# Contribution Guidelines for Adding Tests + +### Contents + +- [Running the tests](#running-the-tests) +- [Directory Layout](#directory-layout) +- [Adding tests](#adding-tests) +- [Available Graphs](#available-graphs) + +## Running the tests + +Execute the following to download the dependencies and run the tests. Make sure you're in a venv. + +```sh +echo ' +HOST_NAME="https://tg-hostname" +USER_NAME=tigergraph +PASS=tigergraph +' >> test/.env +pip install -r requirements.txt +./run.sh +``` + +`test/.env` + +- HOST_NAME: A TG environment that you have querywriter access to so setup.py can load data and queries to a subgraph named `graph_algorithms_testing` +- USER_NAME: user +- PASS: user's password + +`run.sh` does a few things: + +- runs `data/create_baseline.py` + - this creates the baselines from the graphs listed in that file +- runs the setup script to make sure the graph is created and data is loaded +- runs the tests with pytest + +## Directory layout + +Data: stores the satic data for creating graphs, and algorithm baseline results. + +- CSV files under `data/[heterogeneous_edges, unweighted_edges, weighted_edges]` store the adjacency information for creating graphs. The baselines for algorithms are made from these graphs + - For example `data/weighted_edges/line_edges.csv` stores the edges and weights to create a weighted, line graph. +- JSON files under `data/baseline` store the baseline results for a given algorithm on a given graph type. + - For example `data/baseline/centrality/pagerank/Line_Directed.json` stores the baseline results for pagerank on a directed line graph + +test: + +- setup.py: creates the graph, loads the data and installs the queries from pyTG's featurizer. Any new/custom queries need to be manually installed +- test.py: houses the testing code for each family of algorithms + +``` +├── data +│   ├── baseline +│   │   ├── +│   │   │   └── +│   │ │ └── .json +│   ├── +│   │ └── .csv +│   └── create_baseline.py +├── requirements.txt +├── run.sh +├── test +│   ├── pyrightconfig.json +│   ├── setup.py +│   ├── test_centrality.py +│   ├── test_community.py +│   ├── test_path_finding.py +│   ├── test_topological_link_prediction.py +│   └── util.py +``` + +## Adding tests + +Start with creating the baseline. Add a section to `create_baseline.py` that creates a baseline for all the necessary graph types for your algorithm. The output of the baseline should be written to +the correct baseline path (see above [layout](#directory-layout)). + +If you're adding a new algorithm, add a test method for it to the algorithm family that it belongs to (i.e., community algorigthms go in community.py). The first test method in `test/test_centrality.py` +is a good template to follow: + +```py + # this function will run once for each of the graph names in the undirected_graphs list + @pytest.mark.parametrize("test_name", undirected_graphs) + def test_degree_centrality1(self, test_name): + # query params + params = { + "v_type_set": ["V20"], + "e_type_set": [test_name], + "reverse_e_type_set": [test_name], + "in_degree": True, + "out_degree": False, + "top_k": 100, + "print_results": True, + "result_attribute": "", + "file_path": "", + } + with open(f"data/baseline/centrality/degree_centrality/{test_name}.json") as f: + baseline = json.load(f) + baseline = sorted(baseline[0]["top_scores"], key=lambda x: x["Vertex_ID"]) + + # call the the algorithm through the featurizer + result = self.feat.runAlgorithm("tg_degree_cent", params=params) + result = sorted(result[0]["top_scores"], key=lambda x: x["Vertex_ID"]) + + + # check that the results agree with the baseline + for b in baseline: + for r in result: + if r["Vertex_ID"] == b["Vertex_ID"] and r["score"] != pytest.approx( + b["score"] + ): + pytest.fail(f'{r["score"]} != {b["score"]}') +``` + +## Available Graphs + +Example usage: + +- If you want to run a query on a directed, weighted, line graph, use the V20 verts and Line_Directed_Weighted edges. + +| Graph | Type | Vertices | Edges | +| --------------------------- | ------------------------------------------------------------ | -------- | -------------------------------- | +| Null | | V0 | | +| Single node | | V1 | | +| Empty graph | Undirected | V20 | Empty | +| | Directed | | Empty_Directed | +| Line | Undirected, unweighted | V20 | Line | +| | Directed, unweighted | | Line_Directed | +| | Undirected, weighted | | Line_Weighted | +| | Directed, weighted | | Line_Directed_Weighted | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | Line_Heterogeneous | +| Ring | Undirected, unweighted | V20 | Ring | +| | Directed, unweighted | | Ring_Directed | +| | Undirected, weighted | | Ring_Weighted | +| | Directed, weighted | | Ring_Directed_Weighted | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | Ring_Heterogeneous | +| Hub & spoke | Undirected, unweighted | V20 | Hub_Spoke | +| | Directed (towards the spokes), unweighted Hub_Spoke_Directed | | | +| | Undirected, weighted Hub_Spoke_Weighted | | | +| | Directed, weighted Hub_Spoke_Directed_Weighted | | | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | Hub_Spoke_Heterogeneous | +| Hub-connected hub & spoke | Undirected, unweighted | V20 | Hub_Connected_Hub_Spoke | +| | Undirected, weighted | | Hub_Connected_Hub_Spoke_Weighted | +| Tree | Undirected, unweighted | V20 | Tree | +| | Directed, unweighted | | Tree_Directed | +| | Undirected, weighted | | Tree_Weighted | +| | Directed, weighted | | Tree_Directed_Weighted | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | Tree_Heterogeneous | +| Complete | Undirected, unweighted | V8 | Complete | +| | Directed, unweighted | | Complete_Directed | +| | Undirected, weighted | | Complete_Weighted | +| | Directed, weighted | | Complete_Directed_Weighted | +| | Heterogeneous vertex types, directed, weighted | V4, V8 | Complete_Heterogeneous | +| DAG | Directed, unweighted | V20 | DAG_Directed | +| | Directed, weighted | | DAG_Directed_Weighted | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | DAG_Heterogeneous | +| Graph with negative cycles | Directed, weighted | V20 | Negative_cycles | +| | Heterogeneous vertex types, directed, weighted | V20, V8 | Negative_Cycle_Heterogeneous | +| Topological link prediction | Unweighted, undirected | V8 | topo_link1 | +| | topo_link2 | | | +| | topo_link3 | | | +| | topo_link4 | | | +| | topo_link5 | | | +| | topo_link6 | | | +| | Unweighted, directed | | topo_link_directed | +| Same Community | no edges | V4 | | diff --git a/tests/test/test_centrality.py b/tests/test/test_centrality.py index 68b1bcbb..efabdc1f 100644 --- a/tests/test/test_centrality.py +++ b/tests/test/test_centrality.py @@ -6,39 +6,34 @@ class TestCentrality: feat = util.get_featurizer() - # undirected graphs - graph_types1 = [ + undirected_graphs = [ "Empty", "Line", "Ring", "Hub_Spoke", "Tree", ] - # directed graphs - graph_types2 = [ + directed_graphs = [ "Line_Directed", "Ring_Directed", "Hub_Spoke_Directed", "Tree_Directed", ] - # weighted undirected graphs - graph_types3 = [ + weighted_undirected_graphs = [ "Line_Weighted", "Ring_Weighted", "Hub_Spoke_Weighted", "Tree_Weighted", ] - # weighted directed graphs - graph_types4 = [ + weighted_directed_graphs = [ "Line_Directed_Weighted", "Ring_Directed_Weighted", "Hub_Spoke_Directed_Weighted", "Tree_Directed_Weighted", ] - # Complete Graphs - graph_types5 = ["Complete"] + complete_graphs = ["Complete"] - @pytest.mark.parametrize("test_name", graph_types1) + @pytest.mark.parametrize("test_name", undirected_graphs) def test_degree_centrality1(self, test_name): params = { "v_type_set": ["V20"], @@ -66,7 +61,7 @@ def test_degree_centrality1(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types2) + @pytest.mark.parametrize("test_name", directed_graphs) def test_degree_centrality2(self, test_name): params = { "v_type_set": ["V20"], @@ -95,7 +90,7 @@ def test_degree_centrality2(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types2) + @pytest.mark.parametrize("test_name", directed_graphs) def test_degree_centrality3(self, test_name): params = { "v_type_set": ["V20"], @@ -124,7 +119,7 @@ def test_degree_centrality3(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types5) + @pytest.mark.parametrize("test_name", complete_graphs) def test_degree_centrality4(self, test_name): params = { "v_type_set": ["V8"], @@ -148,7 +143,7 @@ def test_degree_centrality4(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types3) + @pytest.mark.parametrize("test_name", weighted_undirected_graphs) def test_weighted_degree_centrality1(self, test_name): params = { "v_type": "V20", @@ -177,7 +172,7 @@ def test_weighted_degree_centrality1(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types4) + @pytest.mark.parametrize("test_name", weighted_directed_graphs) def test_weighted_degree_centrality2(self, test_name): params = { "v_type": "V20", @@ -206,7 +201,7 @@ def test_weighted_degree_centrality2(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types4) + @pytest.mark.parametrize("test_name", weighted_directed_graphs) def test_weighted_degree_centrality3(self, test_name): params = { "v_type": "V20", @@ -235,7 +230,7 @@ def test_weighted_degree_centrality3(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types1) + @pytest.mark.parametrize("test_name", undirected_graphs) def test_closeness_centrality(self, test_name): params = { "v_type_set": ["V20"], @@ -264,7 +259,7 @@ def test_closeness_centrality(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types2) + @pytest.mark.parametrize("test_name", directed_graphs) def test_closeness_centrality2(self, test_name): params = { "v_type_set": ["V20"], @@ -293,7 +288,7 @@ def test_closeness_centrality2(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types1) + @pytest.mark.parametrize("test_name", undirected_graphs) def test_harmonic_centrality(self, test_name): params = { "v_type_set": ["V20"], @@ -322,7 +317,7 @@ def test_harmonic_centrality(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types2) + @pytest.mark.parametrize("test_name", directed_graphs) def test_harmonic_centrality2(self, test_name): params = { "v_type_set": ["V20"], @@ -351,7 +346,7 @@ def test_harmonic_centrality2(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types1 + graph_types2) + @pytest.mark.parametrize("test_name", undirected_graphs + directed_graphs) def test_article_rank(self, test_name): params = { "v_type": "V20", @@ -379,7 +374,7 @@ def test_article_rank(self, test_name): ): pytest.fail(f'{r["score"]} != {b["score"]}') - @pytest.mark.parametrize("test_name", graph_types1 + graph_types2) + @pytest.mark.parametrize("test_name", undirected_graphs + directed_graphs) def test_pagerank(self, test_name): params = { "v_type": "V20",