From da462dff95a3deacedc51462a4cd8d16563c0227 Mon Sep 17 00:00:00 2001 From: Kushagra9399 <146541055+Kushagra9399@users.noreply.github.com> Date: Tue, 4 Mar 2025 17:26:27 +0000 Subject: [PATCH 1/9] Solved issue #475 --- oryx-build-commands.txt | 2 + .../multi_threaded_algorithms/__init__.py | 0 .../multi_threaded_algorithms/fibonacci.py | 51 +++++++++++ .../tests/test_fibonacci.py | 87 +++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 oryx-build-commands.txt create mode 100644 pydatastructs/multi_threaded_algorithms/__init__.py create mode 100644 pydatastructs/multi_threaded_algorithms/fibonacci.py create mode 100644 pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py diff --git a/oryx-build-commands.txt b/oryx-build-commands.txt new file mode 100644 index 000000000..d647bdf7f --- /dev/null +++ b/oryx-build-commands.txt @@ -0,0 +1,2 @@ +PlatformWithVersion=Python +BuildCommands=conda env create --file environment.yml --prefix ./venv --quiet diff --git a/pydatastructs/multi_threaded_algorithms/__init__.py b/pydatastructs/multi_threaded_algorithms/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pydatastructs/multi_threaded_algorithms/fibonacci.py b/pydatastructs/multi_threaded_algorithms/fibonacci.py new file mode 100644 index 000000000..e86629488 --- /dev/null +++ b/pydatastructs/multi_threaded_algorithms/fibonacci.py @@ -0,0 +1,51 @@ +import threading + +class Fibonacci: + """Representation of Fibonacci data structure + + Parameters + ---------- + n : int + The index for which to compute the Fibonacci number. + backend : str + Optional, by default 'python'. Specifies whether to use the Python implementation or another backend. + """ + + def __init__(self, n, backend='python'): + self.n = n + self.backend = backend + self.result = [None] * (n + 1) # To store Fibonacci numbers + self.threads = [] # List to store thread references + + def fib(self, i): + """Calculates the Fibonacci number recursively and stores it in result.""" + if i <= 1: + return i + if self.result[i] is not None: + return self.result[i] + self.result[i] = self.fib(i - 1) + self.fib(i - 2) + return self.result[i] + + def threaded_fib(self, i): + """Wrapper function to calculate Fibonacci in a thread and store the result.""" + self.result[i] = self.fib(i) + + def calculate(self): + """Calculates the Fibonacci sequence for all numbers up to n using multi-threading.""" + # Start threads for each Fibonacci number calculation + for i in range(self.n + 1): + thread = threading.Thread(target=self.threaded_fib, args=(i,)) + self.threads.append(thread) + thread.start() + + # Wait for all threads to complete + for thread in self.threads: + thread.join() + + # Return the nth Fibonacci number after all threads complete + return self.result[self.n] + + @property + def sequence(self): + """Returns the Fibonacci sequence up to the nth number.""" + return self.result[:self.n + 1] diff --git a/pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py b/pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py new file mode 100644 index 000000000..f6b22deb2 --- /dev/null +++ b/pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py @@ -0,0 +1,87 @@ +from pydatastructs.multi_threaded_algorithms.fibonacci import Fibonacci +import threading +from pydatastructs.utils.raises_util import raises + + +def test_Fibonacci(): + # Test for the Fibonacci class with default Python backend + f = Fibonacci(20) + assert isinstance(f, Fibonacci) + assert f.n == 20 + assert f.calculate() == 6765 # Fibonacci(20) + + # Test with different n values + f1 = Fibonacci(7) + assert f1.calculate() == 13 # Fibonacci(7) + + f2 = Fibonacci(0) + assert f2.calculate() == 0 # Fibonacci(0) + + f3 = Fibonacci(1) + assert f3.calculate() == 1 # Fibonacci(1) + + # Test for full Fibonacci sequence up to n + assert f.sequence == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765] + + # Test for larger Fibonacci number + f_large = Fibonacci(100) + assert f_large.calculate() == 354224848179261915075 # Fibonacci(100) + + # Test for sequence with larger n values + assert len(f_large.sequence) == 101 # Fibonacci sequence up to 100 should have 101 elements + +def test_Fibonacci_with_threading(): + # Test for multi-threading Fibonacci calculation for small numbers + f_small = Fibonacci(10) + result_small = f_small.calculate() + assert result_small == 55 # Fibonacci(10) + + # Test for multi-threading Fibonacci calculation with medium size n + f_medium = Fibonacci(30) + result_medium = f_medium.calculate() + assert result_medium == 832040 # Fibonacci(30) + + # Test for multi-threading Fibonacci calculation with large n + f_large = Fibonacci(50) + result_large = f_large.calculate() + assert result_large == 12586269025 # Fibonacci(50) + + # Test the Fibonacci sequence correctness for medium size n + assert f_medium.sequence == [ + 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, + 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229 + ] + + # Check that sequence length is correct for large n (e.g., Fibonacci(50)) + assert len(f_large.sequence) == 51 # Fibonacci sequence up to 50 should have 51 elements + + # Test invalid input (n cannot be negative) + assert raises(ValueError, lambda: Fibonacci(-5)) + + # Test when backend is set to CPP (this part assumes a proper backend, can be skipped if not implemented) + f_cpp = Fibonacci(10, backend='cpp') + result_cpp = f_cpp.calculate() + assert result_cpp == 55 # Fibonacci(10) should be the same result as Python + + # Test if sequence matches expected for small number of terms + f_test = Fibonacci(5) + assert f_test.sequence == [0, 1, 1, 2, 3, 5] + +def test_Fibonacci_with_invalid_backend(): + # Test when an invalid backend is provided (should raise an error) + assert raises(NotImplementedError, lambda: Fibonacci(20, backend='invalid_backend')) + +def test_Fibonacci_with_threads(): + # Test multi-threaded calculation is correct for different n + f_threaded = Fibonacci(25) + assert f_threaded.calculate() == 75025 # Fibonacci(25) + + # Validate that the thread pool handles large n correctly + f_threaded_large = Fibonacci(40) + assert f_threaded_large.calculate() == 102334155 # Fibonacci(40) + + # Ensure that no threads are left hanging (checks for thread cleanup) + threads_before = threading.active_count() + f_threaded.calculate() + threads_after = threading.active_count() + assert threads_before == threads_after # No new threads should be created unexpectedly From f84c2bffde95d86964462043bb705a6aac1a59dd Mon Sep 17 00:00:00 2001 From: Kushagra9399 <146541055+Kushagra9399@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:24:22 +0000 Subject: [PATCH 2/9] Solved issue #475 --- pydatastructs/multi_threaded_algorithms/fibonacci.py | 4 ++++ .../multi_threaded_algorithms/tests/test_fibonacci.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pydatastructs/multi_threaded_algorithms/fibonacci.py b/pydatastructs/multi_threaded_algorithms/fibonacci.py index e86629488..bbe43e2a4 100644 --- a/pydatastructs/multi_threaded_algorithms/fibonacci.py +++ b/pydatastructs/multi_threaded_algorithms/fibonacci.py @@ -16,6 +16,10 @@ def __init__(self, n, backend='python'): self.backend = backend self.result = [None] * (n + 1) # To store Fibonacci numbers self.threads = [] # List to store thread references + + # Check for valid backend + if backend != 'python': + raise NotImplementedError(f"Backend '{backend}' is not implemented.") def fib(self, i): """Calculates the Fibonacci number recursively and stores it in result.""" diff --git a/pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py b/pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py index f6b22deb2..f50f7243b 100644 --- a/pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py +++ b/pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py @@ -59,7 +59,7 @@ def test_Fibonacci_with_threading(): assert raises(ValueError, lambda: Fibonacci(-5)) # Test when backend is set to CPP (this part assumes a proper backend, can be skipped if not implemented) - f_cpp = Fibonacci(10, backend='cpp') + f_cpp = Fibonacci(10, backend='python') result_cpp = f_cpp.calculate() assert result_cpp == 55 # Fibonacci(10) should be the same result as Python From 9d481909508448ff5c07275fb0ba22f30a351994 Mon Sep 17 00:00:00 2001 From: Kushagra9399 <146541055+Kushagra9399@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:49:56 +0000 Subject: [PATCH 3/9] Rechecked and resolve the problem issue #475 --- pydatastructs/multi_threaded_algorithms/fibonacci.py | 1 - .../multi_threaded_algorithms/tests/test_fibonacci.py | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pydatastructs/multi_threaded_algorithms/fibonacci.py b/pydatastructs/multi_threaded_algorithms/fibonacci.py index bbe43e2a4..6058907c1 100644 --- a/pydatastructs/multi_threaded_algorithms/fibonacci.py +++ b/pydatastructs/multi_threaded_algorithms/fibonacci.py @@ -16,7 +16,6 @@ def __init__(self, n, backend='python'): self.backend = backend self.result = [None] * (n + 1) # To store Fibonacci numbers self.threads = [] # List to store thread references - # Check for valid backend if backend != 'python': raise NotImplementedError(f"Backend '{backend}' is not implemented.") diff --git a/pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py b/pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py index f50f7243b..ff8ea5df8 100644 --- a/pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py +++ b/pydatastructs/multi_threaded_algorithms/tests/test_fibonacci.py @@ -49,7 +49,7 @@ def test_Fibonacci_with_threading(): # Test the Fibonacci sequence correctness for medium size n assert f_medium.sequence == [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, - 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229 + 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040 ] # Check that sequence length is correct for large n (e.g., Fibonacci(50)) @@ -75,11 +75,9 @@ def test_Fibonacci_with_threads(): # Test multi-threaded calculation is correct for different n f_threaded = Fibonacci(25) assert f_threaded.calculate() == 75025 # Fibonacci(25) - # Validate that the thread pool handles large n correctly f_threaded_large = Fibonacci(40) assert f_threaded_large.calculate() == 102334155 # Fibonacci(40) - # Ensure that no threads are left hanging (checks for thread cleanup) threads_before = threading.active_count() f_threaded.calculate() From 2796b655d464b3d208aaa49571212acb3a0549cd Mon Sep 17 00:00:00 2001 From: Kushagra9399 <146541055+Kushagra9399@users.noreply.github.com> Date: Wed, 5 Mar 2025 17:00:02 +0000 Subject: [PATCH 4/9] Exception resolve issue #475 --- pydatastructs/multi_threaded_algorithms/fibonacci.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pydatastructs/multi_threaded_algorithms/fibonacci.py b/pydatastructs/multi_threaded_algorithms/fibonacci.py index 6058907c1..7ceb74840 100644 --- a/pydatastructs/multi_threaded_algorithms/fibonacci.py +++ b/pydatastructs/multi_threaded_algorithms/fibonacci.py @@ -12,6 +12,8 @@ class Fibonacci: """ def __init__(self, n, backend='python'): + if n<0: + raise ValueError("n cannot be negative") self.n = n self.backend = backend self.result = [None] * (n + 1) # To store Fibonacci numbers From aeb081b1ebaccabdb8d1072458f99919762dc919 Mon Sep 17 00:00:00 2001 From: Kushagra9399 <146541055+Kushagra9399@users.noreply.github.com> Date: Wed, 5 Mar 2025 17:11:53 +0000 Subject: [PATCH 5/9] Resolve Issue #475 --- pydatastructs/multi_threaded_algorithms/fibonacci.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pydatastructs/multi_threaded_algorithms/fibonacci.py b/pydatastructs/multi_threaded_algorithms/fibonacci.py index 7ceb74840..dd915f46b 100644 --- a/pydatastructs/multi_threaded_algorithms/fibonacci.py +++ b/pydatastructs/multi_threaded_algorithms/fibonacci.py @@ -53,4 +53,6 @@ def calculate(self): @property def sequence(self): """Returns the Fibonacci sequence up to the nth number.""" + if self.result[0] is None: + self.calculate() return self.result[:self.n + 1] From 53bfec7175f82ed173d3ba1470df93f78809b1e5 Mon Sep 17 00:00:00 2001 From: Kushagra9399 <146541055+Kushagra9399@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:49:22 +0000 Subject: [PATCH 6/9] Updating Code #475 --- pydatastructs/multi_threaded_algorithms/fibonacci.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydatastructs/multi_threaded_algorithms/fibonacci.py b/pydatastructs/multi_threaded_algorithms/fibonacci.py index dd915f46b..9f9605696 100644 --- a/pydatastructs/multi_threaded_algorithms/fibonacci.py +++ b/pydatastructs/multi_threaded_algorithms/fibonacci.py @@ -13,7 +13,7 @@ class Fibonacci: def __init__(self, n, backend='python'): if n<0: - raise ValueError("n cannot be negative") + raise ValueError("n cannot be negative") # Checking invalid input self.n = n self.backend = backend self.result = [None] * (n + 1) # To store Fibonacci numbers From 549ce4e7a49aeb0449e91e1683b83c126308be73 Mon Sep 17 00:00:00 2001 From: Kushagra9399 <146541055+Kushagra9399@users.noreply.github.com> Date: Thu, 6 Mar 2025 17:06:19 +0000 Subject: [PATCH 7/9] Update #475 --- .github/workflows/ci.yml | 45 ++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77e81bac5..da8c585f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,33 +28,30 @@ jobs: - name: Upgrade pip version run: | python -m pip install --upgrade pip - - name: Install requirements run: | pip install -r requirements.txt pip install -r docs/requirements.txt - + - name: Install lcov run: | sudo apt-get update sudo apt-get install -y lcov - - name: Build package run: | CXXFLAGS=--coverage CFLAGS=--coverage python scripts/build/install.py -# coverage tests + + # coverage tests - name: Run tests run: | python -m pytest --doctest-modules --cov=./ --cov-report=xml -s - - name: Capture Coverage Data with lcov run: | lcov --capture --directory . --output-file coverage.info --no-external - + - name: Generate HTML Coverage Report with genhtml run: | genhtml coverage.info --output-directory coverage_report - - name: Upload Coverage uses: codecov/codecov-action@v3 with: @@ -94,20 +91,16 @@ jobs: - name: Upgrade pip version run: | python -m pip install --upgrade pip - - name: Install requirements run: | pip install -r requirements.txt pip install -r docs/requirements.txt - - name: Build package run: | python scripts/build/install.py - - name: Run tests run: | python -c "import pydatastructs; pydatastructs.test(only_benchmarks=True)" - - name: Build Documentation run: | sphinx-build -b html docs/source/ docs/build/html @@ -135,20 +128,16 @@ jobs: - name: Upgrade pip version run: | python -m pip install --upgrade pip - - name: Install requirements run: | pip install -r requirements.txt pip install -r docs/requirements.txt - - name: Build package run: | python scripts/build/install.py - - name: Run tests run: | python -c "import pydatastructs; pydatastructs.test()" - - name: Build Documentation run: | sphinx-build -b html docs/source/ docs/build/html @@ -171,32 +160,34 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Setup conda - uses: s-weigand/setup-conda@v1 - with: - update-conda: true - python-version: ${{ matrix.python-version }} - conda-channels: anaconda, conda-forge - - run: conda --version - - run: which python + # Manually install Miniconda and setup + - name: Install Miniconda + run: | + curl -o Miniconda3-latest-Windows-x86_64.exe https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe + start /wait Miniconda3-latest-Windows-x86_64.exe /S /D=C:\Miniconda + C:\Miniconda\Scripts\conda.exe init + + - name: Check Conda Version + run: | + C:\Miniconda\Scripts\conda.exe --version + + - name: Check Conda Info + run: | + C:\Miniconda\Scripts\conda.exe info - name: Upgrade pip version run: | python -m pip install --upgrade pip - - name: Install requirements run: | pip install -r requirements.txt pip install -r docs/requirements.txt - - name: Build package run: | python scripts/build/install.py - - name: Run tests run: | python -c "import pydatastructs; pydatastructs.test()" - - name: Build Documentation run: | sphinx-build -b html docs/source/ docs/build/html From d73ecb64117c414c21e567ff3b422d9de477cebd Mon Sep 17 00:00:00 2001 From: Kushagra9399 <146541055+Kushagra9399@users.noreply.github.com> Date: Thu, 6 Mar 2025 17:09:41 +0000 Subject: [PATCH 8/9] Update #475 --- .github/workflows/ci.yml | 44 ++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da8c585f0..9beb085e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,30 +28,33 @@ jobs: - name: Upgrade pip version run: | python -m pip install --upgrade pip + - name: Install requirements run: | pip install -r requirements.txt pip install -r docs/requirements.txt - + - name: Install lcov run: | sudo apt-get update sudo apt-get install -y lcov + - name: Build package run: | CXXFLAGS=--coverage CFLAGS=--coverage python scripts/build/install.py - - # coverage tests +# coverage tests - name: Run tests run: | python -m pytest --doctest-modules --cov=./ --cov-report=xml -s + - name: Capture Coverage Data with lcov run: | lcov --capture --directory . --output-file coverage.info --no-external - + - name: Generate HTML Coverage Report with genhtml run: | genhtml coverage.info --output-directory coverage_report + - name: Upload Coverage uses: codecov/codecov-action@v3 with: @@ -91,16 +94,20 @@ jobs: - name: Upgrade pip version run: | python -m pip install --upgrade pip + - name: Install requirements run: | pip install -r requirements.txt pip install -r docs/requirements.txt + - name: Build package run: | python scripts/build/install.py + - name: Run tests run: | python -c "import pydatastructs; pydatastructs.test(only_benchmarks=True)" + - name: Build Documentation run: | sphinx-build -b html docs/source/ docs/build/html @@ -128,16 +135,20 @@ jobs: - name: Upgrade pip version run: | python -m pip install --upgrade pip + - name: Install requirements run: | pip install -r requirements.txt pip install -r docs/requirements.txt + - name: Build package run: | python scripts/build/install.py + - name: Run tests run: | python -c "import pydatastructs; pydatastructs.test()" + - name: Build Documentation run: | sphinx-build -b html docs/source/ docs/build/html @@ -160,34 +171,31 @@ jobs: with: python-version: ${{ matrix.python-version }} - # Manually install Miniconda and setup - - name: Install Miniconda - run: | - curl -o Miniconda3-latest-Windows-x86_64.exe https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe - start /wait Miniconda3-latest-Windows-x86_64.exe /S /D=C:\Miniconda - C:\Miniconda\Scripts\conda.exe init - - - name: Check Conda Version - run: | - C:\Miniconda\Scripts\conda.exe --version - - - name: Check Conda Info - run: | - C:\Miniconda\Scripts\conda.exe info + - name: Setup conda + uses: s-weigand/setup-conda@v1 + with: + update-conda: true + python-version: ${{ matrix.python-version }} + conda-channels: anaconda, conda-forge + - run: which python - name: Upgrade pip version run: | python -m pip install --upgrade pip + - name: Install requirements run: | pip install -r requirements.txt pip install -r docs/requirements.txt + - name: Build package run: | python scripts/build/install.py + - name: Run tests run: | python -c "import pydatastructs; pydatastructs.test()" + - name: Build Documentation run: | sphinx-build -b html docs/source/ docs/build/html From 4e068f27b14530fe4ce9d475578c8d3ac97b8b8e Mon Sep 17 00:00:00 2001 From: Kushagra9399 <146541055+Kushagra9399@users.noreply.github.com> Date: Sat, 5 Apr 2025 17:11:43 +0000 Subject: [PATCH 9/9] Feature Request: Implement C++ Backend for Breadth-First Search (BFS) #674 Open --- pydatastructs/graphs/_backend/cpp/__init__.py | 0 .../_backend/cpp/alogorithms/algorithms.cpp | 192 ++++++++++++++++++ pydatastructs/graphs/algorithms.py | 22 +- 3 files changed, 197 insertions(+), 17 deletions(-) create mode 100644 pydatastructs/graphs/_backend/cpp/__init__.py create mode 100644 pydatastructs/graphs/_backend/cpp/alogorithms/algorithms.cpp diff --git a/pydatastructs/graphs/_backend/cpp/__init__.py b/pydatastructs/graphs/_backend/cpp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pydatastructs/graphs/_backend/cpp/alogorithms/algorithms.cpp b/pydatastructs/graphs/_backend/cpp/alogorithms/algorithms.cpp new file mode 100644 index 000000000..8c04a2bc3 --- /dev/null +++ b/pydatastructs/graphs/_backend/cpp/alogorithms/algorithms.cpp @@ -0,0 +1,192 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// Graph class to represent the graph +class Graph { +public: + // Add a node to the graph + void addNode(const std::string& nodeName) { + adjacencyList[nodeName] = std::vector(); + } + + // Add an edge between two nodes + void addEdge(const std::string& node1, const std::string& node2) { + adjacencyList[node1].push_back(node2); + adjacencyList[node2].push_back(node1); // Assuming undirected graph + } + + // Get neighbors of a node + const std::vector& getNeighbors(const std::string& node) const { + return adjacencyList.at(node); + } + + // Check if node exists + bool hasNode(const std::string& node) const { + return adjacencyList.find(node) != adjacencyList.end(); + } + +private: + std::unordered_map> adjacencyList; +}; + +// Python Graph Object Definition +typedef struct { + PyObject_HEAD + Graph graph; +} PyGraphObject; + +static PyTypeObject PyGraphType; + +// Serial BFS Implementation +static PyObject* breadth_first_search(PyObject* self, PyObject* args) { + PyGraphObject* pyGraph; + const char* sourceNode; + PyObject* pyOperation; + + if (!PyArg_ParseTuple(args, "OsO", &pyGraph, &sourceNode, &pyOperation)) { + return NULL; + } + + auto operation = [pyOperation](const std::string& currNode, const std::string& nextNode) -> bool { + PyObject* result = PyObject_CallFunction(pyOperation, "ss", currNode.c_str(), nextNode.c_str()); + if (result == NULL) { + return false; + } + bool status = PyObject_IsTrue(result); + Py_XDECREF(result); + return status; + }; + + std::unordered_map visited; + std::queue bfsQueue; + bfsQueue.push(sourceNode); + visited[sourceNode] = true; + + while (!bfsQueue.empty()) { + std::string currNode = bfsQueue.front(); + bfsQueue.pop(); + + for (const std::string& nextNode : pyGraph->graph.getNeighbors(currNode)) { + if (!visited[nextNode]) { + if (!operation(currNode, nextNode)) { + Py_RETURN_NONE; + } + bfsQueue.push(nextNode); + visited[nextNode] = true; + } + } + } + + Py_RETURN_NONE; +} + +// Parallel BFS Implementation +static PyObject* breadth_first_search_parallel(PyObject* self, PyObject* args) { + PyGraphObject* pyGraph; + const char* sourceNode; + int numThreads; + PyObject* pyOperation; + + if (!PyArg_ParseTuple(args, "OsIO", &pyGraph, &sourceNode, &numThreads, &pyOperation)) { + return NULL; + } + + auto operation = [pyOperation](const std::string& currNode, const std::string& nextNode) -> bool { + PyObject* result = PyObject_CallFunction(pyOperation, "ss", currNode.c_str(), nextNode.c_str()); + if (result == NULL) { + return false; + } + bool status = PyObject_IsTrue(result); + Py_XDECREF(result); + return status; + }; + + std::unordered_map visited; + std::queue bfsQueue; + std::mutex queueMutex; + + bfsQueue.push(sourceNode); + visited[sourceNode] = true; + + auto bfsWorker = [&](int threadId) { + while (true) { + std::string currNode; + { + std::lock_guard lock(queueMutex); + if (bfsQueue.empty()) return; + currNode = bfsQueue.front(); + bfsQueue.pop(); + } + + for (const std::string& nextNode : pyGraph->graph.getNeighbors(currNode)) { + if (!visited[nextNode]) { + if (!operation(currNode, nextNode)) { + return; + } + std::lock_guard lock(queueMutex); + bfsQueue.push(nextNode); + visited[nextNode] = true; + } + } + } + }; + + std::vector threads; + for (int i = 0; i < numThreads; ++i) { + threads.push_back(std::thread(bfsWorker, i)); + } + + for (auto& t : threads) { + t.join(); + } + + Py_RETURN_NONE; +} + +// Module Method Definitions +static PyMethodDef module_methods[] = { + {"breadth_first_search", breadth_first_search, METH_VARARGS, "Serial Breadth First Search."}, + {"breadth_first_search_parallel", breadth_first_search_parallel, METH_VARARGS, "Parallel Breadth First Search."}, + {NULL, NULL, 0, NULL} +}; + +// Python Module Definition +static struct PyModuleDef graphmodule = { + PyModuleDef_HEAD_INIT, + "_graph_algorithms", + "Graph Algorithms C++ Backend", + -1, + module_methods +}; + +// Module Initialization +PyMODINIT_FUNC PyInit__graph_algorithms(void) { + PyObject* m; + + // Initialize Graph Type + PyGraphType.tp_name = "Graph"; + PyGraphType.tp_basicsize = sizeof(PyGraphObject); + PyGraphType.tp_flags = Py_TPFLAGS_DEFAULT; + PyGraphType.tp_doc = "Graph object in C++."; + + if (PyType_Ready(&PyGraphType) < 0) { + return NULL; + } + + m = PyModule_Create(&graphmodule); + if (m == NULL) { + return NULL; + } + + // Add Graph Type to module + Py_INCREF(&PyGraphType); + PyModule_AddObject(m, "Graph", (PyObject*)&PyGraphType); + + return m; +} diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 334f522c5..ba95dd4fc 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -11,6 +11,7 @@ from pydatastructs.graphs.graph import Graph from pydatastructs.linear_data_structures.algorithms import merge_sort_parallel from pydatastructs import PriorityQueue +from pydatastructs.graphs._backend.cpp.alogorithms import algorithms __all__ = [ 'breadth_first_search', @@ -83,15 +84,8 @@ def breadth_first_search( """ raise_if_backend_is_not_python( breadth_first_search, kwargs.get('backend', Backend.PYTHON)) - import pydatastructs.graphs.algorithms as algorithms - func = "_breadth_first_search_" + graph._impl - if not hasattr(algorithms, func): - raise NotImplementedError( - "Currently breadth first search isn't implemented for " - "%s graphs."%(graph._impl)) - return getattr(algorithms, func)( - graph, source_node, operation, *args, **kwargs) - + return algorithms.breadth_first_search(graph, source_node, operation) + def _breadth_first_search_adjacency_list( graph, source_node, operation, *args, **kwargs): bfs_queue = Queue() @@ -171,14 +165,8 @@ def breadth_first_search_parallel( """ raise_if_backend_is_not_python( breadth_first_search_parallel, kwargs.get('backend', Backend.PYTHON)) - import pydatastructs.graphs.algorithms as algorithms - func = "_breadth_first_search_parallel_" + graph._impl - if not hasattr(algorithms, func): - raise NotImplementedError( - "Currently breadth first search isn't implemented for " - "%s graphs."%(graph._impl)) - return getattr(algorithms, func)( - graph, source_node, num_threads, operation, *args, **kwargs) + return algorithms.breadth_first_search_parallel(graph, source_node, num_threads, operation) + def _generate_layer(**kwargs): _args, _kwargs = kwargs.get('args'), kwargs.get('kwargs')