Skip to content
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

added pylint to the CI #14

Merged
merged 6 commits into from
Aug 8, 2022
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
24 changes: 0 additions & 24 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,3 @@

<!-- Add a more detailed description of the changes if needed. -->

## Related Issue

<!-- If your PR refers to a related issue, link it here. -->

## Type of Change

<!-- Mark with an `x` all the checkboxes that apply (like `[x]`) -->

- [ ] 📚 Examples / docs / tutorials / dependencies update
- [ ] 🔧 Bug fix (non-breaking change which fixes an issue)
- [ ] 🥂 Improvement (non-breaking change which improves an existing feature)
- [ ] 🚀 New feature (non-breaking change which adds functionality)
- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to change)
- [ ] 🔐 Security fix

## Checklist

<!-- Mark with an `x` all the checkboxes that apply (like `[x]`) -->

- [ ] I've read the [`CODE_OF_CONDUCT.md`](https://github.com/pedugnat/dynnode2vec/blob/master/CODE_OF_CONDUCT.md) document.
- [ ] I've read the [`CONTRIBUTING.md`](https://github.com/pedugnat/dynnode2vec/blob/master/CONTRIBUTING.md) guide.
- [ ] I've updated the code style using `make codestyle`.
- [ ] I've written tests for all new methods and classes that I created.
- [ ] I've written the docstring in Google format for all the methods and classes that I used.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ jobs:
poetry config virtualenvs.in-project true
poetry install

- name: Run style checks
- name: Run linting
run: |
make check-codestyle
make lint

- name: Run tests
run: |
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,18 @@ check-codestyle:
poetry run isort --diff --check-only --settings-path pyproject.toml ./dynnode2vec ./tests
poetry run black --diff --check --config pyproject.toml ./dynnode2vec ./tests
poetry run darglint --verbosity 2 ./dynnode2vec ./tests
poetry run pylint ./dynnode2vec/ ./tests

.PHONY: pylint
pylint:
poetry run pylint ./dynnode2vec/ ./tests

.PHONY: mypy
mypy:
poetry run mypy --config-file pyproject.toml ./dynnode2vec ./tests

.PHONY: lint
lint: test check-codestyle mypy
lint: check-codestyle mypy

.PHONY: update-dev-deps
update-dev-deps:
Expand Down
5 changes: 4 additions & 1 deletion dynnode2vec/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""dynnode2vec is a package to embed dynamic graphs"""
"""
dynnode2vec is a package to embed dynamic graphs.
"""

import sys
from importlib import metadata as importlib_metadata
Expand All @@ -9,6 +11,7 @@


def get_version() -> str:
# pylint: disable=missing-function-docstring
try:
return importlib_metadata.version(__name__)
except importlib_metadata.PackageNotFoundError: # pragma: no cover
Expand Down
13 changes: 9 additions & 4 deletions dynnode2vec/biased_random_walk.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""
Define a BiasedRandomWalk class to perform biased random walks over graphs.
"""
# pylint: disable=invalid-name
from typing import Any, Dict, Iterable, List, Union

import bisect
Expand Down Expand Up @@ -50,7 +54,7 @@ def weighted_choice(rn: random.Random, weights: Any) -> int:

This method is fastest than built-in numpy functions like `numpy.random.choice`
or `numpy.random.multinomial`.
See https://stackoverflow.com/questions/24140114/fast-way-to-obtain-a-random-index-from-an-array-of-weights-in-python
See https://stackoverflow.com/questions/24140114/fast-way-to-obtain-a-random-index-from-an-array-of-weights-in-python # pylint: disable=line-too-long

Example: for array [1, 4, 4], index 0 will be chosen with probabilty 1/9,
index 1 and index 2 will be chosen with probability 4/9.
Expand All @@ -69,6 +73,7 @@ def _generate_walk(
weighted: bool,
rn: random.Random,
) -> List[int]:
# pylint: disable=too-many-arguments, too-many-locals
"""
Generate a number of random walks starting from a given node.
"""
Expand All @@ -94,8 +99,7 @@ def _generate_walk(
weights = np.ones(neighbours.shape)

if (ip != 1.0) or (iq != 1.0):
# we update the weights according to return (p) and in-out (q)
# parameters
# we update the weights according to return (p) and in-out (q) parameters
mask = neighbours == previous_node
weights[mask] *= ip
mask |= np.isin(neighbours, previous_node_neighbours)
Expand All @@ -120,6 +124,7 @@ def _generate_walk_simple(
weighted: bool,
rn: random.Random,
) -> List[int]:
# pylint: disable=too-many-arguments
"""
Fast implementation for the scenario where:
- the graph is unweighted
Expand All @@ -128,7 +133,7 @@ def _generate_walk_simple(
"""
assert ip == 1.0
assert iq == 1.0
assert weighted == False
assert not weighted

walk = [node]

Expand Down
15 changes: 9 additions & 6 deletions dynnode2vec/dynnode2vec.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""
Define a DynNode2Vec class to run dynnode2vec algorithm over dynamic graphs.
"""
# pylint: disable=invalid-name

from typing import Any, Iterable, List, Optional, Set, Tuple

from collections import namedtuple
Expand All @@ -24,6 +26,8 @@ class DynNode2Vec:
Source paper: http://www.cs.yorku.ca/~aan/research/paper/dynnode2vec.pdf
"""

# pylint: disable=too-many-instance-attributes

def __init__(
self,
*,
Expand All @@ -37,7 +41,6 @@ def __init__(
parallel_processes: int = 4,
plain_node2vec: bool = False,
):
# pylint: disable=too-many-instance-attributes
"""Instantiate a DynNode2Vec object.

:param p: Return hyper parameter (default: 1.0)
Expand Down Expand Up @@ -83,7 +86,7 @@ def __init__(
self.parallel_processes = parallel_processes
self.plain_node2vec = plain_node2vec

# see https://stackoverflow.com/questions/53417258/what-is-workers-parameter-in-word2vec-in-nlp
# see https://stackoverflow.com/questions/53417258/what-is-workers-parameter-in-word2vec-in-nlp # pylint: disable=line-too-long
self.gensim_workers = max(self.parallel_processes - 1, 12)

def _initialize_embeddings(
Expand Down Expand Up @@ -160,11 +163,11 @@ def generate_updated_walks(
# that changed compared to the previous time step
delta_nodes = self.get_delta_nodes(current_graph, previous_graph)

BRW = BiasedRandomWalk(current_graph)
delta_nodes = BRW.convert_true_ids_to_int_ids(delta_nodes)
brw = BiasedRandomWalk(current_graph)
delta_nodes = brw.convert_true_ids_to_int_ids(delta_nodes)

# run walks for updated nodes only
updated_walks = BRW.run(
updated_walks = brw.run(
nodes=delta_nodes,
walk_length=self.walk_length,
n_walks=self.n_walks_per_node,
Expand Down
10 changes: 8 additions & 2 deletions dynnode2vec/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""
Utility file to define miscellaneous functions.
"""
from typing import List

import random
Expand All @@ -15,6 +18,9 @@ def sample_nodes(graph: nx.Graph, k: int) -> List[int]:
def generate_dynamic_graphs(
n_base_nodes: int = 100, n_steps: int = 10, base_density: float = 0.01
) -> List[nx.Graph]:
"""
Generates a list of dynamic graphs, i.e. that depend on the previous graph.
"""
# Create a random graph
graph = nx.fast_gnp_random_graph(n=n_base_nodes, p=base_density)

Expand All @@ -25,8 +31,8 @@ def generate_dynamic_graphs(
change_size = 1 + n_base_nodes // 10
for _ in range(n_steps - 1):
# remove some nodes
for n in sample_nodes(graph, k=change_size):
graph.remove_node(n)
for node in sample_nodes(graph, k=change_size):
graph.remove_node(node)

# add some more nodes
node_idx = max(graph.nodes) + 1
Expand Down
51 changes: 29 additions & 22 deletions tests/test_biased_random_walk.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""
Test the BiasedRandomWalk class
"""
# pylint: disable=missing-function-docstring
import random

import numpy as np
Expand All @@ -6,27 +10,27 @@
import dynnode2vec


@pytest.fixture
def graphs():
@pytest.fixture(name="graphs")
def fixture_graphs():
return dynnode2vec.utils.generate_dynamic_graphs(
n_base_nodes=30, n_steps=5, base_density=0.02
)


def test_init(graphs):
BRW = dynnode2vec.biased_random_walk.BiasedRandomWalk(graphs[0])
brw = dynnode2vec.biased_random_walk.BiasedRandomWalk(graphs[0])

# make sure nodes ids were converted to integers
assert list(BRW.graph.nodes()) == list(range(BRW.graph.number_of_nodes()))
assert list(brw.graph.nodes()) == list(range(brw.graph.number_of_nodes()))


def test_weighted_choice(graphs):
BRW = dynnode2vec.biased_random_walk.BiasedRandomWalk(graphs[0])
rn = random.Random(0)
brw = dynnode2vec.biased_random_walk.BiasedRandomWalk(graphs[0])
rng = random.Random(0)
eps = 0.02
n_try = 1_000

choices = [BRW.weighted_choice(rn, np.array([1, 4, 4])) for _ in range(n_try)]
choices = [brw.weighted_choice(rng, np.array([1, 4, 4])) for _ in range(n_try)]

assert all(choice in [0, 1, 2] for choice in choices)

Expand All @@ -39,40 +43,43 @@ def test_weighted_choice(graphs):
@pytest.mark.parametrize("iq", [1.0, 2.0])
@pytest.mark.parametrize("weighted", [True, False])
def test_generate_walk(graphs, ip, iq, weighted):
# pylint: disable=invalid-name
# make sure that tested node has at least one neighbor
G = graphs[0]
G.add_edge(0, 1, weight=0.5)
graph = graphs[0]
graph.add_edge(0, 1, weight=0.5)

# add random weights to the graph for the weighted case
if weighted:
for _, _, w in G.edges(data=True):
w["weight"] = random.random()
for _, _, data in graph.edges(data=True):
data["weight"] = random.random()

BRW = dynnode2vec.biased_random_walk.BiasedRandomWalk(G)
rn = random.Random(0)
brw = dynnode2vec.biased_random_walk.BiasedRandomWalk(graph)
rng = random.Random(0)

walk = BRW._generate_walk(
node=0, walk_length=10, ip=ip, iq=iq, weighted=weighted, rn=rn
# pylint: disable=protected-access
walk = brw._generate_walk(
node=0, walk_length=10, ip=ip, iq=iq, weighted=weighted, rn=rng
)

assert isinstance(walk, list)
assert all(n in BRW.graph.nodes() for n in walk)
assert all(n in brw.graph.nodes() for n in walk)


@pytest.mark.parametrize("p", [0.5, 1.0])
@pytest.mark.parametrize("q", [1.0, 2.0])
@pytest.mark.parametrize("weighted", [True, False])
def test_run(graphs, p, q, weighted):
G = graphs[0]
# pylint: disable=invalid-name
graph = graphs[0]

# add random weights to the graph for the weighted case
if weighted:
for *_, w in G.edges(data=True):
w["weight"] = random.random()
for *_, data in graph.edges(data=True):
data["weight"] = random.random()

BRW = dynnode2vec.biased_random_walk.BiasedRandomWalk(G)
brw = dynnode2vec.biased_random_walk.BiasedRandomWalk(graph)

random_walks = BRW.run(G.nodes(), p=p, q=q, weighted=weighted)
random_walks = brw.run(graph.nodes(), p=p, q=q, weighted=weighted)

assert all(isinstance(walk, list) for walk in random_walks)
assert all(n in BRW.graph.nodes() for walk in random_walks for n in walk)
assert all(n in brw.graph.nodes() for walk in random_walks for n in walk)
Loading